您的位置:首页 > 运维架构 > Linux

Implementation of exception in Linux

2009-12-10 11:19 411 查看
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
;

exception_handlers
= 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);

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