linux 中断的层层递进 与 IRQ_DOMAIN

层层递进,中断架构本是如此,linux只是做了软件该做的事情

最简单的arm架构是如此
cpu
	gic
		device

层层调用

//以 arm32架构 + vic 控制器 + usb控制器 为例 ,说明 
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) // 这里的irq 为 一个 domain 内的 硬件中断号
			__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();

// 从整个过程来看, 一个 domain 中的 一个中断的 流程就是 
// 1. 读中断状态寄存器,判断是哪个中断源
// 2. 根据 中断源的硬件中断号 得到 软件中断号
// 3. 根据 软件中断号 得到 对应的 irq_desc 
// 4. 调用 irq_desc->handle_irq // 即 驱动中 调用 request_irq 注册 的handler

// 即 中断状态寄存器 -> 中断源的硬件中断号 -> 软件中断号 -> irq_desc  -> (irq_desc->handle_irq)
// 这里面的domain 维护了 "中断源的硬件中断号 -> 软件中断号"
// 软件中断号 -> irq_desc 在整个系统中是一一对应的

层层注册

在arm 的硬件上,不管是arm32,arm64,riscv, 入口都只有简单的1个或两个
但是设备的中断却很多,事实上,他们都得到了正确的调用.
这归功与正确的注册
  • 一级处理函数的注册
vector_irq  这个是写到某个地址 , 这个地址 是个固定值 或者 写到了  一个寄存器 , 等同于 向 硬件注册了这个函数

  • 二级处理函数的注册
set_handle_irq(vic_handle_irq); // kernel/irq/handle.c提供的
	这个比较简单,不描述了参考 https://blog.csdn.net/u011011827/article/details/116232892
  • 三级处理函数的注册
1. 根据硬件中断号获取软件中断号
	参考  https://blog.csdn.net/u011011827/article/details/127510337 
	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 // include/linux/interrupt.h 提供
	这里面 irqnum 的获取 涉及到 了 domain

多个中断控制器的金字塔结构与中断批发商

如果一个系统中存在多个中断控制器,那么所有的中断控制器呈金字塔架构
下级中断控制器向 上级中断控制器批发硬件中断号.
并向下面的设备展示为 更多的中断号.

最顶层的中断控制器是 core 的中断控制
	例如 arm32的 irq中断控制器
		因为只有一根中断线,即irq
			所以只需要一个使能开关就可以了. // 在用户侧呈现为 cpsr 的一个bit, 可以用指令操作.
			不需要 mask 和 pending 相关的逻辑
	例如 riscv 的 plic 的 广义异常
		参考 https://blog.csdn.net/u011011827/article/details/121490606
	

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; // mask 某个bit为 0 时关闭 对应的中断
			while (reg) {
				hwirq = ffs(reg) - 1; // 获取中断原因:是A(1),还是B(2),还是C(3),还是D(4)
				reg &= ~BIT(hwirq); 
				virq = irq_find_mapping(rockchip->irq_domain, hwirq); // 找到该irq domain 中 hwirq 对应的 软件中断号
				generic_handle_irq(virq); // 调用该 软件中断号对应的 链表上的所有的 中断处理函数
			}
	设备树描述为下
    pcie0: pcie@f8000000 {
		
		// 其中 legacy 为 向gic 批发的中断
        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";

		// 下面的是 告诉pcie 设备 pcie 是个中断控制器,有4个硬件中断,号码分别为 1 2 3 4
		// 当然,这是设备树的内容,代码解析过后就是上面一行的内容
        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>;
// 具体是写 0xfee20000  + 0x10000 (offset) + 0x40 (offset)  为 0 1 2

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);// 每32个共用一个
	


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

//main event
irq = platform_get_irq(pdev, 0);
irq_set_chained_handler_and_data(irq, mc_handle_event, port);

// event EVENT_LOCAL_PM_MSI_INT_INTX
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);

// event EVENT_LOCAL_PM_MSI_INT_MSI
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 all event
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 // 对应 plic的119
	会查找发生了哪些event
	哪些 event 发生, 就调用 对应的 函数 mc_event_handler // 每个event 都对应 mc_event_handler
	如果有 EVENT_LOCAL_PM_MSI_INT_INTX 发生,  会额外调用 mc_handle_intx 
	如果有 EVENT_LOCAL_PM_MSI_INT_MSI  发生,  会额外调用 mc_handle_msi

TI PCIe 中断控制器

  • legacy 部分
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 部分
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 实例

// 这是 ok6410a  linux-5.11 上的 中断
# cat /proc/interrupts 
           CPU0       
 58:          0       VIC  26 Edge      s3c2410-wdt			// VIC0
 59:       2367       VIC  27 Edge      samsung_time_irq 	// VIC0
 69:        326       VIC   5 Edge      s3c6400-uart 		// VIC1
 88:       1260       VIC  24 Edge      mmc0 				// VIC1
Err:          0

Int. No.SourcesDescriptionGroup
63(32+31)INT_ADCADC-EOC-interruptVIC1
56(32+24)INT_HSMMC0HSMMC0-interruptVIC1
37(32+5)INT_UART0UART0-interruptVIC1
32(32+0)INT_EINT2External interrupt 12-19VIC1
31INT_LCD[2]LCD interrupt. System I/F doneVIC0
27INT_TIMER3Timer 3 interruptVIC0
26INT_WDTWatchdog timer interruptVIC0
0INT_EINT0External interrupt 0-3VIC0

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值