第十四章 中断自我整理(三)-中断处理过程

14.3中断找寻过程14.3中断处理函数14.3.1 request_irq()14.3.2 驱动代码学习14.4 6ull上机实验14.3中断找寻过程        自我整理(一)中已经简单说了当中断发生时,即2个Key按下时,内核执行的过...

14.3中断找寻过程
        自我整理(一)中已经简单说了当中断发生时,即2个Key按下时,内核执行的过程,代码执行:
__vectors_start->vector_irq -> __irq_usr -> usr_entry ->irq_handler->handle_arch_irq
        在整个内核(Linux-4.9.88)中搜索handle_arch_irq = handle_irq,结果如下:
---- handle_arch_irq = handle_irq Matches (3 in 3 files) ----
set_handle_irq in irq.c (D:\\\arch\arm64\kernel) : handle_arch_irq = handle_irq;
set_handle_irq in irq.c (D:\\\arch\arm\kernel) :   handle_arch_irq = handle_irq;
set_handle_irq in irq.c (D:\\\arch\openrisc\kernel) : 	 handle_arch_irq = handle_irq;
        6ull对arch\arm\kernel\irq.c,rk3288对应arch\arm64\kernel\irq.c吧,
#ifdef CONFIG_MULTI_IRQ_HANDLER

void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
    handle_arch_irq = handle_irq;
}
#endif

        全局搜索set_handle_irq(),看下谁调用了它,结果很多,不过从众多结果中看到了几个眼熟的。
gic_init_bases in irq-gic-v3.c (D:\\\drivers\irqchip) : 	set_handle_irq(gic_handle_irq);
__gic_init_bases in irq-gic.c (D:\\\drivers\irqchip) : 		set_handle_irq(gic_handle_irq);

s3c24xx_init_intc in irq-s3c24xx.c (D:\\\drivers\irqchip) : 	set_handle_irq(s3c24xx_handle_irq);
s3c_init_intc_of in irq-s3c24xx.c (D:\\\drivers\irqchip) : 	set_handle_irq(s3c24xx_handle_irq);

        irq-gic-v3.c是针对v3版本的暂且管,irq-gic.c中__gic_init_bases这个函数有点熟悉,在自我整理(二)中见过,irq-s3c24xx.c 针对s3c24xx系列芯片的,先看下这个,指定s3c24xx_handle_irq()为s3c24xx系列芯片的中断总入口函数。
set_handle_irq(s3c24xx_handle_irq);

        猜测可以找到6ull的中断总入口函数,追溯到drivers\irqchip\__gic_init_bases()找到了,之前怎么没有注意到呢!
static int __init __gic_init_bases(struct gic_chip_data *gic,
				   int irq_start,  struct fwnode_handle *handle)
{
    set_handle_irq(gic_handle_irq); //去自我(二)中加一句

	ret = gic_init_bases(gic, irq_start, handle);

	return ret;
}
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);
        --->generic_handle_irq(irq);
            --->generic_handle_irq_desc(desc);
                --->desc->handle_irq(desc);
        先一路追踪下去再分析,注意形参的传递,通过形参的变化,猜测是找到发送中断的那个domain和hwirq,find_mapping到irq(virq),通过virq找到desc,找到handle_irq。

        整理二中知道了内核在启动时初始化了各个gics和root GIC,也把层级关系明确了,看下是怎么找到发生中断的那个domain的。
