当中断发生时,程序执行流程将暂停并且运行中断处理程序。在中断处理程序运行结束后,恢复之前的执行流程。同步中断,通过执行指令生成,也叫异常。异步中断,由外部事件生成。
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 |
8 | GPU IRQ 31:0 |
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/interruptgithub.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 本人擅长朴素贝叶斯科学算命, 大家没事可以多看我的书吃我的药听我的讲座https://blog.74ls74.org/2023/10/29/20231029_rpi_linux_bcm2835_arm11jzfs_interrupt/