Author Topic: How are interrupts handlers implemented?  (Read 9647 times)

0 Members and 1 Guest are viewing this topic.

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18056
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
How are interrupts handlers implemented?
« on: May 07, 2022, 09:22:31 pm »
I'm curious about what code is written in the back end (and beyond looking at how headers files go down a wormhole of bit's being defined in yet another file) for interrupts. So I know that when an interrupt occurs the processor saves the current state and runs off to a specified memory location. To me the user this translates into the automatic calling of a function that the chip manufacturer has predefined. But what code has the manufacturer written in order to have that function be placed in a certain physical location in memory?
 

Offline DavidAlfa

  • Super Contributor
  • ***
  • Posts: 6266
  • Country: es
Re: How are interrupts handlers implemented?
« Reply #1 on: May 07, 2022, 10:15:26 pm »
It depends on the architecture.
PICs have fixed ISR vectors, uart interrupt kicks in and the program will jump to address ex. 0x20, you cannot change that.
At 0x20 you have a branch/goto to your real uart_ISR_code", the one you declare in the compiler.

I think it also was fixed in older ARM cores, in newer can be configured.
This adds complexity, but reduces interrupt delay, as it jumps directly to the execution code.
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 

Offline Doctorandus_P

  • Super Contributor
  • ***
  • Posts: 3891
  • Country: nl
Re: How are interrupts handlers implemented?
« Reply #2 on: May 07, 2022, 10:45:44 pm »
There are also more architecture dependent implementations.

for some architectures, you have to add a previously #define'd "ISR" (or similar) to the ISR function itself to trigger some special behavior in the compiler.
Sometimes a reti (RETurn from Interrupt) instruction has to be added to the end of the ISR, while other architectures use a normal RET at the end of the ISR.

In C it has always been "undefined / processor specific".
But C++ is getting more standardized in the last 20 years or so and lots of things have been deprecated, changed or improved.  There may be standardized recommendations for compiler source code now.
 

Offline Benta

  • Super Contributor
  • ***
  • Posts: 6261
  • Country: de
Re: How are interrupts handlers implemented?
« Reply #3 on: May 07, 2022, 11:38:31 pm »
You're all talking "C"-level handling here, I think.
The manufatcurer hasn't written anything into the CPU, it's pure hardware handling and subsequent firmware/software handling.

For all CPU architectures that I know, an interrupt causes the CPU to push its current status onto the stack. It will then either indirectly (interrupt vector) or directly jump to a specific address and execute from there.
That's the interrupt handler or ISR subprogram. Very often written in assembler.
The ISR has to find out where the interrupt came from, why and what to do with it, and present its result to the main program somehow for further processing.
Returning from the interrupt, the CPU status is pulled from the stack, and normal program operation resumes.


 

Offline jpanhalt

  • Super Contributor
  • ***
  • Posts: 3806
  • Country: us
Re: How are interrupts handlers implemented?
« Reply #4 on: May 08, 2022, 01:29:26 am »
To me the user this translates into the automatic calling of a function that the chip manufacturer has predefined. But what code has the manufacturer written in order to have that function be placed in a certain physical location in memory?
I agree, an interrupt is very much like a call.  The main difference is that a call is initiated by code and an interrupt is initiated by an event.* (I am only familiar with 8-bit PIC's, but the principles are the same in other PIC's and probably most chips.)  As pointed out, it is hardware.

So, just write your service routine as you would any other subroutine, except as noted, you may need to add code to save context if that is important.  In the 16F1xxx and later chips, there is automatic context saving.  If you enable more than one event, you will may also need to identify which event caused the interrupt.

With 16F PIC's there are three return instructions: return, retlw (return with a literal in WREG), and retfie (return from interrupt and enable interrupts, i.e., set GIE bit in INTCON.  You can actually use any of those three instructions to return from an interrupt or a call.  With a call, one usually uses "return" to go back to its origin.  "retlw" is also used with calls, particularly when one is reading a table of values.  With an interrupt, retfie is more convenient.  Of course, if you just use "return" or "retlw"  from an interrupt, you will not automatically restore context nor will GIE be set.

*You can initiate an interrupt with code, but that is probably done rarely.
« Last Edit: May 08, 2022, 01:31:16 am by jpanhalt »
 

Offline IanB

  • Super Contributor
  • ***
  • Posts: 12398
  • Country: us
Re: How are interrupts handlers implemented?
« Reply #5 on: May 08, 2022, 01:50:35 am »
To me the user this translates into the automatic calling of a function that the chip manufacturer has predefined. But what code has the manufacturer written in order to have that function be placed in a certain physical location in memory?
I agree, an interrupt is very much like a call.  The main difference is that a call is initiated by code and an interrupt is initiated by an event.* (I am only familiar with 8-bit PIC's, but the principles are the same in other PIC's and probably most chips.)  As pointed out, it is hardware.

