[RPi bring up] 从树莓派linux源码中窥探bcm2835和arm1176jzfs的中断管理

[RPi bring up] 从树莓派linux源码中窥探bcm2835和arm1176jzfs的中断管理 | Changchun Master Li 本人擅长朴素贝叶斯科学算命, 大家没事可以多看我的书吃我的药听我的讲座icon-default.png?t=N7T8https://blog.74ls74.org/2023/10/29/20231029_rpi_linux_bcm2835_arm11jzfs_interrupt/

当中断发生时,程序执行流程将暂停并且运行中断处理程序。在中断处理程序运行结束后,恢复之前的执行流程。同步中断,通过执行指令生成,也叫异常。异步中断,由外部事件生成。

bcm2835 datasheet中有关中断控制的描述过于简略,但没关系,我们可以从linux源码中扒有用的东西

用最新的buildroot构建linux系统

获取最新版本buildroot

 
 

git clone https://github.com/buildroot/buildroot.git cd buildroot make raspberrypi_defconfig make all # output/images/sdcard.img

打开jtag

要改一下boot/config.txt,这里有两种改法

第一种,board/raspberrypi/post-image.sh脚本会先检测有没有文件board/raspberrypi/genimage-${BOARD_NAME}.cfg,如果没有就用模板board/raspberrypi/post-image.sh生成一个临时的配置文件output/images/genimage.cfg。在post-image.sh中改config.txt,然后重新跑一次make即可。

第二种,buidlroot有一个配置项BR2_PACKAGE_RPI_FIRMWARE_CONFIG_FILE(package/rpi-firmware/Config.in),打开配置菜单可以看到,make menuconfig

 
 

Target packages -> Hardware handling -> Firmware -> Path to a file stored as boot/config.txt

默认下config.txt对应config_default.txt,在board/raspberrypi/config_default.txt加上下面三行

 
 

# Enable bcm2835 jtag, set GPIO4 instead GPIO 26 enable_jtag_gpio=1 gpio=4=a5

这需要我们重新编译一下rpi-firmware

 
 

make rpi-firmware-rebuild && make

调试信息

在编译的时候保留调试信息,有几个地方也需要定制

make menuconfig

 
 

Build options -> build packages with debugging symbols

选level3,包含宏定义

打开

 
 

Build options -> build packages with runtime debugging info

关闭

 
 

Build options -> strip target binaries

gcc优化选debugging

 
 

Build options -> gcc optimization level

linux kernel config也要改

make linux-menuconfig

 
 

Kernel hacking -> Compile-time checks and compiler options -> Compile the kernel with debug info

最后,在start_kernel里加一个死循环,可以在启动时停住,用gdb命令(gdb) set $pc=$pc+0x4跳过

 
 

__asm ( "lo: b lo\n\t" : : : );

重新编译内核

 
 

make linux-dirclean && make linux-rebuild && make

把output/images/sdcard.img写入sd卡,就可以用jlink调试了

linux中断代码浅析

注意,默认配置中 https://github.com/buildroot/buildroot/blob/master/configs/raspberrypi_defconfig buildroot使用的是raspberry pi kernel而非mainline kernel GitHub - raspberrypi/linux: Kernel source tree for Raspberry Pi-provided kernel builds. Issues unrelated to the linux kernel should be posted on the community forum at https://forums.raspberrypi.com/ 当前版本为rpi-5.10.y

bcm2835的驱动在irq-bcm2835.c,说白了就是用IRQCHIP_DECLARE宏定义了一个struct结构体__of_table_bcm2835_armctrl_ic

 
 

IRQCHIP_DECLARE(bcm2835_armctrl_ic, "brcm,bcm2835-armctrl-ic", bcm2835_armctrl_of_init);

可以打出来看一下

 
 

(gdb) p __of_table_bcm2835_armctrl_ic $10 = {name = '\000' <repeats 31 times>, type = '\000' <repeats 31 times>, compatible = "brcm,bcm2835-armctrl-ic", '\000' <repeats 104 times>, data = 0xc0b8c990 <bcm2835_armctrl_of_init>}

设备树中bcm2835-armctrl-ic,通过bcm2835_armctrl_of_init进行初始化。

armctrl_of_init

跟其他中断控制器驱动的套路一样,驱动drivers/irqchip/irq-bcm2835.c,of_init初始化中断控制器armctrl_ic

 
 

