Beispiel Timer-Interrupt (PI4): Unterschied zwischen den Versionen

Aus C und Assembler mit Raspberry
KKeine Bearbeitungszusammenfassung
KKeine Bearbeitungszusammenfassung
 
(3 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.
# Kapitel: Der erste echte Hardware-Interrupt – Der System-Timer (Rotor)


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.
Nachdem wir im letzten Kapitel die Vektortabelle (`vector.S`) aufgebaut und den Blindtest mit `svc #0` erfolgreich bestanden haben, wissen wir: Unser Prozessor kann auf Ausnahmen reagieren.  


Dieses Beispiel zeigt dir Schritt für Schritt, wie du:
Jetzt machen wir den nächsten großen Schritt: Wir verbinden eine **echte Hardware** mit unserem Code. Wir richten den ARM System Timer so ein, dass er alle 250 Millisekunden einen Interrupt auslöst. Als visuelle Bestätigung zeichnen wir einen kleinen "Rotor" (`/`, `-`, `\`, `|`) in die obere rechte Ecke des Bildschirms.


* einen Timer konfigurierst,
Dieses Kapitel zeigt dir, wie die drei Komponenten zusammenspielen:
* Interrupts über den GIC aktivierst,
1. Der **ARM Virtual Timer** (erzeugt das Signal).
* einen Interrupt-Handler schreibst und
2. Der **GIC-400** (Generic Interrupt Controller, verteilt das Signal an die CPU).
* den Timer nach jeder Auslösung zurücksetzt.
3. Unser **C-Handler** (verarbeitet das Signal und zeichnet den Rotor).


Wir verzichten zunächst auf Gleitkomma-Unterstützung, um den Code einfach und übersichtlich zu halten.
---


== Vektortabelle anpassen ==
## 1. Der Generic Interrupt Controller (GIC-400)


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 ARM-Prozessor selbst weiß nicht, welches Peripheral (Timer, USB, UART) einen Interrupt auslöst. Dafür gibt es den GIC. Er sitzt zwischen der Hardware und der CPU.  


Der folgende Code gehört in die Datei '''vector.s'''.
In deiner `interrupt.c` gibt es bereits die Funktion `Interrupt_Initialize()`. Sie macht Folgendes:
1. Sie schaltet den GIC-Distributor kurz ab.
2. Sie setzt alle Interrupts auf "Level-Triggered" und weist sie standardmäßig CPU 0 zu.
3. Sie aktiviert die CPU-Schnittstelle (`GICC_CTLR`) und erlaubt Interrupts.


<syntaxhighlight lang="asm">
> 💡 **Merke:** Der GIC muss *einmalig* zu Beginn initialisiert werden. Danach können wir gezielt einzelne Interrupt-Quellen "freischalten".
.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)
## 2. Den Virtual Timer aktivieren
    .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)
ARMv8-Prozessoren haben zwei Timer pro Kern: den *Physical Timer* (`cntp`) und den *Virtual Timer* (`cntv`). Für Betriebssysteme (EL1) wird fast immer der **Virtual Timer** verwendet, da das Hypervisor (EL2) ihn transparent verwalten kann, ohne dass unser Kernel-Code davon etwas merkt.
    .align 7
    b hang
    .align 7
    b hang
    .align 7
    b hang
    .align 7
    b hang


    // EL0 32-Bit (nicht erlaubt)
In `timer.c` findest du zwei entscheidende Funktionen:
    .align 7
    b hang
    .align 7
    b hang
    .align 7
    b hang
    .align 7
    b hang


// Einfache Dauerschleife für nicht behandelte Ausnahmen
```c
hang:
void EnableVirtualTimer(void)
    wfe              // CPU wartet auf Ereignis (spart Energie)
{
     b hang          // Zurück zur Schleife
     u64 cntkctl;
</syntaxhighlight>
    asm volatile("mrs %0, cntkctl_el1" : "=r"(cntkctl));
 
   
> '''Hinweis''': Alle nicht behandelten Ausnahmen führen in die Schleife <code>hang</code>. Nur der '''IRQ''' springt zum Handler <code>IRQStub</code>.
     // Erlaube EL1 den Zugriff auf den Physical Count und den Virtual Timer
 
     cntkctl |= (1 << 0); // EL1PCTEN
== IRQ-Handler: IRQStub ==
     cntkctl |= (1 << 1); // EL1PCEN
 
     cntkctl |= (1 << 2);  // EL1VTEN
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.
      
 
     asm volatile("msr cntkctl_el1, %0" :: "r"(cntkctl));
Auch dieser Teil gehört in '''vector.s'''.
}
 
```
<syntaxhighlight lang="asm">
Diese Funktion schaltet quasi den "Hahn" auf, damit unser Code den Timer überhaupt benutzen darf.
.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:
Als Nächstes konfigurieren wir die Auslösezeit:


<syntaxhighlight lang="C">
```c
void Interrupt_Initialize(void)
void InitRotorTimer(void)
{
{
     // Deaktiviere den Distributor vor der Konfiguration
     // 1. Frequenz des Timers auslesen (beim Pi 4 meist 54.000.000 Hz)
     write32(0, GICD_CTLR);
     u64 freq;
 
     asm volatile("mrs %0, cntfrq_el0" : "=r"(freq));
     // Aktiviere den Timer-Interrupt (IRQ-ID = TIMER_IRQ_ID)
   
    write32((1 << TIMER_IRQ_ID), GICD_ISENABLER0);
     // 2. Ziel: Alle 250 ms (freq / 4 Takte)
 
     u64 ticks_per_250ms = freq / 4;
     // Setze Priorität (alle auf mittel)
   
     write32(0xA0A0A0A0, GICD_IPRIORITYR7);
     // 3. Timer-Wert setzen (Countdown beginnt)
 
     asm volatile("msr cntv_tval_el0, %0" :: "r"(ticks_per_250ms));
     // Ziel-CPU festlegen (CPU0)
   
     write32(0x01010101, GICD_ITARGETSR7);
     // 4. Timer aktivieren: Bit 0 = Enable, Bit 1 = Interrupt Mask (0 = erlaubt)
 
     asm volatile("msr cntv_ctl_el0, %0" :: "r"(1));
     // 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>
```
> ⚠️ **Wichtig:** Der ARM Timer ist ein *Countdown*-Timer. Wenn er 0 erreicht, löst er den Interrupt aus und **stoppt**. Wir müssen ihn im Handler also immer wieder neu laden!


=== Erklärung der GIC-Register ===
---


{| class="wikitable"
## 3. Die Interrupt-Verbindung herstellen (IRQ 27)
! 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 Virtual Timer löst standardmäßig den Interrupt mit der ID **27** (`ARM_IRQLOCAL0_CNTPNS`) aus. Wir müssen dem GIC nun zwei Dinge sagen:
1. "Schicke IRQ 27 an CPU 0."
2. "Wenn IRQ 27 kommt, rufe diese C-Funktion auf."


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.
Das erledigen wir mit diesen beiden Funktionen:


Füge folgende Funktion in deine C-Datei ein:
```c
 
void SetIRQ27Target(void)
<syntaxhighlight lang="C">
void InitCoreTimer(void)
{
{
    unsigned long freq;
     // Lese das Target-Register für IRQs 24-31
 
     u32 reg = read32(GICD_ITARGETSR0 + 4*(27/4));
     // Lese die System-Taktfrequenz (z. B. 1 MHz)
   
     asm volatile("mrs %0, cntfrq_el0" : "=r"(freq));
     // Lösche die Bits für IRQ 27 und setze sie auf CPU 0 (Wert 1)
 
    reg &= ~(0xFF << 24);           
     // Setze den Timer auf 1 Sekunde (freq Zyklen)
     reg |= (1 << 24);              
     asm volatile("msr cntp_tval_el0, %0" :: "r"(freq));
   
 
     // Zurückschreiben und sicherstellen, dass die Änderung wirksam wird
     // Aktiviere den Timer (Bit 0 = enable)
     write32(reg, GICD_ITARGETSR0 + 4*(27/4));
     asm volatile("msr cntp_ctl_el0, %0" :: "r"(1));
     DataSyncBarrier();
 
    // 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 ==
Und das Verbinden der Funktion:
```c
// Registriere RotorTimerHandler für IRQ 27
Interrupt_ConnectIRQ(27, RotorTimerHandler, NULL);


Zum Schluss müssen wir globale Interrupts im Prozessor aktivieren. Dazu verwenden wir das <code>DAIF</code>-Register.
// Schalte IRQ 27 im GIC Distributor scharf
write32(1 << 27, GICD_ISENABLER0);
DataSyncBarrier();
```
*Hinweis:* `Interrupt_ConnectIRQ` speichert den Zeiger auf `RotorTimerHandler` in einem Array (`s_Interrupt_apIRQHandler`). Wenn der Interrupt kommt, schaut dein zentraler `InterruptHandler` in `interrupt.c` genau dort nach und ruft deine Funktion auf.


Füge in eine Assembly-Datei (z. B. <code>util.S</code>) folgenden Code ein:
---


<syntaxhighlight lang="asm">
## 4. Der Interrupt-Handler: `RotorTimerHandler`
.globl irq_enable
irq_enable:
    msr daifclr, #0xf    // Lösche alle DAIF-Flags (D, A, I, F)
    ret
</syntaxhighlight>


'''Hinweis''': <code>daifclr, #0xf</code> entsperrt alle Interrupts (IRQ, FIQ, SError, Debug).
Hier passiert die Magie. Diese Funktion wird asynchron aufgerufen, egal was die `main()`-Schleife gerade tut.


== Der Interrupt-Dispatcher ==
```c
 
void RotorTimerHandler(void* pParam)
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
     // 1. Die Rotor-Animation zeichnen
 
    static int rotor = 0;
     if (irq == TIMER_IRQ_ID)
    char ch;
    {
      
         timer_irq_handler();     // Behandele Timer-Interrupt
    switch (rotor % 4) {
        case 0: ch = '/'; break;
        case 1: ch = '-'; break;
         case 2: ch = '\\'; break; // Beachte das Escape-Zeichen \
        case 3: ch = '|'; break;
     }
     }
     else
      
     {
     // Zeichne an Position X-8, Y=0 (oben rechts)
        printf("Unbekannter IRQ: %d\n", irq);
    DrawChar(ch, SCREEN_X - 8, 0);
     }
    rotor++;
 
   
     // Melde "Ende des Interrupts" an den GIC
    // 2. WICHTIG: Timer für die nächste Runde neu laden!
     write32(irq, GICC_EOIR);
    u64 freq;
    asm volatile("mrs %0, cntfrq_el0" : "=r"(freq));
    asm volatile("msr cntv_tval_el0, %0" :: "r"(freq / 4));
      
     // Hinweis: Das Senden des "End of Interrupt" (EOIR) an den GIC  
     // wird bereits automatisch von unserem zentralen InterruptHandler
    // in interrupt.c erledigt, bevor diese Funktion aufgerufen wird!
}
}
</syntaxhighlight>
```


=== Wichtige GIC-Register ===
> 🛡️ **Warum kein `GICC_EOIR` hier?** 
> In älteren Tutorials siehst du oft `write32(irq, GICC_EOIR);` im Handler. Unser Code ist besser: Die Assembly-Routine `GenericIRQHandler` in `vector.S` ruft `InterruptHandler` auf, und *dieser* erledigt das `EOIR` zentral, *nachdem* deine `RotorTimerHandler`-Funktion fertig ist. Das verhindert, dass du es vergisst (was zu einem sofortigen Systemabsturz durch "Interrupt Storms" führen würde).


{| 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!
## 5. Alles in `main.c` zusammenfügen


== Timer-Interrupt-Handler ==
Damit der Rotor sich dreht, müssen die Initialisierungen in der richtigen Reihenfolge in deiner `main.c` (oder `Kernel_Initialize`) stehen:


Der eigentliche Handler führt die gewünschte Aktion aus. In unserem Fall aktualisieren wir einen kleinen Rotor im oberen rechten Bildschirmbereich.
```c
void Kernel_Initialize(CKernel *pKernel)
{
    // 1. Basis-Initialisierung (Bildschirm, Terminal, printf)
    Init_Screen();
    InitTerminal();
    SetFrontColor(satyr);
    printf("System startet...\n");
    SetFrontColor(white);


<syntaxhighlight lang="C">
    // 2. Interrupt-Subsystem initialisieren (GIC-400)
u32 Rotor = 0;
    Interrupt_Initialize();
 
   
void timer_irq_handler(void)
    // 3. Timer vorbereiten
{
    EnableVirtualTimer();
     // Zeichne den nächsten Rotor-Zustand
   
     if (Rotor == 0)
     // 4. IRQ 27 an CPU 0 routen und scharf schalten
     {
     SetIRQ27Target();
        DrawChar('/', SCREEN_X - 8, 0);
     write32(1 << 27, GICD_ISENABLER0);
        Rotor++;
    DataSyncBarrier();
     }
      
     else if (Rotor == 1)
     // 5. Handler registrieren
     {
     Interrupt_ConnectIRQ(27, RotorTimerHandler, NULL);
        DrawChar('-', SCREEN_X - 8, 0);
   
        Rotor++;
     // 6. Timer starten (ab jetzt kommen alle 250ms Interrupts)
     }
     InitRotorTimer();
     else if (Rotor == 2)
      
     {
    printf("Rotor sollte oben rechts rotieren...\n");
        DrawChar('\\', SCREEN_X - 8, 0);
   
        Rotor++;
     // 7. Hauptschleife (kann leer sein, der Interrupt läuft im Hintergrund!)
     }
     while (1)
     else if (Rotor == 3)
     {
     {
         DrawChar('|', SCREEN_X - 8, 0);
         // Optional: CPU in energiesparenden Zustand versetzen,  
         Rotor = 0;
         // bis der nächste Interrupt kommt
        asm volatile("wfi"); // Wait For Interrupt
     }
     }
    // 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 ==
## Zusammenfassung & Lernkontrolle


Du hast jetzt einen vollständigen Timer-Interrupt auf dem Raspberry Pi 4 im Bare-Metal-Modus implementiert!
Du hast soeben einen vollständigen, produktionsreifen Interrupt-Pfad implementiert:
1. **Hardware** (Timer) zählt herunter.
2. **GIC** fängt das Signal ab und reicht es an CPU 0 weiter.
3. **CPU** unterbricht den aktuellen Code, springt in `vector.S` -> `GenericIRQHandler`.
4. **Handler** sichert alle Register (inkl. NEON), ruft `InterruptHandler` auf.
5. **Dispatcher** findet `RotorTimerHandler` in der Tabelle und führt ihn aus.
6. **Rotor** wird gezeichnet, Timer neu geladen.
7. **Rücksprung:** Register werden wiederhergestellt, `eret` kehrt zum unterbrochenen Code zurück.


* 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 21. Juli 2026, 14:38 Uhr

  1. Kapitel: Der erste echte Hardware-Interrupt – Der System-Timer (Rotor)

Nachdem wir im letzten Kapitel die Vektortabelle (`vector.S`) aufgebaut und den Blindtest mit `svc #0` erfolgreich bestanden haben, wissen wir: Unser Prozessor kann auf Ausnahmen reagieren.

Jetzt machen wir den nächsten großen Schritt: Wir verbinden eine **echte Hardware** mit unserem Code. Wir richten den ARM System Timer so ein, dass er alle 250 Millisekunden einen Interrupt auslöst. Als visuelle Bestätigung zeichnen wir einen kleinen "Rotor" (`/`, `-`, `\`, `|`) in die obere rechte Ecke des Bildschirms.

Dieses Kapitel zeigt dir, wie die drei Komponenten zusammenspielen: 1. Der **ARM Virtual Timer** (erzeugt das Signal). 2. Der **GIC-400** (Generic Interrupt Controller, verteilt das Signal an die CPU). 3. Unser **C-Handler** (verarbeitet das Signal und zeichnet den Rotor).

---

    1. 1. Der Generic Interrupt Controller (GIC-400)

Der ARM-Prozessor selbst weiß nicht, welches Peripheral (Timer, USB, UART) einen Interrupt auslöst. Dafür gibt es den GIC. Er sitzt zwischen der Hardware und der CPU.

In deiner `interrupt.c` gibt es bereits die Funktion `Interrupt_Initialize()`. Sie macht Folgendes: 1. Sie schaltet den GIC-Distributor kurz ab. 2. Sie setzt alle Interrupts auf "Level-Triggered" und weist sie standardmäßig CPU 0 zu. 3. Sie aktiviert die CPU-Schnittstelle (`GICC_CTLR`) und erlaubt Interrupts.

> 💡 **Merke:** Der GIC muss *einmalig* zu Beginn initialisiert werden. Danach können wir gezielt einzelne Interrupt-Quellen "freischalten".

---

    1. 2. Den Virtual Timer aktivieren

ARMv8-Prozessoren haben zwei Timer pro Kern: den *Physical Timer* (`cntp`) und den *Virtual Timer* (`cntv`). Für Betriebssysteme (EL1) wird fast immer der **Virtual Timer** verwendet, da das Hypervisor (EL2) ihn transparent verwalten kann, ohne dass unser Kernel-Code davon etwas merkt.

In `timer.c` findest du zwei entscheidende Funktionen:

```c void EnableVirtualTimer(void) {

   u64 cntkctl;
   asm volatile("mrs %0, cntkctl_el1" : "=r"(cntkctl));
   
   // Erlaube EL1 den Zugriff auf den Physical Count und den Virtual Timer
   cntkctl |= (1 << 0);  // EL1PCTEN
   cntkctl |= (1 << 1);  // EL1PCEN
   cntkctl |= (1 << 2);  // EL1VTEN
   
   asm volatile("msr cntkctl_el1, %0" :: "r"(cntkctl));

} ``` Diese Funktion schaltet quasi den "Hahn" auf, damit unser Code den Timer überhaupt benutzen darf.

Als Nächstes konfigurieren wir die Auslösezeit:

```c void InitRotorTimer(void) {

   // 1. Frequenz des Timers auslesen (beim Pi 4 meist 54.000.000 Hz)
   u64 freq;
   asm volatile("mrs %0, cntfrq_el0" : "=r"(freq));
   
   // 2. Ziel: Alle 250 ms (freq / 4 Takte)
   u64 ticks_per_250ms = freq / 4;
   
   // 3. Timer-Wert setzen (Countdown beginnt)
   asm volatile("msr cntv_tval_el0, %0" :: "r"(ticks_per_250ms));
   
   // 4. Timer aktivieren: Bit 0 = Enable, Bit 1 = Interrupt Mask (0 = erlaubt)
   asm volatile("msr cntv_ctl_el0, %0" :: "r"(1));

} ``` > ⚠️ **Wichtig:** Der ARM Timer ist ein *Countdown*-Timer. Wenn er 0 erreicht, löst er den Interrupt aus und **stoppt**. Wir müssen ihn im Handler also immer wieder neu laden!

---

    1. 3. Die Interrupt-Verbindung herstellen (IRQ 27)

Der Virtual Timer löst standardmäßig den Interrupt mit der ID **27** (`ARM_IRQLOCAL0_CNTPNS`) aus. Wir müssen dem GIC nun zwei Dinge sagen: 1. "Schicke IRQ 27 an CPU 0." 2. "Wenn IRQ 27 kommt, rufe diese C-Funktion auf."

Das erledigen wir mit diesen beiden Funktionen:

```c void SetIRQ27Target(void) {

   // Lese das Target-Register für IRQs 24-31
   u32 reg = read32(GICD_ITARGETSR0 + 4*(27/4));  
   
   // Lösche die Bits für IRQ 27 und setze sie auf CPU 0 (Wert 1)
   reg &= ~(0xFF << 24);            
   reg |= (1 << 24);                
   
   // Zurückschreiben und sicherstellen, dass die Änderung wirksam wird
   write32(reg, GICD_ITARGETSR0 + 4*(27/4));
   DataSyncBarrier();

} ```

Und das Verbinden der Funktion: ```c // Registriere RotorTimerHandler für IRQ 27 Interrupt_ConnectIRQ(27, RotorTimerHandler, NULL);

// Schalte IRQ 27 im GIC Distributor scharf write32(1 << 27, GICD_ISENABLER0); DataSyncBarrier(); ```

  • Hinweis:* `Interrupt_ConnectIRQ` speichert den Zeiger auf `RotorTimerHandler` in einem Array (`s_Interrupt_apIRQHandler`). Wenn der Interrupt kommt, schaut dein zentraler `InterruptHandler` in `interrupt.c` genau dort nach und ruft deine Funktion auf.

---

    1. 4. Der Interrupt-Handler: `RotorTimerHandler`

Hier passiert die Magie. Diese Funktion wird asynchron aufgerufen, egal was die `main()`-Schleife gerade tut.

```c void RotorTimerHandler(void* pParam) {

   // 1. Die Rotor-Animation zeichnen
   static int rotor = 0;
   char ch;
   
   switch (rotor % 4) {
       case 0: ch = '/'; break;
       case 1: ch = '-'; break;
       case 2: ch = '\\'; break; // Beachte das Escape-Zeichen \
       case 3: ch = '|'; break;
   }
   
   // Zeichne an Position X-8, Y=0 (oben rechts)
   DrawChar(ch, SCREEN_X - 8, 0);
   rotor++;
   
   // 2. WICHTIG: Timer für die nächste Runde neu laden!
   u64 freq;
   asm volatile("mrs %0, cntfrq_el0" : "=r"(freq));
   asm volatile("msr cntv_tval_el0, %0" :: "r"(freq / 4));
   
   // Hinweis: Das Senden des "End of Interrupt" (EOIR) an den GIC 
   // wird bereits automatisch von unserem zentralen InterruptHandler 
   // in interrupt.c erledigt, bevor diese Funktion aufgerufen wird!

} ```

> 🛡️ **Warum kein `GICC_EOIR` hier?** > In älteren Tutorials siehst du oft `write32(irq, GICC_EOIR);` im Handler. Unser Code ist besser: Die Assembly-Routine `GenericIRQHandler` in `vector.S` ruft `InterruptHandler` auf, und *dieser* erledigt das `EOIR` zentral, *nachdem* deine `RotorTimerHandler`-Funktion fertig ist. Das verhindert, dass du es vergisst (was zu einem sofortigen Systemabsturz durch "Interrupt Storms" führen würde).

---

    1. 5. Alles in `main.c` zusammenfügen

Damit der Rotor sich dreht, müssen die Initialisierungen in der richtigen Reihenfolge in deiner `main.c` (oder `Kernel_Initialize`) stehen:

```c void Kernel_Initialize(CKernel *pKernel) {

   // 1. Basis-Initialisierung (Bildschirm, Terminal, printf)
   Init_Screen();
   InitTerminal();
   SetFrontColor(satyr);
   printf("System startet...\n");
   SetFrontColor(white);
   // 2. Interrupt-Subsystem initialisieren (GIC-400)
   Interrupt_Initialize();
   
   // 3. Timer vorbereiten
   EnableVirtualTimer();
   
   // 4. IRQ 27 an CPU 0 routen und scharf schalten
   SetIRQ27Target();
   write32(1 << 27, GICD_ISENABLER0);
   DataSyncBarrier();
   
   // 5. Handler registrieren
   Interrupt_ConnectIRQ(27, RotorTimerHandler, NULL);
   
   // 6. Timer starten (ab jetzt kommen alle 250ms Interrupts)
   InitRotorTimer();
   
   printf("Rotor sollte oben rechts rotieren...\n");
   
   // 7. Hauptschleife (kann leer sein, der Interrupt läuft im Hintergrund!)
   while (1)
   {
       // Optional: CPU in energiesparenden Zustand versetzen, 
       // bis der nächste Interrupt kommt
       asm volatile("wfi"); // Wait For Interrupt
   }

} ```

---

    1. Zusammenfassung & Lernkontrolle

Du hast soeben einen vollständigen, produktionsreifen Interrupt-Pfad implementiert: 1. **Hardware** (Timer) zählt herunter. 2. **GIC** fängt das Signal ab und reicht es an CPU 0 weiter. 3. **CPU** unterbricht den aktuellen Code, springt in `vector.S` -> `GenericIRQHandler`. 4. **Handler** sichert alle Register (inkl. NEON), ruft `InterruptHandler` auf. 5. **Dispatcher** findet `RotorTimerHandler` in der Tabelle und führt ihn aus. 6. **Rotor** wird gezeichnet, Timer neu geladen. 7. **Rücksprung:** Register werden wiederhergestellt, `eret` kehrt zum unterbrochenen Code zurück.



< Zurück (Interrupt) < Hauptseite > Weiter (Interrupt Teil 2 (PI4)) >