At the most primitive level, yes it is hardware, but not always.

For example, the OpenVMS operating system relied heavily on software interrupts, called AST's (Asynchronous System Traps). In common terms you might think of them as callbacks, but callbacks triggered by events. For example, you might set a timer, or wait for some I/O to complete, and register an AST to be called when the timer expired, or the I/O completed.

In a low level microcontroller context this would typically be a hardware interrupt, but in a high level operating system like OpenVMS you don't want ordinary users to mess with the hardware. The operating system is supposed to protect against that. So OpenVMS provides a carefully controlled way of achieving the same thing, safeguarded by the operating system.

But at the end of the day, an interrupt service routine is simply a subroutine called by the system in response to some event, rather than when decided by your program. Such an interrupt will cause your program to stop processing while the interrupt is handled. The usual work of an interrupt hander is to set some flags, so that your program can later check the flags and decide what to do about them at it's own convenience.

http://www.rlgsc.com/decus/usf96/ad039.pdf
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 15413
  • Country: fr
Re: How are interrupts handlers implemented?
« Reply #6 on: May 08, 2022, 01:54:17 am »
And the context is not necessarily saved automatically by the CPU. It depends on the target, but for many, it just doesn't happen. Compilers (or yourself if using assembly) are responsible for this. And if using GCC, there are some attributes that enable you to control how context is saved (or not) and to what extent. (Unless by "context" you mean the return address - address of the instruction being interrupted, in which case, yes, that's usually the only thing that is pretty much universally done automatically, otherwise execution would be kinda screwed.)
« Last Edit: May 08, 2022, 01:56:46 am by SiliconWizard »
 

Offline IanB

  • Super Contributor
  • ***
  • Posts: 12398
  • Country: us
Re: How are interrupts handlers implemented?
« Reply #7 on: May 08, 2022, 01:57:26 am »
A footnote related to hardware access.

As a user of an OpenVMS system you could not get close to the hardware at all without elevated privileges. And if you did have elevated privileges and got close to the hardware, for example if you were trying to write and debug a device driver, the probability of you crashing the system became quite high.
 

Offline ejeffrey

  • Super Contributor
  • ***
  • Posts: 3922
  • Country: us
Re: How are interrupts handlers implemented?
« Reply #8 on: May 08, 2022, 02:28:47 am »
MIPS and RISCV have a dedicated control register to store the return address when handling an interrupt.  SPARC shifts the register window to free up general purpose registers to store the PC.  This means they need no memory access to start an ISR, but the ISR needs to handle saving state as necessary.

ARM cortex M cores not only store the PC but all of the caller saved registers to the stack.  This allows interrupt handlers to be written in C since they follow the standard ABI.
 

Offline ledtester

  • Super Contributor
  • ***
  • Posts: 3248
  • Country: us
Re: How are interrupts handlers implemented?
« Reply #9 on: May 08, 2022, 04:58:03 am »
... But what code has the manufacturer written in order to have that function be placed in a certain physical location in memory?

The generation of the interrupt vector table is done by the compiler.

For instance, consider the atmega238p:

https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf

Pages 49-51 show the typical assembly code that is placed at the beginning of flash memory.

"avr-gcc" is a derivative of "gcc" which has been modified to recognize the "ISR" keyword and generate something like the code you see on page 50.

So, for example, when you write in your program:

Code: [Select]
ISR(TIMER1_OVF_vect) {
    ...some code...
}

avr-gcc will place a JMP instruction at address 0v001A which will jump to ...some code....
 

Online Siwastaja

  • Super Contributor
  • ***
  • Posts: 8831
  • Country: fi
Re: How are interrupts handlers implemented?
« Reply #10 on: May 08, 2022, 06:41:58 am »
Depending on architecture, the C compiler (or you, if you write assembly) may need to do some tricks, and as a result, the ISR handler is not a "normal" function. These tricks are usually small and simple, like push/pop of some registers, or using a different instruction to return from the function than you usually do. Nevertheless, in such architectures, you need to tell the C compiler "this function is an ISR".

ARM Cortex CPUs have been designed so that interrupt handlers can be completely normal functions. The CPU internally (in hardware, not software) saves the state of whatever was running, by pushing registers in stack and popping them back after the function returns. Also because the vector table is just a list of function addresses (and usually relocatable in RAM), this can't get any easier for the programmer.
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18056
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: How are interrupts handlers implemented?
« Reply #11 on: May 08, 2022, 07:09:46 am »
Right so what I am getting at is how are these interrupts handlers defined. The manufacturer/whoever provided the IDE/compiler/toolchain/"whatever you don't write yourself" gives you a a function name to call. But what have they put behind that? it sounds like each architecture is different but lets take ARM that sounds like the simplest.

