{{Navigation
|Zurück = [[ARM-OS-Dev Teil 4 – Hello World]]
|Vor = [[ARM-OS-Dev Teil 6 – Multitasking]]
}}
== Exceptions ==
Es gibt verschiedene Gründe, die auf einem ARM eine Exception, also eine Unterbrechung des normalen Programmflusses auslösen. An dieser Stelle sei auf den [[ARM#Exceptions|ARM-Artikel]] verwiesen, der all diese auflistet und beschreibt. Versucht man, die x86-Interrupts auf ARM-Exceptions abzubilden, dann entsprechen Exceptions einem Abort oder einer Undefined Instruction Exception, IRQs einem IRQ oder einem FIQ und Software-Interrupts einem SWI.
Jede Exception verlangt einen bestimmten CPU-Modus mit eigenen Registern. Wir verwenden in diesem Tutorial lediglich die Modi User/System, Supervisor und IRQ, weil wir nur die IRQ- und SWI-Exceptions behandeln (User/System werden für Programme verwendet).
== Initialisierung ==
IRQs verwenden ist auf dem ARM sehr einfach. Eigentlich muss man lediglich das I-Bit im CPSR löschen (s. [[ARM#CPSR|ARM-Artikel]]) und schon können IRQs aufgerufen werden. Aborts, Undefined Instruction Exceptions, und SWIs können sowieso jederzeit auftreten. Zusätzlich enthält eigentlich jedes ARM-System jedoch noch einen Interruptcontroller, damit das OS auch schnell feststellen kann, welcher IRQ aufgetreten ist – von sich aus kennt der Prozessor ja keinen Unterschied zwischen einem IRQ, der von der Netzwerkkarte und einem, der von der Tastatur ausgelöst wurde. Daher möchte auch dieser Interruptcontroller initialisiert werden. Auf dem I/CP-Board gibt es sogar mehrere Interruptcontroller, wobei wir hier nur einen einzigen verwenden (die meisten „interessanten“ IRQs werden von diesem Primary Interrupt Controller abgewickelt). Zudem gibt es jeden Interruptcontroller einmal für IRQs und einmal für FIQs. Wir wollen jedoch der Einfachheit halber nur IRQs benutzen.
Der erste Interruptcontroller bietet seine Register im Speicher ab der Adresse 0x14000000 an. Am Offset +8 befindet sich ein 32-Bit-Register (ENABLESET), mit dem IRQs aktiviert werden können, indem das entsprechende Bit gesetzt wird. Am Offset +12 ist ein Register (ENABLECLR), mit dem IRQs deaktiviert werden können, indem ihr Bit gesetzt wird. Beim Start sollten zunächst alle IRQs deaktiviert werden, damit zum Beispiel die drei Systemtimer nicht einfach wild feuern und die gesamte CPU-Zeit stehlen, wenn man sie nicht konfiguriert hat. Dies geschieht also, indem 0xFFFFFFFF nach 0x1400000C geschrieben wird. Wenn ein Handler für einen IRQ registriert wird, muss der IRQ dann aktiviert werden, indem das entsprechende Bit im ENABLESET-Register gesetzt wird.
Nach der Interrupt-Controller-Initialisierung kann dann einfach das I-Bit im CPSR gelöscht werden, sodass IRQs ausgelöst werden können:
.arm
.extern handle_irq
.global asm_handle_irq
asm_handle_irq:
sub r14, #4
/* Nicht gebankte Register speichern */
push {r0 - r12, r14}
/* Supervisor-R13/R14/SPSR und CPSR speichern */
msr cpsr_ctl, #0xD3
mrs r1, spsr
mov r2, r13
mov r3, r14
msr cpsr_ctl, #0xD2
mrs r0, spsr
push {r0 - r3}
/* User-R13/R14 speichern */
stmdb r13, {r13, r14}^
sub r13, #8
/* IRQ behandeln */
mov r0, r13
bl handle_irq
mov r13, r0
/* User-R13/R14 laden */
ldmia r13, {r13, r14}^
add r13, #8
/* Supervisor-R13/R14/SPSR und CPSR laden */
pop {r0 - r3}
msr spsr_all, r0
msr cpsr_ctl, #0xD3
msr spsr_all, r1
mov r13, r2
mov r14, r3
msr cpsr_all, #0xD2
/* Nicht gebankte Register laden und zurückkehren */
pop {r0 - r12, r14}
movs r15, r14
r14 zeigt zu Beginn auf den zweiten Befehl nach dem Befehl, der ausgeführt wurde, bevor der IRQ ausgelöst wurde. Das heißt, wir müssen vier abziehen, damit es auf den als nächstes auszuführenden Befehl zeigt. Dann speichern wir es und alle nicht-Schattenregister (r0 bis r12) auf dem IRQ-Stack. Nun müssen wir auch die Schattenregister speichern. Das betrifft r13, r14 und das SPSR, welches ebenfalls in jedem Prozessormodus extra vorhanden ist (außer im System/User-Modus, dort existiert es gar nicht, weil nur die CPU dieses Register zum Speichern von CPSR im Falle einer Exception benötigt; der User/System-Modus wird von keiner Exception betreten, sodass auch kein SPSR nötig ist). Also wechseln wir mit dem MSR-Befehl in den Prozessormodus 0x13 (mit deaktivierten IRQs und FIQs, deshalb insgesamt 0xD3), also den Supervisormodus, laden r13, r14 und das SPSR in nicht-Schattenregister und speichern dieser Werte nach der Rückkehr in den IRQ-Modus (0x12 ist der Wert für den IRQ-Modus) auf dem Stack, zusammen mit dem IRQ-SPSR, das den CPSR-Wert vor Auslösen des IRQs enthält.
Später möchten wir auch noch den Usermodus verwenden, deshalb müssen wir auch dessen r13 und r14 auf dem Stack speichern. Hier können wir uns einen Wechsel des Prozessormodus sparen, weil es eine spezielle Variante der LDM-/STM-Befehle gibt, die mit Usermoderegistern arbeitet (wird durch das Zirkumflex nach der Registerliste deutlich gemacht). Diese kann allerdings kein Base-Write-Back, sodass wir r13 anschließend manuell um acht verringern müssen.
Dann wird r13 nach r0 geladen, sodass die C-Funktion handle_irq den Stackpointer als ersten (und einzigen) Parameter erhält. Sie gibt einen neuen Stack in r0 zurück, der wieder nach r13 geladen wird. Von diesem Stack werden dann wieder alle Register geladen. Der Aufruf „movs r15, r14“ lädt r14 nach r15 und das SPSR ins CPSR (wechselt also auch den CPU-Modus; dies ist eine Besonderheit der Arithmetik- und Logikbefehle: Werden die ''s''-Varianten (die die Flags modifizierenden Varianten) und r15 als Zielregister verwendet, dann wird das SPSR ins CPSR geladen).
Hinweis: Anstatt am Anfang vier von r14 abzuziehen, könnte man am Ende auch einfach „subs r15, r14, #4“ statt des ''movs'' verwenden. Dies würde aber bedeuten, dass wir beim Erstellen eines neuen Prozesses, bei dem wir mit dem r14-Wert auf dem Stack den Einsprungspunkt des Programms festlegen, den tatsächlichen Wert plus vier nehmen müssten. Kann man machen, man kann aber auch einfach am Anfang des IRQ-Handlers vier von r14 abziehen und sich das somit sparen, so wie das hier getan wird.
Jetzt können wir noch den C-Teil in einer irq.c schreiben:
ldr r13, [r15, #kernel_stack_addr - 8 - .] mov r0, r13 msr cpsr_ctl, #0xD2 add r13, r0, #8192 msr cpsr_ctl, #0xD3Damit das funktioniert, müssen wir jedoch noch mal 8 kB Speicher nach dem Supervisorstack für den IRQ-Stack reservieren:
.space 8192 kernel_stack: .space 8192Irgendwo in der init-Funktion, möglichst am Anfang, sollte jetzt noch die init_irqs-Funktion aufgerufen werden. Theoretisch könnte man auch hier jetzt schon einen Tastaturtreiber schreiben, wenn man unbedingt wöllte. Dieser unterscheidet sich noch nicht einmal besonders vom x86-Treiber, weil auch bei I/CP-Boards PS/2 als Tastaturinterface benutzt wird, es werden also die gleichen Befehle zur Kommunikation mit der Tastatur verwendet. == Weitere Quellen == Der fertige Code für diesen Teil des Tutorials befindet sich [https://gitorious.org/arm-os-dev/kernel/trees/c13720c987aba6d195f60201081cbbb677e4365a hier]. [[Kategorie:OS-Dev_für_Einsteiger]] [[Kategorie:ARM]]