中断子系统

中断,从硬件到软件

  1. 中断是异常向量表中的一种,中断产生,中断向量会被调用,具体Soc软件设置的中断入口handle_arch_irq会被调用,一个Soc仅有一个该中断入口。
  2. 具体Soc有多个硬件中断,具体哪个中断触发了,是通过中断控制器的寄存器bit判断的,确定硬件中断号。
  3. 硬件中断号与虚拟软件中断号之间的映射有一个偏移值,通过硬件中断号可以找到软件中断号,进而调用软件中断处理函数,有多少个硬件中断号就有多少个软件中断处理函数。

Soc中断向量入口函数

不同的Soc调用该函数设置handle_arch_irq
linux-5.1.0\kernel\irq\handle.c
int __init set_handle_irq(void (*handle_irq)(struct pt_regs *));

linux-5.1.0\drivers\irqchip\irq-sun4i.c
对于V3s,set_handle_irq(sun4i_handle_irq);
所以V3s的处理中断的C语言入口函数就是:
static void __exception_irq_entry sun4i_handle_irq(struct pt_regs *regs);

中断触发时,获取硬件中断号

linux-5.1.0\drivers\irqchip\irq-sun4i.c

static void __exception_irq_entry sun4i_handle_irq(struct pt_regs *regs)
{
	u32 hwirq;
	/*
	 * hwirq == 0 can mean one of 3 things:
	 * 1) no more irqs pending
	 * 2) irq 0 pending
	 * 3) spurious irq
	 * So if we immediately get a reading of 0, check the irq-pending reg
	 * to differentiate between 2 and 3. We only do this once to avoid
	 * the extra check in the common case of 1 hapening after having
	 * read the vector-reg once.
	 */
	hwirq = readl(irq_ic_data->irq_base + SUN4I_IRQ_VECTOR_REG) >> 2;
	if (hwirq == 0 &&
		  !(readl(irq_ic_data->irq_base + SUN4I_IRQ_PENDING_REG(0)) &
			  BIT(0)))
		return;

	do {
		handle_domain_irq(irq_ic_data->irq_domain, hwirq, regs);
		hwirq = readl(irq_ic_data->irq_base +
				SUN4I_IRQ_VECTOR_REG) >> 2;
	} while (hwirq != 0);
}

通过硬件中断号获取软件中断号

linux-5.1.0\kernel\irq\irqdesc.c

int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
			bool lookup, struct pt_regs *regs)
{
	........
#ifdef CONFIG_IRQ_DOMAIN
	if (lookup)
		irq = irq_find_mapping(domain, hwirq);
#endif
	......
}

根据软件中断号执行中断处理函数

int generic_handle_irq(unsigned int irq)
{
	struct irq_desc *desc = irq_to_desc(irq);

	if (!desc)
		return -EINVAL;
	generic_handle_irq_desc(desc);
	return 0;
}

中断号的演变与irq_domain(韦老师)

以前中断号(virq)跟硬件密切相关,
现在的趋势是中断号跟硬件无关, 仅仅是一个标号而已

驱动使用中断流程(无设备树时)

以前,在没有设备树的时候,对于每一个硬件中断(hwirq)都预先确定它的中断号(virq),这些中断号一般都写在一个头文件里,例如三星的:arch\arm\mach-s3c24xx\include\mach\irqs.h
使用时,
a. 执行 request_irq(virq, my_handler) :
内核根据头文件中映射好的中断号(virq)可以知道对应的硬件中断, 然后去设置、使能硬件中断等
b. 发生硬件中断时,
内核读取硬件信息, 确定hwirq, 反算出virq,
然后调用 irq_desc[virq].handle_irq, 最终会用到my_handler

根据hwirq计算出virq

硬件上有多个intc(中断控制器),
对于同一个hwirq数值, 会对应不同的virq
所以在讲hwirq时,应该强调"是哪一个intc的hwirq",
在描述hwirq转换为virq时, 引入一个概念: irq_domain, 域, 在这个域里hwirq转换为某一个virq

irq_domain域的引入