#ifdef CONFIG_HANDLE_DOMAIN_IRQ
/**
 * __handle_domain_irq - Invoke调用 the handler for a HWirq belonging to a domain
 */
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
			bool lookup, struct pt_regs *regs)
{
     // 用于保存中断上下文
	struct pt_regs *old_regs = set_irq_regs(regs);
	unsigned int irq = hwirq;
	int ret = 0;
 
   //进入中断
 	irq_enter();
    irq = irq_find_mapping(domain, hwirq); //找到virq
    
    generic_handle_irq(irq); //go on
    
    //退出中断
    irq_exit();
    // 恢复中断寄存器状态
	set_irq_regs(old_regs);
}
#endif
int generic_handle_irq(unsigned int irq)
{
	struct irq_desc *desc = irq_to_desc(irq); //找到virq的中断描述符desc

	generic_handle_irq_desc(desc); //go on
}
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
	desc->handle_irq(desc); //调用中断处理函数
}

        desc->handle_irq(desc); 调用中断处理函数,谁?在整理二中知道当hwirq > 32时,desc->handle_irq指向handle_fasteoi_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) //自己编写的中断处理函数

        是不是可以总结一下了:
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) //找到自己编写的中断处理函数
        好突然就这么完成了中断找寻的过程!

        (这里分析的有问题不应该去执行handle_fasteoi_irq(desc),具体见http://bbs.100ask.net/article/108)
        下面就是中断处理程序的编写了!

14.3中断处理函数
14.3.1 request_irq()
        上一节知道了当中断发生后CPU最终会去调用action->handler,也就是自己编写的中断处理函数。前面章节都是绕着hwirq和virq展开,而CPU只能看到virq,猜测中断处理函数肯定要用到virq,还会用到什么变量呢?

        先看下函数原型:request_irq()
linux\interrupt.h

static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev)
{
	return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
go on ……

/*
*看下都需要那几个参数?
*irq,应该就是virq(IRQ);
*handler,就是action->handler,所谓的中断上半部;
*thread_fn,细节未知,所谓的中断下半部,上下部要后面说了;
*irqflags,IRQF_SHARED or IRQF_TRIGGER_
*devname,name;
*dev_id,子设备ID,独一无二;

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)
{
	
}
        具体代码就不贴了,大家都可以看到,做了一个小概括:
attachments-2020-06-h3znZh8j5ede22e1929ac.png

        有一些知识点需要补充,参考的蜗窝科技的:http://www.wowotech.net/irq_subsystem/request_threaded_irq.html
        
        知识点1:dev_id
        在之前的中断初始化中看见过它,是在指定中断处理函数时,action->handler(irq, action->dev_id)。 “为何对于IRQF_SHARED的中断必须要给出dev id呢?实际上,在共享的情况下,一个IRQ number对应若干个irqaction,当操作irqaction的时候,仅仅给出IRQ number就不是非常的足够了,这时候,需要一个ID表示具体的irqaction,这里就是dev_id的作用了。”是不是可以理解成GPIO4的一个72IRQ下可以有Pin0~Pin15个actions?

        知识点2:IRQ_NOREQUEST
        并不是所有的virq(IRQ)都可以被请求,“一般而言,这些IRQ number有特殊的作用,例如用于级联的那个IRQ number是不能request。” 应该区分一下只有CPU可见的IRQ和级联之间的IRQ,比如IRQ_CPU,IRQ_gics。个人理解:IRQ_gics只是gics,比如GPIOs连接到GPC上,它们之间的hwirq_gics和virq(IRQ)_gics是不能被请求的,CPU也是看不到的。

        知识点3:primary handler和threaded handler
        primary handler应该是中断紧急部分的处理函数(中断上半部),threaded handler非紧急的处理函数(中断下半部)。它们有一个排列组合的设置。
primary handler
threaded handler
描述
NULL
NULL
出错,返回-EINVAL
设定
设定
分为上下部
设定
NULL
全在上部完成
NULL
设定
irq_default_primary_handler
       
        接下来应该分析action的注册过程了,一看__setup_irq()很复杂,暂时跳过吧,知道通过传递过来virq,找到desc,注册action,指定它的handler就可以了。
接下来要看下具体的代码了。

14.3.2 驱动代码学习
        先把keys的设备节点拿过来。
gpio_keys_100ask {
    compatible = “100ask,gpio_key”;
    gpios = < &gpio5 1 GPIO_ACTIVE_LOW
            &gpio4 14 GPIO_ACTIVE_LOW>;
    pinctrl-names = “defualt”;
    pincrtl-0 = <&key1_100ask 
                 &key2_100ask>;
}
        按键中断嘛,按键按下,产生电平信号,CPU收到,找到这个按键,调用handler。CPU做了好多工作哈,我们只需要提供个handler,算轻松的了吧。
        回想下leddrv.c的驱动编写流程,如下图,只看下platform_driver的注册,重点看下probe()和handler。
在leddrv.c中用到了gpiolib.h中的gpiod_get(),返回struct gpio_desc,给gpiod_direction_output()和gpiod_set_value()使用。逐渐展开gpio_desc结构体没有找到irq的变量,猜测probe不会用到这个函数。
struct gpio_desc *__must_check gpiod_get(struct device *dev, const char *con_id,
enum gpiod_flags flags)
{
return gpiod_get_index(dev, con_id, 0, flags);
}
probe()函数
        该函数主要实现3点功能:解析gpios节点->获取virq->request_irq,具体代码如下:
struct gpio_key{ //抽象出的gpio结构体,全局变量
	int gpio;
	struct gpio_desc *gpiod;
	int flag;
	int irq;
} ;
static struct gpio_key *gpio_keys_100ask;

static int gpio_key_probe(struct platform_device *pdev)
{
	int err;
	struct device_node *node = pdev->dev.of_node;   //获取node
	int count;
	int i;
	enum of_gpio_flags flag;
		
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	count = of_gpio_count(node); //获取设备树中gpio_keys_100ask节点的gpios个数  ---(1)
	if (!count)
	{
		printk("%s %s line %d, there isn't any gpio available\n", __FILE__, __FUNCTION__, __LINE__);
		return -1;
	}

	gpio_keys_100ask = kzalloc(sizeof(struct gpio_key) * count, GFP_KERNEL);
 
	for (i = 0; i < count; i++)
	{
		gpio_keys_100ask[i].gpio = of_get_gpio_flags(node, i, &flag);    ---(2)
		if (gpio_keys_100ask[i].gpio < 0)
		{
			printk("%s %s line %d, of_get_gpio_flags fail\n", __FILE__, __FUNCTION__, __LINE__);
			return -1;
		}
		gpio_keys_100ask[i].gpiod = gpio_to_desc(gpio_keys_100ask[i].gpio);  ---(3)
		gpio_keys_100ask[i].flag = flag & OF_GPIO_ACTIVE_LOW;
		gpio_keys_100ask[i].irq  = gpio_to_irq(gpio_keys_100ask[i].gpio);    ---(4)
	}

	for (i = 0; i < count; i++)
	{
		err = request_irq(gpio_keys_100ask[i].irq, gpio_key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 
                              "100ask_gpio_key", &gpio_keys_100ask[i]);  ---(5)
	}      
    return 0;
}
    (1)include\linux\of_gpio.h\of_gpio_count()用的这个函数来统计节点中gpios的数量,针对gpio_keys_100ask 节点,反馈回来的额count = 2,追踪一下:
count = of_gpio_count(node);
--->of_gpio_named_count(np, "gpios"); //找到gpios
    --->of_count_phandle_with_args(np, propname, "#gpio-cells");  //找到#gpio-cells描述个数
        --->rc = of_phandle_iterator_init(&it, np, list_name, cells_name, 0);
        --->while ((rc = of_phandle_iterator_next(&it)) == 0)  //统计gpios个数
                cur_index += 1;
                return cur_index;

    (2)of_get_gpio_flags(node, i, &flag),返回int,前面是获知有几个gpios,现在是把里面的flag提取出来吗?
of_get_gpio_flags(node, i, &flag);
--->of_get_named_gpio_flags(np, "gpios", index, flags);
    --->desc = of_get_named_gpiod_flags(np, list_name, index, flags);
        {
            chip = of_find_gpiochip_by_xlate(&gpiospec);  //找到gpio chip
            desc = of_xlate_and_get_gpiod_flags(chip, &gpiospec, flags); //找到gpio desc
            --->of_gpio_simple_xlate(chip, gpiospec, flags)//  找到cell中的pins和flag
            {
                *flags = gpiospec->args[1];
                gpiospec->args[0];
            }
        }
    --->desc_to_gpio(desc);  //找到gpiox

    (3)gpio_to_desc()根据获得的gpios编号再去找到它们的descs,好麻烦啊!通过desc找到编号,又用编号找到desc,折腾哈!利用list_for_each_entry()函数找到desc;

    (4)gpio_to_irq(),看名字就知道了,根据获得的gpios编号找到它们的irq,gpio5_1对应的irq应该是74,gpio4_14应该是72,可以打印出来看下。
__gpio_to_irq(gpio)
--->gpiod_to_irq(gpio_to_desc(gpio));   
    --->offset = gpio_chip_hwgpio(desc);  //看参数传递知道offset就是hwirq
    --->chip->to_irq(chip, offset); --->gpiochip_to_irq(struct gpio_chip *chip, unsigned offset) //chip回调函数
        --->irq_find_mapping(chip->irqdomain, offset);   //好熟悉的函数,通过hwirq找到virq
            --->domain->linear_revmap[hwirq]; = virq     //线性映射
    (5)request_irq()前面已经分析了,只是看下它的形参:
    a. gpio_keys_100ask[i].irq:virq,不多说了吧;
    b. gpio_key_isr,handler函数名,稍后分析;
    c. IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,上升沿和下降沿触发,不是gpios cell里面的那个flag;
    d.100ask_gpio_key,handler name;
    e.&gpio_keys_100ask[i],独一无二,便于区分。

        折腾半天,依次从节点数出来有几个gpios,xltae各自的gpios编号和flags,通过编号找到gpio_descs,顺着就写出了通过virq找到了virq_desc,通过编号找到找到对应的hwirq找到virq,用找到的virq注册action-handler。

        为了获得virq还是蛮曲折的!

handler()函数
        看下让CPU费劲千辛万苦找到的key handler都做了啥?
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
	struct gpio_key *gpio_key = dev_id;
	int val;
	val = gpiod_get_value(gpio_key->gpiod);
    --->value = _gpiod_get_raw_value(desc); //the logical value 即0/1
    
	printk("key %d %d\n", gpio_key->gpio, val);
	return IRQ_HANDLED;
}
test函数
        简单,open and read;
   fd = open(argv[1], O_RDWR);
   read(fd, &val, 4);

14.4 6ull上机实验

        理论看完了,该上机了,
        1、修改内核中的dts文件,添加keys节点;
        2、编译新的dts文件

        到内核的根目录下100ask_imx6ull-sdk/Linux-4.9.88,make dtbs,生成.dtb文件,复制到挂载文件夹。
cd /home/book/100ask_imx6ull-sdk/Linux-4.9.88
make dtbs
cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/ncd /home/book/nfs_rootfs/
        编译驱动程序,06_gpio_irq\01_simple\gpio_key_drv.c,生成gpio_key_drv.ko,也复制到nfs_roofs文件夹中。

        3、配置开发板
上电,挂载,替换文件等:
mount -t nfs -o nolock,vers=3 192.168.1.5:/home/book/nfs_rootfs /mnt
echo 7	4	1	7 > /proc/sys/kernel/printk
cp gpio_key_drv.ko /
cp 100ask_imx6ull-14x14.dtb /boot/
reboot
        4、加载模块
[root@imx6ull:/mnt]# insmod gpio_key_drv.ko

[ 5308.684042] gpio is  129  
[ 5308.687308] the virq is 208  //不是想象中的IRQ啊!
[ 5308.690075] gpio is  110
[ 5308.692624] the virq is 189  //不是想象中的IRQ啊!

[root@imx6ull:/mnt]# lsmod
Module                  Size  Used by
gpio_key_drv            2948  0
[root@imx6ull:/mnt]# [ 5374.682877] the irq and key is 208 129
[ 5374.907114] the irq and key is 208 129
[ 5378.660543] the irq and key is 208 129
[ 5378.856460] the irq and key is 208 129
[ 5383.291782] the irq and key is 189 110
[ 5383.499297] the irq and key is 189 110
[ 5384.902230] the irq and key is 189 110
[ 5385.118534] the irq and key is 189 110
        开始发现按下按键没有信息打印出来,看了下原来是按键按错了,按下了下图中的20-21按键,正确应该是8-9按键。
attachments-2020-06-EtLXj23R5ede242a74894.png
        简单的一个按键驱动程序体验完了!
        从中断初始化->中断处理过程->驱动程序,挺折腾,不过总算是清楚了内核中中断的内容。

        继续……

1 条评论&回复

请先 登录 后评论
zxq
zxq

11 篇文章

作家榜 »

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