Implementation of exception in Linux
Implementation of exception in Linux
Background
In the MIPS architecture, interrupt, traps, system calls and everything else that can disrupt the normal flow of execution are called exception and are handled by a single mechanism.
This document introduces how these exceptions are supported in Linux kernel and how to add implement interrupt handler for new MIPS platform..
The referenced code for this document is based on:
a) Linux 2.6.18
b) MIPS32 without EIC
Part 1: Introduction for MIPS exception
1. Exception vectors
All exception entry points lie in un-translated regions of memory, kseg1 for uncached entry point and kseg0 for cached ones. The uncached entry points used when SR(BEV) is set are fixed, while EBase register can be programmed to shift all the entry points to another block when SR(BEV) is cleared.
The following table shows the MIPS exception entry point:
Memory region | Entry point | Exception |
Reset | 0xBFC00000 | Reset and NMI |
ROM (SR(BEV) = 1)
| 0xBFC00400 | Interrupts (Cause(IV) = 1) |
0xBFC00380 | General exceptions | |
0xBFC00300 | Cache error | |
0xBFC00200 | Simple TLB refill (SR(EXL) = 0) | |
RAM (SR(BEV) = 0) | EBase + 0x200 | Interrupts (Cause(IV) = 1) |
EBase + 0x180 | General exceptions | |
EBase + 0x100 | Cache error | |
EBase + 0x000 | Simple TLB refill (SR(EXL) = 0) |
When Linux is running, SR(BEV) is cleared and EBase is set to 0x80000000 in default, so we shall copy the general exceptions handler to 0x80000180 and interrupt handler to 0x80000200. Since the size for entry point is limited, only a jump instruction is placed in every entry point in most cases.
2. Cause ExcCode
In CP0 Cause register, there are 5 bits (Cause[2:6]) to tell you what kind of exception happened, when we get exception, we need read this register to know what causes this exception exactly. The following table shows the ExcCode values and it is copied from “See MIPS Run”
The value is not listed in this table is not used currently.
Value | Description |
0 | Interrupt |
1 | Store but page is marked as read-only in TLB |
2/3 | TLBL/TLBS: No TLB translation |
4/5 | AdEL/AdES: Address error |
6/7 | IBE/DBE: Bus error |
8 | SysCall |
9 | Break, for debuggers |
10 | Instruction code not recognized |
11 | Coprocessor is not enabled in SR(CU0-3) |
12 | Overflow from trapping form of integer arithmetic |
13 | Teq register condition is met |
15 | Float point exception |
18 | Exception from coprocessor 2 |
22 | Tried to run MDMX instruction while SR(MX) is not set |
23 | Value in WatchLo/WatchHi register is met |
24 | CPU detected some disastrous error in the CPU control system |
25 | Thread related exception |
26 | Tried to run an DSP ASE instruction while it is not supported |
30 | Parity/ECC error somewhere in the core |
3. Interrupt enable
There are 8 bits in CP0 Status Register SR[8:15] for interrupt mask. If there is no EIC, MIPS can support 8 interrupt sources. When one or some bits of SR[8:15] is set, the interrupt sources corresponding to these bits will be allowed to cause an exception. Six of the interrupt sources are generated by signals from outside the CPU core while the other two are the software-writable interrupt bits in the Cause register. These bits are enabled in arch_init_irq() API according to platform interrupt design. The arch_init_irq() API will be introduced in part 3.
SR[0] is the global interrupt enable bit. If we set this bit, we will allow the interrupts corresponding to the set bits of SR[8:15] to be generated. If we clear this bit, no interrupt will be generated.
Part 2: Implementation in Linux
1. Exception initialization in Linux
The exception initialization is implemented in trap_init() of linux-2.6.18/arch/mips/kernel/trap.c , which includes exception vectors install and exception handlers registering.
1) exception vectors install
a) At the beginning of this API, it will call the following statement to copy the generic exception handler to EBase + 0x180, where is the general exception entry point:
set_handler(0x180, &except_vec3_generic, 0x80);
b) At the end of this API, it will copy the exception handler to the different entry point according to different platform:
if (cpu_has_vce)
/* Special exception: R4[04]00 uses also the divec space. */
memcpy((void *)(CAC_BASE + 0x180), &except_vec3_r4000, 0x100);
else if (cpu_has_4kex)
memcpy((void *)(CAC_BASE + 0x180), &except_vec3_generic, 0x80);
else
memcpy((void *)(CAC_BASE + 0x080), &except_vec3_generic, 0x80);
c) For interrupt handler initialization, it will call the following statement to copy the general handler to entry point:
else if (cpu_has_divec)
set_handler(0x200, &except_vec4, 0x8);
please notice that “EBase + 0x200” is the interrupt handler entry point.
d) Since ExcCode(0) means interrupt, it will set the interrupt handler again when set exception vector for ExcCode(0) with “set_except_vector(0, handle_int);”:
void *set_except_vector(int n, void *addr)
{
unsigned long handler = (unsigned long) addr;
unsigned long old_handler = exception_handlers[n];
exception_handlers[n] = handler;
if (n == 0 && cpu_has_divec) {
*(volatile u32 *)(ebase + 0x200) = 0x08000000 |
(0x03ffffff & (handler >> 2));
flush_icache_range(ebase + 0x200, ebase + 0x204);
}
return (void *)old_handler;
}
According to MIPS instruction encoding, 0x08000000 is a jump instruction, so the actual instruction in EBase + 0x200 is “j handle_int”
2) handlers registering
In trap_init() API, it define a global array unsigned long exception_handlers[32] to contain 32 exception handlers for 32 ExcCodes and it will call set_except_vector() function to register these handlers to this array.
set_except_vector(0, handle_int);
set_except_vector(1, handle_tlbm);
set_except_vector(2, handle_tlbl);
set_except_vector(3, handle_tlbs);
set_except_vector(4, handle_adel);
set_except_vector(5, handle_ades);
set_except_vector(6, handle_ibe);
set_except_vector(7, handle_dbe);
set_except_vector(8, handle_sys);
set_except_vector(9, handle_bp);
set_except_vector(10, handle_ri);
set_except_vector(11, handle_cpu);
set_except_vector(12, handle_ov);
set_except_vector(13, handle_tr);
…
2. How to handle exception
The basic flow of exception processing is like the following diagram:
As said above, exception handlers for different causes are registered to exception_handlers[32] and exception_vec3_generic is copied to EBase + 0x180, so when there is exception, CPU will jump to EBase + 0x180 and except_vec3_generic() will be called.
The except_vec3_generic() is implemented in Linux-2.6.18/arch/mips/kernel/genex.S. In this function, it will read the Cause register to get the ExcCode firstly, then it will use this ExcCode to get the corresponding exception handler from exception_handler[32] array and execute the exception handler.
For example, when user calls system calling function, it will use “syscall” instruction to generate the exception, for which the ExcCode is 8; When CPU gets this exception, it will call except_vec3_generic() to handle it: get the exception handler for ExcCode 8 (handle_sys()) from exception_handler[32] array and execute handle_sys() function.
3. How to handle Interrupt
In Linux 2.6.18, it defines an array irq_desc[NR_IRQS] in linux-2.6.18/include/linux/irq.h to store the information needed by interrupt handler. This array item is a structure “struct irq_desc” which is defined in the same file.
When Linux device driver registers it’s interrupt handler with request_irq() API, this API will call setup_irq() function to check the flags and store the interrupt handler to irq_desc[] array according to the registered irq number. Both these functions are implemented in linux-2.6.18/kernel/irq/manage.c.
When an interrupt is detected by CPU, it will jump to EBase + 0x200 and “handle_int()” will be called which is implemented in Linux-2.6.18/arch/mips/kernel/genex.S. The following is the source code for this function:
NESTED(handle_int, PT_SIZE, sp)
SAVE_ALL
CLI
TRACE_IRQS_OFF
PTR_LA ra, ret_from_irq
move a0, sp
j plat_irq_dispatch
END(handle_int)
From the code, we can see that it will save all GPR firstly, then disable interrupt and call plat_irq_dispatch() function; after it return from plat_irq_dispatch() function, it will call ret_from_irq() function to restore GPR registers and enable the interrupt.
The implementation of plat_irq_dispatch() function is related with different platform. However, it’s basic function is like this: Read Status and Cause register to get the interrupt source firstly, then get the exact irq number according it’s interrupt design; at the end of this function, it will call do_IRQ() function.
The do_IRQ() function will get the interrupt handler from irq_desc[] array according to irq number and execute the interrupt handler. This implementation of this function is in linux-2.6.18/arch/mips/kernel/irq.c.
The following diagram shows the brief processing flow of interrupt.
Implementation of exception in Linux (Cont)
Part 3: How to implement interrupt for new platform
When adding a new platform in Linux kernel, two APIs are needed to be implemented for interrupt part: arch_init_irq() and plat_irq_dispatch().
1. arch_init_irq()
The arch_init_irq() API is used to initialize the platform interrupt controller and enable the MIPS interrupt according to it’s interrupt source.
The reference code for this API is like the following:
static struct irq_chip newplat_irq_type = {
.typename = "Newplat",
.startup = startup_newplat_irq,
.shutdown = shutdown_newplat_irq,
.enable = enable_newplat_irq,
.disable = disable_newplat_irq,
.ack = ack_newplat_irq,
.end = end_newplat_irq,
};
static struct irqaction newplat_irq = {
.handler = no_action,
.name = "Newplat cascade"
};
void __init arch_init_irq(void)
{
int i;
/*TODO: Initialization for Platform interrupt related part */
...
/*Initialize irq_desc[] for the platform used irqs range*/
for (i = NEWPLAT_INT_BASE; i <= NEWPLAT_INT_END; i++) {
irq_desc[i].status = IRQ_DISABLED;
irq_desc[i].action = 0;
irq_desc[i].depth = 1;
irq_desc[i].chip = &newplat_irq_type;
spin_lock_init(&irq_desc[i].lock);
}
/*Assume MIPS interrupt source base is 0 in this platform*/
mips_cpu_irq_init(0);
/*Enable interrupt source 4*/
setup_irq(4, &newplat_irq);
}
In this sample code, we assume only interrupt source 4 is used in this platform and assume the base value for MIPS interrupt source is 0, so the irq number for this platform must be larger than 7, which means “NEWPLAT_INT_BASE ” must be 8 or more.
We define a structure newplat_irq_type in this same code and this structure include many functions’ pointer: startup(), shutdown(), enable(), disable(), ack() and end(). These functions’ implementation is platform dependent and is valid for irq number between NEWPLAT_INT_BASE and NEWPLAT_INT_END . The following shows the general implementation for these functions.
void disable_newplat_irq(unsigned int irq_nr)
{
if(irq_nr < MIRA_INT_BASE)
return;
/*TODO: Disable the irq_nr interrupt, which means clear the interrupt mask bit for irq_nr*/
iob();
}
void enable_newplat_irq(unsigned int irq_nr)
{
if(irq_nr < MIRA_INT_BASE)
return;
/*TODO: Enable the irq_nr interrupt, which means set the interrupt mask bit for irq_nr*/
iob();
}
static unsigned int startup_newplat_irq(unsigned int irq)
{
enable_newplat_irq(irq);
return 0;
}
#define shutdown_newplat_irq disable_newplat_irq
void ack_newplat_irq(unsigned int irq_nr)
{
if(irq_nr < MIRA_INT_BASE)
return;
/*TODO: Clear the interrupt status bit corresponding to irq_nr*/
iob();
}
static void end_newplat_irq(unsigned int irq)
{
if (!(irq_desc[irq].status & (IRQ_DISABLED|IRQ_INPROGRESS)))
enable_newplat_irq(irq);
}
The startup() or enable() function is called in setup_irq() function, which is called by request_irq() API, so when device driver registers the interrupt handler with request_irq(), it also enables this interrupt at the same time. The shutdown() or disable() function is called in free_irq() API to disable this interrupt when device driver calls free_irq().
As said before, the defined structure “newplat_irq_type” is only valid for platform interrupt. For MIPS interrupt source (0-7 in our sample code), the similar structure “mips_cpu_irq_controller” is defined in linux-2.6.18/arch/mips/kernel/irq_cpu.c and this structure is connected with the MIPS interrupt source (2-7) in mips_cpu_irq_init() API which is called in arch_init_irq(). From the implementation of arch_init_irq() API, we can see setup_irq() function is called following mips_cpu_irq_init() function, so the startup() or enable() function in “mips_cpu_irq_controller” structure will be called to set the corresponding bits in Status[10:15] bits.
2. plat_irq_dispatch()
From part2, we can see that the plat_irq_dispatch() function shall get the interrupt source from Status and Cause registers firstly, then it shall get the exact irq number from platform dependent interrupt controller. The following code shows the general implementation:
asmlinkage void plat_irq_dispatch(struct pt_regs *regs)
{
unsigned int pending = read_c0_status() & read_c0_cause() & ST0_IM;
/*Assume only interrupt source 4 is used in this platform*/
if (pending & CAUSEF_IP4)
newplat_hw4_irqdispatch(regs);
else
spurious_interrupt(regs);
}
void newplat_hw4_irqdispatch(struct pt_regs *regs)
{
int irq;
/*TODO: get the irq from platform dependent registers*/
do_IRQ(irq, regs);
}
更多推荐
所有评论(0)