当中断控制器越来越多、当中断越来越多,上述方法(virq和hwirq固定绑定)有缺陷:
a. 增加工作量, 你需要给每一个中断确定它的中断号, 写出对应的宏, 可能有成百上千个
b. 你要确保每一个硬件中断对应的中断号互不重复

irq_domain域的实现

a. hwirq跟virq之间不再绑定
b. 要使用某个hwirq时,
先在irq_desc数组中找到一个空闲项, 它的位置就是virq
再在irq_desc[virq]中放置处理函数

也就说,irq_desc数组是硬件无关的,输出的索引值virq由硬件相关的irq_domain保存绑定。

irq_domain域的使用

a.以前是request_irq发起,
现在是先在设备树文件中声明想使用哪一个中断(哪一个中断控制器下的哪一个中断)

b. 内核解析设备树时,
会根据"中断控制器"确定irq_domain,
根据"哪一个中断"确定hwirq,
然后在irq_desc数组中找出一个空闲项, 它的位置就是virq
并且把virq和hwirq的关系保存在irq_domain中: irq_domain.linear_revmap[hwirq] = virq;

也就说,一个中断控制器对应一个irq_domain,一个irq_domain有多个硬件中断号hwirq。
现在的问题是:根据设备树动态确定的irq_domain和hwirq,如何确保发送中断时,能更加中断硬件状态找到对应的irq_domain和hwirq?

c. 驱动程序 request_irq(virq, my_handler)

d. 发生硬件中断时,
内核读取硬件信息, 确定hwirq, 确定 virq = irq_domain.linear_revmap[hwirq];
然后调用 irq_desc[virq].handle_irq, 最终会用到my_handler

绑定与回找软中断号virq的过程

使用子中断EINT4的过程为例:

  1. 使用父中断(intc,4)设置irq_desc: a.找空闲项,virq=4,保存起来:intc’s irq_domain.linear_revmap[4] = 4; b.设置irq_desc[4].handle_irq = s3c_irq_demux;
  2. 为子中断eint4(subintc,4)设置irq_desc: a.找空闲项,virq=5,保存起来:subintc’s irq_domain.linear_revmap[4] = 5;
  3. 驱动程序request_irq(5.my_handler):会把my_handler保存在irq_desc[5].action链表中;
  4. 发生了中断,内核读取intc,得到hwirq=4,virq = intc’s irq_domain.liner_revmap[4] = 4调用irq_desc[4].handle_irq,即s3c_irq_demux;
  5. s3c_irq_demux: 读取subintc,得到hwirq=4,virq = subintc’s irq_domain.liner_remap[4] = 5,调用irq_desc[5].handle_irq,它会调用action链表中保存的my_handler。
irq_domain、hwirq与具体硬件的关联

由具体Soc的中断控制器驱动实现。
三星中断控制器驱动为例:
设备树:linux-5.1.0\Documentation\devicetree\bindings\interrupt-controller\samsung,s3c24xx-irq.txt
驱动:linux-5.1.0\drivers\irqchip\irq-s3c24xx.c

在设备树中,每个中断 controller都有唯一的节点描述,为每个中断controller节点创建对应的irq_domain,中断触发时,从硬件信息可以知道时哪个中断控制器产生的中断,进而找到之前为这个中断控制器创建的irq_domain。

内核对设备树中断信息的处理过程

从硬件结构上看, 处理过程分上下两个层面: 中断控制器, 使用中断的设备
从软件结构上看, 处理过程分左右两个部分: 在设备树中描述信息, 在驱动中处理设备树
(1) 中断控制器
这又分为root irq controller, gpf/gpg irq controller
a. root irq controller
a.1 在设备树中的描述
a.2 在内核中的驱动

b. 对于S3C2440, 还有: gpf/gpg irq controller
b.1 在设备树中的描述(在pinctrl节点里)
b.2 在内核中的驱动 (在pinctrl驱动中)

(2) 设备的中断
a.1 在设备节点中描述(表明使用"哪一个中断控制器里的哪一个中断, 及中断触发方式")
a.2 在内核中的驱动 (在platform_driver.probe中获得IRQ资源, 即中断号)

