阅读本文您不需要掌握的知识有
- 高深的操作系统理论
- 高深的计算机体系结构理论
阅读本文您需要具备
- 全日制小学生学历及其同等学历 ★★★★★
- 熟读arm1176jzfs datasheet ★★☆☆☆
- ARM汇编语言 ★☆☆☆☆
- C语言 ★★★★☆
当中断发生时,程序执行流程将暂停并且运行中断处理程序。在中断处理程序运行结束后,恢复之前的执行流程。同步中断,通过执行指令生成,也叫异常。异步中断,由外部事件生成
bcm2835 datasheet中有关中断控制的描述过于简略,但没关系,我们可以从linux源码中扒有用的东西
用最新的buildroot构建linux系统
获取最新版本buildroot
1 | git clone https://github.com/buildroot/buildroot.git |
打开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
1 | Target packages -> Hardware handling -> Firmware -> Path to a file stored as boot/config.txt |
默认下config.txt对应config_default.txt,在board/raspberrypi/config_default.txt加上下面三行
1 | # Enable bcm2835 jtag, set GPIO4 instead GPIO 26 |
这需要我们重新编译一下rpi-firmware
1 | make rpi-firmware-rebuild && make |
调试信息
在编译的时候保留调试信息,有几个地方也需要定制
make menuconfig
1 | Build options -> build packages with debugging symbols |
选level3,包含宏定义,打开
1 | Build options -> build packages with runtime debugging info |
关闭
1 | Build options -> strip target binaries |
gcc优化选debugging
1 | Build options -> gcc optimization level |
linux kernel config也要改,make linux-menuconfig
1 | Kernel hacking -> Compile-time checks and compiler options -> Compile the kernel with debug info |
最后,在start_kernel里加一个死循环,可以在启动时停住,用gdb命令(gdb) set
1 | __asm ( |
重新编译内核
1 | 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 https://github.com/raspberrypi/linux.git 当前版本为rpi-5.10.y
bcm2835的驱动在irq-bcm2835.c,说白了就是用IRQCHIP_DECLARE宏定义了一个struct结构体__of_table_bcm2835_armctrl_ic
1 | IRQCHIP_DECLARE(bcm2835_armctrl_ic, "brcm,bcm2835-armctrl-ic", bcm2835_armctrl_of_init); |
可以打出来看一下
1 | (gdb) p __of_table_bcm2835_armctrl_ic |
设备树中bcm2835-armctrl-ic,通过bcm2835_armctrl_of_init进行初始化。
armctrl_of_init
跟其他中断控制器驱动的套路一样,驱动drivers/irqchip/irq-bcm2835.c,of_init初始化中断控制器
1 | armctrl_ic |
初始化阶段
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 |
1 | static u32 get_next_armctrl_hwirq(void) |
原理很简单,用不同的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里敲一个字符就会触发中断
1 | (gdb) b pl011_int |
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上了
一张图胜过千言万语
emperorOS中断框架设计
https://github.com/996refuse/emperorOS/tree/interrupt
中断过程中硬件和软件发生了什么?
- 中断控制器 位于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为
1 | unsigned char inifiniteloop[] = {0xfe, 0xff, 0xff, 0xea}; |
实现为一个单内核栈多用户栈
1 | user -> irq mode(irq stack 0xC0006000) -> supervisor(main/proc_schd stack) |
不可重入,内核态应尽快返回,以避免堵塞其他进程调度(sleep操作需要由用户态函数实现)
所有进程时间平均分配,不考虑优先级,优先级反转,优先级继承等问题
timer中断的实现
armv6 使用p15协处理器指令设置中断向量
1 | "mcr p15, 0, %[v], c12, c0, 0\n\t" |
打开中断,这里模仿intc.enable
1 | arm_intr_reg->gpu_enable[0] |= 1 << bit |
最后由systimer_set设置compare定时器
中断上下文保存和恢复
上下文指r0-r15,cpsr寄存器的当前状态
trap_return保存当前内核态上下文到r1,恢复r0的用户态上下文
schd进入内核态上下文
1 | .globl trap_return |
设备MMIO不要打开cache
DDI0301H_arm1176jzfs_r0p7_trm/P332
1 | ((uint32_t*)PDE)[PDX(0)] = 0|PDX_AP(AP_U_NA)|PDX_TYPE(TYPE_SECTION); |
在设备映射的内存区域,页表中TEX/Cache/Buffer这三个标志位一定是0,Strongly Ordered模式。否则会面临严重的一致性的问题,内核代码读的数据是cache中的脏数据。
爆发于2017年的漏洞,熔断Meltdown,就是一个利用d-cache漏洞的旁路攻击,尝试解释一下
内存地址a仅可在内核态访问。在用户态构造代码
1 | invalid_cache; |
由于现代处理器是超标量乱序多发射处理器,处理器在执行raise_exception时,也在同时访问array[*a],只是array[a]没有最终进入rob提交。但是位于地址a处的cache仍被更新了。因此只要遍历一遍array的数据,看哪一个地址访问速度快,进而得知是从cache中读取的数据,就可以知道a地址存的是什么了
所以修复的办法是不要让内核数据在内存中有固定的位置,在中断处理切换特权模式时要同时修改内核空间的映射
若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏
扫描二维码,分享此文章