Forum: PC-Programmierung C Compiler ISR


von M. M. (blackcow)


Lesenswert?

Hallo,

bei eintritt in eine Interrupt Service Routine müssen alle verwendeten 
Register auf dem Stack gesichert und danach wieder hergestellt werden. 
Wie konfiguriere ich gcc richtig, bzw. wie bekomme ich die zu sichernden 
Register?
Hintergrund ist, dass ich eine RISC-V Architektur mit C programmieren 
will. Das funktioniert soweit ganz gut. Die ISRs allerdings bereiten mir 
Kopfzerbrechen. Muss ich da meine eigenen Assemblerschnipsel einbauen 
(makros mit inline assembler), und wenn ja wie kann sowas aussehen? Oder 
kann das der gcc automatisch?

von Ingo L. (corrtexx)


Lesenswert?

M. M. schrieb:
> Wie konfiguriere ich gcc richtig, bzw. wie bekomme ich die zu sichernden
> Register?
Das macht der GCC normalerweise selber in einer Push/Pop Orgie. Sieh dir 
einfach mal den Disassembler an, dort wirst du fündig...

von Georg G. (df2au)


Lesenswert?

Wenn du der Service Routine das passende Attribut "Interrupt" mitgibst, 
macht der GCC das alles allein. Das gilt leider (noch?) nicht für die 
RISC-V Architektur.

Hier 
https://groups.google.com/a/groups.riscv.org/forum/#!topic/sw-dev/e1A6WdmdHvg 
beschreibt jemand einen Würgaround.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

M. M. schrieb:
> Oder kann das der gcc automatisch?

Das kann er automatisch, ihm muss halt nur mitgeteilt werden, daß es 
sich um eine ISR handelt. Damit wird i.d.R. auch der Interruptvektor 
festgelegt.
Das geschieht mit Funktionsattributen.

Die aber sind von der jeweiligen Zielarchitektur abhängig.

Wenn ich in die gcc-Dokumentation sehe, werden für einige Architekturen 
Interrupts aufgeführt:

https://gcc.gnu.org/onlinedocs/gcc/AVR-Function-Attributes.html#AVR-Function-Attributes

(hier "signal" genannt)

https://gcc.gnu.org/onlinedocs/gcc/ARM-Function-Attributes.html#ARM-Function-Attributes
https://gcc.gnu.org/onlinedocs/gcc/x86-Function-Attributes.html#x86-Function-Attributes
https://gcc.gnu.org/onlinedocs/gcc/m68k-Function-Attributes.html#m68k-Function-Attributes

(jeweils "interrupt")


Aber die Dokumentation für RISC-V schweigt sich darüber aus:

https://gcc.gnu.org/onlinedocs/gcc/RISC-V-Function-Attributes.html#RISC-V-Function-Attributes

Sofern Du keinen anderen an diese Architektur angepassten gcc findest, 
könnte es sein, daß Du ISRs mehr oder weniger vollständig von Hand in 
Assembler stricken musst. Das Attribut "naked" kann Dir vielleicht dabei 
helfen, Funktionen zu bauen, die einfacher aus Deinem Assemblercode 
aufzurufen sind.


Hier hat sich jemand wohl schon mit dem Thema auseinandergesetzt:
https://groups.google.com/a/groups.riscv.org/forum/#!topic/sw-dev/e1A6WdmdHvg

von Nop (Gast)


Lesenswert?

M. M. schrieb:

> Wie konfiguriere ich gcc richtig, bzw. wie bekomme ich die zu sichernden
> Register?

Das hängt davon ab, wie RISC-V denn Interrupts behandelt. U.u. mußt Du 
noch ein function attribute auf die Funktion setzen, mit der Du GCC 
sagst, daß das eine Interrupt-Routine ist. Ich würde auch 
sicherheitshalber bei ISRs und main() immer ein "used"-attribute setzen, 
weil die nicht im normalen Programmablauf aufgerufen werden. Nicht daß 
ein Compiler sowas jetzt oder in einer zukünftigen Version wegoptimiert.

Beim Systick von Cortex-M muß man das beispielsweise nicht, weil die CPU 
bei einer Exception automatisch diejenigen Register sichert, die 
ansonsten vom Aufrufer gesichert werden müssen. Die ISR sichert 
demzufolge dann wie jede andere C-Routine selber Register, die vom 
Aufgerufenen zu sichern sind.

Assembler-Gehacke ist für eine handelsübliche ISR nicht notwendig.
1
#define F_INTERRUPT __attribute__((interrupt))
2
#define F_USED      __attribute__((used))
3
...
4
void F_INTERRUPT F_USED MyISR(void)
5
{
6
    DoStuff();
7
}

von M. M. (blackcow)


Lesenswert?

Vielen Dank für die Antworten, das ging ja schnell! Ich muss mir jetzt 
erstmal die Links durchlesen, aber ich vermute das ich da selber was mit 
Assembler zusammenbasteln muss. Risc V hat ja nichtmal push/pop im 
instruction set...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Genrell gibt es abhängig von Hardare und OS mehrere Möglichkeiten, wie 
das gehandhabt wird:

* Die Hardware kümmert sich darum und sichert alle oder bestimmte 
Register aufm Stack oder in einem speziell dafür vorgesehenen Bereich.

