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

Aus C und Assembler mit Raspberry
KKeine Bearbeitungszusammenfassung
KKeine Bearbeitungszusammenfassung
 
(11 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
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". Andere Ausnahmen werden in eine Dauerschleife versetzt:
# Kapitel: Der erste echte Hardware-Interrupt – Der System-Timer (Rotor)
<syntaxhighlight lang="asm">
.align 11
.globl VectorTable
VectorTable:
// Vektoren für EL1t (Current Exception Level SP_el0)
    .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
// Vektoren für EL1h (Current Exception Level SP_el1)
    .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
// Vektoren für EL0 64-bit Modus
    .align 7
    b hang    // Synchronous EL0 (64-bit)
    .align 7
    b hang      // IRQ EL0 (64-bit)
    .align 7
    b hang      // FIQ EL0 (64-bit)
    .align 7
    b hang    // Error EL0 (64-bit)
// Vektoren für EL0 32-bit Modus
    .align 7
    b hang    // Synchronous EL0 (32-bit)
    .align 7
    b hang      // IRQ EL0 (32-bit)
    .align 7
    b hang      // FIQ EL0 (32-bit)
    .align 7
    b hang    // Error EL0 (32-bit)


hang:
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.
    wfe        // spare CPU cycles
    b hang
</syntaxhighlight>


Im Anschluss müssen wir beschreiben, was passieren soll, wenn der Interrupt ausgelöst wurde:
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.
<syntaxhighlight lang="asm">
.globl IRQStub
IRQStub:
  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]!


    bl irq_dispatch  //Springe zur Auswertung
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).


  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
</syntaxhighlight>
Dies speichern wir als vecotor.s in unser Projekt ab.


Wir verwenden den GIC-400 Kontroller.
## 1. Der Generic Interrupt Controller (GIC-400)
Als nächstes müssen wir noch Interrupts Initialisieren. Dazu erstellen wir eine C-Funktion und legen diese in interrupt.c ab:
 
<syntaxhighlight lang="C">
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.
void Interrupt_Initialize (void)
 
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".
 
---
 
