做人呢,最紧要就系开心啦

ARM64基础13:ARM64的异常处理之中断处理(以树莓派4采用的BCM2711芯片为例)

1,043次阅读
一条评论

ARM64 的中断属于异常模式的一种,对中断的处理流程跟上节的异常处理类似,走其中的一个分支;

树莓派 4b 的中断控制器分两种:传统的中断方式 legacy interruptGIC(BCM2711 支持 GIC-400)

一个典型的 ARM64 中断处理流程:
ARM64 基础 13:ARM64 的异常处理之中断处理(以树莓派 4 采用的 BCM2711 芯片为例)

树莓派 4b 上的硬件资源:

1. 中断控制器:

GIC-400(默认)
传统的中断控制器(legacy interrupt controller)

2. 树莓派 4b 支持多种中断源:

ARM Core N: ARM Core 本身的中断源,例如 Core 里的 generic timer;
ARMC: 可以被 VPU 和 CPU 访问的中断源,例如 mailbox;
ARM_LOCAL:只能被 CPU 访问的中断源,例如本地 timer 等;
ARM64 基础 13:ARM64 的异常处理之中断处理(以树莓派 4 采用的 BCM2711 芯片为例)

3. 树莓派 4b 的 legacy 中断控制器

ARM64 基础 13:ARM64 的异常处理之中断处理(以树莓派 4 采用的 BCM2711 芯片为例)

实例:以 ARM Core 的 generic timer 产生中断信号

3.1 Cortex-A72 支持 4 个 ARM Core 的 generic timer

ARM64 基础 13:ARM64 的异常处理之中断处理(以树莓派 4 采用的 BCM2711 芯片为例)
这里采用 Nonsecure EL1 Physical Timer,演示中断处理过程;
查看 ARMv8 手册,第一个是EL1 TIMER 的控制寄存器 CNTP_CTL_EL0;
ARM64 基础 13:ARM64 的异常处理之中断处理(以树莓派 4 采用的 BCM2711 芯片为例)
计数器寄存器 CNTP_TVAL_EL0
ARM64 基础 13:ARM64 的异常处理之中断处理(以树莓派 4 采用的 BCM2711 芯片为例)
Timer 支持两种触发方式,参考 armv8_D11.2.4;

3.2 ARM_LOCAL 寄存器有:

4 个 IRQ_SOURCE 寄存器,每个 CPU 一个,表示 IRQ 中断源状态;
4 个 FIQ_SOURCE 寄存器,每个 CPU 一个,表示 FIQ 中断源状态;
4 个 TIMER_CNTRL 寄存器,每个 CPU 一个,用来使能对应中断源;

1. 查看 BCM2711 手册可知,ARM_LOCAL 基地址为 0x4c000000 或 0xff800000
ARM64 基础 13:ARM64 的异常处理之中断处理(以树莓派 4 采用的 BCM2711 芯片为例)
对于 IRQ_SOURCE 寄存器,可以读取对应中断源状态
ARM64 基础 13:ARM64 的异常处理之中断处理(以树莓派 4 采用的 BCM2711 芯片为例)
TIMER_CNTRL 寄存器用来使能中断响应
ARM64 基础 13:ARM64 的异常处理之中断处理(以树莓派 4 采用的 BCM2711 芯片为例)

3.3 EL1 的 Nonsecure generic timer 中断处理流程

初始化

1. 初始化 timer,设置 cntp_ctl_el0 寄存器的 enable 域使能;
2. 给 timer 的 TimeValue 一个初值,设置 cntp_tval_el0 寄存器;
3. 打开树莓派 4b 中断控制器中和 timer 相关的中断,设置 TIMER_CNTRL0 寄存器的 CNT_PNS_IRQ 为 1;
4. 打开 PSTATE 寄存器中的 IRQ 中断总开关;

异常中断处理