If you had a chip and a compiler what would you be writing to tell the compiler where to go when an interrupt triggers? As far as I am aware the hardware will go to a memory location, how does the compiler know to put that function name at that memory location?
 

Online brucehoult

  • Super Contributor
  • ***
  • Posts: 4533
  • Country: nz
Re: How are interrupts handlers implemented?
« Reply #12 on: May 08, 2022, 07:17:21 am »
I'm curious about what code is written in the back end (and beyond looking at how headers files go down a wormhole of bit's being defined in yet another file) for interrupts. So I know that when an interrupt occurs the processor saves the current state and runs off to a specified memory location. To me the user this translates into the automatic calling of a function that the chip manufacturer has predefined. But what code has the manufacturer written in order to have that function be placed in a certain physical location in memory?

I'll describe what RISC-V does, in a reasonably minimal but standard-compliant implementation that you might find in a microcontroller or a soft core in an FPGA. There are more sophisticated options, but what I describe here should work on any chip.

First you have to know what a CSR (Control and Status Register) is. It's a a group of 32 bits (on RV32) that directly control the operation of some part of the CPU core, or that show the status of some part of the CPU core. Each individual bit in a CSR might be read-only, and be connected directly to some part of the CPU's circuits. Or it might be read-only and always 0 or always 1. Or it might be a flip-flop that can be set by software, and then it directly feeds into controlling something in the logic. Or it might be a flip-flop that both software and hardware can change.

CSRs have numbers, a bit like memory addresses. On RISC-V the numbers go from 0 to 4095. The flip-flops for the CSRs might be scattered all around the chip, where they are convenient to monitor/control particular hardware. There is some kind of bus or buses to access them using special CSR read/write/update instructions. CSRs are used infrequently, so unlike RAM there is no guarantee that access to them is fast -- it could be over something like I2C or SPI. But on most practical cores it is not super slow.

Every RISC-V core should provide CSRs 0xC00 (cycle), 0xC01 (time), and 0xC02 (instret). These are all 64 bit counters and on 32 bit cores you can read the upper halves at 0xC80, 0xC81, 0xC82.  CSRs from 0xC03 to 0xC1F (and the corresponding upper halves) are for performance monitoring counters.

The most important Machine mode CSRs are:

Code: [Select]
0x300 mstatus  The keys to the machine!
0x301 misa     The instruction set and extensions supported
0x304 mie      Interrupt Enable
0x305 mtvec    trap handler base address (vector)
0x340 mscratch store anything you like here
0x341 mepc     the PC executing when an exception ocurred
0x342 mcause   what happened e.g. interrupt, illegal instruction, memory protection
0x343 mtval    the bad address or opcode
0x344 mip      bits indicating pending interrupts, if any

The mstatus CSR has bitfields for (among others)...

Code: [Select]
MIE  global interrupt enable. Both this and bits in the mie CSR need to be enabled
MPIE the MIE field before the current trap. (illegal instruction etc can happen with MIE=0)
MPP  the privilege level before the current trap (User, Supervisor, Hypervisor, Machine)

Note: there is deliberately no field for the current privilege level. It is stored elsewhere. The machine knows but it won't tell you.

The mtvec CSR controls where execution will go to on an interrupt or exception. The simplest use is to simply store the address of your interrupt handler, which must be a multiple of 4 bytes. All interrupts and exceptions will jump to this address. You can also set the LSB to 1 in which case program exceptions jump to the address but interrupts jump to the address plus 4x the value in mcause.

On a very low end core mtvec might be read-only, in which case you need to put your handler where it says instead of telling it where you put your handler. You can tell by trying to write a setting into it and then reading it back and check whether it's what you tried to write. This is a general principle on many RISC-V CSRs, called WARL (Write Any, Read Legal)  Worst case, just write a Jmp instruction to your handler at the fixed trap vector address it tells you.


When an interrupt is signalled the following happens:

- if mstatus.MIE is set and the mie bit for that interrupt is set and the interrupt level is >= the current interrupt level, the interrupt will be processed. Otherwise just set the pending bit for that interrupt.

- mcause and mtval are set. Interrupts set the hi bit in mcause, exceptions don't.

- mstatus.MIE is copied to mstatus.MPIE. mstatus.MIE is set to 0.

- the current privilege level is copied to mstatus.MPP and the current privilege level is set to M

- the PC is copied to the mepc CSR. mtvec (possibly plus 4x mcause) is copied to the PC

