基于设备树的内核中断子系统分析(二)

内核版本:linux-4.19.8

本文以s3c2440处理器为例,分析内核中断子系统具体函数调用过程。

一、中断控制子系统的初始化

1、irq_desc初始化,是对struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp这个数组的初始化,

start_kernel
	early_irq_init();
int __init early_irq_init(void)
{
	int count, i, node = first_online_node;
	struct irq_desc *desc;

	init_irq_default_affinity();

	printk(KERN_INFO "NR_IRQS: %d\n", NR_IRQS);

	desc = irq_desc;//irq_desc数组指针
	count = ARRAY_SIZE(irq_desc);

	for (i = 0; i < count; i++) {
		desc[i].kstat_irqs = alloc_percpu(unsigned int);//每个cpu的irq统计数据
		alloc_masks(&desc[i], node);
		raw_spin_lock_init(&desc[i].lock);
		lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
		desc_set_defaults(i, &desc[i], node, NULL, NULL);//初始化每一项irq_desc
	}
	return arch_early_irq_init();
}

2、irq domain注册

   在drivers/irqchip/irq-s3c24xx.c中存在这样一个函数,采用宏定义在段属性中,如下所示:

int __init s3c2410_init_intc_of(struct device_node *np,
            struct device_node *interrupt_parent){
    return s3c_init_intc_of(np, interrupt_parent,
                s3c2410_ctrl, ARRAY_SIZE(s3c2410_ctrl));
}
IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of);

#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 是用来声明设备树中的中断控制器的处理函数。

我们分析内核启动:

start_kernel
	init_IRQ();
		if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
			irqchip_init();
				of_irq_init(__irqchip_of_table);
of_irq_init
	for_each_matching_node_and_match(np, matches, &match) //查找device node是否匹配__irqchip_of_table
	desc = kzalloc(sizeof(*desc), GFP_KERNEL);
	list_add_tail(&desc->list, &intc_desc_list);
	while (!list_empty(&intc_desc_list)) {
		ret = desc->irq_init_cb(desc->dev,desc->interrupt_parent);//调用初始化函数,初始化处理器的父中断控制器
	}

如上面分析所示,当内核启动在初始化IRQ时,发现设备树和__irqchip_of_table这个段属性中,双方的compatible相同, 并且设备节点含有"interrupt-controller"属性,则调用of_device_id中的函数来处理。即调用s3c2410_init_intc_of 。

接下来分析s3c2410_init_intc_of:

s3c2410_init_intc_of
	s3c_init_intc_of(np, interrupt_parent,s3c2410_ctrl, ARRAY_SIZE(s3c2410_ctrl));
		reg_base = of_iomap(np, 0);//映射中断控制器地址
		irq_domain_add_linear(np, num_ctrl * 32,&s3c24xx_irq_ops_of, NULL);
			__irq_domain_add(of_node_to_fwnode(of_node), size, size, 0, ops, host_data);//将irq domain添加到链表中,设置irq domain 操作函数、中断控制器基本信息	
		s3c24xx_clear_intc(intc); //清中断	
		set_handle_irq(s3c24xx_handle_irq);//为中断控制器设置callback,当中断发生时,会调用这个函数

分析函数调用关系发现,s3c2410_init_intc_of通过传入的父中断控制器和中断节点为每一个中断建立一个irq domain,并挂接在irq domain链表中,完成irq domain的注册。

二、设备树解析获得中断资源,进行irq domain映射

1、设备树解析获取硬件中断资源

of_platform_default_populate_init
	of_platform_default_populate(NULL, NULL, NULL);
		of_platform_populate(root, of_default_bus_match_table, lookup,parent);
			of_platform_bus_create(child, matches, lookup, parent, true);
				of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
					of_device_alloc(np, bus_id, parent);
						of_irq_to_resource_table//获取到硬件中断资源
							of_irq_to_resource
								of_irq_get(dev, index);
									of_irq_parse_one(dev, index, &oirq);//解析硬件中断信息
									domain = irq_find_host(oirq.np);//根据device node查找之前注册的irq domain