* Die Software kümmert sich teilweise oder ganz darum.  Z.B. ein OS, bei 
dem man "normale" Funktionen als signal-Handler registriert.  Die 
Abstraktion von der Hardware übernimmt das OS, bei RISC-V z.B. Linux. 
Auf einem Bare-Metal muss man sich dann selber darum kümmern.

: Bearbeitet durch User
von M. M. (Gast)


Lesenswert?

Johann L. schrieb:
> Auf einem Bare-Metal muss man sich dann selber darum kümmern.
Genau das trifft bei mir zu.

So wie es aussieht muss ich mir selber was überlegen. Mein Ansatz wäre 
im C Source Code die ISRs mit defines zu markieren. Nach Compiler und 
Linker dann ein disassembly Dump machen. Die markierten Funktionen 
durchsuche ich dann mit einem (Python-)Skript nach benutzten Registern 
und füge vor und nach der Funktion dementsprechende Stackoperationen 
dazu, um die gefundenen Register zu speichern.

Das erscheint mir ein seeeehr übles Workaround zu sein, sollte aber 
funktionieren. Hat jemand eine bessere Idee?

Insbesondere würde mich interessieren ob ich bereits im C-Source Code 
Automatismen (mit defines /inline Assembler) einbauen kann, die mir die 
richtigen Register automatisch auf dem Stack speichern. Ich befürchte 
nicht?!?

Alles Andere, wie die Interrupt Adressen Initialisierung, ist kein 
Problem, das bekomme ich hin.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

M. M. schrieb:
> Mein Ansatz wäre im C Source Code die ISRs mit defines zu markieren.
> Nach Compiler und Linker dann ein disassembly Dump machen.
> Die markierten Funktionen durchsuche ich dann mit einem (Python-)Skript
> nach benutzten Registern und füge vor und nach der Funktion
> ementsprechende Stackoperationen dazu, um die gefundenen Register zu
> speichern.

Also 2 Passes? Die Ausgabe nach dem Linker kannst du wohl nicht 
verwenden, weil sich durch Einfügen von Code Offsets ändern. Außerdem 
ist Disassembly nicht assemblierbar, daher würde ich mit -S übersetzen, 
das .s patchen und dann mit riscv-gcc *.s wie üblich weitermachen.

Allerdings weiß ich nicht, wie du die Epiloge erkennen willst; zudem 
kann eine Funktion mehr als einen Epilog enthalten, und auch hier können 
sich durch Einfügen von Code Offsets ändern, die danach dann nicht mehr 
passen.

> Das erscheint mir ein seeeehr übles Workaround zu sein, sollte aber
> funktionieren. Hat jemand eine bessere Idee?

Für den Anfang Assembler-Stubs schreiben, welche ganz normale 
C-Funktionen als Handler aufrufen.  Wenn das funktioniert, kannst du an 
Optimierung denken.

> Insbesondere würde mich interessieren ob ich bereits im C-Source Code
> Automatismen (mit defines /inline Assembler) einbauen kann, die mir die
> richtigen Register automatisch auf dem Stack speichern. Ich befürchte
> nicht?!?

Nein, alles was du in Inline Assembler machst, geschieht nach dem 
Prolog. riscv unterstützt zwar "naked", aber damit bist du auch nicht 
weiter:

Erstens hat das riscv Backend hier einen Bug: den gleichen, den avr-gcc 
mal hatte, nämlich PR42240, und der Fix wäre auch analog: 
https://gcc.gnu.org/viewcvs/gcc/trunk/gcc/config/avr/avr.c?r1=170534&r2=170533&pathrev=170534

Zweitens hast du mit "naked" garkeinen Prolog mehr, also auch keinen 
Frame-Pointer oder SP-Anpassung.  Und die Größe des Frames bekommst du 
mit einem Skript nicht raus.

Eine Anlaufstelle wäre in der gcc-help@ Mailing-Liste zu fragen, da sind 
zumindest Leute, die sich mit riscv und mit gcc auskennen.

https://gcc.gnu.org/lists.html#subscribe

Andere Lösung ist, selbst was zum gcc hinzuzufügen. riscv hat einiges an 
Registern:
1
/* a0-a7, t0-t6, fa0-fa7, and ft0-ft11 are volatile across calls.
2
   The call RTLs themselves clobber ra.  */
3
4
#define CALL_USED_REGISTERS            \
5
{ /* General registers.  */            \
6
  1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1,      \
7
  1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,      \
8
  /* Floating-point registers.  */          \
9
  1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1,      \
10
  1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,      \
11
  /* Others.  */              \
12
  1, 1                  \
13
}

Und es wären dann mindesten 2 Funktions-Attribute sinnvoll: Eines für 
"normale" ISRs, und eines mit der Assertion, dass keine FP-Register 
verwendet werden, so dass man die nicht behandeln muss (spielt eine 
Rolle wenn die ISR Funktionen aufruft).

Die Handhabung der ganzen Fixed-Register ergibt sich wohl aus dem ABI, 
und -ffixed ist auch zu beachten.

Die Struktur, die das Frame-Layout beschreibt, ist "riscv_frame_info". 
Prolog / Epilog wird ausgegeben von "riscv_expand_prologue" bzw. 
"riscv_expand_epilogue".

https://gcc.gnu.org/viewcvs/gcc/trunk/gcc/config/riscv/riscv.c?view=markup

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.