jgsun's blog Embedded Linux Software

OCTEON MIPS 中断处理

2019-05-27
jgsun

irq

1. 概述

在MIPS中,中断、陷阱、系统调用和任何可以中断程序正常执行流的情况都称异常。 本文讲述OCTEON CN71xx平台的异常初始化和中断异常处理流程, 所用u-boot和linux代码库分别是u-boot-octeon-sdk3.1和linux-octeon-sdk3.1。

2. 异常初始化

为了让中断正常工作,操作系统必须进行必要的初始化,包括exception初始化,中断子系统初始化,中断控制器初始化,使能本地cpu中断和使能中断控制器外设中断共5个步骤,如下图所示。这些初始化建立中断向量入口,创建并初始化核心数据结构:struct irq_desc, struct irq_chip, struct irq_domain等,使能硬件中断。 image

2.1 exception向量初始化

这个步骤包含EBase寄存器初始化和异常向量数组初始化。

2.1.1 Root.EBase 寄存器初始化

当Root.Status[BEV] = 0时,Root.EBase 寄存器设定异常入口地址,其 初始化在u-boot和linux两个阶段完成: (1)u-boot阶段

在do_bootoctlinux调用octeon_setup_boot_desc_block设置boot参数 boot_info_block_array[core].exception_base: boot_info_block_array[core].exception_base = cur_exception_base cur_exception_base等于多少?从下面的代码片段得出其等于0x1000( 4K对齐 ) 。

uint32_t cur_exception_base = EXCEPTION_BASE_INCR;
/* Increment size for exception base addresses (4k minimum) */
#ifndef EXCEPTION_BASE_INCR
#define EXCEPTION_BASE_INCR     (4*1024)
#endif

接着do_bootoctlinux/start_linux调用 start_os_common写 Root.EBase 寄存器:

start_os_common
    boot_info_ptr = &boot_info_block_array[core_num]
    set_except_base_addr(boot_info_ptr->exception_base)
        write_c0_ebase(addr) //将0x1000写入 Root.EBase

u-boot在 do_bootoctlinux函数中,先调用 octeon_setup_boot_desc_block将每个core 启动参数的 exception_base 设置为 0x1000;然后调用 start_linux/ start_os_common将 exception_base写入Root.EBase Register;所以此时 core0 Root.EBase Register 的值是 0xffffffff80001000, core2 Root.EBase Register 的值是 0xffffffff80001002,3 core Root.EBase Register 的值是 0xffffffff80001003。(见linux阶段trap_init函数的打印) (2) linux阶段

主核读取 EBase 寄存器确定异常入口地址,以用于将异常handler拷贝到该地址和初始化从核 EBase 寄存器。

#define CKSEG0	_CONST64_(0xffffffff80000000)
ebase = CKSEG0 // ebase=0xffffffff80000000
ebase += (read_c0_ebase() & 0x3ffff000) //加入u-boot写入的0x1000之后 ebase=0xffffffff80001000

2.1.2 start_kernel/ trap_init

主核core0启动阶段调用start_kernel/ trap_init,主要完成:

(1)set ebase全局变量,等于 CKSEG0加上u-boot阶段写入的offset 0x1000,即 0xffffffff80001000

(2)per_cpu_trap_init配置Root.Status寄存器(clear BEV)和计算 系统timer中断号码cp0_compare_irq等于7

(3)set_except_vector(0, handle_int),初始化异常向量数组exception_handlers[32],即注册各种异常处理函数set_except_vector函数将上32个异常的处理函数地址放到一个全局数组exception_handlers中,例如外部中断异常处理函数:exception_handlers[0] = handle_int

(4)set_handler(0x180, &except_vec3_generic, 0x80),拷贝异常入口函数except_vec3_generic到ebase寄存器设定的入口地址 + 0x180处

#define CKSEG0			_CONST64_(0xffffffff80000000)
ebase = CKSEG0 // ebase=0xffffffff80000000
ebase += (read_c0_ebase() & 0x3ffff000) //加入u-boot写入的0x1000之后 ebase=0xffffffff80001000
per_cpu_trap_init(true)
set_except_vector(23, handle_watch)
set_except_vector(0, using_rollback_handler() ? rollback_handle_int: handle_int)
    old_handler = xchg(&exception_handlers[n], handler)
set_except_vector(1, handle_tlbm)
set_handler(0x180, &except_vec3_generic, 0x80)
    memcpy((void *)(ebase + offset), addr, size)

trap_init的打印输出:

