Programmable Interrupt Controller
Nachdem wir zuletzt die IDT angelegt haben, wollen wir jetzt nicht mehr nur CPU-Interrupts, sondern auch andere Hardware-Interrupts empfangen. Diese gehen über den sogenannten Programmable Interrupt Controller. Dieser leitet die Interrupts von den einzelnen Hardware-Geräten an die CPU weiter.
Er ist standardmäßig jedoch so konfiguriert, dass es zu einer Kollision mit den Interrupts der x86-Spezifikation im Protected Mode kommt. Deshalb müssen wir ihn umprogrammeren, bevor wir ihn verwenden können.
Viele Freiheiten gibt es hier eigentlich nicht, wir müssen nur einem definierten Ablauf folgen.
Initialisierung
Genau genommen gibt es zwei PIC im Master-Slave-Modus, die jeweils 8 Interrupts signalisieren können. Kommuniziert wird pro PIC über einen Command- und einen Data-Port. Für den Master-PIC sind dies 0x20
(Command) und 0x21
(Data), für den Slave-PIC 0xa0
(Command) und 0xa1
(Data).
Die Initialisierung sieht folgendermaßen aus:
- ICW1 (Initialization Command Word 1) auf Port
0x20
schreiben - ICW2 auf Port
0x21
schreiben (ICW2 = Offset der Interrupt-Nummern) - Falls Bit D1 von ICW1 Null ist, schreibe ICW3 auf Port
0x20
- Schreibe ICW4 auf Port
0x21
- OCWs (Operation Control Word)
Zunächst einmal können wir die Adressen und die von uns exakt benötigten ICWs definieren:
Diese müssen wir dann in oben beschriebener Reihenfolge an die Geräte senden. ICW2 ist wie oben geschrieben der gewünschte Anfang der Interrupt-Nummern, kommt also im Idealfall von außen. ICW3 ist für die beiden Chips unterschiedlich, daher habe ich sie mal nicht extra definiert.
Im OSDev-Wiki wurden die ICWs etwas sauberer definiert, indem für jedes Bit ein #define
angelegt wurde, sodass man diese hinterher leicht addieren kann. Außerdem wird dort vor dem Schreiben der PICs noch der aktuelle Status gesichert und später zurückgeschrieben. Letztere Idee wollen wir auch gleich übernehmen:
Diese Funktion können wir dann beim Hochfahren des Betriebssystems in etwa so aufrufen:
Meines Wissens kamen die inb()
- und outb()
-Funktionen bisher noch nicht vor, daher müssen wir die auch noch definieren. Diese stellen im Grunde nur Wrapper um die gleichlautenden Assembler-Befehle dar:
End of Interrupt
Wichtig für das Interrupt-Handling ist zudem noch, dass man dem PIC mitteilen muss, wenn man einen Interrupt erfolgreich abgearbeitet hat. Ist nur der Master-PIC in den Interrupt involviert, muss man nur diesen signalisieren. Kommt der Interrupt vom Slave-PIC, muss man beide signalisieren. Dies geht glücklicherweise extrem einfach, man muss lediglich 0x20
an den entsprechenden Command-Port senden.
; Slave PIC
mov al, 0x20
out 0xa0, al
; Master PIC
mov al, 0x20
out 0x20, al
Timer-Interrupt
Auf IRQ0 sendet der Interrupt Controller in Abständen von 55ms einen Timer-Interrupt. Nach unserer obigen Umprogrammierung wäre das also auf dem Interrupt-Vektor 0x20
.
Diesen können wir im Folgenden behandeln und uns eine neue Anzeige aufs Display schreiben, z.B. eine ganz grobe Uhr. Ein Timer von 55ms bedeutet, dass der Timer ganz grob 18mal pro Sekunde aufgerufen wird. Um uns die Sache noch etwas einfacher zu machen, wollen wir zunächst auch mal keine Zahlen, sondern einfach nur wechselnde ASCII-Symbole. Es reicht also, wenn wir ein Byte durchzählen von 0 bis 256 und dieses auf den Ausgabespeicher schreiben.
global int_handler_32
int_handler_32:
mov ax, 0x10
mov gs, ax ; set right data segment (should already be, but still)
xor ax, ax ; make sure higher part of ax is zero
mov al, byte [gs:int_counter] ; read the current count
inc al
mov byte [gs:int_counter], al ; store back the incremented count
mov bl, 18 ; one second = 18 ticks
div bl
mov byte [gs:0xB8000], al ; write the current second (= current ASCII symbol)
mov al, 0x20 ; interrupt is finished
out 0x20, al
iret
Diesen Interrupt müssen wir natürlich auch noch in der IDT registrieren:
Und wenn alles richtig gemacht wurde, sollten oben links dann verschiedene Symbole im Takt blinken. In Bochs eventuell viel zu schnell (das kann passieren und ist normal), in QEMU sollte die Geschwindigkeit stimmen.
Den aktuellen Stand auf Github gibt es diesmal leider nicht, weil ich mit meiner eigenen Entwicklung bei den Interrupts in einer ganz anderen Situation bin. Demnächst machen wir in einem Tutorial mit Tastatureingaben weiter und dann sollte sich der Status wieder grob angleichen.
I do not maintain a comments section. If you have any questions or comments regarding my posts, please do not hesitate to send me an e-mail to blog@stefan-koch.name.