分析内核代码,就可以发现,内核platform在处理设备树信息时,就会解析硬件中断资源。为irq domain提供硬件中断信息。

2、irq domain映射

irq_create_of_mapping(&oirq);//进入irq domain开始映射
	irq_create_fwspec_mapping(&fwspec);
		irq_domain_translate(domain, fwspec, &hwirq, &type)
			if (d->ops->xlate)//这里会调用xlate函数判断中断是不是父中断,如果是父中断,设分发函数
				return d->ops->xlate(d, to_of_node(fwspec->fwnode),
									fwspec->param, fwspec->param_count,
									hwirq, type);	
		virq = irq_find_mapping(domain, hwirq);
		if (virq) {
			/*如果HW interrupt number已经映射,那么获取中断触发方式,如果没有设置,就设置它,如果已经设置则判断设置是否正确*/
			irq_get_trigger_type
			irqd_set_trigger_type
			
			return 0;
		}
		virq = irq_create_mapping(domain, hwirq);//没有创建映射,就创建映射
			of_node = irq_domain_get_of_node(domain);
			/* Check if mapping already exists */
			virq = irq_find_mapping(domain, hwirq);//再次检查,搞不懂为啥要检查两次
			//如果没有映射,就在irq_desc[NR_IRQS]查找以hwirq为下标依次第一项标记为0的项,数组标号即为虚拟中断号
			virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL);
				__irq_alloc_descs(-1, hint, cnt, node, THIS_MODULE,affinity);
					bitmap_find_next_zero_area(allocated_irqs, IRQ_BITMAP_BITS,from, cnt, 0);
			irq_domain_associate
				struct irq_data *irq_data = irq_get_irq_data(virq);
				if (domain->ops->map) {
					ret = domain->ops->map(domain, virq, hwirq);
						irq_domain_set_mapping(domain, hwirq, irq_data);
							if (hwirq < domain->revmap_size)
								domain->linear_revmap[hwirq] = irq_data->irq;//将HW interrupt number和 IRQ number的映射保存
				irq_clear_status_flags(virq, IRQ_NOREQUEST);//表示这个中断号IRQ number可以被驱动程序使用了

irq domain通过platform传过来的硬件中断信息,处理中断控制器的各种复杂情况,如:级联、直达CPU等。设置irq desc结构数据,chip结构数据。为中断提供如屏蔽中断、使能中断、中断回复等接口。最终将HW interrupt number和IRQ number一一对应,保存在domain->linear_revmap[hwirq] = irq_data->irq。

3、关于map xlate

这两个函数在irq domain映射中,至关重要。xlate用来识别中断源,输出硬件中断号,获取中断触发方式,调用irq domain为每一个HW interrupt number映射一个IRQ number。map函数为每个虚拟中断号绑定的irq desc结构分配handle、chip结构函数,以及一些私有的数据。

static const struct irq_domain_ops s3c24xx_irq_ops_of = {
	.map = s3c24xx_irq_map_of,
	.xlate = s3c24xx_irq_xlate_of,
};
irq domain在映射IRQ number时,会先调用xlate,判断是不是父中断节点
	*out_hwirq = intspec[0] * 32 + intspec[2];//获取硬件中断号
	*out_type = intspec[3] & IRQ_TYPE_SENSE_MASK;//获取中断触发方式
	if (parent_intc) {//如果是父中断,设置分发函数
		irq_data = &intc->irqs[intspec[2]];
		irq_data->parent_irq = intspec[1];
		parent_irq_data = &parent_intc->irqs[irq_data->parent_irq];
		parent_irq_data->sub_intc = intc;
		parent_irq_data->sub_bits |= (1UL << intspec[2]);

		/* parent_intc is always s3c_intc[0], so no offset */
		irqno = irq_create_mapping(parent_intc->domain, intspec[1]);
		if (irqno < 0) {
			pr_err("irq: could not map parent interrupt\n");
			return irqno;
		}
		irq_set_chained_handler(irqno, s3c_irq_demux);
	}
map 函数,为每个虚拟中断号的irq_desc设置handler、chip和chip data
s3c24xx_irq_map_of
	if (!parent_intc)
		irq_set_chip_and_handler(virq, &s3c_irq_chip, handle_edge_irq);
	else
		irq_set_chip_and_handler(virq, &s3c_irq_level_chip,handle_edge_irq);
	irq_set_chip_data(virq, irq_data);	
irq_set_chip_and_handler
	irq_set_chip_and_handler_name
		irq_set_chip(irq, chip);
			desc->irq_data.chip = chip;//设置desc的chip
		__irq_set_handler(irq, handle, 0, name);
			__irq_do_set_handler(desc, handle, is_chained, name);
				desc->handle_irq = handle;//设置desc的handle
				desc->name = name;
				irq_activate_and_startup(desc, IRQ_RESEND);//使能中断

三、中断发生

在处理器注册irq domain时,s3c_init_intc_of中提供了中断接口set_handle_irq(s3c24xx_handle_irq);,当中断发生时,进入s3c24xx_handle_irq,通过硬件中断号,获取虚拟中断号,调用handle处理中断。

s3c24xx_handle_irq
	s3c24xx_handle_intc
		handle_domain_irq(intc->domain, intc_offset + offset, regs);
			__handle_domain_irq(domain, hwirq, true, regs);
				irq = irq_find_mapping(domain, hwirq);
				generic_handle_irq(irq);
					generic_handle_irq_desc(desc);
						desc->handle_irq(desc);
				//执行irq domain映射IRQ number时绑定的irq_desc中的handle,处理中断。最终调用action中的handler通知驱动,处理中断数据。

四、驱动程序申请中断

int request_threaded_irq(unsigned int irq, 
                        irq_handler_t handler,
                        irq_handler_t thread_fn, 
                        unsigned long irqflags,
                        const char *devname, 
                        void *dev_id)             
irq            //要注册handler的那个IRQ number。
handler        //当中断发生时,调用这个handle,如果handler == NULL,thread_fn != NULL,则默认primary handler已经存在
thread_fn    //threaded interrupt handler。如果该参数不是NULL,那么系统会创建一个kernel thread,调用的function就是thread_fn
irqflags    //中断类型
devname         //中断名字,这个不重要
dev_id        //如果您的中断是共享的,那么在释放中断时必须传递一个非空的dev_id。


irqflags:
IRQF_SHARED         //允许在多个设备之间共享irq
IRQF_PROBE_SHARED    //由调用者在期望发生共享不匹配时设置
IRQF_TIMER            //标记此中断为计时器中断
IRQF_PERCPU            //中断为每个cpu
IRQF_NOBALANCING    //从irq平衡中排除此中断的标志
IRQF_IRQPOLL        //中断用于轮询(出于性能原因,只考虑在共享中断中首先注册的中断)
IRQF_ONESHOT        //在hardirq处理程序完成后,不重新启用中断。用于线程中断,它需要在线程处理程序运行之前禁用irq中                                        断线。
IRQF_NO_SUSPEND        //在挂起期间不要禁用此IRQ。不保证此中断将系统从挂起状态唤醒
IRQF_FORCE_RESUME    //即使设置了IRQF_NO_SUSPEND,也要在恢复时强制启用它
IRQF_NO_THREAD        //中断不能线程化
IRQF_EARLY_RESUME     //在syscore期间尽早恢复IRQ,而不是在设备恢复时。
IRQF_COND_SUSPEND    //如果IRQ与NO_SUSPEND用户共享,那么在挂起中断之后执行这个中断处理程序。对于系统唤醒                                                设备,用户需要在中断处理程序中实现唤醒检测。

对于request_threaded_irq申请中断简析

request_threaded_irq
	desc = irq_to_desc(irq);
	action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
	if (!action)
		return -ENOMEM;
	action->handler = handler;
	action->thread_fn = thread_fn;
	action->flags = irqflags;
	action->name = devname;
	action->dev_id = dev_id;	
	retval = __setup_irq(irq, desc, action);	
__setup_irq
	nested = irq_settings_is_nested_thread(desc);
	if (nested) {//如果是nested
		if (!new->thread_fn) {
			ret = -EINVAL;
			goto out_mput;
		}
		/*
		 * Replace the primary handler which was provided from
		 * the driver for non nested interrupt handling by the
		 * dummy function which warns when called.
		 */
		new->handler = irq_nested_primary_handler;//重新设置handler
	} else {
		if (irq_settings_can_thread(desc)) {//判断是否可以线程化
			ret = irq_setup_forced_threading(new);
			if (ret)
				goto out_mput;
		}
	}
/*
	 * Create a handler thread when a thread function is supplied
	 * and the interrupt does not nest into another interrupt
	 * thread.
	 */
	if (new->thread_fn && !nested) {
		ret = setup_irq_thread(new, irq, false);//创建线程
		if (ret)
			goto out_mput;
		if (new->secondary) {
			ret = setup_irq_thread(new->secondary, irq, true);
			if (ret)
				goto out_thread;
		}
	}
	setup_irq_thread
		irq_thread
			while (!irq_wait_for_interrupt(action)) {//等待中断
			}

五、s3c2440按键中断实例

1、不使用设备树

加载编译好的buttons.ko ,在后台运行测试程序buttons_test ,    cat     /proc/interrupts

基于设备树的内核中断子系统分析(二)_第1张图片

这里测试了IRQ_EINT0、IRQ_EINT2、IRQ_EINT11、IRQ_EINT19四个中断,虚拟中断号跟arch/arm/mach-s3c24xx/include/mach/irqs.h中定义的是一致的,这充分说明了之前的分析,在为引入设备树时,处理器的虚拟中断号,是查阅datasheet后直接定义在arch/arm/mach-s3c24xx/include/mach/irqs.h,再调用irq domain接口,完成irq domain相应操作。

2、使用设备树

使用设备树时,硬件中断号在dts文件中指定,内核在解析设备树时,会根据dtb文件,来进入irq domain动态映射虚拟中断号。

如下所示的dts文件,指定了中断的父中断,中断号,中断产生的触发方式。GPIO控制器这里也属于一个中断控制器。

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>;
    };

