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?
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...
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.
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
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 | } |
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...
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
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.