Beispiel Timer-Interrupt (PI4): Unterschied zwischen den Versionen
KKeine Bearbeitungszusammenfassung |
KKeine Bearbeitungszusammenfassung |
||
| Zeile 137: | Zeile 137: | ||
*** Bit 0–1: Kann Timer-Interrupts aktivieren/deaktivieren. | *** Bit 0–1: Kann Timer-Interrupts aktivieren/deaktivieren. | ||
** Es stellt sicher, dass der Timer-Interrupt an den Prozessor weitergeleitet wird. | ** 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: | |||
<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 der Interrupt ausgelöst wird. Dazu erstellen wir ein Programm, welches einen Rotor auf dem oberen rechten Rand unserer Bildschirmes zeichnen. | |||
<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 | |||
unsigned long freq; | |||
asm volatile("mrs %0, cntfrq_el0" : "=r"(freq)); | |||
asm volatile("msr cntp_tval_el0, %0" :: "r"(freq)); | |||
} | |||
</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. | |||
Version vom 25. Juli 2025, 12:04 Uhr
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:
.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:
wfe // spare CPU cycles
b hang
Im Anschluss müssen wir beschreiben, was passieren soll, wenn der Interrupt ausgelöst wurde:
.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
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
Dies speichern wir als vecotor.s in unser Projekt ab.
Wir verwenden den GIC-400 Kontroller. Als nächstes müssen wir noch Interrupts Initialisieren. Dazu erstellen wir eine C-Funktion und legen diese in interrupt.c ab:
void Interrupt_Initialize (void)
{
write32(0,GICD_CTLR);
// Enable interrupt 30 (falls < 32, dann ISENABLER0)
write32 ((1 << TIMER_IRQ_ID),GICD_ISENABLER0);
write32 (0xA0A0A0A0,GICD_IPRIORITYR7); // Priority
write32 (0x01010101,GICD_ITARGETSR7); // CPU0
write32(1,GICD_CTLR);
write32(0xff,GICC_PMR);
write32(1,GICC_CTLR);
}
Timer erstellen
void InitCoreTimer(void)
{
unsigned long freq;
asm volatile("mrs %0, cntfrq_el0" : "=r"(freq));
asm volatile ("msr cntp_tval_el0, %0" :: "r"(freq)); // 1 Sekunde
asm volatile ("msr cntp_ctl_el0, %0" :: "r"(1)); // Timer aktivieren
write32(2,TIMER_CNTRL0);
}
- 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).
- 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 1 wird der Timer aktiviert (Bitfeld):
- Mit diesem Befehl startet der Timer und zählt von cntp_tval_el0 (freq, d. h. 1 Sekunde) rückwärts.
- cntp_ctl_el0: Das Control-Register für den cntp-Timer.
- 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.
- 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):
Im Anschluss müssen wir noch Interrupts erlauben. Dazu verwenden wir das DAIF-Register:
.globl irq_enable
irq_enable:
msr daifclr, #0xf
ret
Nun haben wir unserer ersten Interrupt programmiert. In der Vectorbeschreibung springen wir in das Unterprogramm "irq_dispatch", wenn der Interrupt ausgelöst wird. Dazu erstellen wir ein Programm, welches einen Rotor auf dem oberen rechten Rand unserer Bildschirmes zeichnen.
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
unsigned long freq;
asm volatile("mrs %0, cntfrq_el0" : "=r"(freq));
asm volatile("msr cntp_tval_el0, %0" :: "r"(freq));
}
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.