Interrupts (PI4): Unterschied zwischen den Versionen
| (9 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
| Zeile 1: | Zeile 1: | ||
Interrupts | == Interrupts auf dem Raspberry Pi 4 (Bare Metal) == | ||
Interrupts sind Signale, die den normalen Ablauf des Prozessors unterbrechen, um auf bestimmte Ereignisse zu reagieren. Sie sind besonders wichtig in Echtzeitsystemen und bei der Steuerung von Hardware – zum Beispiel, wenn ein Timer abläuft oder eine Taste gedrückt wird. | |||
In diesem Kapitel lernst du, wie Interrupts auf dem Raspberry Pi 4 im Bare-Metal-Modus funktionieren und wie du sie sicher einrichten kannst. | |||
== | == Was ist ein Interrupt? == | ||
Ein '''Interrupt''' (zu Deutsch: „Unterbrechung“) ist ein Signal, das den Prozessor dazu bringt, den gerade laufenden Code kurz zu pausieren. Stattdessen führt er eine spezielle Funktion aus – den sogenannten '''Interrupt-Handler''' (auch ''Interrupt Service Routine'', kurz ISR). | |||
Sobald der Handler fertig ist, kehrt der Prozessor an die Stelle zurück, an der er unterbrochen wurde, und arbeitet dort weiter. | |||
== Wann treten Interrupts auf? == | |||
Interrupts können durch verschiedene Ereignisse ausgelöst werden: | |||
* | * '''Externe Hardware''': | ||
* | Zum Beispiel: Tastatur, Maus, Timer, GPIO-Pins. | ||
* '''Interne Geräte''': | |||
Netzwerkadapter, Speichercontroller, Sensoren (Temperatur, Beschleunigung). | |||
* '''Software-Ereignisse''': | |||
Systemaufrufe (z. B. svc-Befehl) oder Fehler wie Division durch Null. | |||
== | == Arten von Interrupts == | ||
Es gibt zwei wichtige Arten: | |||
* '''Maskierbare Interrupts (IRQ)''': | |||
Diese können vom Prozessor vorübergehend '''ausgeschaltet''' werden. Die meisten Interrupts (z. B. von Timern oder GPIO) sind IRQs. | |||
Interrupts können | * '''Nicht maskierbare Interrupts (NMI)''': | ||
Diese '''können nicht ignoriert''' werden und haben höchste Priorität. Sie werden nur bei kritischen Fehlern oder Systemereignissen verwendet. | |||
== Betriebsmodi und Interrupts (ARMv8) == | |||
Der Raspberry Pi 4 nutzt einen '''ARM Cortex-A72'''-Prozessor mit 64-Bit-Architektur (AArch64). ARM unterscheidet verschiedene '''Exception Levels (EL)''': | |||
* '''EL0''': Anwendungen (wenig Rechte) | |||
* '''EL1''': Betriebssystem oder Bare-Metal-Kernel (hohe Rechte) | |||
* '''EL2''': Hypervisor (z. B. für Virtualisierung) | |||
* '''EL3''': Firmware (höchste Rechte) | |||
'''Wichtig''': Interrupts können nur in EL1 oder höher korrekt behandelt werden. Beim Start landet der Pi meist in '''EL2''', daher müssen wir in '''EL1''' wechseln. | |||
== Interrupts aktivieren: Die DAIF-Flags == | |||
Ob Interrupts erlaubt sind, wird über das '''DAIF-Register''' gesteuert. Es enthält vier Flags: | |||
{| class="wikitable" | {| class="wikitable" | ||
| D | ! Flag | ||
! Bedeutung | |||
|- | |||
| D | |||
| Debug-Exceptions sperren | |||
|- | |- | ||
| A | | | A | ||
| Asynchrone Fehler (SError) | |||
|- | |- | ||
| I | | | I | ||
| Normale Interrupts (IRQ) | |||
|- | |- | ||
| F | | | F | ||
| Schnelle Interrupts (FIQ) | |||
|} | |} | ||
Wenn ein Flag '''gesetzt''' ist (1), ist der entsprechende Interrupt '''gesperrt'''. | |||
Um '''IRQs zu erlauben''', muss das '''I-Flag gelöscht''' werden (also auf 0 gesetzt). | |||
=== Beispiel: IRQ-Interrupts aktivieren === | |||
<source lang="armasm"> | |||
< | |||
.global enable_interrupts | .global enable_interrupts | ||
enable_interrupts: | enable_interrupts: | ||
mrs x0, DAIF | mrs x0, DAIF // Lese aktuelle DAIF-Werte | ||
bic x0, x0, #(1<<7) | bic x0, x0, #(1<<7) // I-Flag löschen (Bit 7) | ||
msr DAIF, x0 | msr DAIF, x0 // Zurückschreiben → IRQs freigegeben | ||
ret | ret | ||
</ | </source> | ||
'''Hinweis''': <code>bic</code> steht für „bit clear“. <code>(1<<7)</code> ist Bit 7 – das I-Flag im DAIF-Register. | |||
== Vorbereitung: Nur ein Kern aktivieren == | |||
Der Raspberry Pi 4 hat '''vier CPU-Kerne'''. Beim Start laufen '''alle gleichzeitig'''. Wenn wir nicht aufpassen, könnte jeder Kern versuchen, Interrupts zu bearbeiten – das führt zu Chaos. | |||
Daher: Wir erlauben nur '''Kern 0 (CPU0)''' weiterzulaufen. Die anderen Kerne legen wir „in den Schlaf“. | |||
<syntaxhighlight lang="asm"> | <syntaxhighlight lang="asm"> | ||
.section .init | .section .init | ||
.global _start | .global _start | ||
_start: | |||
mrs x1, mpidr_el1 // Lese die Core-ID | |||
and x1, x1, #3 // Extrahiere die unteren 2 Bits (0 bis 3) | |||
cbz x1, core0 // Wenn ID = 0 → weiter auf Core0 | |||
core_sleep: | |||
wfe // Warte auf Ereignis (sleep) | |||
b core_sleep // Dauerschleife | |||
core0: | core0: | ||
// Nur Core 0 kommt hierher | |||
</syntaxhighlight> | </syntaxhighlight> | ||
> '''Tipp''': <code>mpidr_el1</code> enthält die Core-ID. | |||
> <code>wfe</code> (Wait For Event) hält den Kern ruhig, bis er geweckt wird. | |||
== In EL1 wechseln == | |||
Wie gesagt: Interrupts gehören in '''EL1''', aber der Pi startet oft in '''EL2'''. Wir müssen also wechseln. | |||
=== Schritt 1: Prüfen, ob wir schon in EL1 sind === | |||
<syntaxhighlight lang="asm"> | <syntaxhighlight lang="asm"> | ||
mrs x0, CurrentEL // Lese aktuelles Exception Level | |||
cmp x0, #8 // EL1 hat Wert 8 | |||
beq switch_to_el1 // Wenn EL1, direkt weiter | |||
</syntaxhighlight> | </syntaxhighlight> | ||
> <code>CurrentEL</code> gibt z. B. <code>8</code> für EL1. Der Wert ist <code>ELn << 2</code>. | |||
> Also: EL1 = 8, EL2 = 16. | |||
=== Schritt 2: Wechsel von EL2 nach EL1 === | |||
<syntaxhighlight lang="asm"> | |||
ldr x0, =EXCEPTION_STACK // Lade Stack für EL1 | |||
msr sp_el1, x0 // Setze Stackpointer für EL1 | |||
ldr x0, =VectorTable // Adresse der Vektortabelle | |||
msr vbar_el2, x0 // VBAR_EL2 zeigt auf Tabelle | |||
// Timer-Einstellungen | |||
mrs x0, cnthctl_el2 | |||
orr x0, x0, #0x3 | |||
msr cnthctl_el2, x0 | |||
msr cntvoff_el2, xzr | |||
// Virtualisierung deaktivieren | |||
mov x0, #0x33ff | |||
msr cptr_el2, x0 | |||
msr hstr_el2, xzr | |||
mov x0, #(1 << 31) | |||
msr hcr_el2, x0 | |||
// CPACR_EL1 für Gleitkomma | |||
mov x0, #3 << 20 | |||
msr cpacr_el1, x0 | |||
// Systemsteuerregister EL1 | |||
mov x0, #0x0800 | |||
movk x0, #0x30d0, lsl #16 | |||
msr sctlr_el1, x0 | |||
// SPSR für Rücksprung nach EL1 | |||
mov x0, #0x3c4 | |||
msr spsr_el2, x0 | |||
adr x0, el1_return | |||
msr elr_el2, x0 | |||
eret // Wechsel nach EL1 | |||
</syntaxhighlight> | |||
> Nach <code>eret</code> springt der Prozessor zu <code>el1_return</code> – aber jetzt in EL1. | |||
== Initialisierung in EL1 == | |||
Nach dem Wechsel: | |||
<syntaxhighlight lang="asm"> | <syntaxhighlight lang="asm"> | ||
el1_return: | el1_return: | ||
switch_to_el1: | switch_to_el1: | ||
ldr x1, =_start | |||
mov sp, x1 // Stack setzen | |||
ldr x0, =VectorTable | |||
msr vbar_el1, x0 // Vektortabelle für EL1 aktivieren | |||
// BSS-Segment löschen (uninitialisierte Daten auf 0 setzen) | |||
ldr x1, =__bss_start | |||
ldr w2, =__bss_size | |||
clean_bss_loop: | |||
cbz w2, bss_clean_done | |||
str xzr, [x1], #8 | |||
clean_bss_loop: | sub w2, w2, #1 | ||
cbnz w2, clean_bss_loop | |||
bss_clean_done: | |||
b main // Starte Hauptprogramm | |||
</syntaxhighlight> | </syntaxhighlight> | ||
== Die Vektortabelle == | |||
Die '''Vektortabelle''' sagt dem Prozessor, welche Funktion er bei einem Interrupt aufrufen soll. Sie enthält eine Liste von Sprüngen („Vectors“) für verschiedene Ausnahmetypen. | |||
=== | === Aufbau der Tabelle (ARMv8) === | ||
Jeder Eintrag behandelt einen bestimmten Typ: | |||
* '''Synchronous''': Fehler oder Systemaufrufe | |||
* '''IRQ''': Normaler Interrupt | |||
* '''FIQ''': Schneller Interrupt (höhere Priorität) | |||
* '''SError''': Systemfehler (z. B. Speicherfehler) | |||
Und jeweils für: | |||
* Aktuelles Exception Level (z. B. EL1h) | |||
* Niedrigeres Level (z. B. EL0) | |||
== | === Beispiel: Vektortabelle für EL1 === | ||
<syntaxhighlight lang="asm"> | |||
.align 11 // Muss auf 2048-Byte-Grenze | |||
.globl VectorTable | |||
VectorTable: | |||
// EL1 mit SP_EL0 (Benutzermodus) | |||
.align 7 | |||
b sync_exception_el1t | |||
.align 7 | |||
b irq_handler_el1t | |||
.align 7 | |||
b fiq_handler_el1t | |||
.align 7 | |||
b serror_handler_el1t | |||
// EL1 mit SP_EL1 (Kernelmodus) | |||
.align 7 | |||
b sync_exception_el1h | |||
.align 7 | |||
b irq_handler_el1h | |||
.align 7 | |||
b fiq_handler_el1h | |||
.align 7 | |||
b serror_handler_el1h | |||
// EL0 64-Bit (ungültig – nicht erlaubt) | |||
.align 7 | |||
b sync_invalid_el0_64 | |||
.align 7 | |||
b irq_invalid_el0_64 | |||
.align 7 | |||
b fiq_invalid_el0_64 | |||
.align 7 | |||
b error_invalid_el0_64 | |||
// EL0 32-Bit (ebenfalls ungültig) | |||
.align 7 | |||
b sync_invalid_el0_32 | |||
.align 7 | |||
b irq_invalid_el0_32 | |||
.align 7 | |||
b fiq_invalid_el0_32 | |||
.align 7 | |||
b error_invalid_el0_32 | |||
.align 7 | |||
b sync_invalid_el0_32 | |||
.align 7 | |||
b irq_invalid_el0_32 | |||
.align 7 | |||
b fiq_invalid_el0_32 | |||
.align 7 | |||
b error_invalid_el0_32 | |||
</syntaxhighlight> | </syntaxhighlight> | ||
'''Hinweis''': | |||
* | * <code>.align 11</code> = 2048 Bytes für die gesamte Tabelle | ||
* <code>.align 7</code> = 128 Bytes pro Eintrag (Abstand zwischen Vektoren) | |||
= | == Ein Interrupt-Handler: Schritt für Schritt == | ||
Hier ein Beispiel für einen '''IRQ-Handler''': | |||
= | <syntaxhighlight lang="asm"> | ||
irq_handler_el1t: | |||
irq_handler_el1h: | |||
stp x29, x30, [sp, #-16]! // x29/x30 sichern | |||
mrs x29, elr_el1 // Rückkehradresse sichern | |||
mrs x30, spsr_el1 // Statusregister sichern | |||
stp x29, x30, [sp, #-16]! | |||
msr DAIFSet, #1 // FIQs sperren (nur für IRQ) | |||
// Alle Register sichern (inkl. Gleitkomma) | |||
stp q0, q1, [sp, #-32]! | |||
stp q2, q3, [sp, #-32]! | |||
// ... bis q30, q31 | |||
stp x1, x2, [sp, #-16]! | |||
// ... bis x27, x28 | |||
str x0, [sp, #-16]! | |||
bl InterruptHandler // Aufruf der C-Funktion | |||
// Register wiederherstellen | |||
ldr x0, [sp], #16 | |||
ldp x1, x2, [sp], #16 | |||
// ... bis x27, x28 | |||
ldp q0, q1, [sp], #32 | |||
// ... bis q30, q31 | |||
ldp x29, x30, [sp], #16 // elr_el1, spsr_el1 | |||
msr elr_el1, x29 | |||
msr spsr_el1, x30 | |||
ldp x29, x30, [sp], #16 // x29/x30 wiederherstellen | |||
// | |||
eret // Zurück zum unterbrochenen Code | |||
</syntaxhighlight> | |||
== Wichtige Prinzipien beim Interrupt-Handling == | |||
# '''Register sichern''': | |||
Beim Interrupt werden Register überschrieben. Daher '''alle Register auf den Stack speichern'''. | |||
# '''Interrupts maskieren''': | |||
Während ein Handler läuft, sollten keine weiteren Interrupts kommen. | |||
→ Setze <code>DAIFSet</code> am Anfang, um FIQs zu blockieren (wenn nötig). | |||
# '''Schnell sein''': | |||
Interrupt-Handler sollten '''schnell''' sein. Komplexe Aufgaben besser in den Hintergrund verlegen. | |||
# '''eret verwenden''': | |||
Der Befehl <code>eret</code> kehrt zum unterbrochenen Code zurück und stellt den Zustand wieder her. | |||
== Zusammenfassung == | |||
* Interrupts unterbrechen den normalen Programmablauf. | |||
* Sie werden über die '''Vektortabelle''' behandelt. | |||
* Nur '''EL1 oder höher''' darf Interrupts verarbeiten. | |||
* Mehrere Kerne? Nur '''einen aktivieren''', die anderen schlafen legen. | |||
* '''DAIF-Register''' kontrolliert, welche Interrupts erlaubt sind. | |||
* Im Handler: '''Register sichern''', '''Interrupts steuern''', '''schnell arbeiten''', '''eret''' benutzen. | |||
Mit diesem Wissen bist du bereit, echte Hardware-Interrupts (z. B. vom Timer oder GPIO) auf dem Raspberry Pi 4 zu nutzen! | |||
Im nächsten Kapitel kümmern wir uns um den '''Systemtimer''', um den ersten echten Interrupt auszulösen. | |||
----- | |||
{| style="width: 100%; | |||
| style="width: 33%;" | [[Systeminformationen (PI4)|< Zurück (Systeminformationen)]] | |||
| style="width: 33%; text-align:center;" | [[Hauptseite|< Hauptseite >]] | |||
| style="width: 33%; text-align:right;" | [[Beispiel Timer-Interrupt (PI4)|Weiter (Beispiel Timer-Interrupt) >]] | |||
|} | |||
Aktuelle Version vom 31. Juli 2025, 06:41 Uhr
Interrupts auf dem Raspberry Pi 4 (Bare Metal)
Interrupts sind Signale, die den normalen Ablauf des Prozessors unterbrechen, um auf bestimmte Ereignisse zu reagieren. Sie sind besonders wichtig in Echtzeitsystemen und bei der Steuerung von Hardware – zum Beispiel, wenn ein Timer abläuft oder eine Taste gedrückt wird.
In diesem Kapitel lernst du, wie Interrupts auf dem Raspberry Pi 4 im Bare-Metal-Modus funktionieren und wie du sie sicher einrichten kannst.
Was ist ein Interrupt?
Ein Interrupt (zu Deutsch: „Unterbrechung“) ist ein Signal, das den Prozessor dazu bringt, den gerade laufenden Code kurz zu pausieren. Stattdessen führt er eine spezielle Funktion aus – den sogenannten Interrupt-Handler (auch Interrupt Service Routine, kurz ISR).
Sobald der Handler fertig ist, kehrt der Prozessor an die Stelle zurück, an der er unterbrochen wurde, und arbeitet dort weiter.
Wann treten Interrupts auf?
Interrupts können durch verschiedene Ereignisse ausgelöst werden:
- Externe Hardware:
Zum Beispiel: Tastatur, Maus, Timer, GPIO-Pins.
- Interne Geräte:
Netzwerkadapter, Speichercontroller, Sensoren (Temperatur, Beschleunigung).
- Software-Ereignisse:
Systemaufrufe (z. B. svc-Befehl) oder Fehler wie Division durch Null.
Arten von Interrupts
Es gibt zwei wichtige Arten:
- Maskierbare Interrupts (IRQ):
Diese können vom Prozessor vorübergehend ausgeschaltet werden. Die meisten Interrupts (z. B. von Timern oder GPIO) sind IRQs.
- Nicht maskierbare Interrupts (NMI):
Diese können nicht ignoriert werden und haben höchste Priorität. Sie werden nur bei kritischen Fehlern oder Systemereignissen verwendet.
Betriebsmodi und Interrupts (ARMv8)
Der Raspberry Pi 4 nutzt einen ARM Cortex-A72-Prozessor mit 64-Bit-Architektur (AArch64). ARM unterscheidet verschiedene Exception Levels (EL):
- EL0: Anwendungen (wenig Rechte)
- EL1: Betriebssystem oder Bare-Metal-Kernel (hohe Rechte)
- EL2: Hypervisor (z. B. für Virtualisierung)
- EL3: Firmware (höchste Rechte)
Wichtig: Interrupts können nur in EL1 oder höher korrekt behandelt werden. Beim Start landet der Pi meist in EL2, daher müssen wir in EL1 wechseln.
Interrupts aktivieren: Die DAIF-Flags
Ob Interrupts erlaubt sind, wird über das DAIF-Register gesteuert. Es enthält vier Flags:
| Flag | Bedeutung |
|---|---|
| D | Debug-Exceptions sperren |
| A | Asynchrone Fehler (SError) |
| I | Normale Interrupts (IRQ) |
| F | Schnelle Interrupts (FIQ) |
Wenn ein Flag gesetzt ist (1), ist der entsprechende Interrupt gesperrt. Um IRQs zu erlauben, muss das I-Flag gelöscht werden (also auf 0 gesetzt).
Beispiel: IRQ-Interrupts aktivieren
.global enable_interrupts
enable_interrupts:
mrs x0, DAIF // Lese aktuelle DAIF-Werte
bic x0, x0, #(1<<7) // I-Flag löschen (Bit 7)
msr DAIF, x0 // Zurückschreiben → IRQs freigegeben
retHinweis: bic steht für „bit clear“. (1<<7) ist Bit 7 – das I-Flag im DAIF-Register.
Vorbereitung: Nur ein Kern aktivieren
Der Raspberry Pi 4 hat vier CPU-Kerne. Beim Start laufen alle gleichzeitig. Wenn wir nicht aufpassen, könnte jeder Kern versuchen, Interrupts zu bearbeiten – das führt zu Chaos.
Daher: Wir erlauben nur Kern 0 (CPU0) weiterzulaufen. Die anderen Kerne legen wir „in den Schlaf“.
.section .init
.global _start
_start:
mrs x1, mpidr_el1 // Lese die Core-ID
and x1, x1, #3 // Extrahiere die unteren 2 Bits (0 bis 3)
cbz x1, core0 // Wenn ID = 0 → weiter auf Core0
core_sleep:
wfe // Warte auf Ereignis (sleep)
b core_sleep // Dauerschleife
core0:
// Nur Core 0 kommt hierher
> Tipp: mpidr_el1 enthält die Core-ID.
> wfe (Wait For Event) hält den Kern ruhig, bis er geweckt wird.
In EL1 wechseln
Wie gesagt: Interrupts gehören in EL1, aber der Pi startet oft in EL2. Wir müssen also wechseln.
Schritt 1: Prüfen, ob wir schon in EL1 sind
mrs x0, CurrentEL // Lese aktuelles Exception Level
cmp x0, #8 // EL1 hat Wert 8
beq switch_to_el1 // Wenn EL1, direkt weiter
> CurrentEL gibt z. B. 8 für EL1. Der Wert ist ELn << 2.
> Also: EL1 = 8, EL2 = 16.
Schritt 2: Wechsel von EL2 nach EL1
ldr x0, =EXCEPTION_STACK // Lade Stack für EL1
msr sp_el1, x0 // Setze Stackpointer für EL1
ldr x0, =VectorTable // Adresse der Vektortabelle
msr vbar_el2, x0 // VBAR_EL2 zeigt auf Tabelle
// Timer-Einstellungen
mrs x0, cnthctl_el2
orr x0, x0, #0x3
msr cnthctl_el2, x0
msr cntvoff_el2, xzr
// Virtualisierung deaktivieren
mov x0, #0x33ff
msr cptr_el2, x0
msr hstr_el2, xzr
mov x0, #(1 << 31)
msr hcr_el2, x0
// CPACR_EL1 für Gleitkomma
mov x0, #3 << 20
msr cpacr_el1, x0
// Systemsteuerregister EL1
mov x0, #0x0800
movk x0, #0x30d0, lsl #16
msr sctlr_el1, x0
// SPSR für Rücksprung nach EL1
mov x0, #0x3c4
msr spsr_el2, x0
adr x0, el1_return
msr elr_el2, x0
eret // Wechsel nach EL1
> Nach eret springt der Prozessor zu el1_return – aber jetzt in EL1.
Initialisierung in EL1
Nach dem Wechsel:
el1_return:
switch_to_el1:
ldr x1, =_start
mov sp, x1 // Stack setzen
ldr x0, =VectorTable
msr vbar_el1, x0 // Vektortabelle für EL1 aktivieren
// BSS-Segment löschen (uninitialisierte Daten auf 0 setzen)
ldr x1, =__bss_start
ldr w2, =__bss_size
clean_bss_loop:
cbz w2, bss_clean_done
str xzr, [x1], #8
sub w2, w2, #1
cbnz w2, clean_bss_loop
bss_clean_done:
b main // Starte Hauptprogramm
Die Vektortabelle
Die Vektortabelle sagt dem Prozessor, welche Funktion er bei einem Interrupt aufrufen soll. Sie enthält eine Liste von Sprüngen („Vectors“) für verschiedene Ausnahmetypen.
Aufbau der Tabelle (ARMv8)
Jeder Eintrag behandelt einen bestimmten Typ:
- Synchronous: Fehler oder Systemaufrufe
- IRQ: Normaler Interrupt
- FIQ: Schneller Interrupt (höhere Priorität)
- SError: Systemfehler (z. B. Speicherfehler)
Und jeweils für:
- Aktuelles Exception Level (z. B. EL1h)
- Niedrigeres Level (z. B. EL0)
Beispiel: Vektortabelle für EL1
.align 11 // Muss auf 2048-Byte-Grenze
.globl VectorTable
VectorTable:
// EL1 mit SP_EL0 (Benutzermodus)
.align 7
b sync_exception_el1t
.align 7
b irq_handler_el1t
.align 7
b fiq_handler_el1t
.align 7
b serror_handler_el1t
// EL1 mit SP_EL1 (Kernelmodus)
.align 7
b sync_exception_el1h
.align 7
b irq_handler_el1h
.align 7
b fiq_handler_el1h
.align 7
b serror_handler_el1h
// EL0 64-Bit (ungültig – nicht erlaubt)
.align 7
b sync_invalid_el0_64
.align 7
b irq_invalid_el0_64
.align 7
b fiq_invalid_el0_64
.align 7
b error_invalid_el0_64
// EL0 32-Bit (ebenfalls ungültig)
.align 7
b sync_invalid_el0_32
.align 7
b irq_invalid_el0_32
.align 7
b fiq_invalid_el0_32
.align 7
b error_invalid_el0_32
Hinweis:
.align 11= 2048 Bytes für die gesamte Tabelle.align 7= 128 Bytes pro Eintrag (Abstand zwischen Vektoren)
Ein Interrupt-Handler: Schritt für Schritt
Hier ein Beispiel für einen IRQ-Handler:
irq_handler_el1t:
irq_handler_el1h:
stp x29, x30, [sp, #-16]! // x29/x30 sichern
mrs x29, elr_el1 // Rückkehradresse sichern
mrs x30, spsr_el1 // Statusregister sichern
stp x29, x30, [sp, #-16]!
msr DAIFSet, #1 // FIQs sperren (nur für IRQ)
// Alle Register sichern (inkl. Gleitkomma)
stp q0, q1, [sp, #-32]!
stp q2, q3, [sp, #-32]!
// ... bis q30, q31
stp x1, x2, [sp, #-16]!
// ... bis x27, x28
str x0, [sp, #-16]!
bl InterruptHandler // Aufruf der C-Funktion
// Register wiederherstellen
ldr x0, [sp], #16
ldp x1, x2, [sp], #16
// ... bis x27, x28
ldp q0, q1, [sp], #32
// ... bis q30, q31
ldp x29, x30, [sp], #16 // elr_el1, spsr_el1
msr elr_el1, x29
msr spsr_el1, x30
ldp x29, x30, [sp], #16 // x29/x30 wiederherstellen
eret // Zurück zum unterbrochenen Code
Wichtige Prinzipien beim Interrupt-Handling
- Register sichern:
Beim Interrupt werden Register überschrieben. Daher alle Register auf den Stack speichern.
- Interrupts maskieren:
Während ein Handler läuft, sollten keine weiteren Interrupts kommen.
→ Setze DAIFSet am Anfang, um FIQs zu blockieren (wenn nötig).
- Schnell sein:
Interrupt-Handler sollten schnell sein. Komplexe Aufgaben besser in den Hintergrund verlegen.
- eret verwenden:
Der Befehl eret kehrt zum unterbrochenen Code zurück und stellt den Zustand wieder her.
Zusammenfassung
- Interrupts unterbrechen den normalen Programmablauf.
- Sie werden über die Vektortabelle behandelt.
- Nur EL1 oder höher darf Interrupts verarbeiten.
- Mehrere Kerne? Nur einen aktivieren, die anderen schlafen legen.
- DAIF-Register kontrolliert, welche Interrupts erlaubt sind.
- Im Handler: Register sichern, Interrupts steuern, schnell arbeiten, eret benutzen.
Mit diesem Wissen bist du bereit, echte Hardware-Interrupts (z. B. vom Timer oder GPIO) auf dem Raspberry Pi 4 zu nutzen!
Im nächsten Kapitel kümmern wir uns um den Systemtimer, um den ersten echten Interrupt auszulösen.
| < Zurück (Systeminformationen) | < Hauptseite > | Weiter (Beispiel Timer-Interrupt) > |