第十九章 驱动程序基石(三)-中断线程化处理

19.8 线程 听课听到这里还不清楚什么是进程?什么是线程?正好《Linux内核设计与实现》中有很好的说明,摘录: 进程就是处于执行器的程序,但进程并不仅仅局限于一段可执行的代码。通常进...
19.8 线程
        听课听到这里还不清楚什么是进程?什么是线程?正好《Linux内核设计与实现》中有很好的说明,摘录:
        进程就是处于执行器的程序,但进程并不仅仅局限于一段可执行的代码。通常进程还要包含其他资源,像打开的文件,挂起的信号,内核内核数据,处理器状态,一个或多个具有内存映射的内存地址空间及一个或多次执行线程,当然还包括用来存放全局变量的数据段等。实际上,进程就是正在执行的程序代码的实时结果
        执行线程,简称线程,是在进程中的活动的对象。每个线程都拥有一个独立的程序计数器、进城栈和一组进程寄存器。内核调度的对象是线程、而不是进程。
内核线程-独立运行在内核空间的标准进程。内核线程和普通的进程间区别在于内核线程没有独立的地址空间。它们只在内核空间运行,从来不切换到用户状态空间去。内核进程和普通进程一样,可以被调度,也可以被强占。
    看了这个定义还是有点晕晕的,书中又有一句话“Linux系统的线程实现非常特别:它对线程和进程并不特别区分。”

        前面介绍了使用tasklet、workqueue来处理中断下半部,但是还有存在缺陷:自我理解就是tasklet和workqueue执行的过程其他进程不能执行。 衍生出threaded irq,中断下半部创建一个内核线程来处理,使用专用函数:request_threaded_irq()。这个函数在之前《第十四章 中断自我整理(三)-中断处理过程》中涉及到,当时只是简单看了下,现在可以具体分析下。
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)
{
	struct irqaction *action;
	struct irq_desc *desc;
	int retval;

	desc = irq_to_desc(irq);  //获取virq 的desc   

	if (!handler) {            
		if (!thread_fn)
			return -EINVAL;
           ////如果没有提供handler(),内核帮忙提供一个,只是return IRQ_WAKE_THREAD  
		handler = irq_default_primary_handler;
	}

	action = kzalloc(sizeof(struct irqaction), GFP_KERNEL); //申请action

	action->handler = handler;   //初始化
	action->thread_fn = thread_fn;
	action->flags = irqflags;
	action->name = devname;
	action->dev_id = dev_id;

	chip_bus_lock(desc);
	retval = __setup_irq(irq, desc, action);  //注册action
            --->ret = setup_irq_thread(irqaction, irq, false); 
                --->t = kthread_create(irq_thread, new, "irq/%d-%s", irq, new->name); //创建irq_thread
                    --->handler_fn = irq_thread_fn;
                    --->irq_wait_for_interrupt(action); //休眠等待
                    --->action_ret = handler_fn(desc, action); //执行thred_fn
                        --->ret = action->thread_fn(action->irq, action->dev_id);
                    --->wake_threads_waitq(desc); //唤醒等待的thred_fn
                --->new->thread = t; //放入action
	return retval;
}
        这个函数的大体功能流程就知道了,创建一个action结构体,初始化它,注册到内核中,创建内核irq_thread,并把thread_fn添加到action结构体中,等待中断发生。
        1、发生中断后先执行上半部,返回IRQ_HANDLED,则表示中断处理完毕;返回,IRQ_WAKE_THREAD,则唤醒线程。
        2、内核线程被唤醒之后,执行thread_fn函数,执行完之后调用wake_threads_waitq()来唤醒排队等待的其他进程。
        有了之前的分析基础现在看这个还是挺清晰的,完全手册中贴出来中断执行的代码过程,看看和之前自己的分析是不是一致,为了便于和自己整理的对比,把格式修改了下:
__irq_usr () at arch/arm/kernel/entry-armv.S:464
--->gic_handle_irq (regs=0xc8) at drivers/irqchip/irq-gic.c:364
    --->handle_domain_irq (regs=, hwirq=,
        --->__handle_domain_irq (domain=0x86006000, hwirq=32, lookup=true,
            --->generic_handle_irq (irq=) at kernel/irq/irqdesc.c:590
                --->generic_handle_irq_desc (desc=)
                    --->mx3_gpio_irq_handler (desc=) at drivers/gpio/gpio-mxc.c:291
                        --->mxc_gpio_irq_handler (port=0xc8, irq_stat=2252237104) at drivers/gpio/gpiomxc.c:274
                            --->generic_handle_irq (irq=) at kernel/irq/irqdesc.c:590
                              --->generic_handle_irq_desc (desc=)
                                  --->handle_level_irq (desc=0x8616e300) at kernel/irq/chip.c:518
                                      --->handle_irq_event (desc=0x8616e300) at kernel/irq/handle.c:202
                                          --->handle_irq_event_percpu (desc=0x8616e300) at kernel/irq/handle.c:185
                                              --->__handle_irq_event_percpu (desc=0x8616e300, flags=0x86517edc) at
                                                  --->gpio_keys_gpio_isr (irq=200, dev_id=0x863e6930) at drivers/input/keyboard/gpio_keys.c:393
        
        自我:
set_handle_irq(gic_handle_irq);
--->irqnr = irqstat & GICC_IAR_INT_ID_MASK;             //找到hwirq    
--->handle_domain_irq(gic->domain, irqnr, regs);        //找到domain
    --->__handle_domain_irq(domain, hwirq, true, regs);
        --->irq = irq_find_mapping(domain, hwirq);      //找到virq
        --->generic_handle_irq(irq);
            --->desc = irq_to_desc(irq);                //找到desc
            --->generic_handle_irq_desc(desc);
                --->desc->handle_irq(desc);             //找到handle_irq,执行
                    handle_fasteoi_irq(desc);
                        --->handle_irq_event(desc);
                            --->handle_irq_event_percpu(desc);
                                --->__handle_irq_event_percpu(desc, &flags);
                                    --->action->handler(irq, action->dev_id) //找到自己编写的中断处理函数
        发现问题了
///include/linux/irqdesc.h:150

static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
	desc->handle_irq(desc);
}
        
老师提供的执行过程中这个handle_irq()分别指向了mx3_gpio_irq_handler(),而不是我分析的handle_fasteoi_irq()。跳转到mx3_gpio_irq_handler()看下,在drivers/gpio/gpio-mxc.c中,看了这个文件有gpio_mxc_init(void)、platform_driver mxc_gpio_driver、mxc_gpio_probe()和mxc_gpio_free(),这不是驱动程序的基本函数吗?依次看下:
static int __init gpio_mxc_init(void)
{
	return platform_driver_register(&mxc_gpio_driver);  //很熟悉了,注册platform_driver
}
        
        找下platform_driver结构体:
static struct platform_driver mxc_gpio_driver = {
	.driver		= {
		.name	= "gpio-mxc",
		.pm = &mxc_gpio_dev_pm_ops,
		.of_match_table = mxc_gpio_dt_ids,
	},
	.probe		= mxc_gpio_probe,
	.id_table	= mxc_gpio_devtype,
};
        继续……
static const struct of_device_id mxc_gpio_dt_ids[] = {
	{ .compatible = "fsl,imx1-gpio", .data = &mxc_gpio_devtype[IMX1_GPIO], },
	{ .compatible = "fsl,imx21-gpio", .data = &mxc_gpio_devtype[IMX21_GPIO], },
	{ .compatible = "fsl,imx31-gpio", .data = &mxc_gpio_devtype[IMX31_GPIO], },
	{ .compatible = "fsl,imx35-gpio", .data = &mxc_gpio_devtype[IMX35_GPIO], },
	{ /* sentinel */ }
};
        compatible应该对应的是设备树中的节点属性,我们用的imx6系列,没见啊!
到这里该去看下设备树文件了……果然发现gpio节点中有 "fsl,imx35-gpio",以前没怎么注意,那"fsl,imx6ul-gpio"呢???
//imx6ull.dtsi

gpio1: gpio@0209c000 {
	compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
	reg = <0x0209c000 0x4000>;
	interrupts = ,
			 ;
	gpio-controller;
	#gpio-cells = <2>;
	interrupt-controller;
	#interrupt-cells = <2>;
};
        以前居然没有在内核中搜过"fsl,imx6ul-gpio",结果出乎意料,内核中居然没有"fsl,imx6ul-gpio",都得靠 "fsl,imx35-gpio"去匹配。
---- fsl,imx6ul-gpio Matches (10 in 2 files) ----
imx6ul.dtsi (arch\arm\boot\dts) line 530 : compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
imx6ul.dtsi (arch\arm\boot\dts) line 543 : compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
imx6ul.dtsi (arch\arm\boot\dts) line 555 : compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
imx6ul.dtsi (arch\arm\boot\dts) line 567 : compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
imx6ul.dtsi (arch\arm\boot\dts) line 579 : compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
imx6ull.dtsi (arch\arm\boot\dts) line 513 : compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
imx6ull.dtsi (arch\arm\boot\dts) line 524 : compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
imx6ull.dtsi (arch\arm\boot\dts) line 535 : compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
imx6ull.dtsi (arch\arm\boot\dts) line 546 : compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
imx6ull.dtsi (arch\arm\boot\dts) line 557 : compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
        
        6系列为什么用35系列呢?看下这个宏IMX35_GPIO定义才知道,原来35适用于多系列。
enum mxc_gpio_hwtype {
	IMX1_GPIO,	/* runs on i.mx1 */
	IMX21_GPIO,	/* runs on i.mx21 and i.mx27 */
	IMX31_GPIO,	/* runs on i.mx31 */
	IMX35_GPIO,	/* runs on all other i.mx */  好吧,适用于多款imx
};
        在之前的文章分析时参考过海风怒红的这个系列,后面关于GPIO的中断处理没有仔细看,当时是看的晕,现在补上了,网上没有找到其他关于该内容的分析,海风怒红分析的很好!http://www.lilonghua.top/linux/src/linux4x_gpio7_irq_flow2.html

        返回看下mxc_gpio_probe()都做了什么,
// drivers/gpio/gpio-mxc.c

static int mxc_gpio_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
	struct mxc_gpio_port *port; ---(1)
	struct resource *iores;
	int irq_base = 0;
	int err;

	mxc_gpio_get_hw(pdev);  //识别是IMX35_GPIO

	port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);  

	iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); //获取资源
	port->base = devm_ioremap_resource(&pdev->dev, iores);  //地址映射

	port->irq_high = platform_get_irq(pdev, 1);
	port->irq = platform_get_irq(pdev, 0); //获取irq,分高低还

	/* disable the interrupt and clear the status */
	writel(0, port->base + GPIO_IMR);
	writel(~0, port->base + GPIO_ISR);
 
   /* setup one handler for each entry */
  irq_set_chained_handler_and_data(port->irq, mx3_gpio_irq_handler, port); ---(2)
  
	err = bgpio_init(&port->gc, &pdev->dev, 4,
			 port->base + GPIO_PSR,
			 port->base + GPIO_DR, NULL,
			 port->base + GPIO_GDIR, NULL,
			 BGPIOF_READ_OUTPUT_REG_SET); //初始化gpio寄存器

	port->gc.request = mxc_gpio_request;
	port->gc.free = mxc_gpio_free;
	port->gc.parent = &pdev->dev;
	port->gc.to_irq = mxc_gpio_to_irq;  ---(3)
	port->gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 :
					     pdev->id * 32;

	err = devm_gpiochip_add_data(&pdev->dev, &port->gc, port);

	irq_base = irq_alloc_descs(-1, 0, 32, numa_node_id()); //很熟悉了申请desc

	port->domain = irq_domain_add_legacy(np, 32, irq_base, 0,
					     &irq_domain_simple_ops, NULL); //很熟悉了,申请domain

	/* gpio-mxc can be a generic irq chip */
	err = mxc_gpio_init_gc(port, irq_base, &pdev->dev);

	list_add_tail(&port->node, &mxc_gpio_ports);

	platform_set_drvdata(pdev, port);
	pm_runtime_put(&pdev->dev);

	return 0;
}
(1)一个陌生的结构体,mxc_gpio_port,包含domain和chip,层级更高一些。
struct mxc_gpio_port {
struct list_head node;
struct clk *clk;
void __iomem *base;
int irq;
int irq_high; //irq高位吗?
struct irq_domain *domain;
struct gpio_chip gc; //也是熟悉的结构体
u32 both_edges;
int saved_reg[6];
bool gpio_ranges;
};
(3)mxc_gpio_to_irq,陌生,看下定义,居然是调用的irq_find_mapping()。
static int mxc_gpio_to_irq(struct gpio_chip *gc, unsigned offset)
{
	struct mxc_gpio_port *port = gpiochip_get_data(gc);

	return irq_find_mapping(port->domain, offset);
}
(2)irq_set_chained_handler_and_data(),这个函数有点熟悉,看下执行过程:
irq_set_chained_handler_and_data(port->irq, mx3_gpio_irq_handler, port); 
--->__irq_do_set_handler(desc, handle, 1, NULL);
    --->desc->handle_irq = handle; //设置desc的handle
        
        看函数执行的过程,gpios的handle是mxc_gpio_irq_handler(),(怎么指定的呢???)看下这个函数:(注释部分借用海风怒吼的注释)