... and execution continues ...

That's it. That's all.

In particular, all user registers remain untouched. Nothing has happened to RAM -- nothing is pushed, nothing is written anywhere.

What happens next is up to you. Probably you want to free up some registers to work in. How many is up to you. Where you put them is up to you. If you never have nested interrupts then you could store some registers to absolute addresses -- but they better be in memory locations 0..2047 or in the top 2k of the address space because that's all you can access without getting a pointer into some register.

You can free up one register by writing it to the mscratch CSR. Then you can load a pointer into it and start storing other registers relative to that pointer.

You can keep a pointer to your register save area permanently in mscratch and just swap it with a register. You can use that register save area as a stack to support nested interrupts. Hey -- maybe the register you swap with mscratch is SP...

Or maybe you just trust that the running program always keeps SP valid, so you can just decrement it and save your registers there. The standard ABI says this should be ok. Compilers do make sure this is always true, and assembly language programmers should too. But careless or malicious code might not practice stack hygiene. Do you feel lucky?

Many embedded systems will just use a single stack and back themselves to get it right. But if you're paranoid you can keep another stack just for interrupt handling. And if you have U mode available you can prevent the normal code from messing with it.

How fast is this?

All the CSR shuffling happens in parallel. You should be up and running with a new PC value in 1 clock cycle. Then it's just a question of how long it takes to load the instruction from that address and start executing it. That should be the same as any unpredicted or mis-predicted branch/jump. Probably 2-3 clock cycles on a machine with SRAM and a short pipeline.

When you're done, restore any of the registers you touched and then the MRET instruction will take you back to the original program in 1 clock cycle (plus instruction fetch time)

This is real minimalist RISC stuff.

Current ARM Cortex-M CPUs do all kinds of fancy interrupt handling tricks. The CPU saves registers for you so you can jump right into C code. When you do a return from interrupt the CPU checks if other interrupts are pending and jumps right to them without pointlessly restoring and again saving registers. And a few other tricks. For example the CPU might start responding to a low priority interrupt and while the registers are being saved a higher priority interrupt comes in. The hardware can switch tracks and immediately go to the higher priority handler instead.

RISC-V hardware doesn't do any of that stuff. But because it does almost nothing at all, it is possible to write a standard interrupt entry handler that implements the same features in software -- and that runs just as quickly as the fancy ARM interrupt handling.

The interested can find details on various ways to do this here..

https://github.com/riscv/riscv-fast-interrupt/blob/master/clic.adoc#interrupt-handling-software
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18056
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: How are interrupts handlers implemented?
« Reply #13 on: May 08, 2022, 08:09:35 am »
So if I understand correctly, taking the M0/M0+ as an example. It has 32 interrupt lines. The compiler is what is responsible for providing the code functionality to deal with the interrupt. The chip maker will create the function name and generally a dummy function that will be defined as a certain interrupt line number so the work is actually being done by the compiler that knows the architecture, the chip maker just assigns a name to a line number.
 

Online T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 22436
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: How are interrupts handlers implemented?
« Reply #14 on: May 08, 2022, 08:24:53 am »
When it involves fixed memory locations, in GCC it's defined by linker script, and specs files.

Which, I don't know the exact syntax of, I couldn't write one for 'ya, but you'll see the sections and locations in there.

Example: for AVR, you can see where .vectors is in your program:
1. Get a listing of your program.  I have this in my build script so it runs automatically every time:
Code: [Select]
avr-objdump -h -S $(TARGET_OUTPUT_FILE) > $(TARGET_OUTPUT_DIR)$(TARGET_OUTPUT_BASENAME).lssThe $() are replacers for (automatic) project variables in Code::Blocks, output_file being e.g. /Release/output.elf, basename being output.elf, etc.  See __vectors at the start, just where they should be.

2. Where is __vectors defined?

objdump gives locations by, whatever symbol(s) map to that location, maybe just the first (alphabetically??) if multiple map to the same location, not sure?  So, at least __vectors maps to 0x0.  Of course, it doesn't say what object (*.o) that came from.  (Can ld or g++ tell this I wonder?)  Well, probably you don't have this symbol in your code, so it's elsewhere.

Linker scripts are in $(GCC_ROOT)/$(PLATFORM)/lib/ldscripts.  In my case, avr/lib/ldscripts.  I... don't know what the difference is between the *.x* files honestly, but they're fairly self-explanatory at least(?).