intc: interrupt-controller@7e00b200 { compatible = "brcm,bcm2835-armctrl-ic"; reg = <0x7e00b200 0x200>; interrupt-controller; #interrupt-cells = <2>; };

初始化阶段

of_iomap映射node设备地址0x7e00b200到base,

irq_domain_add_linear返回一个线性映射的domain

中断控制器的MMIO地址记录在intc.pending,intc.enable,intc.disable数组上,和bcm2835 datasheet的描述是对应的

address offset

register name

intc

0x200

IRQ basic pending

intc.pending[0]

0x204

IRQ pending 1

intc.pending[1]

0x208

IRQ pending 2

intc.pending[2]

0x20C

FIQ control

0x210

Enable IRQs 1

intc.enable[1]

0x214

Enable IRQs 2

intc.enable[2]

0x218

Enable Basic IRQs

intc.enable[0]

0x21C

Disable IRQs 1

intc.disable[1]

0x220

Disable IRQs 2

intc.disable[2]

0x224

Disable Basic IRQs

intc.disable[0]

irq_create_mapping分配一个linux系统irq,建立硬件hwirq到irq的map

irq_set_chip_and_handler设置handler为handle_level_irq

set_handle_irq设置bcm2835_handle_irq为中断处理函数

bcm2835_handle_irq每次被触发都会通过get_next_armctrl_hwirq获得hwirq,从bcm2835的datasheet找到IRQ basic pending的定义

IRQ pend base Address: 0x200 Reset: 0x000

bits

function

20:15

selected interrupts from GPU IRQ 63:32

14:10

selected interrupts from GPU IRQ 31:0

9

GPU IRQ 63:32
One or more bits set in pending register 2

8

GPU IRQ 31:0
One or more bits set in pending register 1

7

Illegal access type 0 IRQ pending

6

Illegal access type 1 IRQ pending

5

GPU1 halted IRQ pending

4

GPU0 halted IRQ pending

3

ARM Doorbell 1 IRQ pending

2

ARM Doorbell 0 IRQ pending

1

ARM Mailbox IRQ pending

0

ARM Timer IRQ pending

 
 

static u32 get_next_armctrl_hwirq(void) { u32 stat = readl_relaxed(intc.pending[0]) & BANK0_VALID_MASK; if (stat == 0) return ~0; else if (stat & BANK0_HWIRQ_MASK) return MAKE_HWIRQ(0, ffs(stat & BANK0_HWIRQ_MASK) - 1); else if (stat & SHORTCUT1_MASK) return armctrl_translate_shortcut(1, stat & SHORTCUT1_MASK); else if (stat & SHORTCUT2_MASK) return armctrl_translate_shortcut(2, stat & SHORTCUT2_MASK); else if (stat & BANK1_HWIRQ) return armctrl_translate_bank(1); else if (stat & BANK2_HWIRQ) return armctrl_translate_bank(2); else BUG(); }

原理很简单,用不同的mask检查pending0的bits

 
 

0xff arm中断 0x7c00 gpu1中断 0x1f8000 gpu2中断 0x100 gpu1中断 0x200 gpu2中断

armctrl_of_init建立了硬件hwirq 到 linux irq 到 desc->handle_irq 的映射

linux中断处理

让我们跟踪一下uart中断处理程序,pl011_int位于drivers/tty/serial/amba-pl011.c

打断点,然后在uart里敲一个字符就会触发中断

 
 

(gdb) b pl011_int ... (gdb) bt #0 pl011_int (irq=81, dev_id=0xc1912020) at drivers/tty/serial/amba-pl011.c:1504 #1 0xc0071484 in __handle_irq_event_percpu (desc=desc@entry=0xc10d8400, flags=flags@entry=0xc0bd7e70) at kernel/irq/handle.c:156 #2 0xc00716f0 in handle_irq_event_percpu (desc=0xc10d8400) at kernel/irq/handle.c:196 #3 handle_irq_event (desc=desc@entry=0xc10d8400) at kernel/irq/handle.c:213 #4 0xc007578c in handle_level_irq (desc=0xc10d8400) at kernel/irq/chip.c:653 #5 0xc0070c80 in generic_handle_irq_desc (desc=<optimized out>) at ./include/linux/irqdesc.h:152 #6 generic_handle_irq (irq=81) at kernel/irq/irqdesc.c:650 #7 __handle_domain_irq (domain=0xc10d2000, hwirq=<optimized out>, lookup=lookup@entry=true, regs=regs@entry=0xc0bd7ee8) at kernel/irq/irqdesc.c:687 #8 0xc000932c in handle_domain_irq (regs=0xc0bd7ee8, hwirq=<optimized out>, domain=<optimized out>) at ./include/linux/irqdesc.h:170 #9 bcm2835_handle_irq (regs=0xc0bd7ee8) at drivers/irqchip/irq-bcm2835.c:339 #10 0xc0008c7c in __irq_svc () at arch/arm/kernel/entry-armv.S:205

hwirq=89 (0b1011001) bank2,source25

查阅Documentation/devicetree/bindings/interrupt-controller/brcm,bcm2835-armctrl-ic.txt可知,中断源是VC_UART

irq=81

desc=0xc10d8400

generic_handle_irq调用irq_to_desc(irq)找到对应的中断描述,irq_desc在内核中的数据结构实现是基数树Radix Tree,

generic_handle_irq_desc调用desc->handle_irq(desc)

这里desc->handle_irq指向了handle_level_irq

handle_level_irq最终会遍历action列表desc->action,调用每一个action->handler,pl011_int已由pl011_allocate_irq注册到action->handler上了

一张图胜过千言万语

linux interrupt calling stack

emperorOS中断框架设计

https://github.com/996refuse/emperorOS/tree/interrupt​github.com/996refuse/emperorOS/tree/interrupt

中断过程中硬件和软件发生了什么?

操作系统, arm核心, 中断控制器, 硬件在中断中的工作流程

中断控制器 位于offset 0xb200,linux内核intc.enable变量,在对应bit写1打卡对应的irq

cpu开中断 位于CPSR寄存器的I bit,清零,cpu可响应中断

中断源 外部或者内部中断,比如timer compare引起的时钟中断

中断队列 soc内部的队列,当发生中断时,队列不为空,触发中断,cpu会挂起当前状态进入中断模式

触发中断 cpu进入中断模式,此时CPSR I bit置1,屏蔽cpu响应中断,以免进入中断嵌套

中断向量 cpu根据中断向量运行中断处理函数

中断返回 cpu从中断模式中恢复

框架实现

main函数最终进入用户态执行"init"进程来测试中断处理程序,目前init只有loop: b loop死循环,其binary为

 
 

unsigned char inifiniteloop[] = {0xfe, 0xff, 0xff, 0xea};

实现为一个单内核栈多用户栈

 
 

user -> irq mode(irq stack 0xC0006000) -> supervisor(main/proc_schd stack)

不可重入,内核态应尽快返回,以避免堵塞其他进程调度(sleep操作需要由用户态函数实现)

所有进程时间平均分配,不考虑优先级,优先级反转,优先级继承等等深度问题

timer中断的实现

armv6 使用p15协处理器指令设置中断向量

 
 

"mcr p15, 0, %[v], c12, c0, 0\n\t"

打开中断,这里模仿intc.enable

 
 

arm_intr_reg->gpu_enable[0] |= 1 << bit

最后由systimer_set设置compare定时器

中断上下文保存和恢复

上下文指r0-r15,cpsr寄存器的当前状态

trap_return保存当前内核态上下文到r1,恢复r0的用户态上下文

schd进入内核态上下文

 
 

.globl trap_return trap_return: add r1, #64 stmfd r1!, {r0-r14, lr} mrs lr, cpsr stmfd r1!, {lr} mov sp, r0 /* restore cpsr */ ldmfd sp!, {lr} msr spsr, lr /* restore r0-r14 */ ldmfd sp!, {r0-r14}^ /* restore pc */ ldmfd sp!, {lr} movs pc,lr .global schd schd: ldmfd r0!, {lr} msr cpsr, lr ldmfd r0!, {r0-r14} bx lr

设备MMIO不要打开cache

DDI0301H_arm1176jzfs_r0p7_trm/P332

 
 

((uint32_t*)PDE)[PDX(0)] = 0|PDX_AP(AP_U_NA)|PDX_TYPE(TYPE_SECTION);

在设备映射的内存区域,页表中TEX/Cache/Buffer这三个标志位一定是0,Strongly Ordered模式。否则会面临严重的一致性的问题,内核代码读的数据是cache中的脏数据。

熔断Meltdown,就是一个利用d-cache漏洞的旁路攻击,尝试解释一下

 
 

内存地址a仅可在内核态访问。在用户态构造代码 invalid_cache; raise_exception; array[*a];

由于现代处理器是超标量乱序多发射处理器,处理器在执行raise_exception时,也在同时访问array[*a],只是array[*a]没有最终进入rob提交。但是位于地址*a处的cache仍被更新了。因此只要遍历一遍array的数据,看哪一个地址访问速度最快就可以知道a地址存的是什么了

所以修复的办法是不要让内核数据在内存中有固定的位置,在中断处理切换特权模式时要同时修改内核空间的映射

[RPi bring up] 从树莓派linux源码中窥探bcm2835和arm1176jzfs的中断管理​


[RPi bring up] 从树莓派linux源码中窥探bcm2835和arm1176jzfs的中断管理 | Changchun Master Li 本人擅长朴素贝叶斯科学算命, 大家没事可以多看我的书吃我的药听我的讲座icon-default.png?t=N7T8https://blog.74ls74.org/2023/10/29/20231029_rpi_linux_bcm2835_arm11jzfs_interrupt/

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值