irq_domain是核心:
a. 每一个中断控制器都有一个irq_domain
b. 对设备中断信息的解析,
b.1 需要调用 irq_domain->ops->xlate (即从设备树中获得hwirq, type)
b.2 获取未使用的virq, 保存: irq_domain->linear_revmap[hwirq] = virq;
b.3 在hwirq和virq之间建立联系:
要调用 irq_domain->ops->map, 比如根据hwirq的属性设置virq的中断处理函数(是一个分发函数还是可以直接处理中断)
irq_desc[virq].handle_irq = 常规函数;
如果这个hwirq有上一级中断, 假设它的中断号为virq’, 还要设置:
irq_desc[virq’].handle_irq = 中断分发函数;

s3c2440设备树中断相关代码调用关系:

(1) 上述处理过程如何触发?
a. 内核启动时初始化中断的入口:
start_kernel // init/main.c

init_IRQ();
    if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
        irqchip_init();   // 一般使用它
    else
        machine_desc->init_irq();

b. 设备树中的中断控制器的处理入口:
irqchip_init // drivers/irqchip/irqchip.c

of_irq_init(__irqchip_of_table); // 对设备树文件中每一个中断控制器节点, 调用对应的处理函数
为每一个符合的"interrupt-controller"节点,
分配一个of_intc_desc结构体, desc->irq_init_cb = match->data; // = IRQCHIP_DECLARE中传入的函数
并调用处理函数
(先调用root irq controller对应的函数, 再调用子控制器的函数, 再调用更下一级控制器的函数…)

(2) root irq controller的驱动调用过程:

a. 为root irq controller定义处理函数:
IRQCHIP_DECLARE(s3c2410_irq, “samsung,s3c2410-irq”, s3c2410_init_intc_of); //drivers/irqchip/irq-s3c24xx.c

