Assembly Avanzato con NASM
Capitolo 3: Il PIC 8259 - Programmable Interrupt Controller
Un computer è un sistema complesso costituito da una Unità Centrale di Elaborazione
(CPU) e da un insieme più o meno numeroso di dispositivi periferici chiamati,
semplicemente, periferiche; tra la CPU ed una qualsiasi periferica si
deve necessariamente stabilire un sistema di comunicazione che consiste, in sostanza,
in una richiesta di I/O da parte della periferica stessa.
Si pone allora il problema fondamentale di come far dialogare la CPU con le
periferiche nel modo più efficiente possibile; per risolvere un tale problema
esistono due metodi principali denominati polling (sondaggio) e
interrupts (interruzioni).
Il metodo del polling consiste nel fatto che la CPU, ad intervalli di tempo
regolari, "sonda" a rotazione ciascuna delle periferiche per sapere se c'è una
eventuale richiesta di I/O; non ci vuole molto a capire che si tratta di un
metodo altamente inefficiente in quanto provoca un enorme rallentamento generale
del sistema.
Una periferica che impiega molto tempo per rispondere al sondaggio, tiene inutilmente
occupata la CPU e finisce anche per creare una lunga coda di richieste di
I/O da parte di altre periferiche costrette ad attendere il loro turno;
possiamo affermare quindi che il metodo del polling, grazie anche ad una notevole
semplicità circuitale, diventa vantaggioso solo nel caso in cui siano presenti poche
periferiche, tutte molto veloci nel rispondere al sondaggio effettuato dalla
CPU!
Il metodo delle interrupts comporta una complessità circuitale nettamente superiore,
ma garantisce una enorme efficienza generale del sistema; proprio per questo motivo,
si tratta di un metodo largamente utilizzato sui PC e su molte altre
piattaforme hardware.
Il metodo delle interruzioni prevede che tutte le richieste di I/O vengano
intercettate da un apposito dispositivo; lo scopo di tale dispositivo è quello di
creare una coda di attesa dove le varie richieste di I/O vengono ordinate
in base alla priorità assegnata a ciascuna di esse.
Al momento opportuno, il dispositivo invia alla CPU una richiesta di dialogo
da parte della periferica alla quale è stata assegnata la priorità maggiore; solo
in quel momento, la CPU interrompe il programma in esecuzione (da cui la
denominazione di "interrupt") e soddisfa la richiesta della periferica.
In sostanza, grazie a questo metodo, la CPU viene "disturbata" solo quando
è strettamente necessario; il programma in esecuzione viene quindi interrotto per
il minor tempo possibile!
In base ad uno standard imposto dalla IBM, per la gestione delle richieste di
I/O nei PC è stato scelto un dispositivo denominato PIC 8259;
l'acronimo PIC sta per Programmable Interrupt Controller (controllore
programmabile delle interruzioni).
3.1 Classificazione delle interruzioni
Possiamo suddividere le interruzioni in quattro categorie fondamentali analizzate
nel seguito.
3.1.1 Interruzioni hardware
Le interruzioni hardware sono quelle provocate dalle periferiche; in tal caso
si parla di IRQ o Interrupt Request (richiesta di interruzione).
In base a quanto detto in precedenza, tutte le IRQ vengono inviate ad uno o
più PIC (dipende da quante IRQ differenti vogliamo gestire); il
PIC provvede a disporre le varie IRQ in ordine di priorità e le
invia, una alla volta, alla CPU.
Appare evidente il fatto che le IRQ sono "eventi asincroni"; in altre
parole, una IRQ può arrivare in qualsiasi momento, anche mentre la
CPU sta eseguendo una istruzione.
3.1.2 Interruzioni software
Le interruzioni software sono quelle provocate direttamente dai programmi; come
sappiamo, per generare una interruzione software bisogna servirsi dell'istruzione
INT n, dove n è un valore intero senza segno a 8 bit compreso
tra 0 e 255 (tra 00h e FFh).
Appare evidente il fatto che le interruzioni software sono "eventi sincroni"; in
altre parole, una interruzione software viene generata da un programma e quindi la
sua gestione è sincronizzata con l'esecuzione del programma stesso.
3.1.3 Eccezioni della CPU
In particolari circostanze, anche le CPU possono generare automaticamente
vere e proprie interruzioni software; in tal caso si parla di CPU exceptions
(eccezioni della CPU).
Il termine "exception" indica il fatto che queste particolari interruzioni vengono
generate dalla CPU quando si verificano casi eccezionali; la Figura 3.1
illustra alcune delle principali eccezioni.
Alcune delle eccezioni illustrate in Figura 3.1 sono state già analizzate nella sezione
Assembly Base; altre numerosissime eccezioni, come la 0Dh e la 0Eh,
si verificano solo quando la CPU opera in modalità protetta e saranno analizzate
nella apposita sezione di questo sito.
3.1.4 NMI - Non Maskable Interrupt
Osservando la Figura 9.3 e la Figura 9.6 del Capitolo 9, nella sezione Assembly
Base, si può notare che le CPU della famiglia 80x86 sono dotate di
un pin di input indicato con NMI; attraverso tale pin arriva un apposito
segnale per indicare che si è verificato un evento particolarmente grave!
NMI è l'acronimo di Non Maskable Interrupt (interruzione non mascherabile);
tale definizione indica il fatto che la NMI è di vitale importanza per il
corretto funzionamento del sistema e non può essere quindi mascherata (interdetta)
dall'utente attraverso i metodi illustrati nel seguito del capitolo.
Tra i casi che provocano una NMI si possono citare, i cali di tensione tali da
impedire il corretto funzionamento del sistema, un errore di parità in memoria, un
trasferimento dati che non è stato portato a termine nel tempo stabilito, etc;
l'utente quindi non può che augurarsi che una NMI non arrivi mai all'apposito
pin della CPU!
3.2 Gestione delle interruzioni da parte della CPU
Come è stato abbondantemente spiegato nel precedente capitolo e nella sezione
Assembly Base, per convenzione i primi 1024 byte della RAM
(compresi quindi tra gli indirizzi fisici 00000h e 003FFh) sono
riservati a particolari informazioni le quali, nel loro insieme, formano la cosiddetta
IVT o Interrupts Vector Table (tabella dei vettori di interruzione);
questi 1024 byte vengono suddivisi in 256 locazioni da 4 byte
ciascuna (256*4=1024).
Ogni locazione da 4 byte contiene un indirizzo logico Seg:Offset di
tipo FAR che prende il nome di Interrupt Vector (vettore di
interruzione); tale indirizzo logico punta ad una procedura che deve trovarsi nel
primo MiB della RAM (modalità operativa reale).
I 256 vettori di interruzione vengono rappresentati con un indice compreso
tra 0 e 255 (tra 00h e FFh); tale indice prende il
nome di Interrupt Type (tipo di interruzione).
Ad ogni richiesta di interruzione viene associato un ben preciso Interrupt Type
che possiamo rappresentare attraverso il relativo indice n; la CPU
soddisfa una richiesta di interruzione chiamando la procedura associata
all'Interrupt Vector di indice n nella IVT.
La CPU compie i seguenti passi:
- salva il registro FLAGS nello stack
- pone TF=0 (Trap Flag) e IF=0 (Interrupt Enable Flag)
- salva l'indirizzo di ritorno completo Seg:Offset nello stack
- legge l'indirizzo Seg:Offset che si trova in posizione n*4
nella IVT
- carica tale indirizzo in CS:IP e salta a CS:IP
La procedura appena chiamata dalla CPU ha il compito di soddisfare la
richiesta di interruzione; proprio per questo motivo, una tale procedura prende
il nome di ISR o Interrupt Service Routine (procedura di servizio
per le interruzioni).
Notiamo che la CPU, prima di chiamare una ISR, pone a zero il
Trap Flag TF (per evitare l'eccezione INT 01h ad ogni istruzione
eseguita) e l'Interrupt Enable Flag IF (per mettere in stato di attesa
eventuali altre richieste di interruzione); tutte le interruzioni che possono
essere bloccate (mascherate) con IF=0 prendono il nome di Maskable
Interrupts (interruzioni mascherabili).
Come si può facilmente immaginare, le NMI sono chiamate "non mascherabili"
proprio perché non vengono bloccate da IF=0; anche le interruzioni
software non possono essere mascherate in quanto vengono imposte dal programma
in esecuzione, indipendentemente dallo stato di IF.
Una ISR deve rigorosamente terminare con una istruzione IRET; in
presenza di tale istruzione, la CPU esegue i seguenti passi:
- estrae due WORD dallo stack e le carica in CS:IP
- estrae una WORD dallo stack e la carica nel registro FLAGS
- salta a CS:IP (indirizzo di ritorno)
Si noti che il ripristino del registro FLAGS comporta anche il ripristino
di TF e IF; come sappiamo, normalmente si ha TF=0 e
IF=1.
Il Trap Flag deve essere tenuto, possibilmente, sempre a 0 per evitare che
la CPU generi una eccezione (INT 01h) ad ogni istruzione eseguita;
tale caratteristica viene messa a disposizione dei debuggers, ma chiaramente
provoca un sensibile rallentamento generale del sistema!
L'Interrupt Enable Flag deve essere tenuto, possibilmente, sempre a 1 per
fare in modo che la CPU elabori tutte le interruzioni mascherabili; se
IF=0, le interruzioni mascherabili vengono bloccate e la CPU non
può dialogare con le periferiche!
In base alle considerazioni esposte in precedenza, possiamo affermare che la
gestione, da parte della CPU, delle interruzioni software e delle eccezioni,
si svolge in modo molto semplice in quanto viene specificato in modo diretto anche
l'indice n nella IVT; la CPU quindi non deve fare altro che
accedere alla posizione n*4 nella IVT, leggere l'indirizzo
Seg:Offset associato, caricarlo in CS:IP e saltare a CS:IP.
Nel caso, invece, delle interruzioni hardware, è necessario analizzare il
meccanismo che permette di associare una IRQ ad un indice n nella
IVT; come si può intuire, il compito di effettuare tale associazione spetta
al PIC.
3.3 Funzionamento del PIC 8259
La Figura 3.2 illustra lo schema semplificato di un PIC 8259.
Come possiamo notare, sono presenti 8 ingressi, indicati con IR0,
IR1, etc, sino a IR7; attraverso questi 8 ingressi possiamo
gestire le IRQ provenienti da 8 periferiche diverse.
Non appena una determinata IRQ giunge all'ingresso IR a cui è collegata,
il PIC modifica un apposito registro a 8 bit denominato Interrupt
Request Register o IRR; in pratica, il bit di IRR la cui posizione
corrisponde al numero della IRQ viene posto a livello logico 1 per
indicare che la relativa richiesta di I/O è in attesa di elaborazione.
Un secondo registro a 8 bit, denominato Interrupt Mask Register o
IMR, permette al PIC di sapere se la IRQ è mascherata o meno;
una IRQ è mascherata quando il bit di IMR la cui posizione corrisponde
al numero della IRQ stessa viene posto a livello logico 1.
Se il bit mask è a 1, il PIC blocca l'elaborazione della IRQ
associata; in caso contrario, la IRQ viene inviata ad un dispositivo del
PIC denominato Priority Resolver o PR.
Come si intuisce dal nome, il PR ordina le varie IRQ in base alla loro
priorità, rappresentata da un numero compreso tra 0 e 7 (interamente
riprogrammabile dall'utente); per convenzione, 0 è la priorità più alta,
mentre 7 è la più bassa.
La IRQ con priorità più alta viene inviata ad un ulteriore registro a
8 bit denominato In-Service Register o ISR (da non confondere
con le ISR); il PIC ora invia un impulso alla CPU attraverso
la linea INT collegata al Control Bus (CB).
Tale impulso arriva all'omonimo pin INT (o INTR) della CPU
(Figura 9.3 e Figura 9.6 del Capitolo 9, sezione Assembly Base); la CPU
porta a termine l'eventuale istruzione che stava eseguendo e invia due impulsi
(separati da un piccolo intervallo di tempo) attraverso il proprio pin INTA
(interrupt acknowledge) collegato al Control Bus.
I due impulsi giungono in successione all'omonimo ingresso INTA del PIC.
Quando arriva il primo impulso, il PIC pone a 0 il bit che in
IRR occupa la posizione corrispondente al numero della IRQ da elaborare;
analogamente, il PIC pone a 1 il bit che in ISR occupa la
posizione corrispondente al numero della IRQ da elaborare.
Quando arriva il secondo impulso, il PIC utilizza il Data Bus per inviare
alla CPU l'Interrupt Type, cioè l'indice n nella IVT in cui
si trova l'indirizzo Seg:Offset della ISR da chiamare; come vedremo più
avanti, il valore n viene stabilito in fase di inizializzazione del PIC.
A questo punto, il controllo passa alla CPU la quale procede come descritto
nel paragrafo 3.2; in particolare, la CPU provvede a porre IF=0
nel registro FLAGS prima di chiamare la ISR.
Porre IF=0 equivale ad eseguire una istruzione CLI (clear interrupt
enable flag); l'effetto che si ottiene è il mascheramento di tutte le IRQ che
giungono al PIC (in sostanza, tutti gli 8 bit dell'IMR vengono
posti a 1)!
Questo comportamento è necessario per evitare che l'elaborazione di una ISR
venga interrotta dall'arrivo di un'altra IRQ; nel caso più semplice, quindi,
il PIC rimane in attesa finché non termina l'elaborazione di una ISR.
Appare evidente, però, che una tale situazione può portare a gravi latenze dovute,
ad esempio, ad una ISR piuttosto lenta; per evitare questo problema, il
programmatore può inserire all'inizio della stessa ISR una istruzione
STI (set interrupt enable flag) permettendo così al PIC di riprendere
subito a funzionare.
In un caso del genere, l'arrivo di una IRQ avente una determinata priorità,
può interrompere l'esecuzione di una ISR associata ad un'altra IRQ
di priorità strettamente inferiore; si parla allora di nested interrupts
(interruzioni innestate)!
3.3.1 Collegamento dei PIC in cascata
Lo schema di Figura 3.2 è tipico dei primissimi PC di classe XT,
comparsi sul mercato all'inizio degli anni 80; sin da allora, però, ci
si è resi subito conto che 8 sole periferiche gestibili erano veramente
poche.
Fortunatamente, il PIC 8259 presenta la caratteristica di potersi
collegare in "cascata" ad altri PIC 8259; a tale proposito, è necessario
servirsi dell'apposito Cascade Bus costituito dalle tre linee CAS0,
CAS1 e CAS2.
Per capire il funzionamento dei PIC in cascata, è necessario partire dal
fatto che le CPU della famiglia 80x86 sono dotate di un unico pin
INT; solamente uno dei PIC in cascata può quindi inviare gli
impulsi verso la CPU!
Per risolvere questo problema, è stato adottato lo schema di Figura 3.3 che si
riferisce al caso molto diffuso di due soli PIC in cascata.
Osserviamo subito che l'uscita INT del PIC inferiore è collegata
ad uno degli ingressi IR del PIC superiore; in base a questa
configurazione, il PIC superiore viene definito Master
(letteralmente, "padrone") mentre il PIC inferiore viene definito
Slave (letteralmente, "schiavo").
Come vedremo più avanti, durante la fase di inizializzazione possiamo informare
il PIC Master sul fatto che uno dei suoi ingressi è collegato, non ad una
periferica, bensì ad un PIC Slave; in questo modo, il PIC Master è
in grado di sapere se una determinata IRQ è arrivata direttamente allo
stesso PIC Master o indirettamente da un PIC Slave.
Se una IRQ arriva direttamente al PIC Master, l'elaborazione procede
come già descritto in precedenza; in tal caso, il PIC Master invia l'impulso
INT e riceve i due impulsi INTA provvedendo poi a fornire l'Interrupt
Type alla CPU.
Se una IRQ arriva ad uno dei PIC Slave, lo stesso PIC Slave
invia l'impulso INT il quale, però, raggiunge il PIC Master; lo
stesso PIC Master è stato programmato in modo da poter determinare quale
PIC Slave ha inviato l'impulso.
Il PIC Master invia l'impulso INT alla CPU e poi, attraverso
il Cascade Bus, seleziona il PIC Slave che ha ricevuto la IRQ;
di conseguenza, i due impulsi INTA inviati dalla CPU raggiungono il
PIC Slave selezionato, il quale può così fornire l'Interrupt Type per
l'elaborazione.
Osserviamo che il Cascade Bus è formato da 3 linee attraverso le
quali il PIC Master può selezionare sino a 23=8 PIC
Slave differenti; ciascuno dei PIC Slave può gestire 8
periferiche, per un totale quindi di 8*8=64 periferiche!
Dalle considerazioni appena esposte risulta evidente che solo uno dei PIC
può svolgere il ruolo di Master; tutti gli altri possono svolgere solamente
il ruolo di Slave e quindi, le loro uscite INT devono essere
collegate ai vari ingressi IR del PIC Master.
3.4 Programmazione del PIC 8259
I PIC possono essere totalmente inizializzati e configurati in base alle
esigenze del programmatore; è chiaro però che la riprogrammazione completa dei
PIC ha senso solamente in circostanze del tutto particolari (ad esempio,
quando si intende scrivere un proprio SO)!
I PC meno vecchi della famiglia 80x86 sono dotati di due PIC
8259 collegati in cascata; in fase di avvio del computer, il BIOS
provvede ad effettuare tutto il lavoro di diagnosi, inizializzazione e
configurazione dei due PIC.
Lo schema adottato è proprio quello di Figura 3.3. L'uscita INT del
PIC Slave è collegata all'ingresso IR2 del PIC Master; la
IRQ2 viene "dirottata" all'ingresso IR1 del PIC Slave (e
si comporta quindi come una IRQ9).
A sua volta, anche il SO può provvedere a riprogrammare i PIC in
base alle proprie esigenze; in genere, tale lavoro consiste nel redistribuire
le IRQ alle varie periferiche.
Per l'accesso a ciascun PIC sono disponibili due sole porte hardware
denominate, simbolicamente, P0 e P1; gli indirizzi di tali porte
(come di qualsiasi altra porta hardware) sono stati scelti in base a precise
convenzioni.
La Figura 3.4 indica le convenzioni legate ai PC della famiglia
80x86; il PIC Master viene indicato con MPIC, mentre il
PIC Slave viene indicato con SPIC.
Nella sezione Assembly Base abbiamo visto che la Control Logic
(CL) permette alla CPU di distinguere tra indirizzi appartenenti
alla memoria RAM e indirizzi appartenenti alle porte hardware delle
periferiche; a tale proposito, la CL non fa altro che analizzare il tipo
di indirizzamento specificato in una istruzione.
In presenza di istruzioni del tipo IN (Input From Port) e OUT
(Output To Port), la CL capisce che vogliamo effettuare una operazione di
I/O che coinvolge una periferica; di conseguenza, la stessa CL
provvede a disabilitare la RAM e a mettere in comunicazione la
CPU con la periferica stessa!
3.4.1 Comandi di inizializzazione del PIC
Per l'inizializzazione dei PIC sono disponibili 4 comandi a 8
bit denominati ICW o Initialization Command Word; questi comandi
devono essere specificati in perfetto ordine: ICW1, ICW2 e, se
richiesto, ICW3 e ICW4.
Un aspetto fondamentale riguarda il fatto che la fase di inizializzazione, per
motivi abbastanza ovvi, deve svolgersi con tutte le interruzioni mascherate. Prima
di dare il via a tale fase, è necessaria quindi una istruzioni CLI;
terminata l'inizializzazione, sarà necessaria una istruzione STI per
ripristinare l'elaborazione delle interruzioni mascherabili.
La Figura 3.5 illustra la struttura del comando ICW1; tale comando deve essere
scritto nella porta 20h del PIC Master e, se necessario
(PIC in cascata), anche nella porta A0h del PIC Slave.
Non appena viene raggiunto da un comando ICW1, individuato dal bit 4
che deve valere 1, il PIC si resetta completamente e resta in attesa,
come minimo, di un successivo comando ICW2; se il bit 0 di ICW1
vale 1, allora il PIC attende anche i due ulteriori comandi ICW3
e ICW4.
Se i vari comandi non vengono impartiti in perfetto ordine, l'inizializzazione
fallisce; in particolare, se dopo una sequenza di ICW si invia nuovamente
un ICW1, il PIC fa ripartire da zero la fase di inizializzazione.
Il bit in posizione 1 indica se è presente un unico PIC o se sono
presenti più PIC in cascata; nel caso di Figura 3.3, ad esempio, questo bit
deve valere 0.
Il bit in posizione 2 indica la dimensione in byte di ogni Interrupt
Vector; nella modalità reale 80x86 tale dimensione è di 4 byte, per
cui questo bit deve valere 0.
Il bit in posizione 3 indica la modalità di rilevamento di una IRQ
da parte del PIC; le due modalità disponibili sono edge-triggered e
level-triggered.
In modalità edge-triggered, la IRQ viene rilevata quando il relativo
ingresso IR del PIC si trova nella fase di transizione da livello
logico 0 a livello logico 1; in modalità level-triggered, la
IRQ viene rilevata quando il relativo ingresso IR del PIC passa
da livello logico 0 a livello logico 1 stabile.
Le CPU della famiglia 80x86 utilizzano la modalità
edge-triggered, per cui il bit di ICW1 in posizione 3 deve
valere 0; la modalità level-triggered viene impiegata nelle
architetture IBM PS/2.
I bit in posizione 5, 6 e 7 vengono utilizzati solo con le
CPU della famiglia MCS; per le CPU della famiglia 80x86
tali bit devono valere 000b.
Dopo aver ricevuto ICW1, il PIC si aspetta un ICW2; la
Figura 3.6 illustra la struttura di tale comando che deve essere scritto
nella porta 21h del PIC Master e, se necessario (PIC in
cascata), anche nella porta A1h del PIC Slave.
Il comando ICW2 serve per associare un Interrupt Type ad una IRQ;
in questo modo, il PIC può ricavare il valore n necessario alla
CPU per sapere quale ISR chiamare (INT n).
Come si nota in Figura 3.6, ICW2 deve specificare solamente i 5 bit
più significativi di n; questi 5 bit, nel loro insieme, formano
il cosiddetto BASE_TYPE. I 3 bit meno significativi di n
vengono ricavati dal numero che identifica la IRQ da elaborare.
Supponiamo, ad esempio, di aver assegnato al PIC Master un
BASE_TYPE=01010000b=50h; di conseguenza, alla IRQ0 sarà associato
n=50h+00h=50h, alla IRQ1 sarà associato n=50h+01h=51h e
così via, sino alla IRQ7 alla quale sarà associato n=50h+07h=57h .
Durante la fase di inizializzazione svolta dal BIOS, al PIC Master
viene assegnato un BASE_TYPE=00001000b=08h; al PIC Slave, invece,
viene assegnato un BASE_TYPE=01110000b=70h.
Se il bit 0 di ICW1 vale 1, allora il PIC attende
anche i due ulteriori comandi ICW3 e ICW4; in particolare, il
comando ICW3 ha lo scopo di impostare il collegamento in cascata tra
due o più PIC.
La Figura 3.7 illustra la struttura del comando ICW3 che deve essere
scritto esclusivamente nella porta 21h del PIC Master.
In sostanza, se il bit in posizione k (con k compreso tra 0
e 7) vale 0, allora alla linea IRk del PIC Master
arriva direttamente una IRQk; se, invece, il bit in posizione k vale
1, allora alla linea IRk del PIC Master arriva un impulso
INT da un PIC Slave identificato da CAS=k.
A questo punto dobbiamo programmare opportunamente anche i vari PIC Slave;
a tale proposito, a ciascun PIC Slave dobbiamo inviare un ICW3
che contiene il valore (CAS) corrispondente all'ingresso IR del
PIC Master a cui lo stesso PIC Slave è collegato.
La Figura 3.8 illustra la struttura del comando ICW3 che deve essere
scritto esclusivamente nella porta A1h di ogni PIC Slave;
si tratta, in sostanza, del valore che il PIC Master inserisce nel
Cascade Bus per attivare il PIC Slave che ha ricevuto la
IRQ da elaborare.
Nel caso di Figura 3.3, ad esempio, dobbiamo inviare il valore 00000100b=04h
alla porta 21h del PIC Master e il valore 00000010b=02h alla
porta A1h del PIC Slave; in questo modo il PIC Master, quando
riceve un impulso INT sull'input IR2, inserisce nel Cascade
Bus il valore 010b=2 per attivare il PIC Slave destinatario
della IRQ da elaborare.
L'ultimo comando di inizializzazione che dobbiamo esaminare è ICW4; tale
comando deve essere scritto nella porta 21h del PIC Master e,
se necessario (PIC in cascata), anche nella porta A1h del PIC
Slave.
Il bit in posizione 0 deve valere sempre 1; il valore 0 viene
usato solo con le CPU della famiglia MCS.
Il bit in posizione 1 è molto importante in quanto specifica la modalità
secondo la quale viene segnalato un End Of Interrupt al PIC che ha
rilevato la IRQ appena elaborata; come sappiamo, l'EOI serve per
riportare a zero l'opportuno bit del registro ISR (In Service Register)
del PIC.
Il "modo normale" (0) è quello convenzionalmente usato con le CPU
della famiglia 80x86; in tale modalità, è compito della ISR inviare
l'impulso EOI al PIC (vedere il comando OCW2, più avanti).
Il "modo automatico" (1) lascia al PIC stesso il compito di gestire
l'EOI; in tale modalità, dopo aver ricevuto il secondo impulso INTA
dalla CPU, il PIC riporta a 0 l'opportuno bit del registro
ISR e invia l'Interrupt Type attraverso il Data Bus.
I bit in posizione 2 e 3 permettono di attivare o disattivare la
bufferizzazione delle informazioni da inviare attraverso il Data Bus. La
bufferizzazione viene usata su sistemi complessi comprendenti numerosi PIC
collegati in cascata; nel caso dei PC con due soli PIC, la
bufferizzazione è disabilitata, per cui questi due bit vengono inizializzati dal
BIOS a 00b.
Il bit in posizione 4 indica il metodo seguito dal PIC per la
gestione delle varie IRQ in attesa di elaborazione; la modalità standard
prevede una gestione di tipo sequenziale (0). In sostanza, il PIC
attende la fine dell'elaborazione di una IRQ prima di inviare la successiva
richiesta alla CPU; come sappiamo, una ISR può anche servirsi della
istruzione STI per abilitare le IRQ innestate (in tal caso, una
ISR può essere interrotta dall'arrivo di una IRQ con priorità più
alta).
Se il bit in posizione 4 vale 1, viene utilizzata la modalità
SFNM (Special Fully Nested Mode) che permette complessi livelli di innesto
per le IRQ; per maggiori dettagli su questo delicato argomento, si consiglia
di leggere la documentazione tecnica citata nella Bibliografia.
Per riassumere tutti i concetti appena esposti, possiamo analizzare un esempio
pratico di inizializzazione; bisogna ribadire, comunque, che normalmente tale
lavoro è di competenza del BIOS e, se necessario, del SO.
Come sappiamo, gli indirizzi di porta devono essere specificati attraverso il
registro DX; se l'indirizzo occupa solo 8 bit, può essere specificato
anche attraverso un Imm8.
In seguito all'inizializzazione standard effettuata dal BIOS, risultano
definite le associazioni tra periferiche (IRQ) e Interrupt Type (n);
la Figura 3.10 illustra la situazione più diffusa per il PIC Master.
La Figura 3.11 illustra la situazione più diffusa per il PIC Slave.
Gli utenti DOS possono verificare l'assegnamento delle IRQ attraverso
programmi come MSD (Microsoft Diagnostics); in ambiente Windows si
può utilizzare il Microsoft System Information.
In ambiente Linux sono disponibili programmi come Centro Informazioni
di KDE/Plasma; in alternativa, da un terminale si può impartire il comando:
cat /proc/interrupts
Cosa succede quando arriva una IRQ2?
La periferica che invia una IRQ2 installa in memoria una ISR associata
alla INT 0Ah; come si vede, però, in Figura 3.3, la IRQ2 viene dirottata
all'ingresso IR1 del PIC Slave e diventa quindi una IRQ9,
associata ad una INT 71h.
Per ovviare a questo problema, la ISR associata alla INT 71h deve
contenere sempre una istruzione INT 0Ah; sui moderni PC, la IRQ2
(dirottata alla IRQ9) è spesso associata alla gestione dell'ACPI
(Advanced Control Power Interface).
3.4.2 Comandi operazionali del PIC
Dopo aver ricevuto l'ultimo comando ICW, il PIC tratta eventuali altri
comandi come OCW o Operational Command Word; si tratta di tre comandi a
8 bit (OCW1, OCW2 e OCW3), destinati a modificare la
modalità operativa del PIC.
I tre comandi OCW possono essere inviati in qualsiasi momento e in qualsiasi
ordine al PIC; la Figura 3.12 illustra il comando OCW1 che deve essere
letto/scritto attraverso la porta 21h del PIC Master o
A1h del PIC Slave.
Come si può notare, OCW1 permette l'accesso al registro IMR attraverso
il quale possiamo mascherare o "smascherare" determinate IRQ; il PIC
riconosce questo comando in quanto lo riceve attraverso la porta 21h (o
A1h) dopo che la fase di inizializzazione era già stata completata.
OCW1 è l'unico comando accessibile, sia in lettura, sia in scrittura;
l'accesso in lettura è necessario per dare al programmatore la possibilità di
modificare solo determinati bit dell'IMR, lasciando inalterati tutti gli
altri.
Supponiamo, ad esempio, di voler mascherare la IRQ1 (PIC Master); a
tale proposito possiamo scrivere:
Analogamente, per riabilitare la IRQ1 (PIC Master) possiamo scrivere:
La Figura 3.13 illustra il comando OCW2 che deve essere scritto nella
porta 20h del PIC Master o A0h del PIC Slave.
Il comando OCW2 viene riconosciuto dal fatto che i bit in posizione 3
e 4 valgono 00b; inoltre, tale comando deve essere inviato alla porta
20h del PIC Master o alla porta A0h del PIC Slave.
La struttura di OCW2 è piuttosto contorta per cui necessita di una analisi
attenta; attraverso questo comando possiamo inviare impulsi EOI e/o
modificare le priorità predefinite assegnate alle IRQ.
Come è stato già anticipato, a ciascuno degli 8 ingressi IR di un
PIC viene assegnata una priorità distinta, rappresentata da un numero
compreso tra 0 (max) e 7 (min); l'inizializzazione predefinita del
PIC prevede che venga assegnata la priorità più alta (0) all'ingresso
IR0 e la priorità più bassa (7) all'ingresso IR7.
Il programmatore ha la possibilità di alterare completamente questa situazione
attraverso il comando OCW2; in particolare, è possibile richiedere una
rotazione di 1 delle priorità, oppure una rotazione di un certo numero di
posti.
Il bit 7 di OCW2 indica se è richiesta (1) o meno (0)
una rotazione delle priorità; se questo bit vale 1, allora il livello di
rotazione viene specificato dal bit in posizione 6.
Se il bit 7 vale 1 e il bit 6 vale 0 tutte le
priorità vengono ruotate di 1 posto verso sinistra; partendo allora dalla
situazione predefinita (IR0=max e IR7=min), il nuovo ordine diventa:
IR7=max, IR6=min. Si ottiene cioè la situazione seguente:
Se il bit 7 vale 1 e il bit 6 vale 1, l'ingresso
con priorità minima diventa quello specificato dai bit in posizione 0, 1, 2
(livello di rotazione); ovviamente, questi tre bit permettono di specificare un
valore compreso tra 0 (IR0) e 7 (IR7).
Partendo allora dalla situazione predefinita (IR0=max e IR7=min) e
supponendo che il livello di rotazione sia 4 (IR4), il nuovo ordine
diventa: IR5=max, IR4=min. Si ottiene cioè la situazione seguente:
La tecnica appena descritta viene utilizzata per evitare che una IRQ con
elevata priorità, possa interrompere continuamente le richieste di I/O
da parte di periferiche con priorità inferiore; in sostanza, la IRQ con
elevata priorità viene "servita" e poi le viene assegnata la priorità più
bassa in modo da lasciare spazio anche alle altre richieste di I/O con
priorità inferiore.
Sicuramente, il bit più utilizzato di OCW2 è quello in posizione 5;
se tale bit vale 1, viene inviato un segnale EOI al PIC.
Come è stato già spiegato in precedenza, il segnale EOI informa il
PIC sul fatto che l'elaborazione di una IRQ è terminata; in
conseguenza dell'EOI, il PIC pone a zero il bit che nel registro
ISR rappresenta la IRQ stessa.
Nel caso più frequente, quindi, il comando OCW2 assume il valore
00010000b=20h (EOI senza alcuna rotazione delle priorità); si
parla allora di "Specific EOI", cioè EOI relativo alla specifica
IRQ individuata dal corrispondente bit del registro ISR.
Il BIOS inizializza a 0 il bit 1 di ICW4 e questo
significa che, normalmente, il compito di inviare il segnale EOI attraverso
OCW2 spetta alla ISR associata alla IRQ da elaborare; è
importante tenere presente che il procedimento da seguire dipende dalla eventualità
che la IRQ sia arrivata al PIC Master o al PIC Slave.
Se la IRQ è arrivata al PIC Master, il segnale EOI deve
essere inviato solo allo stesso PIC Master; dobbiamo scrivere, quindi:
Se la IRQ è arrivata al PIC Slave, il segnale EOI deve
essere inviato, prima allo stesso PIC Slave e subito dopo al PIC
Master; dobbiamo scrivere, quindi:
La Figura 3.14 illustra il comando OCW3 che deve essere scritto nella
porta 20h del PIC Master o A0h del PIC Slave.
Attraverso i bit in posizione 0 e 1 possiamo effettuare la lettura
dei registri IRR e ISR del PIC; a tale proposito, dobbiamo
utilizzare i due valori 10b e 11b, mentre 00b e 01b
sono riservati.
Se scriviamo OCW3=00001010b nel PIC, una successiva lettura della
porta P0 ci fornisce il contenuto del registro IRR; se scriviamo
OCW3=00001011b nel PIC, una successiva lettura della porta P0
ci fornisce il contenuto del registro ISR.
Il bit in posizione 2 permette di attivare (1) o disattivare
(0) il polling delle IRQ; quando la modalità di polling è attiva,
la CPU può "ordinare" al PIC di inviare la prossima IRQ da
elaborare!
I computer che utilizzano la tecnica del polling delle IRQ non hanno
ovviamente bisogno della linea INT che mette in collegamento il PIC
Master con la CPU; a tale proposito, il pin INT della stessa
CPU viene lasciato scollegato.
Se scriviamo OCW3=00001100b nel PIC, una successiva lettura della
porta P0 ci fornisce un valore a 8 bit che assume il seguente aspetto:
Se il bit IN vale 1, allora una nuova IRQ è in attesa di
elaborazione; in tal caso, i tre bit W0, W1 e W2 indicano
a quale ingresso IR è arrivata la IRQ con priorità maggiore.
In questo modo, conoscendo il BASE_TYPE, possiamo ricavarci il valore
n da passare all'istruzione INT; da parte sua, il PIC
provvede ad auto inviarsi un EOI che notifica l'avvenuta elaborazione
della IRQ.
Se il bit in posizione 6 vale 1, allora il bit in posizione
5 permette di attivare (1) o disattivare (0) la modalità
speciale di mascheramento; se questa modalità è attiva, una ISR che
sta elaborando una IRQ può essere interrotta dall'arrivo di un'altra
IRQ avente priorità strettamente inferiore o strettamente superiore!
3.5 Un esempio pratico: interfaccia con la tastiera
Ogni volta che premiamo un tasto, l'hardware della tastiera genera un codice che
prende il nome di scan code (codice di scansione); tale codice è standard
(per tutte le tastiere compatibili) e non ha niente a che vedere con il simbolo
stampato sul tasto premuto.
Nel caso più semplice, lo scan code di un tasto premuto ha una ampiezza di
8 bit, con il bit più significativo che vale zero; i 7 bit meno
significativi permettono quindi di rappresentare un totale di
27=128 scan codes differenti.
Quando un tasto viene rilasciato, la tastiera genera l'analogo scan code dello
stesso tasto premuto; la differenza fondamentale sta nel fatto che il bit più
significativo dello scan code questa volta vale 1.
Ad esempio, lo scan code del tasto [A] premuto vale 00011110b=1Eh;
lo scan code del tasto [A] rilasciato vale 10011110b=9Eh.
Diversi tasti della tastiera, quando vengono premuti, generano uno scan code
formato da due codici a 8 bit, con il primo codice che vale sempre
E0h e il secondo codice che ha il bit più significativo che vale 0;
questi particolari tasti prendono il nome di extended keys (tasti estesi).
Quando un tasto esteso viene rilasciato, l'hardware della tastiera genera
nuovamente due codici, con il primo che vale ugualmente E0h; il secondo
codice, come al solito, presenta il bit più significativo che vale 1.
Ad esempio, lo scan code del tasto [Ctrl Right] premuto vale
11100000b 00011101b = E0h 1Dh; lo scan code del tasto [Ctrl Right]
rilasciato vale 11100000b 10011101b = E0h 9Dh.
La Figura 3.15 illustra gli scan codes (tasti premuti) che caratterizzano le
tastiere IBM compatibili, utilizzate dai PC della famiglia hardware
80x86; gli stessi scan codes sono disponibili anche nella apposita
tabella.
Osserviamo i due casi particolari rappresentati dai due tasti [Print]
e [Pause]; la pressione del tasto [Print] produce lo scan code
E0h 2Ah E0h 37h, mentre la pressione del tasto [Pause] produce
lo scan code E1h 1Dh 45h E1h 9Dh C5h!
Ogni singolo codice a 8 bit generato dall'hardware della tastiera, viene
inserito in un apposito buffer dati, accessibile attraverso la porta 60h
del dispositivo (controller) che permette al sistema di interfacciarsi con la
tastiera stessa; subito dopo, il controller genera una richiesta di I/O
che viene inviata ad un PIC.
Come si nota in Figura 3.10, tale richiesta di I/O giunge alla linea IR1
del PIC Master, per cui si tratta di una IRQ1; di conseguenza, lo
stesso PIC Master associa la IRQ1 all'Interrupt Type 09h.
Nel caso particolare dei tasti estesi, i due codici a 8 bit generati dalla
tastiera risultano disponibili attraverso due IRQ1 consecutive; questo
perché, come è stato appena spiegato, viene generata una IRQ1 per ogni
singolo codice a 8 bit (stesso discorso per i 4 codici del tasto
[Print] e per i 6 codici del tasto [Pause])!
La CPU soddisfa la IRQ1 attraverso l'istruzione INT 09h; il
compito della ISR, chiamata da questa istruzione, è quello di leggere il
prossimo codice a 8 bit dalla porta 60h e di metterlo a disposizione
dei programmi dopo averlo sottoposto alle opportune elaborazioni.
Uno dei compiti più importanti svolto dalla ISR è quello di convertire lo
scan code nel codice ASCII
del simbolo stampato sul tasto premuto; questa tecnica permette la cosiddetta
"internazionalizzazione delle tastiere", nel senso che, in base alla nazione a cui
la tastiera è destinata, basta cambiare gli opportuni simboli sui tasti senza
apportare alcuna modifica all'hardware!
Un compito piuttosto complesso, svolto dalla ISR, è quello di gestire
adeguatamente anche il caso in cui l'utente prema più tasti contemporaneamente
(ad esempio, [Alt Left] + [F1]); si tenga presente, infatti, che anche in
tali situazioni la tastiera genera separatamente gli scan codes dei singoli
tasti!
Se vogliamo analizzare in pratica le considerazioni appena esposte, non dobbiamo
fare altro che intercettare la INT 09h; a tale proposito, come è stato
già spiegato nella sezione Assembly Base, le fasi da svolgere sono le
seguenti:
- salvare il vecchio vettore di interruzione 09h
- installare la nuova ISR
- completare l'esecuzione del programma
- ripristinare il vecchio vettore di interruzione 09h
- terminare il programma
Questa volta la novità è data dal fatto che stiamo intercettando una richiesta
di interruzione che proviene da una periferica; di conseguenza, la nostra
ISR dovrà anche procedere all'invio dell'EOI al PIC che
ha ricevuto la IRQ!
La Figura 3.16 illustra un semplice esempio che si serve della libreria
COMLIB; la nuova ISR installata dal programma KEYBOARD.COM
si limita a visualizzare sullo schermo i vari scan codes letti dalla porta
hardware 60h.
Osserviamo il metodo seguito nel listato di Figura 3.16 per attivare/disattivare
le interruzioni mascherabili; anziché utilizzare le istruzioni CLI e
STI, ci serviamo di una tecnica più sofisticata che permette di agire
solamente sulla linea IR che ci interessa.
Per disabilitare temporaneamente la linea IR1 del PIC Master,
possiamo scrivere:
Analogamente, per riabilitare la linea IR1 del PIC Master,
possiamo scrivere:
La nuova ISR installata dal programma, verifica se è stato premuto un
tasto "normale" o "esteso"; nel primo caso, viene visualizzato direttamente
il relativo scan code rappresentato da un unico codice a 8 bit. Nel
secondo caso, viene visualizzato il primo codice E0h; subito dopo, si
attende la successiva IRQ1 per poter leggere e visualizzare il secondo
codice da 8 bit.
La ISR è anche in grado di visualizzare correttamente lo scan code da
6 byte del tasto [Pause]; per il tasto [Print], invece,
vengono mostrati solo gli ultimi due codici.
Si noti che nella ISR, prima di leggere il prossimo codice dalla porta
60h, viene effettuato un loop il cui scopo è quello di attendere il "via
libera" per la lettura dalla tastiera; più avanti vengono illustrati maggiori
dettagli su questo importante aspetto.
Se proviamo a commentare le istruzioni della ISR che inviano il segnale
EOI, possiamo constatare che il programma non risponde più ai nostri
comandi; infatti, il bit 1 del registro ISR nel PIC Master
rimane settato a 1 bloccando l'elaborazione di ulteriori IRQ1. In
un caso del genere, se si sta lavorando in ambiente DOS puro, è necessario
spegnere il computer in quanto non è ovviamente possibile riavviare con la
sequenza di tasti [Ctrl]+[Alt]+[Canc]!
Se, al termine del programma, dimentichiamo di ripristinare il vecchio vettore
09h, non saremo più in grado di usare la tastiera; come al solito, se
un problema del genere si verifica in ambiente DOS puro, è necessario
spegnere il computer!
3.5.1 Considerazioni sulla programmazione della tastiera
Nel corso degli anni, l'hardware della tastiera ha subito notevoli evoluzioni,
spesso legate a particolari modelli di PC; proprio per questo motivo, la
programmazione di tale periferica risulta spesso difficoltosa.
Inizialmente, ai tempi dei primi PC di classe XT, la IBM
impose una tastiera standard denominata, appunto, "XT keyboard"; si tratta
di un modello di tastiera riconoscibile dal fatto che sono presenti su di essa
solamente 84 tasti.
In seguito, con l'avvento dei PC di classe AT, la IBM ha
aggiornato anche la tastiera imponendo un modello standard denominato, appunto,
"AT keyboard"; in questo caso, il numero dei tasti è salito a 101
o a 102.
Una ulteriore evoluzione è arrivata con l'avvento dell'architettura PS/2,
imposta dalla IBM per i PC; questa nuova classe di PC è
stata affiancata da un nuovo modello di tastiera denominato, appunto, PS/2
keyboard.
Le tastiere PS/2 sono totalmente compatibili con quelle AT, per
cui vengono largamente impiegate anche sui PC che non utilizzano
l'architettura PS/2; il successo ottenuto da questo standard è legato
anche al fatto che l'hardware (8042 controller) preposto alla gestione
della tastiera PS/2, permette di controllare anche un mouse il quale,
proprio per questo motivo, prende il nome di PS/2 mouse (riconoscibile
dal classico connettore tondo).
A complicare ulteriormente la situazione sono poi arrivati numerosi modelli di
tastiere personalizzate; questi nuovi modelli, pur mantenendo la piena
compatibilità con gli standard AT e PS/2, hanno introdotto una
serie di tasti speciali destinati al particolare modello di PC a cui è
associata la tastiera (e spesso anche al particolare SO installato sul
PC).
Si possono citare, ad esempio, i tasti ("play", "pause", "eject", ...) per la
gestione dei CD Audio, i tasti per la connessione ad Internet, i tasti
personalizzati per Windows, etc; in genere, questi tasti speciali possono
essere rimappati in modo da poterli usare anche con altri SO.
In riferimento ai modelli più recenti di tastiere (compatibili con gli standard
AT e PS/2), è necessario sottolineare che l'interfacciamento
con il PC è gestito attraverso un doppio controller denominato,
genericamente, 8042; uno dei controller è installato sulla tastiera e
prende il nome di keyboard controller (KBC), mentre l'altro è
installato sul PC e prende il nome di on-board controller
(OBC).
Lo scopo di questi due controller è quello di gestire le comunicazioni
bidirezionali tra PC e tastiera; infatti, contrariamente a quanto molti
pensano, la tastiera oltre a inviare dati al PC può anche ricevere una
serie di appositi comandi!
La gestione delle comunicazioni tra tastiera e PC è di competenza del
BIOS e del SO; il programmatore, a meno che non stia scrivendo
un proprio SO, deve quindi rigorosamente evitare di modificare lo stato
operativo della tastiera stessa.
Tanto per citare un aspetto emblematico, tutte le operazioni (pressione e
rilascio dei vari tasti) compiute dall'utente sulla tastiera, vengono registrate
in dettaglio dal BIOS e memorizzate in una apposita area della BDA;
la Figura 3.17, ad esempio, mostra le informazioni presenti all'indirizzo
0040h:0017h della stessa BDA.
Appare evidente quindi che qualunque modifica apportata dal programmatore alla
configurazione della tastiera, necessita del conseguente aggiornamento delle
informazioni presenti nella BDA; in caso contrario, si impedisce agli altri
programmi di funzionare correttamente (a causa di incongruenze tra il contenuto
della BDA e lo stato hardware della tastiera)!
Le operazioni di I/O con l'OBC avvengono attraverso la porta
64h; tale porta permette di scrivere comandi o di leggere lo stato della
tastiera; la Figura 3.18 illustra le informazioni che si ottengono in seguito
alla lettura della porta 64h.
Lo Status Byte è molto utile in quanto ci permette di sapere in quale
esatto momento possiamo dare inizio alle comunicazioni con la tastiera; ad
esempio, prima di inviare un comando alla tastiera dobbiamo attendere che il
bit 1 dello Status Byte si sia portato a 0 (input buffer
vuoto).
Le operazioni di I/O con il KBC avvengono attraverso le porte
60h e 64h; dopo aver verificato lo Status Byte attraverso
la porta 64h, possiamo dare il via alla lettura/scrittura di dati o
comandi attraverso la porta 60h.
Ad esempio, prima di leggere un dato dalla porta 60h, dobbiamo attendere
che il bit 0 dello Status Byte si sia portato a 1 (output
buffer pieno); a tale proposito, possiamo servirci del seguente codice:
Osserviamo che CX viene inizializzato a 0; di conseguenza, alla
prima iterazione si ottiene:
CX = 0 - 1 = FFFFh = 65535
Si tratta del classico trucco che, sfruttando il wrap around, ci
permette di ripetere un loop sino a 65536 volte, anche se il registro
contatore (CX) è a 16 bit!
Il loop viene ripetuto solo se CX è maggiore di zero e se,
contemporaneamente, l'istruzione TEST produce ZF=1; tenendo
conto dei tempi di risposta dell'hardware della tastiera, abbiamo la certezza
che durante le 65536 iterazioni il bit 0 dello Status
Byte si porterà sicuramente a 1!
Vediamo un semplice esempio pratico che mostra come gestire i tre diodi LED
posizionati sulla tastiera in alto a destra; come molti sanno, tali "spie
luminose" indicano lo stato delle opzioni "Caps Lock", "Numeric Lock" e
"Scroll Lock".
Questo esempio funziona solo quando si opera in ambiente DOS puro; è
chiaro, infatti, che i SO come Windows e Linux impediscono
l'accesso diretto all'hardware del computer!
I LED della tastiera possono essere pilotati attraverso il comando EDh
(set/reset mode indicators); tale comando deve essere inviato attraverso la
porta 60h.
Dopo aver ricevuto il comando EDh, la tastiera resta in attesa di un
secondo comando contenente le impostazioni per i tre diodi led; la struttura
del secondo comando, da scrivere sempre nella porta 60h, è illustrata
in Figura 3.19.
Prima di tutto creiamoci una apposita procedura il cui compito è quello di
scrivere nella porta 60h dopo aver atteso il via libera (input buffer
vuoto) tramite la porta 64h.
A questo punto, per accendere tutti i tre LED della tastiera possiamo
scrivere:
Dopo aver eseguito queste istruzioni, è importante riportare i LED allo stato
precedente, in modo che non ci siano incongruenze con le informazioni presenti
nella BDA; a tale proposito, è necessario evitare la pressione
dei tre appositi tasti.
La cosa migliore da fare consiste nel rieseguire il precedente codice caricando
l'opportuno valore in AL; ad esempio, se in origine era accesa la sola
spia "Numeric Lock", dobbiamo porre AL=00000010b.
Bibliografia
Intel - Interfacing the 82C59A to Intel 186 Family Processors
(27282201.pdf)
Intel - Understanding the Interrupt Control Unit of the 80C186EC/80C188EC Processor
(27282301.pdf)
Intel - 80C186EB/80C188EB Microprocessor User's Manual (Capitolo 8)
(27083003.pdf)
Intel - 82093AA I/O ADVANCED PROGRAMMABLE INTERRUPT CONTROLLER (IOAPIC)
(29056601.pdf)
IBM Technical Reference Manual - 8042 Keyboard Controller
(8042.pdf)