[    0.000000] trap_init(1902)ebase=0xffffffff80000000, c0_ebase=0xffffffff80001000
[    0.000000] trap_init(1908)ebase=0xffffffff80001000, c0_ebase=0xffffffff80001000
[    0.000000] trap_init(1911)ebase=0xffffffff80001000, c0_ebase=0xffffffff80001000
[    0.000000] 0-per_cpu_trap_init(1787)ebase=0xffffffff80001000, c0_ebase=0xffffffff80001000
[    0.000000] trap_init(1913)ebase=0xffffffff80001000, c0_ebase=0xffffffff80001000
[   37.130784] SMP: Booting CPU01 (CoreId  2)...
[   37.134985] 1-per_cpu_trap_init(1787)ebase=0xffffffff80001000, c0_ebase=0xffffffff80001002
[   37.135039] CPU revision is: 000d9602 (Cavium Octeon III)
[   37.135041] FPU revision is: 00739600
[   37.150784] SMP: Booting CPU02 (CoreId  3)...
[   37.172277] 2-per_cpu_trap_init(1787)ebase=0xffffffff80001000, c0_ebase=0xffffffff80001003
[   37.172316] CPU revision is: 000d9602 (Cavium Octeon III)
[   37.172318] FPU revision is: 00739600
[   37.172430] Brought up 3 CPUs

Root.EBase Register reset 之后是 0xffffffff80000,trap_init读取的是 0xffffffff80001,何时改写的?答案是在u-boot启动linux引导阶段: do_bootoctlinux/start_linux/ start_os_common(见 2.1.1节 )

2.2 start_kernel/init_IRQ/octeon_irq_init_ciu

主核core0调用start_kernel/init_IRQ/octeon_irq_init_ciu初始化中断控制器CIU:

(1)disable core0的各CIU外设中断,后面由具体驱动使能;

(2)注册外设中断的irq_domain;

(3)给Mailbox,wagchdog中断分配 irq_desc并初始化;

(4)给timer,pci等外设分配 irq_desc并初始化( 这里比较特别,不用在驱动里面分配 irq_desc了,直接 request_irq即可 )。

octeon_irq_init_ciu_percpu()// Disable All CIU Interrupts
octeon_irq_setup_secondary = octeon_irq_setup_secondary_ciu
octeon_irq_ip2 = octeon_irq_ip2_ciu//IP2中断handler,在plat_irq_dispatch调用
octeon_irq_ip3 = octeon_irq_ip3_ciu// IP3中断handler,在plat_irq_dispatch调用
octeon_irq_ip4 =  octeon_irq_ip4_ciu// IP4中断handler,在plat_irq_dispatch调用
chip = &octeon_irq_chip_ciu_v2//set irq_chip
octeon_irq_ciu_chip = chip
ciu_domain = irq_domain_add_tree(ciu_node, &octeon_irq_domain_ciu_ops, dd)//注册irq domain
octeon_irq_force_ciu_mapping(ciu_domain, i + OCTEON_IRQ_TIMER0, 0, i + 52)//创建4个timer中断的irq_desc,这里循环4次
   irq_alloc_desc_at(irq, 0)//分配 irq_desc
   irq_domain_associate(domain, irq, line << 6 | bit)
        domain->ops->map(domain, virq, hwirq)//调用 irq domain的map handler,建立软硬中断号的映射关系
            octeon_irq_ciu_map
                octeon_irq_set_ciu_mapping(virq, line, bit, 0, oc teon_irq_ciu_chip, handle_level_irq)
                    irq_set_chip_and_handler//设置 irq_desc的irq_chip和handle_irq

将给irq 121对应的irq_desc分配irq_chip octeon_irq_ciu_chip

2.3 start_kernel/ early_irq_init

irq号码采用静态分配,共支持511个中断号, early_irq_init 初始化struct irq_desc[511]部分成员,如锁等。

2.4 start_kernel/ local_irq_enable

start_kernel调用 local_irq_enable 使能本地cpu中断。

local_irq_enable  
    raw_local_irq_enable
        arch_local_irq_enable <arch\mips\include\asm\irqflags.h>
            ei //set Root.Status Register( CP0 Register 12, Select 0 )bit0(Interrupt enable)

2.5 request_irq

使用timer的驱动调用 request_irq设置irq_desc的action->handler指针和写CIU 中断控制器CVMX_CIU_INTX_EN1_W1S使能timer外部中断。 这里以驱动arch\mips\cavium-octeon\oct_ilm.c为例。

request_irq
    request_threaded_irq
        action->handler = handler
        __setup_irq
            irq_startup
                irq_enable(desc)
                    desc->irq_data.chip->irq_enable(&desc->irq_data)\\调用 octeon_irq_ciu_enable_v2

