层层递进, 中断架构本是如此, linux只是做了软件该做的事情
最简单的arm架构是如此
cpu
gic
device
层层调用
vector_irq
vic_handle_irq
usb_hcd_irq
[ < c03920c4> ] ( usb_hcd_irq) from [ < c0147678> ] ( __handle_irq_event_percpu+ 0x40 / 0x128 )
[ < c0147678> ] ( __handle_irq_event_percpu) from [ < c0147788> ] ( handle_irq_event_percpu+ 0x28 / 0x7c )
[ < c0147788> ] ( handle_irq_event_percpu) from [ < c0147804> ] ( handle_irq_event+ 0x28 / 0x3c )
[ < c0147804> ] ( handle_irq_event) from [ < c014b2bc> ] ( handle_level_irq+ 0x9c / 0x114 )
[ < c014b2bc> ] ( handle_level_irq) from [ < c0146f34> ] ( generic_handle_irq+ 0x30 / 0x44 )
[ < c0146f34> ] ( generic_handle_irq) from [ < c0146f90> ] ( __handle_domain_irq+ 0x48 / 0xa8 )
[ < c0146f90> ] ( __handle_domain_irq) from [ < c030cedc> ] ( vic_handle_irq+ 0x58 / 0x9c )
[ < c030cedc> ] ( vic_handle_irq) from [ < c0100b4c> ] ( __irq_svc+ 0x6c / 0x90 )
层层调用中最复杂的是 vic_handle_irq 调用到 usb_hcd_irq
vic_handle_irq
for_each_vic handle_one_vic ( & vic_devices[ i] , regs) ;
for_each_which_int_status_is_1_on_vic handle_domain_irq ( vic-> domain, irq, regs)
__handle_domain_irq ( domain, hwirq, true, regs) ;
irq_enter ( ) ;
irq = irq_find_mapping ( domain, hwirq) ;
generic_handle_irq ( irq) ;
struct irq_desc * desc = irq_to_desc ( irq) ;
generic_handle_irq_desc ( desc) ;
desc-> handle_irq ( desc) ;
irq_exit ( ) ;
层层注册
在arm 的硬件上, 不管是arm32, arm64, riscv, 入口都只有简单的1 个或两个
但是设备的中断却很多, 事实上, 他们都得到了正确的调用.
这归功与正确的注册
vector_irq 这个是写到某个地址 , 这个地址 是个固定值 或者 写到了 一个寄存器 , 等同于 向 硬件注册了这个函数
set_handle_irq ( vic_handle_irq) ;
这个比较简单, 不描述了参考 https:
1. 根据硬件中断号获取软件中断号
参考 https:
int linux_irq = of_irq_get ( pdev-> dev. of_node, 0 ) ;
int linux_irq = irq_of_parse_and_map ( pdev-> dev. of_node, 0 ) ;
struct resource res; of_irq_to_resource ( pdev-> dev. of_node, 0 , & r) ;
int linux_irq = res. start;
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
int linux_irq = platform_get_irq ( pdev, index) ;
int linux_irq = platform_get_irq_by_name ( pdev, name) ;
int linux_irq = platform_get_resource ( pdev, IORESOURCE_IRQ, index) ;
int linux_irq = platform_get_resource_byname ( pdev, IORESOURCE_IRQ, name) ;
struct resource * res = platform_get_resource ( pdev, IORESOURCE_IRQ, 0 ) ;
int linux_irq = res-> start;
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
以上这些api 都是
1. 获取 硬件中断号
2. 找到 设备树指定的 设备节点的 interrupt- parents
3. 去 对应的 domain 中 根据 硬件中断号 去获取 软件中断号
4. 返回 软件中断号
2. 利用软件中断号注册中断处理函数
usb_add_hcd ( hcd, irqnum -> request_irq ( irqnum, & usb_hcd_irq
这里面 irqnum 的获取 涉及到 了 domain
多个中断控制器的金字塔结构与中断批发商
如果一个系统中存在多个中断控制器, 那么所有的中断控制器呈金字塔架构
下级中断控制器向 上级中断控制器批发硬件中断号.
并向下面的设备展示为 更多的中断号.
最顶层的中断控制器是 core 的中断控制
例如 arm32的 irq中断控制器
因为只有一根中断线, 即irq
所以只需要一个使能开关就可以了.
不需要 mask 和 pending 相关的逻辑
例如 riscv 的 plic 的 广义异常
参考 https:
rk3399 PCIe legacy 中断控制器
多个中断控制器的金字塔结构与中断批发商
例如 pcie 的legacy 中断
向 gic 中断控制器 批发了1 个
作为gic中断控制器的下级中断控制器, 告诉pcie上连接的所有设备有4 个中断
批发了一个
irq = platform_get_irq_byname ( pdev, "legacy" ) ;
irq_set_chained_handler_and_data ( irq, rockchip_pcie_legacy_int_handler, rockchip) ;
告诉所有的设备有4 个
pcie 的中断控制器呈现给下面的设备的硬件中断号 number 为 1 2 3 4
在系统中注册的软件中断号为 N , N+ 1 , N+ 2 , N+ 3
N 对应的irq_desc 的链表上 连接了 所有 支持 legacy INTA的 中断处理函数
N+ 1 对应的irq_desc 的链表上 连接了 所有 支持 legacy INTB的 中断处理函数
N+ 2 对应的irq_desc 的链表上 连接了 所有 支持 legacy INTC的 中断处理函数
N+ 3 对应的irq_desc 的链表上 连接了 所有 支持 legacy INTD的 中断处理函数
以 rk3399 的pcie 为例, 中断函数调用流程为
rockchip_pcie_legacy_int_handler
u32 reg = rockchip_pcie_read ( rockchip, PCIE_CLIENT_INT_STATUS) ;
reg = ( reg & PCIE_CLIENT_INTR_MASK) >> PCIE_CLIENT_INTR_SHIFT;
while ( reg) {
hwirq = ffs ( reg) - 1 ;
reg &= ~ BIT ( hwirq) ;
virq = irq_find_mapping ( rockchip-> irq_domain, hwirq) ;
generic_handle_irq ( virq) ;
}
设备树描述为下
pcie0: pcie@f8000000 {
interrupts = < GIC_SPI 49 IRQ_TYPE_LEVEL_HIGH 0 > ,
< GIC_SPI 50 IRQ_TYPE_LEVEL_HIGH 0 > ,
< GIC_SPI 51 IRQ_TYPE_LEVEL_HIGH 0 > ;
interrupt- names = "sys" , "legacy" , "client" ;
interrupt- map- mask = < 0 0 0 7 > ;
interrupt- map = < 0 0 0 1 & pcie0_intc 0 > ,
< 0 0 0 2 & pcie0_intc 1 > ,
< 0 0 0 3 & pcie0_intc 2 > ,
< 0 0 0 4 & pcie0_intc 3 > ;
pcie0_intc: interrupt- controller {
interrupt- controller;
# address - cells = < 0 > ;
# interrupt - cells = < 1 > ;
} ;
} ;
当枚举完设备时, 会读设备配置的 Interrupt Pin ( RO) , 读出来是 1 2 3 4 , 然后构造 pci_dev , 在其成员 irq 中填充 一个 对应的 软件中断号
如果pci设备 A 的 Interrupt Pin 是 1 , B的也是 1 , 那么 两个 pci_dev 的irq 成员的值是一样的.
-- -- -- -- -- -- -- -- -- -- -- -- -- -- - 以下是不建议的写法
interrupt- map = < 0 0 0 1 & pcie0_intc 0 > ,
< 0 0 0 2 & pcie0_intc 0 > ,
< 0 0 0 3 & pcie0_intc 0 > ,
< 0 0 0 4 & pcie0_intc 0 > ;
注意 pcie0_intc 后面的 0
相当于 只有一个软件中断, 只要一个pci设备的中断发生了, 那么下面的软件流程会调用所有pci设备的中断处理函数
而如果是 0 1 2 3 的话,
如果一个 pci 的中断发生了, 他的Interrupt Pin 为 A,
那么下面的软件流程会调用 A 对应的 软件中断号 链表上的中断处理函数
而不会调用 B/ C/ D 对应的 软件中断号 链表上的中断处理函数
pcie 实例说明了一个问题
一个linux irq 上可以注册多个中断处理函数
所以中断处理函数 第一件事 就是要判定自己的中断 是否触发! ! !
为什么pcie 可以作为中断控制器
1. 他需要外接设备 且 设备有中断需求
2. 一级中断控制器gic 没有 pin脚
3. pcie 有中断相关的寄存器
3.1 使能开关
3.2 mask
3.3 pending寄存器
rk3399 PCIe msi
its: interrupt- controller@fee20000 {
compatible = "arm,gic-v3-its" ;
msi- controller;
reg = < 0x0 0xfee20000 0x0 0x20000 > ;
} ;
msi- map = < 0x0 & its 0x0 0x1000 > ;
hifive unmatched PCIe MSI 中断控制器
unmatched 用的是 pcie ip 是 dwc 的
这个ip 使用的 msi 的实现 在 pcie 中. 设备通过写 pcie 中的msi寄存器来发出中断, 而不是写plic- sw.
然后所有由于 pcie msi 产生的中断 又给了 plic < 56 >
plic0: interrupt- controller@c000000 {
# interrupt - cells = < 1 > ;
# address - cells = < 0 > ;
compatible = "sifive,fu540-c000-plic" , "sifive,plic-1.0.0" ;
reg = < 0x0 0xc000000 0x0 0x4000000 > ;
riscv, ndev = < 69 > ;
interrupt- controller;
interrupts- extended =
< & cpu0_intc 0xffffffff > ,
< & cpu1_intc 0xffffffff > , < & cpu1_intc 9 > ,
< & cpu2_intc 0xffffffff > , < & cpu2_intc 9 > ,
< & cpu3_intc 0xffffffff > , < & cpu3_intc 9 > ,
< & cpu4_intc 0xffffffff > , < & cpu4_intc 9 > ;
} ;
pcie@e00000000 {
compatible = "sifive,fu740-pcie" ;
interrupts = < 56 > , < 57 > , < 58 > , < 59 > , < 60 > , < 61 > , < 62 > , < 63 > , < 64 > ;
interrupt- names = "msi" , "inta" , "intb" , "intc" , "intd" ;
interrupt- parent = < & plic0> ;
interrupt- map- mask = < 0x0 0x0 0x0 0x7 > ;
interrupt- map = < 0x0 0x0 0x0 0x1 & plic0 57 > ,
< 0x0 0x0 0x0 0x2 & plic0 58 > ,
< 0x0 0x0 0x0 0x3 & plic0 59 > ,
< 0x0 0x0 0x0 0x4 & plic0 60 > ;
} ;
drivers/ pci/ controller/ dwc/ pcie- fu740. c
pp-> msi_irq[ 0 ] = platform_get_irq_byname_optional ( pdev, "msi" ) ;
dw_pcie_allocate_domains
pp-> irq_domain = irq_domain_create_linear
pp-> msi_domain = pci_msi_create_irq_domain
irq_set_chained_handler_and_data ( pp-> msi_irq[ 0 ] , dw_chained_msi_isr, pp) ;
dw_chained_msi_isr
dw_handle_msi_irq
u32 status = dw_pcie_readl_dbi ( pci, PCIE_MSI_INTR0_STATUS + ( i * MSI_REG_CTRL_BLOCK_SIZE) ) ;
while ( ( pos = find_next_bit ( & status , MAX_MSI_IRQS_PER_CTRL, pos) ) != MAX_MSI_IRQS_PER_CTRL) {
generic_handle_domain_irq ( pp-> irq_domain, ( i * MAX_MSI_IRQS_PER_CTRL) + pos) ;
pos++ ;
}
microchip PCIe MSI 中断控制器
msi 和 intx 共用了一个中断 119 , 接入了plic
MSI_INT 包括 MSI_INT_INTX 和 MSI_INT_MSI
msi 和 intx 都是 microchip 实现的
和 dwc 唯一不同的是
mc 的 msi 和 intx 合并成了1 个中断接入了 上级 中断控制器
dwc 的 msi 以1 个中断线接入了 上级 中断控制器 , dwc 的 intx 以4 个中断线接入了 上级 中断控制器 ,
pcie: pcie@3000000000 {
compatible = "microchip,pcie-host-1.0" ;
# address - cells = < 0x3 > ;
# interrupt - cells = < 0x1 > ;
# size - cells = < 0x2 > ;
device_type = "pci" ;
reg = < 0x30 0x0 0x0 0x8000000 > , < 0x0 0x43000000 0x0 0x10000 > ;
reg- names = "cfg" , "apb" ;
bus- range = < 0x0 0x7f > ;
interrupt- parent = < & plic> ;
interrupts = < 119 > ;
interrupt- map = < 0 0 0 1 & pcie_intc 0 > ,
< 0 0 0 2 & pcie_intc 1 > ,
< 0 0 0 3 & pcie_intc 2 > ,
< 0 0 0 4 & pcie_intc 3 > ;
interrupt- map- mask = < 0 0 0 7 > ;
clocks = < & ccc_nw CLK_CCC_PLL0_OUT1> , < & ccc_nw CLK_CCC_PLL0_OUT3> ;
clock- names = "fic1" , "fic3" ;
ranges = < 0x3000000 0x0 0x8000000 0x30 0x8000000 0x0 0x80000000 > ;
dma- ranges = < 0x02000000 0x0 0x00000000 0x0 0x00000000 0x1 0x00000000 > ;
msi- parent = < & pcie> ;
msi- controller;
status = "disabled" ;
pcie_intc: interrupt- controller {
# address - cells = < 0 > ;
# interrupt - cells = < 1 > ;
interrupt- controller;
} ;
} ;
mc_pcie_init_irq_domains
irq = platform_get_irq ( pdev, 0 ) ;
irq_set_chained_handler_and_data ( irq, mc_handle_event, port) ;
intx_irq = irq_create_mapping ( port-> event_domain, EVENT_LOCAL_PM_MSI_INT_INTX) ;
irq_set_chained_handler_and_data ( intx_irq, mc_handle_intx, port) ;
msi_irq = irq_create_mapping ( port-> event_domain, EVENT_LOCAL_PM_MSI_INT_MSI) ;
irq_set_chained_handler_and_data ( msi_irq, mc_handle_msi, port) ;
for ( i = 0 ; i < NUM_EVENTS; i++ ) {
event_irq = irq_create_mapping ( port-> event_domain, i) ;
devm_request_irq ( dev, event_irq, mc_event_handler, 0 , event_cause[ i] . sym, port) ;
中断发生时 , 进入 mc_handle_event
会查找发生了哪些event
哪些 event 发生, 就调用 对应的 函数 mc_event_handler
如果有 EVENT_LOCAL_PM_MSI_INT_INTX 发生, 会额外调用 mc_handle_intx
如果有 EVENT_LOCAL_PM_MSI_INT_MSI 发生, 会额外调用 mc_handle_msi
TI PCIe 中断控制器
gic 的 202 中断
做出了一个域, 这个域有4 个硬件中断号, 1 2 3 4 , 接到了 同一个软件中断号上.
932 #interrupt- cells = < 1 > ;
933 interrupt- map- mask = < 0 0 0 7 > ;
934 interrupt- map = < 0 0 0 1 & pcie0_intc 0 > ,
935 < 0 0 0 2 & pcie0_intc 0 > ,
936 < 0 0 0 3 & pcie0_intc 0 > ,
937 < 0 0 0 4 & pcie0_intc 0 > ;
938
939 pcie0_intc: interrupt- controller {
940 interrupt- controller;
941 #interrupt- cells = < 1 > ;
942 interrupt- parent = < & gic500> ;
943 interrupts = < 0 202 1 > ;
944 } ;
msi 实现用的是 gic 用的是
207 gic_its: msi- controller@1820000 {
208 compatible = "arm,gic-v3-its" ;
209 reg = < 0x00 0x01820000 0x00 0x10000 > ;
210 socionext, synquacer- pre- its = < 0x1000000 0x400000 > ;
211 msi- controller;
212 #msi- cells = < 1 > ;
213 } ;
msi- map = < 0x0 & gic_its 0x0 0x10000 > ;
irq domain
irq domain的历史
The current design of the Linux kernel uses a single large number
space where each separate IRQ source is assigned a different number.
This is simple when there is only one interrupt controller, but in
systems with multiple interrupt controllers the kernel must ensure
that each one gets assigned non- overlapping allocations of Linux
IRQ numbers.
The number of interrupt controllers registered as unique irqchips
show a rising tendency: for example subdrivers of different kinds
such as GPIO controllers avoid reimplementing identical callback
mechanisms as the IRQ core system by modelling their interrupt
handlers as irqchips, i. e. in effect cascading interrupt controllers.
Here the interrupt number loose all kind of correspondence to
hardware interrupt numbers: whereas in the past, IRQ numbers could
be chosen so they matched the hardware IRQ line into the root
interrupt controller ( i. e. the component actually fireing the
interrupt line to the CPU) nowadays this number is just a number.
For this reason we need a mechanism to separate controller- local
interrupt numbers, called hardware irq's, from Linux IRQ numbers.
Linux内核的当前设计使用单个大数字空间,其中每个单独的IRQ源都被分配了不同的数字。
当只有一个中断控制器时,这很简单,但在具有多个中断控制器的系统中,内核必须确保为每个中断控制器分配Linux IRQ编号的非重叠分配。
注册为唯一IRQ芯片的中断控制器的数量呈上升趋势:例如,不同类型的子驱动器,如GPIO控制器,通过将其中断处理程序建模为IRQ芯片,即实际上的级联中断控制器,避免重新实现与IRQ核心系统相同的回调机制。
在这里,中断号与硬件中断号没有任何对应关系:而在过去,可以选择IRQ号,使其与硬件IRQ线匹配到根中断控制器(即实际向CPU触发中断线的组件),现在这个数字只是一个数字。
出于这个原因,我们需要一种将控制器本地中断编号(称为硬件irq)与Linux irq编号分离的机制。
以上说的 层层中断控制器 中的 每一个中断控制器都是一个irq domain
irq domain 实例
# cat / proc/ interrupts
CPU0
58 : 0 VIC 26 Edge s3c2410- wdt
59 : 2367 VIC 27 Edge samsung_time_irq
69 : 326 VIC 5 Edge s3c6400- uart
88 : 1260 VIC 24 Edge mmc0
Err: 0
Int. No. Sources Description Group 63(32+31) INT_ADC ADC-EOC-interrupt VIC1 56(32+24) INT_HSMMC0 HSMMC0-interrupt VIC1 37(32+5) INT_UART0 UART0-interrupt VIC1 32(32+0) INT_EINT2 External interrupt 12-19 VIC1 31 INT_LCD[2] LCD interrupt. System I/F done VIC0 27 INT_TIMER3 Timer 3 interrupt VIC0 26 INT_WDT Watchdog timer interrupt VIC0 0 INT_EINT0 External interrupt 0-3 VIC0
irq domain 如何注册
搜索 irq_domain_add/ irq_domain_create
看 整个系统中有多少个irq domain , 就看代码中调用了几次
简单的系统 ok6410, vic 是一个 irq domain , gpio 是一个 irq domain
还有些系统带pcie , pcie控制器 驱动代码 为了 实现 legacy 中断, 也调用了 irq_domain_add
在设备树中也可以看到, 搜索interrupt- controller, 和 代码是一对一的
s3c64xx. dtsi 中 vic0 和 vic1 是 interrupt- controller
s3c64xx- pinctrl. dtsi 中 gpa gpb . . . gpq 也都是 interrupt- controller