/* handle 32 interrupts in one status register */
static void mxc_gpio_irq_handler(struct mxc_gpio_port *port, u32 irq_stat)
{
	while (irq_stat != 0) {
             //fls或去最高有效位(从低位往左数最后的有效bit位)
             //这里就是得到哪个管脚触发的中断,一一处理,而且优先级就是按照这个规则来了。
		int irqoffset = fls(irq_stat) - 1;
  
            //这里的做法是为了让没有双边沿触发的芯片,用轮流高低电平触发的方式解决。
            //imx6芯片忽略就好,也看了半天才看清楚原来是这样。
		if (port->both_edges & (1 << irqoffset))
			mxc_flip_edge(port, irqoffset);

		generic_handle_irq(irq_find_mapping(port->domain, irqoffset));

		irq_stat &= ~(1 << irqoffset);
	}
}
        又是generic_handle_irq()->generic_handle_irq_desc(desc)->desc->handle_irq(desc),这次指向的是handle_level_irq (),真是麻烦哈,依次指向action->handler,即自己定义的函数。

        分析到这里似乎又明白了一下,回看讲课笔记《18.3.1 irq_desc 数组》,这里描述的很清楚了,只是现在才彻底明白!
attachments-2020-06-RNO243aK5ef88a88efa27.png
        外部设备 1、外部设备 n 共享一个 GPIO 中断 B,多个 GPIO 中断汇聚到 GIC(通用中断控制器)的 A 号中断, GIC 再去中断 CPU。那么软件处理时就是反过来,先读取 GIC 获得中断号 A,再细分出 GPIO 中断 B,最后判断是哪一个外部芯片发生了中断。
