Beispiel Timer-Interrupt (PI4): Unterschied zwischen den Versionen
Die Seite wurde neu angelegt: „Nun versuchen wir, einen Timer-Interrupt zu erstellen und verwenden eine Vector-Tabelle, wie diese zuvor beschrieben wurde. Wir verzichten zunächst auf die Gleitkomma-Unterstützung. Wir verwenden die Vectortabelle aus der vorigen Beschreibung. Unterstützen allerdings nur "IRQs" und "Synchronous Exception". Andere Ausnahmen werden in eine Dauerschleife versetzt: <syntaxhighlight lang="asm"> .align 11 .globl VectorTable VectorTable: // Vektoren für EL1…“ |
|||
| (18 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
| Zeile 1: | Zeile 1: | ||
== Beispiel: Timer-Interrupt auf dem Raspberry Pi 4 == | |||
Nachdem wir im letzten Kapitel die Grundlagen zu Interrupts kennengelernt haben, setzen wir nun das Gelernte in die Praxis um. In diesem Kapitel richten wir einen '''Timer-Interrupt''' ein, der alle '''eine Sekunde''' auslöst. | |||
Wir verwenden den Systemtimer des ARM-Prozessors (Generic Timer) zusammen mit dem '''GIC-400 Interrupt-Controller''' (Generic Interrupt Controller). Der Interrupt führt eine einfache Aktion aus: Ein kleiner "Rotor" (Ladeanimation) dreht sich im oberen rechten Bildschirmbereich. | |||
Dieses Beispiel zeigt dir Schritt für Schritt, wie du: | |||
* einen Timer konfigurierst, | |||
* Interrupts über den GIC aktivierst, | |||
* einen Interrupt-Handler schreibst und | |||
* den Timer nach jeder Auslösung zurücksetzt. | |||
Wir verzichten zunächst auf Gleitkomma-Unterstützung, um den Code einfach und übersichtlich zu halten. | |||
== Vektortabelle anpassen == | |||
Zuerst erweitern wir unsere [[Interrupts_(PI4)|Vektortabelle]], um den Timer-Interrupt korrekt zu verarbeiten. Da wir uns zunächst nur auf IRQs konzentrieren, leiten wir alle anderen Ausnahmen in eine Dauerschleife um. | |||
Der folgende Code gehört in die Datei '''vector.s'''. | |||
<syntaxhighlight lang="asm"> | <syntaxhighlight lang="asm"> | ||
.align 11 | .section .text | ||
.globl VectorTable | |||
.align 11 | |||
.globl VectorTable | |||
VectorTable: | VectorTable: | ||
// | // EL1 mit SP_EL0 (Benutzermodus) | ||
.align 7 | .align 7 | ||
b | b hang // Synchronous Exception | ||
.align 7 | .align 7 | ||
b IRQStub | b IRQStub // IRQ - Normal Interrupt | ||
.align 7 | .align 7 | ||
b hang | b hang // FIQ - Fast Interrupt | ||
.align 7 | .align 7 | ||
b hang | b hang // SError - System Error | ||
// | |||
.align 7 | // EL1 mit SP_EL1 (Kernelmodus) | ||
b | .align 7 | ||
.align 7 | b hang // Synchronous Exception | ||
b IRQStub | .align 7 | ||
.align 7 | b IRQStub // IRQ - Normal Interrupt | ||
b hang | .align 7 | ||
.align 7 | b hang // FIQ - Fast Interrupt | ||
b hang | .align 7 | ||
// | b hang // SError - System Error | ||
.align 7 | |||
b hang // | // EL0 64-Bit (nicht erlaubt) | ||
.align 7 | .align 7 | ||
b hang | b hang | ||
.align 7 | |||
b hang | |||
.align 7 | |||
b hang | |||
// | .align 7 | ||
. | b hang | ||
// EL0 32-Bit (nicht erlaubt) | |||
.align 7 | |||
b hang | |||
.align 7 | |||
b hang | |||
.align 7 | |||
b hang | |||
.align 7 | |||
b hang | |||
// Einfache Dauerschleife für nicht behandelte Ausnahmen | |||
hang: | |||
wfe // CPU wartet auf Ereignis (spart Energie) | |||
b hang // Zurück zur Schleife | |||
</syntaxhighlight> | |||
> '''Hinweis''': Alle nicht behandelten Ausnahmen führen in die Schleife <code>hang</code>. Nur der '''IRQ''' springt zum Handler <code>IRQStub</code>. | |||
== IRQ-Handler: IRQStub == | |||
Wenn ein Interrupt ausgelöst wird, springt der Prozessor zum Label <code>IRQStub</code>. Dort sichern wir zunächst alle Register auf dem Stack, rufen eine C-Funktion auf und stellen danach alles wieder her. | |||
Auch dieser Teil gehört in '''vector.s'''. | |||
<syntaxhighlight lang="asm"> | |||
.globl IRQStub | |||
IRQStub: | |||
// Kontext sichern: Alle Register auf den Stack | |||
stp x29, x30, [sp, #-16]! | |||
stp x27, x28, [sp, #-16]! | |||
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]! | |||
// Aufruf der C-Funktion: irq_dispatch | |||
bl irq_dispatch | |||
// Kontext wiederherstellen | |||
ldr x0, [sp], #16 | |||
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 x29, x30, [sp], #16 | |||
eret // Zurück zum unterbrochenen Code | |||
</syntaxhighlight> | |||
'''Wichtig''': | |||
* Der Handler sichert alle Register, da er nicht weiß, welcher Code unterbrochen wurde. | |||
* Nach dem Aufruf von <code>irq_dispatch</code> werden alle Register wiederhergestellt. | |||
* <code>eret</code> kehrt zum ursprünglichen Code zurück. | |||
== Interrupt-Controller initialisieren (GIC-400) == | |||
Wir verwenden den '''Generic Interrupt Controller (GIC-400)''', der im Raspberry Pi 4 integriert ist. Um den Timer-Interrupt zu nutzen, müssen wir den GIC konfigurieren. | |||
Erstelle die Datei '''interrupt.c''' und füge folgende Funktion hinzu: | |||
<syntaxhighlight lang="C"> | |||
void Interrupt_Initialize(void) | |||
{ | |||
// Deaktiviere den Distributor vor der Konfiguration | |||
write32(0, GICD_CTLR); | |||
// Aktiviere den Timer-Interrupt (IRQ-ID = TIMER_IRQ_ID) | |||
write32((1 << TIMER_IRQ_ID), GICD_ISENABLER0); | |||
// Setze Priorität (alle auf mittel) | |||
write32(0xA0A0A0A0, GICD_IPRIORITYR7); | |||
// Ziel-CPU festlegen (CPU0) | |||
write32(0x01010101, GICD_ITARGETSR7); | |||
// Distributor wieder aktivieren | |||
write32(1, GICD_CTLR); | |||
// Prioritätsmaske setzen (akzeptiere alle Prioritäten) | |||
write32(0xFF, GICC_PMR); | |||
// CPU-Schnittstelle aktivieren | |||
write32(1, GICC_CTLR); | |||
} | |||
</syntaxhighlight> | |||
=== Erklärung der GIC-Register === | |||
{| class="wikitable" | |||
! Register | |||
! Bedeutung | |||
|- | |||
| <code>GICD_CTLR</code> | |||
| Steuert den Distributor (0 = aus, 1 = an). | |||
|- | |||
| <code>GICD_ISENABLER0</code> | |||
| Aktiviert Interrupts mit ID 0–31. Hier wird Bit <code>TIMER_IRQ_ID</code> gesetzt. | |||
|- | |||
| <code>GICD_IPRIORITYR7</code> | |||
| Setzt die Priorität für Interrupts 28–31. 0xA0 = mittlere Priorität. | |||
|- | |||
| <code>GICD_ITARGETSR7</code> | |||
| Legt fest, welcher CPU-Kern den Interrupt erhält. 0x01 = CPU0. | |||
|- | |||
| <code>GICC_PMR</code> | |||
| Prioritätsmaske: Nur Interrupts mit höherer Priorität als 0xFF werden zugelassen. | |||
|- | |||
| <code>GICC_CTLR</code> | |||
| Aktiviert die CPU-Schnittstelle des GIC. | |||
|} | |||
== Systemtimer konfigurieren == | |||
Der ARMv8-Prozessor verfügt über einen eingebauten '''System Counter''' und einen '''Core Timer (cntp)'''. Wir nutzen diesen, um alle 1 Sekunde einen Interrupt auszulösen. | |||
Füge folgende Funktion in deine C-Datei ein: | |||
<syntaxhighlight lang="C"> | |||
void InitCoreTimer(void) | |||
{ | |||
unsigned long freq; | |||
// Lese die System-Taktfrequenz (z. B. 1 MHz) | |||
asm volatile("mrs %0, cntfrq_el0" : "=r"(freq)); | |||
// Setze den Timer auf 1 Sekunde (freq Zyklen) | |||
asm volatile("msr cntp_tval_el0, %0" :: "r"(freq)); | |||
// Aktiviere den Timer (Bit 0 = enable) | |||
asm volatile("msr cntp_ctl_el0, %0" :: "r"(1)); | |||
// Zusätzlich: Aktiviere den lokalen Timer im SoC (BCM2711) | |||
write32(2, TIMER_CNTRL0); | |||
} | |||
</syntaxhighlight> | |||
=== Erklärung der Timer-Register === | |||
{| class="wikitable" | |||
! Register | |||
! Bedeutung | |||
|- | |||
| <code>cntfrq_el0</code> | |||
| Enthält die Frequenz des Systemzählers in Hz (z. B. 1.000.000). | |||
|- | |||
| <code>cntp_tval_el0</code> | |||
| Legt fest, nach wie vielen Takten der Timer auslöst. <code>freq</code> = 1 Sekunde. | |||
|- | |||
| <code>cntp_ctl_el0</code> | |||
| Steuerregister: Bit 0 aktiviert den Timer. | |||
|- | |||
| <code>TIMER_CNTRL0</code> | |||
| SoC-internes Steuerregister (Broadcom), aktiviert den Timer-Interrupt. | |||
|} | |||
== Globale Interrupts aktivieren == | |||
Zum Schluss müssen wir globale Interrupts im Prozessor aktivieren. Dazu verwenden wir das <code>DAIF</code>-Register. | |||
Füge in eine Assembly-Datei (z. B. <code>util.S</code>) folgenden Code ein: | |||
<syntaxhighlight lang="asm"> | |||
.globl irq_enable | |||
irq_enable: | |||
msr daifclr, #0xf // Lösche alle DAIF-Flags (D, A, I, F) | |||
ret | |||
</syntaxhighlight> | </syntaxhighlight> | ||
'''Hinweis''': <code>daifclr, #0xf</code> entsperrt alle Interrupts (IRQ, FIQ, SError, Debug). | |||
== Der Interrupt-Dispatcher == | |||
Die Funktion <code>irq_dispatch</code> ist der zentrale Punkt, an dem alle IRQs landen. Sie prüft, welcher Interrupt ausgelöst wurde, und ruft den passenden Handler auf. | |||
<syntaxhighlight lang="C"> | |||
void irq_dispatch(void) | |||
{ | |||
u32 irq = read32(GICC_IAR); // Lese die IRQ-ID | |||
if (irq == TIMER_IRQ_ID) | |||
{ | |||
timer_irq_handler(); // Behandele Timer-Interrupt | |||
} | |||
else | |||
{ | |||
printf("Unbekannter IRQ: %d\n", irq); | |||
} | |||
// Melde "Ende des Interrupts" an den GIC | |||
write32(irq, GICC_EOIR); | |||
} | |||
</syntaxhighlight> | |||
=== Wichtige GIC-Register === | |||
{| class="wikitable" | |||
! Register | |||
! Bedeutung | |||
|- | |||
| <code>GICC_IAR</code> | |||
| Interrupt Acknowledge Register: Gibt die ID des auslösenden Interrupts zurück. | |||
|- | |||
| <code>GICC_EOIR</code> | |||
| End of Interrupt Register: Signalisiert, dass der Interrupt behandelt ist. | |||
|} | |||
'''Wichtig''': Ohne <code>GICC_EOIR</code> bleibt der Interrupt aktiv und kann nicht erneut ausgelöst werden! | |||
== Timer-Interrupt-Handler == | |||
Der eigentliche Handler führt die gewünschte Aktion aus. In unserem Fall aktualisieren wir einen kleinen Rotor im oberen rechten Bildschirmbereich. | |||
<syntaxhighlight lang="C"> | |||
u32 Rotor = 0; | |||
void timer_irq_handler(void) | |||
{ | |||
// Zeichne den nächsten Rotor-Zustand | |||
if (Rotor == 0) | |||
{ | |||
DrawChar('/', SCREEN_X - 8, 0); | |||
Rotor++; | |||
} | |||
else if (Rotor == 1) | |||
{ | |||
DrawChar('-', SCREEN_X - 8, 0); | |||
Rotor++; | |||
} | |||
else if (Rotor == 2) | |||
{ | |||
DrawChar('\\', SCREEN_X - 8, 0); | |||
Rotor++; | |||
} | |||
else if (Rotor == 3) | |||
{ | |||
DrawChar('|', SCREEN_X - 8, 0); | |||
Rotor = 0; | |||
} | |||
// Timer für nächste Sekunde zurücksetzen | |||
unsigned long freq; | |||
asm volatile("mrs %0, cntfrq_el0" : "=r"(freq)); | |||
asm volatile("msr cntp_tval_el0, %0" :: "r"(freq)); | |||
} | |||
</syntaxhighlight> | |||
'''Hinweis''': Der Timer wird nach jeder Auslösung neu gestartet, um den 1-Sekunden-Takt beizubehalten. | |||
== Zusammenfassung == | |||
Du hast jetzt einen vollständigen Timer-Interrupt auf dem Raspberry Pi 4 im Bare-Metal-Modus implementiert! | |||
* Die '''Vektortabelle''' leitet den IRQ an <code>IRQStub</code> weiter. | |||
* <code>IRQStub</code> sichert den Kontext und ruft <code>irq_dispatch</code> in C auf. | |||
* Der '''GIC''' wird konfiguriert, um den Timer-Interrupt zu empfangen. | |||
* Der '''Systemtimer''' wird auf 1 Sekunde eingestellt. | |||
* Der '''Handler''' aktualisiert eine Anzeige und setzt den Timer zurück. | |||
Mit diesem Wissen kannst du nun auch andere Interrupts (z. B. von GPIO, UART oder dem Network-Controller) einrichten. | |||
Den Kompletten Sourcecode bekommst du hier: [https://www.satyria.de/arm/sources/RPI4/C/10.zip Source] | |||
----- | |||
{| style="width: 100%; | |||
| style="width: 33%;" | [[Interrupts (PI4)|< Zurück (Interrupt)]] | |||
| style="width: 33%; text-align:center;" | [[Hauptseite|< Hauptseite >]] | |||
| style="width: 33%; text-align:right;" | [[Interrupt Teil 2 (PI4)|Weiter (Interrupt Teil 2 (PI4)) >]] | |||
|} | |||
Aktuelle Version vom 8. August 2025, 08:55 Uhr
Beispiel: Timer-Interrupt auf dem Raspberry Pi 4
Nachdem wir im letzten Kapitel die Grundlagen zu Interrupts kennengelernt haben, setzen wir nun das Gelernte in die Praxis um. In diesem Kapitel richten wir einen Timer-Interrupt ein, der alle eine Sekunde auslöst.
Wir verwenden den Systemtimer des ARM-Prozessors (Generic Timer) zusammen mit dem GIC-400 Interrupt-Controller (Generic Interrupt Controller). Der Interrupt führt eine einfache Aktion aus: Ein kleiner "Rotor" (Ladeanimation) dreht sich im oberen rechten Bildschirmbereich.
Dieses Beispiel zeigt dir Schritt für Schritt, wie du:
- einen Timer konfigurierst,
- Interrupts über den GIC aktivierst,
- einen Interrupt-Handler schreibst und
- den Timer nach jeder Auslösung zurücksetzt.
Wir verzichten zunächst auf Gleitkomma-Unterstützung, um den Code einfach und übersichtlich zu halten.
Vektortabelle anpassen
Zuerst erweitern wir unsere Vektortabelle, um den Timer-Interrupt korrekt zu verarbeiten. Da wir uns zunächst nur auf IRQs konzentrieren, leiten wir alle anderen Ausnahmen in eine Dauerschleife um.
Der folgende Code gehört in die Datei vector.s.
.section .text
.align 11
.globl VectorTable
VectorTable:
// EL1 mit SP_EL0 (Benutzermodus)
.align 7
b hang // Synchronous Exception
.align 7
b IRQStub // IRQ - Normal Interrupt
.align 7
b hang // FIQ - Fast Interrupt
.align 7
b hang // SError - System Error
// EL1 mit SP_EL1 (Kernelmodus)
.align 7
b hang // Synchronous Exception
.align 7
b IRQStub // IRQ - Normal Interrupt
.align 7
b hang // FIQ - Fast Interrupt
.align 7
b hang // SError - System Error
// EL0 64-Bit (nicht erlaubt)
.align 7
b hang
.align 7
b hang
.align 7
b hang
.align 7
b hang
// EL0 32-Bit (nicht erlaubt)
.align 7
b hang
.align 7
b hang
.align 7
b hang
.align 7
b hang
// Einfache Dauerschleife für nicht behandelte Ausnahmen
hang:
wfe // CPU wartet auf Ereignis (spart Energie)
b hang // Zurück zur Schleife
> Hinweis: Alle nicht behandelten Ausnahmen führen in die Schleife hang. Nur der IRQ springt zum Handler IRQStub.
IRQ-Handler: IRQStub
Wenn ein Interrupt ausgelöst wird, springt der Prozessor zum Label IRQStub. Dort sichern wir zunächst alle Register auf dem Stack, rufen eine C-Funktion auf und stellen danach alles wieder her.
Auch dieser Teil gehört in vector.s.
.globl IRQStub
IRQStub:
// Kontext sichern: Alle Register auf den Stack
stp x29, x30, [sp, #-16]!
stp x27, x28, [sp, #-16]!
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]!
// Aufruf der C-Funktion: irq_dispatch
bl irq_dispatch
// Kontext wiederherstellen
ldr x0, [sp], #16
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 x29, x30, [sp], #16
eret // Zurück zum unterbrochenen Code
Wichtig:
- Der Handler sichert alle Register, da er nicht weiß, welcher Code unterbrochen wurde.
- Nach dem Aufruf von
irq_dispatchwerden alle Register wiederhergestellt. eretkehrt zum ursprünglichen Code zurück.
Interrupt-Controller initialisieren (GIC-400)
Wir verwenden den Generic Interrupt Controller (GIC-400), der im Raspberry Pi 4 integriert ist. Um den Timer-Interrupt zu nutzen, müssen wir den GIC konfigurieren.
Erstelle die Datei interrupt.c und füge folgende Funktion hinzu:
void Interrupt_Initialize(void)
{
// Deaktiviere den Distributor vor der Konfiguration
write32(0, GICD_CTLR);
// Aktiviere den Timer-Interrupt (IRQ-ID = TIMER_IRQ_ID)
write32((1 << TIMER_IRQ_ID), GICD_ISENABLER0);
// Setze Priorität (alle auf mittel)
write32(0xA0A0A0A0, GICD_IPRIORITYR7);
// Ziel-CPU festlegen (CPU0)
write32(0x01010101, GICD_ITARGETSR7);
// Distributor wieder aktivieren
write32(1, GICD_CTLR);
// Prioritätsmaske setzen (akzeptiere alle Prioritäten)
write32(0xFF, GICC_PMR);
// CPU-Schnittstelle aktivieren
write32(1, GICC_CTLR);
}
Erklärung der GIC-Register
| Register | Bedeutung |
|---|---|
GICD_CTLR
|
Steuert den Distributor (0 = aus, 1 = an). |
GICD_ISENABLER0
|
Aktiviert Interrupts mit ID 0–31. Hier wird Bit TIMER_IRQ_ID gesetzt.
|
GICD_IPRIORITYR7
|
Setzt die Priorität für Interrupts 28–31. 0xA0 = mittlere Priorität. |
GICD_ITARGETSR7
|
Legt fest, welcher CPU-Kern den Interrupt erhält. 0x01 = CPU0. |
GICC_PMR
|
Prioritätsmaske: Nur Interrupts mit höherer Priorität als 0xFF werden zugelassen. |
GICC_CTLR
|
Aktiviert die CPU-Schnittstelle des GIC. |
Systemtimer konfigurieren
Der ARMv8-Prozessor verfügt über einen eingebauten System Counter und einen Core Timer (cntp). Wir nutzen diesen, um alle 1 Sekunde einen Interrupt auszulösen.
Füge folgende Funktion in deine C-Datei ein:
void InitCoreTimer(void)
{
unsigned long freq;
// Lese die System-Taktfrequenz (z. B. 1 MHz)
asm volatile("mrs %0, cntfrq_el0" : "=r"(freq));
// Setze den Timer auf 1 Sekunde (freq Zyklen)
asm volatile("msr cntp_tval_el0, %0" :: "r"(freq));
// Aktiviere den Timer (Bit 0 = enable)
asm volatile("msr cntp_ctl_el0, %0" :: "r"(1));
// Zusätzlich: Aktiviere den lokalen Timer im SoC (BCM2711)
write32(2, TIMER_CNTRL0);
}
Erklärung der Timer-Register
| Register | Bedeutung |
|---|---|
cntfrq_el0
|
Enthält die Frequenz des Systemzählers in Hz (z. B. 1.000.000). |
cntp_tval_el0
|
Legt fest, nach wie vielen Takten der Timer auslöst. freq = 1 Sekunde.
|
cntp_ctl_el0
|
Steuerregister: Bit 0 aktiviert den Timer. |
TIMER_CNTRL0
|
SoC-internes Steuerregister (Broadcom), aktiviert den Timer-Interrupt. |
Globale Interrupts aktivieren
Zum Schluss müssen wir globale Interrupts im Prozessor aktivieren. Dazu verwenden wir das DAIF-Register.
Füge in eine Assembly-Datei (z. B. util.S) folgenden Code ein:
.globl irq_enable
irq_enable:
msr daifclr, #0xf // Lösche alle DAIF-Flags (D, A, I, F)
ret
Hinweis: daifclr, #0xf entsperrt alle Interrupts (IRQ, FIQ, SError, Debug).
Der Interrupt-Dispatcher
Die Funktion irq_dispatch ist der zentrale Punkt, an dem alle IRQs landen. Sie prüft, welcher Interrupt ausgelöst wurde, und ruft den passenden Handler auf.
void irq_dispatch(void)
{
u32 irq = read32(GICC_IAR); // Lese die IRQ-ID
if (irq == TIMER_IRQ_ID)
{
timer_irq_handler(); // Behandele Timer-Interrupt
}
else
{
printf("Unbekannter IRQ: %d\n", irq);
}
// Melde "Ende des Interrupts" an den GIC
write32(irq, GICC_EOIR);
}
Wichtige GIC-Register
| Register | Bedeutung |
|---|---|
GICC_IAR
|
Interrupt Acknowledge Register: Gibt die ID des auslösenden Interrupts zurück. |
GICC_EOIR
|
End of Interrupt Register: Signalisiert, dass der Interrupt behandelt ist. |
Wichtig: Ohne GICC_EOIR bleibt der Interrupt aktiv und kann nicht erneut ausgelöst werden!
Timer-Interrupt-Handler
Der eigentliche Handler führt die gewünschte Aktion aus. In unserem Fall aktualisieren wir einen kleinen Rotor im oberen rechten Bildschirmbereich.
u32 Rotor = 0;
void timer_irq_handler(void)
{
// Zeichne den nächsten Rotor-Zustand
if (Rotor == 0)
{
DrawChar('/', SCREEN_X - 8, 0);
Rotor++;
}
else if (Rotor == 1)
{
DrawChar('-', SCREEN_X - 8, 0);
Rotor++;
}
else if (Rotor == 2)
{
DrawChar('\\', SCREEN_X - 8, 0);
Rotor++;
}
else if (Rotor == 3)
{
DrawChar('|', SCREEN_X - 8, 0);
Rotor = 0;
}
// Timer für nächste Sekunde zurücksetzen
unsigned long freq;
asm volatile("mrs %0, cntfrq_el0" : "=r"(freq));
asm volatile("msr cntp_tval_el0, %0" :: "r"(freq));
}
Hinweis: Der Timer wird nach jeder Auslösung neu gestartet, um den 1-Sekunden-Takt beizubehalten.
Zusammenfassung
Du hast jetzt einen vollständigen Timer-Interrupt auf dem Raspberry Pi 4 im Bare-Metal-Modus implementiert!
- Die Vektortabelle leitet den IRQ an
IRQStubweiter. IRQStubsichert den Kontext und ruftirq_dispatchin C auf.- Der GIC wird konfiguriert, um den Timer-Interrupt zu empfangen.
- Der Systemtimer wird auf 1 Sekunde eingestellt.
- Der Handler aktualisiert eine Anzeige und setzt den Timer zurück.
Mit diesem Wissen kannst du nun auch andere Interrupts (z. B. von GPIO, UART oder dem Network-Controller) einrichten.
Den Kompletten Sourcecode bekommst du hier: Source
| < Zurück (Interrupt) | < Hauptseite > | Weiter (Interrupt Teil 2 (PI4)) > |