irq_enable将调irq_desc对应 irq_chip( octeon_irq_ciu_chip ) 的 成员函数 irq_enable( e octeon_irq_ciu_enable_v2 ) , 写CIU 中断控制器CVMX_CIU_INTX_EN1_W1S使能timer外部中断 ,如timer3对应bit56,如打印所示: octeon_irq_ciu_enable_v2(519)index=2, mask=0x80000000000000

2.6 SMP从核异常初始化

SMP从核被唤醒后, 在start_secondary函数中完成从核异常初始化,具体包括:

(1)在mp_ops->init_secondary函数调用write_c0_ebase初始化从核Root.EBase Register,其值是主核core0在start_kernel/trap_init初始化的全局变量ebase=0xffffffff80001000,写入之后core2的Root.EBase Register等于0xffffffff80001002;

(2)在mp_ops->init_secondary函数调用octeon_irq_setup_secondary,初始化中断控制器;

(3)在mp_ops->smp_finish调用local_irq_enable使能本地cpu中断。

start_secondary
    per_cpu_trap_init(false)//clear BEV
    mp_ops->init_secondary() //调用octeon_init_secondary
        write_c0_ebase((u32)ebase) //写 Root.EBase Register
        octeon_irq_setup_secondary //调用octeon_irq_setup_secondary_ciu
            octeon_irq_init_ciu_percpu//Disable All CIU Interrupts
            octeon_irq_percpu_enable//调用irq_chip->irq_cpu_online(没有定义)
    mp_ops->smp_finish() //调用octeon_smp_finish
        local_irq_enable()

3. 中断异常处理流程

当MIPS CPU发生 外部 中断后,CPU的状态变化总结如下:

(1)EPC寄存器保存了发生中断是程序执行指令的地址

(2)CP0中STATUS寄存器EXL置位为1,表示正在异常状态,这个时候CPU不能再响应中断(不管IE是否开启),自动进入核心状态

(3)CP0中CAUSE寄存器中ExcCode为0,表示是中断异常

(4)CP0中CAUSE寄存器IP被设置为不同的中断号

当发生timer中断,CPU会跳转到异常入口地址(即初始化写入的ebase寄存器指定的入口地址 0xffffffff80001000 )开始执行异常处理程序except_vec3_generic,如下图所示: image

3.1 except_vec3_generic

读取Root.Cause寄存器,得到exception code,其中0号即外部中断,然后跳转到异常向量数组 exception_handler[0]

3.2 handle_int

handle_int函数实际上就是注册到exception_handler[0]上专门处理中断引发的异常,该函数主要做的工作就是: (1)调用SAVE_ALL保存中断现场 SAVE_ALL在arch\mips\include\asm\stackframe.h中定义:

.macro  SAVE_ALL docfi=0
SAVE_SOME \docfi
SAVE_AT \docfi
SAVE_TEMP \docfi
SAVE_STATIC \docfi
.endm

(2)调用CLI关闭中断

(3)调用plat_irq_dispatch判断中断类型进行分别处理

(4)处理中断完成后从ret_from_irq退出中断处理流程

3.3 plat_irq_dispatch

plat_irq_dispatch函数读取status(中断mask位)和cause寄存器相与,确定中断类型,如果是ip2则调用 octeon_irq_ip2_ciu 函数。

3.4 octeon_irq_ip2_ciu

octeon_irq_ip2_ciu读取ciu中断sum寄存器, 查表获取硬件中断号码,然后调用do_IRQ进入内核通用中断处理部分。

3.5 do_IRQ

do_IRQ调用generic_handle_irq最终执行request_irq注册的中断处理函数。

4 其他

4.1 set_c0_status函数的宏定义

set_c0_status用来设置status寄存器相应bit,其定义在文件 arch/mips/include/asm/mipsregs.h中 (1)首先定义 __BUILD_SET_C0宏用于定义 set_c0_##name函数 :

#define __BUILD_SET_C0(name)					\
static inline unsigned int					\
set_c0_##name(unsigned int set)					\
{								\
 unsigned int res, new;					\
        \
 res = read_c0_##name();					\
 new = res | set;					\
 write_c0_##name(new);					\
        \
 return res;						\
} 

(2)然后使用 __BUILD_SET_C0(status) 定义 set_c0_status 函数:

__BUILD_SET_C0(status)
__BUILD_SET_C0(config)
...

4.2 OCTEON CN71xx interrupt features

不支持外部中断控制器和矢量粥中断?实验验证, CN71xx ebase寄存器bit[63:30]并不可写即使set WG bit,所以可编程的异常入口地址范围是[0x0, 0x3FFFF]。

5 参考资料


Comments

Content