基于设备树的内核中断子系统分析(二)_第2张图片

可以查看到虚拟中断号跟之前固化在.h文件中不同。结合之前的分析,这个映射是动态的,若果实现注册几个其他的中断,再来注册按键中断,虚拟中断号又不一样了。

还可以通过irq domain的log分析:

直达CPU的中断,会以硬件中断号为下标,查找irq_desc数组,若HW interrupt number这一项已经被占用时,就会继续寻找下一项。

通过 父中断控制器级联的中断,会根据父中断硬件中断号查找到irq_desc数组中HW interrupt number这一项,当然这里假设这一项没有被占用。根据irq desc的handle读取具体是哪一个中断源产生的中断,并将irq_desc数组中HW interrupt number这一项的下一项没有被占用的数组标号设置为当前硬件中断源的虚拟中断号。

irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 28, hwirq = 28
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 32, hwirq = 32
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 33, hwirq = 33
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 9, hwirq = 9
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 59, hwirq = 59
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 30, hwirq = 30
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 8, hwirq = 8
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 24, hwirq = 24
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 26, hwirq = 26
irq_create_mapping, domain's name = gpf, virq = 7, hwirq = 7
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 16, hwirq = 16
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 31, hwirq = 31
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 41, hwirq = 41
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 42, hwirq = 42
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 17, hwirq = 17
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 18, hwirq = 18
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 19, hwirq = 19
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 20, hwirq = 20
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 1, hwirq = 0
irq_create_mapping, domain's name = interrupt-controller@4a000000, virq = 2, hwirq = 2
irq_create_mapping, domain's name = gpg, virq = 3, hwirq = 3
irq_create_mapping, domain's name = gpg, virq = 15, hwirq = 11

 

你可能感兴趣的:(linux驱动,中断子系统,设备树,irq,domain)