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:

1

 

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.

 

 2

 

 

 

 

 

 

 

 

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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
《TCP/IP架构、设计与实现在Linux中的应用》是一本介绍TCP/IP协议在Linux系统中的架构、设计和实现的书籍。TCP/IP协议是Internet的核心协议之一,负责实现网络通信。 该书主要分为三个部分。第一部分介绍了TCP/IP协议的基本概念、层次结构和相关协议。TCP/IP协议栈由多个层次组成,分为物理层、数据链路层、网络层、传输层和应用层,每个层次负责不同的功能。该部分还介绍了一些常用的TCP/IP协议,如IP、ICMP、TCP和UDP等。 第二部分关注TCP/IP协议在Linux系统中的实现。Linux是一个开源操作系统,具有良好的可扩展性和灵活性,因此被广泛应用于网络服务器等领域。该书介绍了Linux内核中对TCP/IP协议的实现细节,包括数据结构、算法和函数调用等。读者可以了解到Linux内核如何使用这些技术来构建一个可靠的网络通信系统。 第三部分则介绍了一些实际应用案例,包括网络编程、网络安全、网络性能优化等。读者可以通过这些案例了解如何在Linux系统中实际应用TCP/IP协议,解决实际问题。 总的来说,《TCP/IP架构、设计与实现在Linux中的应用》是一本介绍TCP/IP协议在Linux系统中实现的权威指南。通过阅读该书,读者可以深入了解TCP/IP协议的原理和实现,掌握在Linux系统中使用TCP/IP协议进行网络通信的技术。这对于网络工程师和系统管理员来说是一本非常有价值的参考书。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值