Interrupt handlers - ylab-nsu/cdm16-llvm-neo GitHub Wiki
In this guide, you will learn how to write interrupt service routines in C and register them in the interrupt vector table.
This guide assumes that you have a project set up as described in Setting up a project. You should have also read Using C and assembly together.
Interrupts are a common hardware mechanism that allows asynchronous external events to interrupt the processor's normal execution and make it temporarily switch to executing an interrupt service routine (ISR). After completing the routine, the processor goes back to the code it was executing before receiving the interrupt request (IRQ). Basically all modern processors support interrupts, including CDM-16.
CDM-16, like many other processors, has an interrupt vector table (IVT). It is an array of interrupt vectors located at address zero. Each interrupt has an associated number, which is used to differentiate between different events. When a processor receives an IRQ, it uses this number to index into the IVT and fetch the corresponding interrupt vector. On CDM-16, each interrupt vector is a pair of 16-bit integers. The first one is the address of the ISR, and the second one is the value loaded into the status register before the ISR is executed.
The very first entry in the IVT is special. It is called the reset vector, because the processor fetches it on hardware resets. Thus, it defines the main entry point to the program. The standard library (crt0), which provides the default IVT, also provides the reset vector. It performs some initialization, like setting up the stack, before passing control over to the user-defined main function. See Standard library.
The next few entries (4 on base CDM-16) are also special. They are fetched when the processor encounters an exceptional situation and cannot continue normal execution. See the CDM-16 docs for more information on hardware exceptions and the IVT.
The compiler reserves the first 16 IVT slots for the reset vector, base CDM-16 exceptions and the ones used in ISA extensions. User-defined vectors begin at index 16.
Interrupt service routines have a special calling convention. They must save all of the caller's registers and return with a special instruction: rti. The C compiler provides a way to automatically use this calling convention for a function.
Each ISR must be a function with a void return type with a single pointer parameter. Use the ISR macro from <cdm/ivt.h> to set the correct calling convention:
#include <cdm/ivt.h>
ISR void interrupt_service_routine(context_t *ctx) {
// handler code
}Alternatively, use the built-in interrupt attribute:
__attribute__((interrupt)) void interrupt_service_routine(void *ctx) {
// handler code
}The parameter is a pointer to the processor context saved on the stack. You can modify it to implement things like system calls, preemptive multitasking, software instruction emulation, etc. Use the default context_t type defined in <cdm/ivt.h> and <cdm/register.h>, which matches the layout of the compiler-generated context on the stack.
The steps to add the ISR to the IVT vary depending on the target. See LLVM targets.
Due to limitations of Cocas, it is impossible to extend the default IVT. We will have to replace it with our own IVT.
To disable the default IVT, pass -nostartfiles to the linker. In our build system, we can use the LDFLAGS variable in the Makefile:
LDFLAGS := -nostartfilesNext, create a new assembly file in src and name it ivt.asm.
Implement the reset routine. Create a new relocatable section and define a symbol named _start for the routine.
Initialize both the frame pointer (fp) and the stack pointer (sp) to 0, call the main function and halt the processor if main returns.
rsect start
main: ext
_start>
ldi fp, 0
stsp fp
addsp -8
jsr main
addsp 8
haltImplement the exception handler routine. Create another relocatable section and define a symbol named _exception_handler for the routine.
This routine will halt the processor immediately on entry.
rsect handler
_exception_handler>
haltDefine the IVT. Create an absolute section starting at address 0. Define the reset vector and the remaining exception vectors (there should be 16 exception vectors, including the reset vector), followed by your custom interrupt vector. Finally, zero-fill the rest of the IVT using align 0x100.
asect 0
_start: ext
_exception_handler: ext
interrupt_service_routine: ext
dc _start, 0
dc _exception_handler, 0
dc _exception_handler, 0
dc _exception_handler, 0
dc _exception_handler, 0
dc _exception_handler, 0
dc _exception_handler, 0
dc _exception_handler, 0
dc _exception_handler, 0
dc _exception_handler, 0
dc _exception_handler, 0
dc _exception_handler, 0
dc _exception_handler, 0
dc _exception_handler, 0
dc _exception_handler, 0
dc _exception_handler, 0
dc interrupt_service_routine, 0
align 0x100
end.As usual, remember to add the assembly file to the ASM_SOURCES variable in your Makefile:
ASM_SOURCES := ivt.asmNote
With this configuration, interrupt_service_routine is mapped to interrupt vector 5. This is the first index available for application-specific interrupts. Keep this in mind when writing your program and designing your Logisim circuit.
The steps are significantly simpler for the ELF target, because we do not need to redefine the entire IVT. Of course, you can still do this, but you will likely also need to replace the default linker script. See Default linker scripts and Standard Library.
Inside your main.c file (or any other C file), use the INTERRUPT_VECTORS macro from <cdm/ivt.h> as follows:
#include <cdm/ivt.h>
ISR void interrupt_service_routine(context_t *ctx);
INTERRUPT_VECTORS(
VECTOR(interrupt_service_routine, 0)
);
int main(void) {
// main application code
}The macro places the IVT entries sequentially starting at index 16. For more information, refer to the API documentation
On the CDM-16, bit 15 (MSB) of the status register acts as the interrupt enable bit. When this bit is set, the processor handles hardware interrupts normally. When it is cleared, the processor holds hardware interrupts until the bit is set again. The default IVT on both targets (Cocas and ELF) has this bit unset in the reset vector, so you have to enable interrupts explicitly with the int_enable function from <cdm/interrupt.h>. This is done to prevent any ISRs from executing before the system has finished initializing.
You can use the following pattern to protect critical sections in your program:
#include <cdm/register.h>
#include <cdm/interrupt.h>
void perform_operation(void) {
psr_t saved_ps = ldps();
int_disable();
// critical section
stps(saved_ps);
}Before starting a critical section, the value of the status register is saved, then interrupts are disabled. When the critical section completes, the original value of the status register is restored.