其中:
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
#define OF_DECLARE_2(table, name, compat, fn)
_OF_DECLARE(table, name, compat, fn, of_init_fn_2)
#define _OF_DECLARE(table, name, compat, fn, fn_type)
static const struct of_device_id _of_table##name
__used section(##table##_of_table)
= { .compatible = compat,
.data = (fn == (fn_type)NULL) ? fn : fn }

展开为:
static const struct of_device_id __of_table_s3c2410_irq
__used __section(“__irqchip_of_table”)
= { .compatible = “samsung,s3c2410-irq”,
.data = s3c2410_init_intc_of }

它定义了一个of_device_id结构体, 段属性为"__irqchip_of_table", 在编译内核时这些段被放在__irqchip_of_table地址处。
即__irqchip_of_table起始地址处,
放置了一个或多个 of_device_id, 它含有compatible成员;
设备树中的设备节点含有compatible属性,
如果双方的compatible相同, 并且设备节点含有"interrupt-controller"属性,
则调用of_device_id中的函数来处理该设备节点。

所以: IRQCHIP_DECLARE 是用来声明设备树中的中断控制器的处理函数。

b. root irq controller处理函数的执行过程:

s3c2410_init_intc_of  // drivers/irqchip/irq-s3c24xx.c
    // 初始化中断控制器: intc, subintc
    s3c_init_intc_of(np, interrupt_parent, s3c2410_ctrl, ARRAY_SIZE(s3c2410_ctrl));              
        // 为中断控制器创建irq_domain
        domain = irq_domain_add_linear(np, num_ctrl * 32,
                                 &s3c24xx_irq_ops_of, NULL);

        intc->domain = domain;
        // 设置handle_arch_irq, 即中断处理的C语言总入口函数
        set_handle_irq(s3c24xx_handle_irq);

(3) pinctrl系统中gpf/gpg irq controller的驱动调用过程:

a. pinctrl系统的驱动程序:
a.1 源代码: drivers/pinctrl/samsung/pinctrl-samsung.c

static struct platform_driver samsung_pinctrl_driver = {
    .probe      = samsung_pinctrl_probe,
    .driver = {
        .name   = "samsung-pinctrl",
        .of_match_table = samsung_pinctrl_dt_match, // 含有 { .compatible = "samsung,s3c2440-pinctrl", .data = &s3c2440_of_data },
        .suppress_bind_attrs = true,
        .pm = &samsung_pinctrl_pm_ops,
    },
};

a.2 设备树中:
pinctrl@56000000 {
reg = <0x56000000 0x1000>;
compatible = “samsung,s3c2440-pinctrl”; // 据此找到驱动

a.3 驱动中的操作:
samsung_pinctrl_probe // drivers/pinctrl/samsung/pinctrl-samsung.c
最终会调用到 s3c24xx_eint_init // drivers/pinctrl/samsung/pinctrl-s3c24xx.c

    // eint0,1,2,3的处理函数在处理root irq controller时已经设置; 
    // 设置eint4_7, eint8_23的处理函数(它们是分发函数)
    for (i = 0; i < NUM_EINT_IRQ; ++i) {
        unsigned int irq;

        if (handlers[i]) /* add by weidongshan@qq.com, 不再设置eint0,1,2,3的处理函数 */
        {
            irq = irq_of_parse_and_map(eint_np, i);
            if (!irq) {
                dev_err(dev, "failed to get wakeup EINT IRQ %d\n", i);
                return -ENXIO;
            }

            eint_data->parents[i] = irq;
            irq_set_chained_handler_and_data(irq, handlers[i], eint_data);
        }
    }

    // 为GPF、GPG设置irq_domain
    for (i = 0; i < d->nr_banks; ++i, ++bank) {
    
        ops = (bank->eint_offset == 0) ? &s3c24xx_gpf_irq_ops
                           : &s3c24xx_gpg_irq_ops;

        bank->irq_domain = irq_domain_add_linear(bank->of_node, bank->nr_pins, ops, ddata);
    }

(4) 使用中断的驱动调用过程:
a. 在设备节点中描述(表明使用"哪一个中断控制器里的哪一个中断, 及中断触发方式")
比如:

buttons {
    compatible = "jz2440_button";
    eint-pins  = <&gpf 0 0>, <&gpf 2 0>, <&gpg 3 0>, <&gpg 11 0>;
    interrupts-extended = <&intc 0 0 0 3>,
                          <&intc 0 0 2 3>,
                          <&gpg 3 3>,
                          <&gpg 11 3>;
};

b. 设备节点会被转换为 platform_device,
“中断的硬件信息” 会转换为"中断号",
保存在platform_device的"中断资源"里

第3课第05节_device_node转换为platform_device, 讲解了设备树中设备节点转换为 platform_device 的过程;
我们只关心里面对中断信息的处理:

of_device_alloc (drivers/of/platform.c)
dev = platform_device_alloc(“”, PLATFORM_DEVID_NONE); // 分配 platform_device

num_irq = of_irq_count(np);  // 计算中断数

of_irq_to_resource_table(np, res, num_irq) // drivers/of/irq.c, 根据设备节点中的中断信息, 构造中断资源
    of_irq_to_resource
        int irq = of_irq_get(dev, index);  // 获得virq, 中断号
                        rc = of_irq_parse_one(dev, index, &oirq); // drivers/of/irq.c, 解析设备树中的中断信息, 保存在of_phandle_args结构体中
                        
                        domain = irq_find_host(oirq.np);   // 查找irq_domain, 每一个中断控制器都对应一个irq_domain
                        
                        irq_create_of_mapping(&oirq);             // kernel/irq/irqdomain.c, 创建virq和中断信息的映射
                            irq_create_fwspec_mapping(&fwspec);
                                irq_create_fwspec_mapping(&fwspec);
                                    irq_domain_translate(domain, fwspec, &hwirq, &type) // 调用irq_domain->ops->xlate, 把设备节点里的中断信息解析为hwirq, type
                                    
                                    virq = irq_find_mapping(domain, hwirq); // 看看这个hwirq是否已经映射, 如果virq非0就直接返回
                                    
                                    virq = irq_create_mapping(domain, hwirq); // 否则创建映射
                                                virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL);  // 返回未占用的virq
                                                
                                                irq_domain_associate(domain, virq, hwirq) // 调用irq_domain->ops->map(domain, virq, hwirq), 做必要的硬件设置

c. 驱动程序从platform_device的"中断资源"取出中断号, 就可以request_irq了

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

fiveyear_chen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值