## 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)
{
{
     write32(0,GICD_CTLR);
     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.


    // Enable interrupt 30 (falls < 32, dann ISENABLER0)
Als Nächstes konfigurieren wir die Auslösezeit:
    write32 ((1 << TIMER_IRQ_ID),GICD_ISENABLER0);
    write32 (0xA0A0A0A0,GICD_IPRIORITYR7); // Priority
    write32 (0x01010101,GICD_ITARGETSR7); // CPU0


     write32(1,GICD_CTLR);
```c
     write32(0xff,GICC_PMR);
void InitRotorTimer(void)
     write32(1,GICC_CTLR);
{
     // 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));
}
}
</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!
 
---
 
## 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:


Timer erstellen
```c
<syntaxhighlight lang="C">
void SetIRQ27Target(void)
void InitCoreTimer(void)
{
{
  unsigned long freq;
    // Lese das Target-Register für IRQs 24-31
  asm volatile("mrs %0, cntfrq_el0" : "=r"(freq));
    u32 reg = read32(GICD_ITARGETSR0 + 4*(27/4));
  asm volatile ("msr cntp_tval_el0, %0" :: "r"(freq)); // 1 Sekunde
   
  asm volatile ("msr cntp_ctl_el0, %0" :: "r"(1));     // Timer aktivieren
    // 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);


  write32(2,TIMER_CNTRL0);
// Schalte IRQ 27 im GIC Distributor scharf
}
write32(1 << 27, GICD_ISENABLER0);
</syntaxhighlight>
DataSyncBarrier();
* asm volatile("mrs %0, cntfrq_el0" : "=r"(freq)):  
```
** cntfrq_el0: Dieses Systemregister enthält die Frequenz (in Hz) des Generators für das Systemzählregister (Counter Frequency Register). Hier wird die Frequenz in die Variable freq geladen (z. B. ist bei einem typischen ARMv8-Prozessor die Frequenz oft 1 MHz, abhängig von der Systemkonfiguration).
*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.
* asm volatile ("msr cntp_tval_el0, %0" :: "r"(freq)):
 
** cntp_tval_el0: Dieses Register legt fest, nach wie vielen Zählschritten der cntp-Timer (permanent Timer) einen Interrupt auslösen soll.
---
** Der Wert von freq wird in dieses Register geschrieben, was bedeutet: Der Timer löst einen Interrupt nach 1 Sekunde aus (da freq die Taktfrequenz in Hz angibt und tval die Anzahl der Takte bis zur Auslösung enthält).
* asm volatile ("msr cntp_ctl_el0, %0" :: "r"(1)):
** cntp_ctl_el0: Das Control-Register für den cntp-Timer.
*** Mit 1 wird der Timer aktiviert (Bitfeld):
**** Bit 0: Aktiviert den Timer (1 = an, 0 = aus).
**** Bit 1: (Optional) Kann konfiguriert werden, um den Timer zu maskieren (nicht im Code verwendet).
**** Bit 2: Konfiguriert, ob Timer abläuft oder permanent fortgesetzt wird.
** Mit diesem Befehl startet der Timer und zählt von cntp_tval_el0 (freq, d. h. 1 Sekunde) rückwärts.
* write32(2,TIMER_CNTRL0):
** Hier wird ein Wert 2 (binär: 10) in das Steuerregister des Timers (TIMER_CNTRL0) geschrieben. Das ist ein typischer Schritt zum Aktivieren eines lokalen Timers im SoC-Interrupt-Controller (z. B. ARM GIC oder Broadcom Interrupt Controller auf Raspberry Pi):
*** Bit 0–1: Kann Timer-Interrupts aktivieren/deaktivieren.
** Es stellt sicher, dass der Timer-Interrupt an den Prozessor weitergeleitet wird.


Im Anschluss müssen wir noch Interrupts erlauben. Dazu verwenden wir das DAIF-Register:
## 4. Der Interrupt-Handler: `RotorTimerHandler`
<syntaxhighlight lang="asm">
.globl irq_enable
irq_enable:
msr daifclr, #0xf
    ret
</syntaxhighlight>


Nun haben wir unserer ersten Interrupt programmiert. In der Vectorbeschreibung springen wir in das Unterprogramm "irq_dispatch", wenn ein Interrupt ausgelöst wird.
Hier passiert die Magie. Diese Funktion wird asynchron aufgerufen, egal was die `main()`-Schleife gerade tut.


Die Funktion "irq_dispatch" ist der zentrale Interrupt-Dispatcher, der beim Auftreten eines Interrupts aufgerufen wird. Hier wird analysiert, welcher Interrupt ausgelöst wurde, und entsprechend reagiert.
```c
<syntaxhighlight lang="C">
void RotorTimerHandler(void* pParam)
void irq_dispatch(void) {
{
     u32 irq = read32(GICC_IAR);
     // 1. Die Rotor-Animation zeichnen
    if (irq == TIMER_IRQ_ID) {
    static int rotor = 0;
         timer_irq_handler();
    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;
     }
     }
else
   
{
    // Zeichne an Position X-8, Y=0 (oben rechts)
printf("Other IRQ: %d\n",irq);
    DrawChar(ch, SCREEN_X - 8, 0);
}
    rotor++;
     write32(irq,GICC_EOIR);
   
    // 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!
}
}
</syntaxhighlight>
```


* GICC_IAR: Das Interrupt Acknowledge Register (Interrupt-Anerkennungsregister) des Generic Interrupt Controller (GIC) gibt die ID des auslösenden Interrupts aus.
> 🛡️ **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).


Jeder Interrupt hat eine eindeutige ID (Interrupt Request Identifier, IRQ-ID).
---


Der Wert des Registers wird hier in die lokale Variable irq geladen. Dadurch weiß die Funktion, welcher Interrupt ausgelöst wurde.
## 5. Alles in `main.c` zusammenfügen


* TIMER_IRQ_ID: Die ID des Timer-Interrupts (eine vordefinierte Konstante, für den Wert der IRQ-ID des Timers).
Damit der Rotor sich dreht, müssen die Initialisierungen in der richtigen Reihenfolge in deiner `main.c` (oder `Kernel_Initialize`) stehen:


Diese Zeile vergleicht die aus dem GIC gelesene IRQ-ID (irq) mit TIMER_IRQ_ID.
```c
void Kernel_Initialize(CKernel *pKernel)
{
    // 1. Basis-Initialisierung (Bildschirm, Terminal, printf)
    Init_Screen();
    InitTerminal();
    SetFrontColor(satyr);
    printf("System startet...\n");
    SetFrontColor(white);


Wenn der Timer-Interrupt ausgelöst wurde (z. B. durch den cntp-Timer aus dem vorherigen Code), wird die Interrupt-Service-Routine (ISR) timer_irq_handler aufgerufen.
    // 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
    }
}
```
 
---
 
## Zusammenfassung & Lernkontrolle


Sollte die IRQ-ID nicht mit TIMER_IRQ_ID übereinstimmen, deutet das darauf hin, dass ein anderer Interrupt ausgelöst wurde. Dies wird abgefangen und mittels printf protokolliert.
Du hast soeben einen vollständigen, produktionsreifen Interrupt-Pfad implementiert:
* Beispiel: Angenommen, ein Interrupt wird durch ein GPIO-Signal ausgelöst, dann könnte als Ergebnis z. B. "Other IRQ: 42" ausgegeben werden.
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.


* GICC_EOIR: Das End of Interrupt Register (Ende-des-Interrupt-Register) des GIC meldet, dass die Behandlung für den aktuellen Interrupt abgeschlossen ist.
Der Wert (irq) wird in dieses Register geschrieben, um den aktuellen Interrupt aus der aktiven Liste des Controllers zu entfernen.
Der GIC gibt damit den Prozessor oder das System für neue Interrupts frei.
Ohne diesen Schritt würde der GIC den Interrupt als "noch nicht abgeschlossen" betrachten, was zur Blockade weiterer Interrupts führen könnte.


Nun können wir uns ein Programm erstellen, welches darauf regiert, wenn der Timer nach 1 Sekunde einen Interrupt auslöst. Dazu erstellen wir ein Programm, welches einen Rotor auf dem oberen rechten Rand unserer Bildschirmes zeichnet.
-----
<syntaxhighlight lang="C">
u32 Rotor = 0;
void timer_irq_handler(void)
{
  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;
  }


    // Reset Timer
{| style="width: 100%;
  unsigned long freq;
| style="width: 33%;" | [[Interrupts (PI4)|< Zurück (Interrupt)]]
  asm volatile("mrs %0, cntfrq_el0" : "=r"(freq));
| style="width: 33%; text-align:center;" | [[Hauptseite|< Hauptseite >]]
  asm volatile("msr cntp_tval_el0, %0" :: "r"(freq));
| style="width: 33%; text-align:right;" | [[Interrupt Teil 2 (PI4)|Weiter (Interrupt Teil 2 (PI4)) >]]
}
|}
</syntaxhighlight>
Das Zeichnen des Rotors übernimmt unsere DrawChar-Funktion und ist selbsterklärend. Wenn der Rotor gezeichnet wurde, müssen wird den Timer wieder zurücksetzen, so dass er nach 1 Sekunde wieder ausgelöst wird. Dazu verwenden wir die entsprechenden Register, die wir bereits unter "InitCoreTimer" verwendet haben.

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)) >