5.Timer 中断发生;
6. 跳转到异常向量表的 el1_irq1 函数;
7. 保存中断上下文(kernel_entry 宏);
8. 跳转到中断处理函数;
9. 读取 ARM_LOCAL 中断状态寄存器 IRQ_SOURCE0;
10. 判断中断源,是否为 CNT_PNS_IRQ 中断;
11. 如果是 CNT_PNS_IRQ,重置 TimerValue;

返回现场

12. 返回到 el1_irq 函数;
13. 恢复中断上下文;
14. 返回中断现场

中断源的判断:

以一个稍微复杂的中断源为例,根据树莓派 4b BCM2711 手册:
ARM64 基础 13:ARM64 的异常处理之中断处理(以树莓派 4 采用的 BCM2711 芯片为例)
由上图可知,最多分三级:
1. 读 SOURCEn 中断寄存器;
2. 若 SOURCEn[8]置位,继续读取 PENDING2 寄存器;
3. 若 PENDING2[24]置位,则读 PENDING0 寄存器;
4. 若 PENDING2[25]置位,则读 PENDING1 寄存器;

可见当中断源数量增加到一定程度,中断处理程序将会变得十分繁杂,GIC 控制器 就是为解决这个问题而生,下篇将详细介绍;

3.4 中断现场(上下文)

3.4.1 中断上下文内容

中断发生瞬间,CPU 状态包括:
PSTATE 寄存器
PC 值
SP 值
X0~X30 寄存器

Linux 使用一个栈框数据结构来描述需要保存的中断现场;
ARM64 基础 13:ARM64 的异常处理之中断处理(以树莓派 4 采用的 BCM2711 芯片为例)

3.4.2 保存中断现场

中断发生时,中断上下文保存到当前进程的内核栈里;
ARM64 基础 13:ARM64 的异常处理之中断处理(以树莓派 4 采用的 BCM2711 芯片为例)
代码实现:

.macro kernel_entry
    sub sp, sp, #S_FRAME_SIZE

    /*
       保存通用寄存器 x0~x29 到栈框里 pt_regs->x0~x29
     */
    stp x0, x1, [sp, #16 *0]
    stp x2, x3, [sp, #16 *1]
    stp x4, x5, [sp, #16 *2]
    stp x6, x7, [sp, #16 *3]
    stp x8, x9, [sp, #16 *4]
    stp x10, x11, [sp, #16 *5]
    stp x12, x13, [sp, #16 *6]
    stp x14, x15, [sp, #16 *7]
    stp x16, x17, [sp, #16 *8]
    stp x18, x19, [sp, #16 *9]
    stp x20, x21, [sp, #16 *10]
    stp x22, x23, [sp, #16 *11]
    stp x24, x25, [sp, #16 *12]
    stp x26, x27, [sp, #16 *13]
    stp x28, x29, [sp, #16 *14]

    /* x21: 栈顶 的位置 */
    add     x21, sp, #S_FRAME_SIZE

    mrs     x22, elr_el1
    mrs     x23, spsr_el1

    /* 把 lr 保存到 pt_regs->lr, 把 sp 保存到 pt_regs->sp 位置 */
    stp     lr, x21, [sp, #S_LR]
    /* 把 elr_el1 保存到 pt_regs->pc 中
       把 spsr_elr 保存到 pt_regs->pstate 中 */
    stp     x22, x23, [sp, #S_PC]
    .endm

3.4.3 恢复中断现场

中断处理完成后,从内核栈中恢复中断现场;
ARM64 基础 13:ARM64 的异常处理之中断处理(以树莓派 4 采用的 BCM2711 芯片为例)
代码实现:

.macro kernel_exit
    /* 从 pt_regs->pc 中恢复 elr_el1,
       从 pt_regs->pstate 中恢复 spsr_el1
       */
    ldp     x21, x22, [sp, #S_PC]           // load ELR, SPSR

    msr     elr_el1, x21                    // set up the return data
    msr     spsr_el1, x22
    ldp     x0, x1, [sp, #16 * 0]
    ldp     x2, x3, [sp, #16 * 1]
    ldp     x4, x5, [sp, #16 * 2]
    ldp     x6, x7, [sp, #16 * 3]
    ldp     x8, x9, [sp, #16 * 4]
    ldp     x10, x11, [sp, #16 * 5]
    ldp     x12, x13, [sp, #16 * 6]
    ldp     x14, x15, [sp, #16 * 7]
    ldp     x16, x17, [sp, #16 * 8]
    ldp     x18, x19, [sp, #16 * 9]
    ldp     x20, x21, [sp, #16 * 10]
    ldp     x22, x23, [sp, #16 * 11]
    ldp     x24, x25, [sp, #16 * 12]
    ldp     x26, x27, [sp, #16 * 13]
    ldp     x28, x29, [sp, #16 * 14]

    /* 从 pt_regs->lr 中恢复 lr*/
    ldr     lr, [sp, #S_LR]
    add     sp, sp, #S_FRAME_SIZE           // restore sp
    eret
    .endm

3.5 实例代码:

在树莓派上实现 generic timer, 关键代码
timer 初始化

#include <asm/timer.h>
#include <asm/irq.h>
#include <io.h>
#include <asm/arm_local_reg.h>
#include <timer.h>

#define HZ 250
#define NSEC_PER_SEC    6000000000L

static unsigned int val = NSEC_PER_SEC / HZ;

static int generic_timer_init(void)
{
    asm volatile(
        "mov x0, #1\n"
        "msr cntp_ctl_el0, x0"
        :
        :
        : "memory");

    return 0;
}

static int generic_timer_reset(unsigned int val)
{
    asm volatile("msr cntp_tval_el0, %x[timer_val]"
        :
        : [timer_val] "r" (val)
        : "memory");

    return 0;
}

static void enable_timer_interrupt(void)
{writel(CNT_PNS_IRQ, TIMER_CNTRL0);
}

void timer_init(void)
{generic_timer_init();
    generic_timer_reset(val);

    enable_timer_interrupt();}

void handle_timer_irq(void)
{generic_timer_reset(val);
    printk("Core0 Timer interrupt received\r\n");
}

中断服务程序:

#include <asm/irq.h>
#include <io.h>
#include <asm/arm_local_reg.h>

void irq_handle(void)
{unsigned int irq = readl(ARM_LOCAL_IRQ_SOURCE0);

    switch (irq) {case (CNT_PNS_IRQ):
        handle_timer_irq();
        break;
    default:
        printk("Unknown pending irq: %x\r\n", irq);
    }
}

主应用程序:

void kernel_main(void)
{uart_init();
    init_printk_done();
    uart_send_string("Welcome BenOS!\r\n");
    printk("printk init done\n");

    //my_ldr_test();
    my_data_process();
    //my_cmp_test();
    //other_test();
    print_func_name(0x800880);
    //unsigned long val1 = 0,val2=0;
    //val1 = macro_test1(3,5);
    //val2 = macro_test2(3,5);

    print_memmap();

    my_memcpy_asm_test(0x80000, 0x100000, 32);

    my_memset_16bytes_asm(0x600000,0xaabbccdd,4096);

    //test_asm_goto(1);

    //trigger_alignment();

    printk("init done\n");
    timer_init(); // 初始化 timer
    raw_local_irq_enable();// 启动 timer

    my_ops_test();

    test_sysregs();
    int count=0;
    while (1) {//uart_send(uart_recv());
        printk("printk while...%ds\n",count++);
        delay_s(1);
    }
}

3.6 在 qemu 上模拟结果如下:

ARM64 基础 13:ARM64 的异常处理之中断处理(以树莓派 4 采用的 BCM2711 芯片为例)

正文完
 
admin
版权声明:本站原创文章,由 admin 2021-11-02发表,共计4281字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(一条评论)