Interrupt Teil 2 (PI4): Unterschied zwischen den Versionen
| Zeile 298: | Zeile 298: | ||
| style="width: 33%;" | [[Beispiel Timer-Interrupt (PI4)|< Zurück (Beispiel Timer-Interrupt (PI4))]] | | style="width: 33%;" | [[Beispiel Timer-Interrupt (PI4)|< Zurück (Beispiel Timer-Interrupt (PI4))]] | ||
| style="width: 33%; text-align:center;" | [[Hauptseite|< Hauptseite >]] | | style="width: 33%; text-align:center;" | [[Hauptseite|< Hauptseite >]] | ||
| style="width: 33%; text-align:right;" | | style="width: 33%; text-align:right;" | Weiter (Speicherverwaltung (PI4)) > | ||
|} | |} | ||
Aktuelle Version vom 8. August 2025, 09:00 Uhr
Im vorhergehenden Kapitel haben wir einen Interrupt für den Timer erstellt. Dies ist zur Zeit auch der einzige, der in unserem System, welches wir entwickeln, zur Verfügung haben. Im nächsten Kapitel wird es dann aufwendig. Wir werden uns um den USB-Eingang kümmern und zunächst einen Treiber für die Tatstatur und anschließend noch auf Mausbewegungen reagieren.
Dazu werden wir dann auch Interrupts benötigen, die ausgelöst werden, wenn jemand auf die Tastatur verwendet, oder dann später auch die Maus bedient.
Damit wir dann grundsätzlich Interrupts programmieren können, werden wir unseren Code etwas erweitern und alle Arten von Interrupts erlauben. Leider werden wir hier nicht viel sehen, aber es ist essentiell, um später unser System zu erweitern.
Vektortabelle
.align 11
.globl VectorTable
VectorTable:
// Vektoren für EL1t (Current Exception Level SP_el0)
.align 7
b sync_exception // Synchronous Exception
.align 7
b irq_handler // IRQ - Normal Interrupt
.align 7
b fiq_handler // FIQ - Fast Interrupt
.align 7
b serror_handler // SError - System Error
// Vektoren für EL1h (Current Exception Level SP_el1)
.align 7
b sync_exception // Synchronous Exception
.align 7
b irq_handler // IRQ - Normal Interrupt
.align 7
b fiq_handler // FIQ - Fast Interrupt
.align 7
b serror_handler // SError - System Error
// Vektoren für EL0 64-bit Modus
.align 7
b hvc_handler // Synchronous EL0 (64-bit), Hypervisor Call
.align 7
b not_used // IRQ EL0 (64-bit)
.align 7
b not_used // FIQ EL0 (64-bit)
.align 7
b not_used // Error EL0 (64-bit)
// Vektoren für EL0 32-bit Modus
.align 7
b not_used // Synchronous EL0 (32-bit)
.align 7
b not_used // IRQ EL0 (32-bit)
.align 7
b not_used // FIQ EL0 (32-bit)
.align 7
b not_used // Error EL0 (32-bit)
Bisher haben wir "nur" den normalen Interrupt unterstützt. Mit diesem Beispiel werden wir auch das "fast Interrupt" programmieren. Genaus gehen wir auch auf Fehler (sync_exception und serror_handler) ein, wenn etwas nicht so läuft wie wir es wollen.
Einige Vectoren verwenden wir nicht. Hier springen wir zu "not_used".
Exceptions
Fangen wir zunächst mit den Exceptions (Abweichungen) an. Da das System hier auf einen Fehler läuft, werden wir in diesem auf eine Sicherung der Register verzichten und direkt in die Fehlerbehandlung gehen:
sync_exception:
mov x0,#0 //Fehlercode
b ExceptionHandler
serror_handler:
mov x0,#1 //Fehlercode
b ExceptionHandler
not_used:
mov x0,#2 //Fehlercode
b ExceptionHandler
Wir übergeben jeweils in x0 (Erster Parameter für die C-Funktion) einen Fehlercode, den wir verwenden können, um uns dann in C entsprechend zu reagieren. Dazu schreiben wir ein kleines C-Programm:
void ExceptionHandler (int code)
{
const char *Exmsg;
switch (code)
{
case 0:
Exmsg = "sync_exception";
break;
case 1:
Exmsg = "serror_handler";
break;
case 2:
Exmsg = "not used";
break;
default:
Exmsg = "Unbekannter Code\n";
break;
}
printf ("Error: Exception '%s' ausgelöst\n", Exmsg);
Stop();
}
FastInterrupt (FIQ)
Obwohl unsere Vectortabelle dies ermöglicht, verzichten wir zunächst darauf. Allerdings werden wir in den kommenden Code-Abschnitte, dort wo es eventuell wichtig ist, darauf achten, dass wir richtig handeln und diese Art von Interrupt zunächst verbieten.
Damit wir aber einen Überblick haben, ob dennoch irgendwo ein FastInterrupt entstanden ist, werden wir diesen wie eine Exception behandeln:
fiq_handler:
mov x0,#3 //Fehlercode
b ExceptionHandler
Unserer C-Funktion für die Exceptionverwaltung erweitern wir mit:
void ExceptionHandler (int code)
{
...
case 3:
Exmsg = "FastInterrupt (FIQ)";
break;
...
}
Hypervisor Call
Diesen fangen wir hier ab, da es unter umständen dazu kommen könnte, dass ein Anwendungsprogramm einen Hypervisors Zugriff benötigt. Damit aber unser Programm weiter sinnvoll weiterläuft, müssen wir das spsr_el2 Register reinigen:
hvc_handler: // Hypervisor Call, return to EL2h mode
mrs x0, spsr_el2
bic x0, x0, #0xF
mov x1, #9
orr x0, x0, x1
msr spsr_el2, x0
eret
Standard Interrupt (IRQ)
Da wir nun etwas mehr erlauben, als nur einen Timer-Interrupt, müssen wir etwas mehr tun, damit unser System stabil bleibt. Zunächst hier der Code:
.globl GenericIRQHandler
GenericIRQHandler:
stp x29, x30, [sp, #-16]! //Zunächst x29, x30 sichern
mrs x29, elr_el1 //Lies das Exception Link Register von EL1
mrs x30, spsr_el1 //Lies das Saved Program Status Register von EL1
stp x29, x30, [sp, #-16]! //Speichere `elr_el1` und `spsr_el1` auf dem Stack
msr DAIFClr, #1 //Deaktiviere FIQ-Maskierung (ermögliche Fast Interrupts)
stp q30, q31, [sp, #-32]! // speichert q0-q31 auf den Stack
stp q28, q29, [sp, #-32]!
stp q26, q27, [sp, #-32]!
stp q24, q25, [sp, #-32]!
stp q22, q23, [sp, #-32]!
stp q20, q21, [sp, #-32]!
stp q18, q19, [sp, #-32]!
stp q16, q17, [sp, #-32]!
stp q14, q15, [sp, #-32]!
stp q12, q13, [sp, #-32]!
stp q10, q11, [sp, #-32]!
stp q8, q9, [sp, #-32]!
stp q6, q7, [sp, #-32]!
stp q4, q5, [sp, #-32]!
stp q2, q3, [sp, #-32]!
stp q0, q1, [sp, #-32]!
stp x27, x28, [sp, #-16]! // speichert x0-x28 auf den Stack
stp x25, x26, [sp, #-16]!
stp x23, x24, [sp, #-16]!
stp x21, x22, [sp, #-16]!
stp x19, x20, [sp, #-16]!
stp x17, x18, [sp, #-16]!
stp x15, x16, [sp, #-16]!
stp x13, x14, [sp, #-16]!
stp x11, x12, [sp, #-16]!
stp x9, x10, [sp, #-16]!
stp x7, x8, [sp, #-16]!
stp x5, x6, [sp, #-16]!
stp x3, x4, [sp, #-16]!
stp x1, x2, [sp, #-16]!
str x0, [sp, #-16]!
ldr x0, =IRQReturnAddress // store return address for profiling ??????????????????????????
str x29, [x0]
bl irq_dispatch //Sprung in die Interruptroutine
ldr x0, [sp], #16 // Reaktivieren der Register x0-x28 vom Stack
ldp x1, x2, [sp], #16
ldp x3, x4, [sp], #16
ldp x5, x6, [sp], #16
ldp x7, x8, [sp], #16
ldp x9, x10, [sp], #16
ldp x11, x12, [sp], #16
ldp x13, x14, [sp], #16
ldp x15, x16, [sp], #16
ldp x17, x18, [sp], #16
ldp x19, x20, [sp], #16
ldp x21, x22, [sp], #16
ldp x23, x24, [sp], #16
ldp x25, x26, [sp], #16
ldp x27, x28, [sp], #16
ldp q0, q1, [sp], #32 // Reaktivieren der Register q0-q31 vom Stack
ldp q2, q3, [sp], #32
ldp q4, q5, [sp], #32
ldp q6, q7, [sp], #32
ldp q8, q9, [sp], #32
ldp q10, q11, [sp], #32
ldp q12, q13, [sp], #32
ldp q14, q15, [sp], #32
ldp q16, q17, [sp], #32
ldp q18, q19, [sp], #32
ldp q20, q21, [sp], #32
ldp q22, q23, [sp], #32
ldp q24, q25, [sp], #32
ldp q26, q27, [sp], #32
ldp q28, q29, [sp], #32
ldp q30, q31, [sp], #32
msr DAIFSet, #1 //Reaktiviere FIQ-Maskierung (sperre Fast Interrupts)
ldp x29, x30, [sp], #16 // Reaktivieren der Register elr_el1, spsr_el1 vom Stack
msr elr_el1, x29
msr spsr_el1, x30
ldp x29, x30, [sp], #16 // Reaktivieren der Register x29, x30 vom Stack
eret //Zurück zum "normalen" Programmablauf
Der gezeigte GenericIRQHandler implementiert die Verarbeitung eines Standard Interrupts (IRQ). Abgesehen von der Sicherung der allgemeinen Register (z. B. x0-x28 und q0-q31) führt der Code zusätzliche Aktionen aus, um sicherzustellen, dass der Interrupt sicher und ohne Seiteneffekte verarbeitet wird.
Zusätzliche Schritte und deren Gründe
Speichern von `elr_el1` und `spsr_el1` (Programmcounter und Statusregister)
mrs x29, elr_el1 // Lies den Exception Link Register von EL1
mrs x30, spsr_el1 // Lies den Saved Program Status Register von EL1
stp x29, x30, [sp, #-16]! // Speichere `elr_el1` und `spsr_el1` auf dem Stack
- elr_el1: Speichert die Adresse des Befehls, der unterbrochen wurde. Nach Abschluss des Interrupt-Handlers muss der Prozessor an diese Stelle zurückspringen, um die reguläre Ausführung des Programms fortzusetzen.
- spsr_el1: Enthält den gespeicherten Prozessorstatus (z. B. Flags, Interrupt-Status), der wiederhergestellt werden muss, damit der Zustand des Gastsystems nicht verändert wird.
Zusammenhang mit den Interrupts:
Interrupts können überall auftreten – der Prozessor muss daher sicherstellen, dass er nach der Bearbeitung an genau der gleichen Stelle und im genau gleichen Zustand weitermacht.
Das Speichern dieser Register stellt sicher, dass alle Informationen zum zuvor ausgeführten Kontext gesichert sind.
Interrupt-Maskierung: Aktivieren/Deaktivieren von Interrupts
msr DAIFClr, #1 // Deaktiviere FIQ-Maskierung (ermögliche Fast Interrupts)
msr DAIFSet, #1 // Reaktiviere FIQ-Maskierung (sperre Fast Interrupts)
- FIQ aktivieren: Vor dem Betreten des Interrupt-Handlers werden in manchen Fällen FIQs (Fast Interrupt Requests) wieder aktiviert. Dadurch können zeitkritische Operationen durch spezielle, priorisierte Interrupts verarbeitet werden.
- FIQ deaktivieren: Vor Rückkehr aus dem IRQ-Handler werden FIQs abgeschaltet, um mögliche Unterbrechungen während des Kontextwechsels zu verhindern.
Durch selektives Aktivieren/Deaktivieren der Interrupts wird sichergestellt, dass ein Interrupt sicher verarbeitet werden kann, ohne dass derselbe oder ein anderer Interrupt erneut eingreift.
Speichern der Gleitkomma-/SIMD-Register (`q0-q31`)
stp q30, q31, [sp, #-32]! // Sichere die letzten NEON-/SIMD-Register
...
stp q0, q1, [sp, #-32]!
Warum die SIMD-Register speichern?
In ARMv8 speichern die Gleitkomma-/SIMD-Register (NEON-Register, q0-q31) Werte, die in parallelen Berechnungen (z. B. Vektorrechnungen, Mediendatenverarbeitung) oder Gleitkommaberechnungen verwendet werden. Ein IRQ kann zu einem beliebigen Zeitpunkt auftreten, auch während einer Berechnung oder eines Programms, das diese Register verwendet.
Ziel:
Durch das Speichern aller Gleitkomma-/SIMD-Register wird sichergestellt, dass kein Kontextverlust (z. B. Änderungen der Gleitkommadaten oder SIMD-Daten) stattfindet.
Speichern aller allgemeinen Register (`x0-x28`)
stp x27, x28, [sp, #-16]!
...
stp x1, x2, [sp, #-16]!
str x0, [sp, #-16]!
Warum alle Register sichern?
Selbst wenn in einer Unterbrechung (IRQ) nur bestimmte Register verwendet werden, sichert man in einem generischen IRQ-Handler alle General-Purpose-Register (x0-x31). Dies verhindert Probleme durch unvorhergesehene Änderungen oder Seiteneffekte im laufenden Code.
Kontext-Sicherheit:
Der Interrupt kann nicht kontrollieren, welche Register zum Zeitpunkt der Unterbrechung gerade in Benutzung sind. Das Sichern des gesamten CPU-Registersatzes garantiert einen vollständigen Schutz, bis der IRQ vollständig abgeschlossen ist.
Reaktivieren der Register und Fortsetzung
ldr x0, [sp], #16 // Stelle `x0-x28` wieder her
...
ldp q0, q1, [sp], #32 // Stelle `q0-q31` wieder her
...
msr elr_el1, x29 // Wiederherstellen des Programmcounters (ELR_EL1)
msr spsr_el1, x30 // Wiederherstellen des Prozessorstatus
eret // Rückkehr zum unterbrochenen Kontext
Reaktivierung:
Nach Abschluss des Interrupts wird der Prozessorzustand wiederhergestellt, indem alle zuvor gesicherten Register und Statusinformationen zurückgeschrieben werden.
Den Kompletten Sourcecode bekommst du hier: Source
| < Zurück (Beispiel Timer-Interrupt (PI4)) | < Hauptseite > | Weiter (Speicherverwaltung (PI4)) > |