所以,中断的处理函数来源有三:
① GIC 的处理函数:
        假设 irq_desc[A].handle_irq 是 XXX_gpio_irq_handler(XXX 指厂家),这个函数需要读取芯片的 GPIO控制器,细分发生的是哪一个 GPIO 中断(假设是 B),再去调用 irq_desc[B].handle_irq。
注意: irq_desc[A].handle_irq 细分出中断后 B,调用对应irq_desc[B].handle_irq。
        显然中断A是CPU感受到的顶层的中断, GIC 中断 CPU 时, CPU 读取 GIC 状态得到中断 A。
② 模块的中断处理函数:
        比如对于 GPIO 模块向 GIC 发出的中断 B,它的处理函数是 irq_desc[B].handle_irq。
BSP 开发人员会设置对应的处理函数,一般是 handle_level_irq 或 handle_edge_irq,从名字上看是用来处理电平触发的中断、边沿触发的中断。
注意:导致 GPIO 中断 B 发生的原因很多,可能是外部设备 1,可能是外部设备 n,可能只是某一个设备,也可能是多个设备。所以 irq_desc[B].handle_irq 会调        用某个链表里的函数,这些函数由外部设备提供。这些函数自行判断该中断是否自己产生,若是则处理。
③ 外部设备提供的处理函数:
        这里说的“外部设备”可能是芯片,也可能总是简单的按键。它们的处理函数由自己驱动程序提供,这是最熟悉这个设备的“人”:它知道如何判断设备是否发生了中断,如何处理中断。
        对于共享中断,比如 GPIO 中断 B,它的中断来源可能有多个,每个中断源对应一个中断处理函数。所以 irq_desc[B]中应该有一个链表,存放着多个中断源的处理函数。
        一旦程序确定发生了 GPIO 中断 B,那么就会从链表里把那些函数取出来,一一执行。
attachments-2020-06-uqzni59W5ef88ab48538f.png
        中断这一路真是折腾……



你可能感兴趣的文章

相关问题

0 条评论&回复

请先 登录 后评论
zxq
zxq

11 篇文章

作家榜 »

  1. 百问网-周老师 19 文章
  2. st_ashang 14 文章
  3. 渐进 12 文章
  4. zxq 11 文章
  5. helloworld 8 文章
  6. CMOneMO 5 文章
  7. Infinite 5 文章
  8. 谢工 5 文章