Important part:
Code: [Select]
  /* Internal text space or external memory.  */
  .text   :
  {
    *(.vectors)
    KEEP(*(.vectors))

So the section .text starts with a (sub)section .vectors, which presumably has the __vectors symbol inside it.  OK.

Runtimes and specs, are in $(GCC_ROOT)/lib/gcc/$(PLATFORM)/$(GCC_VERSION)/.  In my case, lib/gcc/avr/8.1.0.  (There's also some runtimes under avr/lib/[CPU core], not sure which ones GCC chooses or why..)

C rarely runs completely bare metal, in the sense that everything in the program is in your source.  A run-time library supports it.  This is not the same as libc, which has all your memcpy, strlen and such.  If you need an #include, that's libc; but if you don't have hardware float support, or division, or any way to handle certain datatypes -- it's all gotta go in here!  Most of which, I think, gets included at the end of .text, as subroutines.  Possibly a few things get inlined, not sure.  It also provides, as you can guess, the interrupt vectors, initialization, etc. Everything main() needs to run.

So, like I'm currently working on a AVR64DA64 project, so let's see what's in that.  It's an xmega2 type so go under lib/gcc/avr/8.1.0/avrxmega2 and run,

Code: [Select]
>avr-nm crtavr64da64.o
00000000 T __bad_interrupt
00002000 W __DATA_REGION_LENGTH__
00806000 W __DATA_REGION_ORIGIN__
00000200 W __EEPROM_REGION_LENGTH__
00000010 W __FUSE_REGION_LENGTH__
00000000 W __heap_end
00000000 W __init
00007fff W __stack
00010000 W __TEXT_REGION_LENGTH__
00000000 W __TEXT_REGION_ORIGIN__
00000000 W __vector_1
00000000 W __vector_10
00000000 W __vector_11
00000000 W __vector_12
[...]
00000000 W __vector_7
00000000 W __vector_8
00000000 W __vector_9
00000000 W __vector_default
00000000 T __vectors
         U exit
         U main

(Or if we use avr-objdump -t ctravr64da64.o we get a bit more info.  Not really sure honestly what the point of all the different binutils is, they overlap a lot...)

Aha, there's __vectors.  And all the vectors in it (though not where).  (The 0's are, I think, default values, i.e., saying they're all pointing to 0 (__bad_interrupt, the reset vector), which is indeed the default.  When you ISR(TCA0_OVF_vect) {} it's declaring a new [duplicate] symbol over one of the __vector_s; the defaults are [W]eak so get overridden by your program code.)

All the arithmetic and other fill-ins, by the way, is in libgcc.a.  Disassemble if you dare (avr-objdump -d libgcc.a).  There's a lot of these, including one in the version root, and avr/lib... again, not sure how it chooses.

*.a files by the way are archives, so, packs of *.o's.  I'm not aware of any way to inspect one individual .o inside the .a using objdump(!??), but you can certainly unzip it (it's a standard Unix Archive) and inspect individual pieces.

And finally, specs files, I think are used to tweak things on top of whatever built-in defaults there are?

And then, for other platforms, similar stuff.  arm-no-eabi-gcc comes with the basic libraries for instruction sets, and you need device-specific CMSIS or (mfg-specific as well) HAL on top, plus ld script to get everything in the right places.  And so on.

Tim

(Actually, is that misrepresenting a bit, what all goes into libc?  All of it, actually?  There's quite a lot of symbols in all those libs, GCC has to know which ones to call to construct a given expression.  They must be tightly integrated.  Which, I suppose how else is it gonna be?  All the more reason these projects move slowly indeed (GCC, libc, etc.), besides the obvious complexity of a whole-ass compiler..)
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 4314
  • Country: us
Re: How are interrupts handlers implemented?
« Reply #15 on: May 08, 2022, 08:45:55 am »
Quote
For all CPU architectures that I know, an interrupt causes the CPU to push its current status onto the stack.
Note that the AMOUNT of "current status" that is saved by the hardware is pretty variable.  PIC8 and AVR pretty much save only the PC, just like a "call" instruction, and the code (compiler-generated code or user code) has to save all the registers that it touches, including the "processor status register" (carry flag/etc)   An ARM chip does that, plus prehaps switches stack point, saves the previous interrupt level, plus the "privilege level", plus all the registers that the normal C ABI would save.   This means that the ARM ISR code can be simpler, but also that latency is greater.

Quote
taking the M0/M0+ as an example. It has 32 interrupt lines. The compiler is what is responsible for providing the code functionality to deal with the interrupt.
Actually, it's sort-of the linker.  Included in an ARM build is usually a file startup_xxx.c (or .S) that contains the vector table, usually something like (this is for SAMD21):
Code: [Select]
__attribute__ ((section(".vectors")))
const DeviceVectors exception_table = {

        /* Configure Initial Stack Pointer, using linker-generated symbols */
        (void*) (&_estack),

        (void*) Reset_Handler,
        (void*) NMI_Handler,
         :
        (void*) PendSV_Handler,
        (void*) SysTick_Handler,

        /* Configurable interrupts */
        (void*) PM_Handler,             /*  0 Power Manager */
        (void*) SYSCTRL_Handler,        /*  1 System Control */
        (void*) WDT_Handler,            /*  2 Watchdog Timer */
        (void*) RTC_Handler,            /*  3 Real-Time Counter */
        (void*) EIC_Handler,            /*  4 External Interrupt Controller */
        (void*) NVMCTRL_Handler,        /*  5 Non-Volatile Memory Controller */
It will also contain "weak" default handlers:
Code: [Select]
/* Peripherals handlers */
void PM_Handler              ( void ) __attribute__ ((weak, alias("Dummy_Handler")));
void SYSCTRL_Handler         ( void ) __attribute__ ((weak, alias("Dummy_Handler")));
void WDT_Handler             ( void ) __attribute__ ((weak, alias("Dummy_Handler")));
void RTC_Handler             ( void ) __attribute__ ((weak, alias("Dummy_Handler")));
Between the two of these, you get behavior where a WDT interrupt will call the Dummy_Handler() function, UNLESS there is a function called WDT_Handler() defined elsewhere in the project's code.  Because of the ARM's extensive context saving I mentioned earlier, WDT_Handler() is just a normal C function - the hardware does all the needed bits "beyond" normal function handling.
 

Online brucehoult

  • Super Contributor
  • ***
  • Posts: 4533
  • Country: nz
Re: How are interrupts handlers implemented?
« Reply #16 on: May 08, 2022, 09:09:23 am »
Quote
For all CPU architectures that I know, an interrupt causes the CPU to push its current status onto the stack.
Note that the AMOUNT of "current status" that is saved by the hardware is pretty variable.  PIC8 and AVR pretty much save only the PC, just like a "call" instruction, and the code (compiler-generated code or user code) has to save all the registers that it touches, including the "processor status register" (carry flag/etc)   An ARM chip does that, plus prehaps switches stack point, saves the previous interrupt level, plus the "privilege level", plus all the registers that the normal C ABI would save.   This means that the ARM ISR code can be simpler, but also that latency is greater.

As can be extracted somewhere from my long post, on an interrupt RISC-V doesn't touch RAM or save any of the normal integer (or FP) registers. Just the PC, priv level, interrupt level are saved to internal CSRs and execution jumps to the address stored in the mtvec CSR (plus 4x the interrupt number, if that mode is enabled)

Ultra-short latency -- as little as 2 or 3 cycles just like a mispredicted branch, but you have to clean up after yourself.
 

Offline emece67

  • Frequent Contributor
  • **
  • !
  • Posts: 614
  • Country: 00
Re: How are interrupts handlers implemented?
« Reply #17 on: May 08, 2022, 09:13:39 am »
.
« Last Edit: August 19, 2022, 05:24:27 pm by emece67 »
 

Offline SimonTopic starter

  • Global Moderator
  • *****
  • Posts: 18056
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: How are interrupts handlers implemented?
« Reply #18 on: May 08, 2022, 09:17:13 am »
so basically it's the compiler + other code bundled up as a toolchain that handle the interrupts, this is of course independent of the IDE. What I am I suppose sort of thinking is say I wanted to use a particular IDE that is not the manufacturer one, What do I have to do to get up and running with a particular micro-controller. Manufacturers header files for register definitions, toolchain (compiler + device specific handling). That only leaves something to do the programming with?
 

Online mikeselectricstuff

  • Super Contributor
  • ***
  • Posts: 13998
  • Country: gb
    • Mike's Electric Stuff
Re: How are interrupts handlers implemented?
« Reply #19 on: May 08, 2022, 09:41:21 am »
so basically it's the compiler + other code bundled up as a toolchain that handle the interrupts, this is of course independent of the IDE. What I am I suppose sort of thinking is say I wanted to use a particular IDE that is not the manufacturer one, What do I have to do to get up and running with a particular micro-controller. Manufacturers header files for register definitions, toolchain (compiler + device specific handling). That only leaves something to do the programming with?
Interrupt handling is usually architecture-specific rather than manufacturer specific, so a compiler for a given processor architecture will usually come with the necessary stuff to deal with interrupts.
Youtube channel:Taking wierd stuff apart. Very apart.
Mike's Electric Stuff: High voltage, vintage electronics etc.
Day Job: Mostly LEDs
 

Online Siwastaja

  • Super Contributor
  • ***
  • Posts: 8831
  • Country: fi
Re: How are interrupts handlers implemented?
« Reply #20 on: May 08, 2022, 11:50:23 am »
Right so what I am getting at is how are these interrupts handlers defined. The manufacturer/whoever provided the IDE/compiler/toolchain/"whatever you don't write yourself" gives you a a function name to call. But what have they put behind that?

It's either by a compiler folks, or the MCU manufacturer. What they exactly do is this:
* Provide a linker script
* Provide startup code

Linker script tells what goes where:
* Place interrupt vectors starting at address x
* Place code starting at address y
* Place unitialized variables at address z, and initialized variables somewhere else again.

Startup code (can be in C, but sometimes in asm for tradition) would define the symbols for those interrupt vectors (function names, so you can use them in your code), and it will also contain first code to execute, which will take care of initializing variables and then calling main().

You totally can write them all by yourself, this is what I always do for ARM projects (basically copy-paste from earlier projects, of course). Call me a control freak if you like, but I think this is also simplest; I see exactly what happens.

In some architectures, interrupt handler vectors are actually jump instructions, but in ARM, they are the actual function addresses. Like this:

Code: [Select]
#define VECTOR_TBL_LEN 166

// Vector table on page 730 on the Reference Manual RM0433
unsigned int * the_nvic_vector[VECTOR_TBL_LEN] __attribute__ ((section(".nvic_vector"))) =
{
/* 0x0000                    */ (unsigned int *) &_STACKTOP,
/* 0x0004 RESET              */ (unsigned int *) stm32init,
/* 0x0008 NMI                */ (unsigned int *) nmi_handler,
/* 0x000C HARDFAULT          */ (unsigned int *) hardfault_handler,
/* 0x0010 MemManage          */ (unsigned int *) memmanage_handler,
/* 0x0014 BusFault           */ (unsigned int *) invalid_handler,
/* 0x0018 UsageFault         */ (unsigned int *) invalid_handler,
/* 0x001C                    */ (unsigned int *) invalid_handler,
/* 0x0020                    */ (unsigned int *) invalid_handler,
/* 0x0024                    */ (unsigned int *) invalid_handler,
/* 0x0028                    */ (unsigned int *) invalid_handler,
/* 0x002C SVcall             */ (unsigned int *) invalid_handler,
/* 0x0030 DebugMonitor       */ (unsigned int *) invalid_handler,
/* 0x0034                    */ (unsigned int *) invalid_handler,
/* 0x0038 PendSV             */ (unsigned int *) invalid_handler,
/* 0x003C SysTick            */ (unsigned int *) invalid_handler,
/* 0x0040 WWDG1              */ (unsigned int *) invalid_handler,
/* 0x0044 PVD (volt detector)*/ (unsigned int *) shutdown_handler,
. . .

The "__attribute__ ((section(".nvic_vector")))" is important because this allows the use of linker script to tell where this table exactly needs to go:

Code: [Select]
MEMORY
{
  ram_axi    (rwx)      : ORIGIN = 0x24000000, LENGTH = 512K
  ram_sram12 (rwx)      : ORIGIN = 0x30000000, LENGTH = 256K
  ram_dtcm   (rwx)      : ORIGIN = 0x20000000, LENGTH = 112K /* Leave 16k for stack*/
  ram_itcm   (rwx)      : ORIGIN = 0x00000000, LENGTH = 63K
  ram_vectors(rwx)      : ORIGIN = 0x0000FC00, LENGTH = 1K  /* last 1K of ITCM dedicated for relocated vector table*/
  stack(rwx)            : ORIGIN = 0x2001fff8, LENGTH = 0K /* In DTCM*/

  rom_b1s0 (rx)         : ORIGIN = 0x08000000, LENGTH = 128K
  rom_b1s1 (rx)         : ORIGIN = 0x08020000, LENGTH = 128K
  rom_b1s234567 (rx)    : ORIGIN = 0x08040000, LENGTH = 768K
}

SECTIONS
{
    .nvic_vector :
    {
        *(.nvic_vector)  /* THIS refers to the section name in the C code*/
    } >ram_vectors AT>rom_b1s0

. . .

So at the end of the day, it's a little bit of code, and understanding the tools, to make the function addresses go in the right place in the memory.
« Last Edit: May 08, 2022, 11:53:03 am by Siwastaja »
 

Offline DiTBho

  • Super Contributor
  • ***
  • Posts: 4247
  • Country: gb
Re: How are interrupts handlers implemented?
« Reply #21 on: May 08, 2022, 12:29:20 pm »
My old Atlas MIPS board comes with a very old C-toolchain, and you cannot define interrupt sections like you can do with Gcc, so ... what I am doing is a pretty wild hack:

crt0.s is written in assembly, its first instruction disables interrupts.

The main C-function contains a call to ISR_init(), which is where the interrupt table is defined.
ISR_init() does nothing but copying the address of every ISR_function into the proper address of the ISR_table.

Then it enables interrupts.

It wastes a bit of code-space, but not so much. I use it to make my code more portable among different toolchains.
The opposite of courage is not cowardice, it is conformity. Even a dead fish can go with the flow
 

Online Siwastaja

  • Super Contributor
  • ***
  • Posts: 8831
  • Country: fi
Re: How are interrupts handlers implemented?
« Reply #22 on: May 08, 2022, 02:11:27 pm »
Of course, at the end of the day, all that matters is that the memory addresses of (or jump instructions to) the ISR functions go to the right place (usually near the beginning) of the flash memory. This part would be utterly trivial if you were programming by writing the output binary directly with a hex editor, without any tools (compilers or linkers) at all!

So the challenge is entirely figuring out how to use the tools to do this for you. Nowadays, it's very often gcc and ld. Linker scripts specifically seem weird even for quite seasoned people because often the built-in scripts are good, but you can figure out the basics by looking at examples and just modify them to your needs without completely understanding how ld works.
 
The following users thanked this post: DiTBho

Online T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 22436
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: How are interrupts handlers implemented?
« Reply #23 on: May 08, 2022, 07:14:14 pm »
My old Atlas MIPS board comes with a very old C-toolchain, and you cannot define interrupt sections like you can do with Gcc, so ... what I am doing is a pretty wild hack:

crt0.s is written in assembly, its first instruction disables interrupts.

The main C-function contains a call to ISR_init(), which is where the interrupt table is defined.
ISR_init() does nothing but copying the address of every ISR_function into the proper address of the ISR_table.

Then it enables interrupts.

It wastes a bit of code-space, but not so much. I use it to make my code more portable among different toolchains.

Mind, shouldn't be necessary on most any MCU that's got IVT in Flash -- but anything that has it in RAM, you bet.  For example, this is one of the first things the IBM-PC BIOS did -- 8086 boots to ffff0h (reset vector) with IVT hard wired at 0h and BIOS hard wired at f000:0-ffffh.  For basic operation (as in, RAM being usable at all), several peripherals need to be initialized first (PIT, DMA), and then interrupts can be installed (CPU faults, timers, DMA, keyboard..).  In the mean time, RAM is useless!  (It's DRAM: state leaks away over time.  Peripherals (PIT, DMA) have to be initialized to enable automatic refresh.)

Or likewise, anything that has IVT remappable into RAM -- but this is a simple matter of memcpy-ing the IVT and making whatever changes as needed, ahead of the remap.

Also, to further clarify this diversion --

In some architectures, interrupt handler vectors are actually jump instructions, but in ARM, they are the actual function addresses. Like this:

On 8086, they're DWORDs which give the "long" address, as is the usual format for the CPU.  (It's a 20 bit address space, but segmented, the top 4 bits only being accessible via segment registers (CS, DS, ES, SS).  The middle 12 bits overlap -- the physical address is ((segment) << 4) + (index).  Very redundant, but flexible in a backwards-compatible way I guess, which was the intent AFAIK.  So, "long" pointers are both 16-bit values together.  (It's different on 386+ protected mode, but, you're almost always going to be using an OS to handle that for you, so, unless you're writing the OS yourself -- the protected mode IVT isn't something to worry about.  Or the various other tables that PM uses.)

Also to be clear, AVR literally jumps to the interrupt address -- you could write the whole ISR right there in the IVT, if you guarantee nothing ever uses the intervening vectors and jumps into the middle of that ISR!  Neat, but not very useful. :D  So, 99.99% of use cases, you jump out into normal PROGMEM space and finish things up there.

Tim
« Last Edit: May 08, 2022, 07:22:30 pm by T3sl4co1l »
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 
The following users thanked this post: DiTBho

Offline nctnico

  • Super Contributor
  • ***
  • Posts: 28059
  • Country: nl
    • NCT Developments
Re: How are interrupts handlers implemented?
« Reply #24 on: May 08, 2022, 07:53:50 pm »
I'm curious about what code is written in the back end (and beyond looking at how headers files go down a wormhole of bit's being defined in yet another file) for interrupts. So I know that when an interrupt occurs the processor saves the current state and runs off to a specified memory location. To me the user this translates into the automatic calling of a function that the chip manufacturer has predefined. But what code has the manufacturer written in order to have that function be placed in a certain physical location in memory?
The only right answer to this question is: it depends. It depends on which microcontroller / processor is used; there is no universal way. So please specify the microcontroller / processor you are interested in.
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf