Linux中断子系统【1】- 中断控制器GIC驱动分析

系列文章目录

Linux中断子系统【1】- 中断控制器GIC驱动分析
Linux中断子系统【2】- Linux内核软中断softirq和小任务tasklet分析
Linux中断子系统【3】- Linux内核request_irq源码分析
Linux中断子系统【4】- Linux中断子系统之中断映射(初始化中断控制器)
Linux中断子系统【5】- 中断控制器及驱动分析(图像化分析)
Linux中断子系统【6】- Linux 内核笔记之高层中断处理(中断入口->中断服务函数)
Linux中断子系统【7】- irq_desc数据结构分析
Linux中断子系统【8】- linux-3.4.6内核中断流程分析
Linux中断子系统【1】- linux级联中断控制器的处理流程



前言

分析Linux内核中断驱动的系列文章

参考:
GIC驱动程序分析


一、一级中断控制器处理流程

对于irq_desc,内核有两种分配方法:

  1. 一次分配完所有iqr_desc
  2. 按需分配(用到某个中断才分配它的irq_desc)。
    现在的内核基本使用第二种方法。
    在这里插入图片描述
  • 假设GIC可以向CPU发出16 ~ 1019号中断,这些数字被称为hwirq0 ~ 15用于Process之间通信,比较特殊
  • 假设要使用UART模块,它发出的中断连接到GIC32号中断,分配的irq_desc序号为16
    GIC domain中会记录(32,16)
  • 那么注册中断时就是:request_irq(16, ...),把处理程序注册进irqaction
  • 发生UART中断时
    • 程序从GIC中读取寄存器知道发生了32号中断,通过GIC irq_domain可以知道virq为16
    • 调用irq_desc[16]中的handlerA函数,它的作用是调用action链表中用户注册的函数

二、多级中断控制器处理流程

在这里插入图片描述

  • 假设GPIO模块下有4个引脚,都可以产生中断,都连接到GIC33号中断
  • GPIO也可以看作一个中断控制器,对于它的4个中断
  • 对于GPIO模块中0 ~ 3这四个hwirq,一般都会一下子分配四个irq_desc
  • 假设这4irq_desc的序号为100 ~ 103,在GPIO domain中记录(0, 100) (1,101) (2,102) (3,103)
  • 对于KEY,注册中断时就是:request_irq(102, ...)
    • 程序从GIC中读取寄存器知道发生了33号中断,通过GIC irq_domain可以知道virq16
    • 调用irq_desc[16]中的handleB函数
    • handleB读取GPIO寄存器,确定是GPIO2号引脚发生中断
    • 通过GPIO irq_domain可以知道virq102
    • 调用irq_desc[102]中的handleA函数,它的作用是调用action链表中用户注册的函数

程序先是通过request_irq注册处理函数,和分配虚拟中断号,使得硬件中断号虚拟中断号产生联系,并存储在irq_domain中。
当硬件触发时,CPU通过硬件中断号,判断哪个中断触发了,通过关联的软件中断号,找到对应的处理函数。
处理函数时,需要把对应的中断屏蔽住,处理完后再恢复。

有时候另一些情况时,像GPIO会有多个中断号,那么每个不同的中断号也会有不同的软件中断号,对应的处理函数也不一样。
platform_get_irq(pdev, 1);为例,获取中断号之后要注册中断服务函数Linux中断子系统【3】- Linux内核request_irq源码分析

	struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
	[0 ... NR_IRQS-1] = {
		.handle_irq	= handle_bad_irq,
		.depth		= 1,
		.lock		= __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
	}
};
	gpio2: gpio@020a0000 {
		compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
		reg = <0x020a0000 0x4000>;
		interrupts = <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>,
			     <GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>;
		gpio-controller;
		#gpio-cells = <2>;
		interrupt-controller;
		#interrupt-cells = <2>;
	};
	/*最终目的通过临时创建一个irq_desc,并和中断控制器(GIC)的域中的某一个硬件中断源产生映射关系,将来硬件中断来临就会执行相应的中断服务函数
	以便reques_irq根据虚拟中断号在irq_desc_tree中找到相对应的irq_desc,并注册中断服务函数。
	最终完成了:virq-->irq_desc(radix_tree_insert(&irq_desc_tree, virq, desc))
	以及:     hwirq->irq_data (radix_tree_insert(&domain->revmap_tree, hwirq, data)
	而 irq_data->irq中保存者virq,这样硬件中断来临会先取出irq-data,并执行相对应的virq->action.
	*/
	int platform_get_irq(struct platform_device *dev, unsigned int num)
	-->return of_irq_get(dev->dev.of_node, num);
      -->of_irq_parse_one(dev, index, &oirq)/*struct of_phandle_args oirq*/
      -->irq_find_host(oirq.np);
	        {
	        	struct irq_domain *irq_find_host(struct device_node *node){
		        	struct irq_domain *h = NULL;
	       			list_for_each_entry(h, &irq_domain_list, link) {
						rc = (h->of_node != NULL) && (h->of_node == node);
						if (rc) {
							return h
						}
					}
				}
	        }
       -->return irq_create_of_mapping(&oirq);/*找到GIC/或者某一个中断控制器的节点,来创建SPI终端号与irq_desc的映射*/
       		{
       			irq_hw_number_t hwirq;
       			xlate(&hwirq);/*找到硬件中断源68(0~1024)<GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>*/
				if (irq_domain_is_hierarchy(domain)) {/*查看是否是层级中断控制器 domain->flags & IRQ_DOMAIN_FLAG_HIERARCHY;*/
					/*
					 * If we've already configured this interrupt,
					 * don't do it again, or hell will break loose.
					 */
					virq = irq_find_mapping(domain, hwirq);
					if (virq)
						return virq;
			
					virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, irq_data);
					{
						virq = __irq_domain_alloc_irqs(domain, -1, nr_irqs, node, arg, false);
					}
				}
       			return virq;/*返回虚拟中断号*/
			}
		   -->	__irq_domain_alloc_irqs(domain, -1, nr_irqs, node, arg, false)
			--> irq_domain_alloc_descs(irq_base, nr_irqs, 0, node)
			 --> static inline int alloc_descs(unsigned int start, unsigned int cnt, int node,
				      struct module *owner)/*返回再gic域上的空irq_desc序列编号*/
				     /* Allocate a virtual interrupt number */
				     /*virq = irq_domain_alloc_descs(-1, 1, hwirq,of_node_to_nid(domain->of_node));
				     {
				     	 data->hwirq = hwirq/*实际走了这个不想改了*/
				     }
				     */
	    	  --> alloc_desc(start + i, node, owner);/*desc->irq_data.irq = irq;*/
	    	  --> irq_insert_desc(start + i, desc);{
	    	  		radix_tree_insert(&irq_desc_tree, start + i, desc);/*虚拟终端号*/
	    	  	  }
		   -->irq_domain_alloc_irq_data(domain, virq, nr_irqs)/*创建struct irq_data *irq_data*/
		    -->irq_domain_insert_irq(virq + i);/*直到这里virq与hwirq正式映射了*/
			    {
			    	struct irq_data *data = irq_get_irq_data(virq)
			    	/*irq_data->hwirq = hwirq;*/
			    	/*int irq_domain_associate(struct irq_domain *domain, unsigned int virq,irq_hw_number_t hwirq)*/
			    	irq_hw_number_t hwirq = data->hwirq;
			    	radix_tree_insert(&domain->revmap_tree, hwirq, data);/*硬件中断号与data相匹配*/
			    }
        

Linux中断子系统【3】- Linux内核request_irq源码分析可知层级中断使用dem_requeset_irq即可,那么链式中断服务注册函数 irq_set_chained_handler(port->irq, mx3_gpio_irq_handler); irq_set_handler_data(port->irq, port);与其区别是什么?

  • 区别是:requeset_irq在申请irq_desc同时会使用action的链表功能添加多个中断服务函数
    irq_set_chained_handler因为是链式只能注册一个中断服务函数,在函数中判断最终的二级中断号
static inline void irq_set_chained_handler(unsigned int irq, irq_flow_handler_t handle)
-->void __irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,const char *name
--> struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);/*根据终端号寻找对应的irq_desc*/
-->static inline struct irq_desc *irq_get_desc_buslock(unsigned int irq, unsigned long *flags, unsigned int check)
 --> return __irq_get_desc_lock(irq, flags, true, check);
  --> struct irq_desc *__irq_get_desc_lock(unsigned int irq, unsigned long *flags, bool bus,
		    unsigned int check){
			struct irq_desc *desc = irq_to_desc(irq); return desc
		}
   -->struct irq_desc *irq_to_desc(unsigned int irq)
		{
			return radix_tree_lookup(&irq_desc_tree, irq);
		}

三、GIC中的重要函数和结构体

沿着中断的处理流程,GIC涉及这4个重要部分:

  • CPU从异常向量表中调用handle_arch_irq,这个函数指针是由GIC驱动设置的,GIC才知道怎么判断发生的是那个GIC中断
  • GIC获取hwirq后,要转换为virq:需要有GIC Domain
  • 调用irq_desc[virq].handle_irq函数:这也应该由GIC驱动提供
  • 处理中断时,要屏蔽中断清除中断等:这些函数保存在irq_chip里,由GIC驱动提供
    从硬件上看,GIC功能:

可以使能、屏蔽中断
发生中断时,可以从GIC里判断是哪个中断

在内核里,使用gic_chip_data结构体表示GIC,gic_chip_data里有:

  • irq_chip:中断使能、屏蔽、清除,放在irq_chip中的各个函数里实现
// drivers/irqchip/irq-gic.c
static const struct irq_chip gic_chip = {
    .irq_mask       = gic_mask_irq,
    .irq_unmask     = gic_unmask_irq,
    .irq_eoi        = gic_eoi_irq,
    .irq_set_type       = gic_set_type,
    .irq_get_irqchip_state  = gic_irq_get_irqchip_state,
    .irq_set_irqchip_state  = gic_irq_set_irqchip_state,
    .flags          = IRQCHIP_SET_TYPE_MASKED |
                  IRQCHIP_SKIP_SET_WAKE |
                  IRQCHIP_MASK_ON_SUSPEND,
};
  • irq_domain
    申请中断
    在设备树里指定hwirqflag,可以使用irq_domain的函数来解析设备树
    根据hwirq可以分配virq,把(hwirq, virq)存入irq_domain
    发生中断时,从GIC读取hwirq,可以通过irq_domain找到virq,再找到处理函数
    所以GICgic_chip_data来表示,gic_chip_data中重要的成员是:irq_chip、irq_domain

总结

以上都在说中断号映射。
中断号分为硬件中断号(HW ID)和软件中断号(IRQ number)。
在这里插入图片描述
这里有两个中断控制器,处理完毕进入 CPU。外设和中断控制器连接在一起,外设给中断控制器的是硬件中断号,如果中断控制器有级联,那么硬件中断号在不同的中断控制器中可能会重复。但是到了 CPU 以后,我们需要对不同中断控制器给过来的硬件中断号进行翻译,翻译成在软件中唯一的软件中断号,叫 irq number

注意,我们在设备树中配置的是硬件中断号,在软件中申请中断 request_irq 使用的是软件中断号,所以申请之前要先从设备树获取硬件中断号,然后使用 irq_of_parse_and_map 进行翻译再使用

  • 硬件中断号与软件中断号

    Linux kernel 中我们使用下面两个 ID 来标识一个来自外设的中断:

    1、IRQ number。CPU需要为每个外设中断编号,我们称之 IRQ Number。这个 IRQ number 是一个虚拟的 interrupt ID,和硬件无关,仅仅是被 CPU 用来标识个外设中断。

    2、HW interrupt ID。对于 interrupt controller 而言,它收集了多个外设的 interrupt request line 并向上传递,因此,interrupt controller 需要对外设中进行编码。Interrupt controllerHW interrupt ID 来标识外设的中断。在 interrupt controller 级联的情况下,仅仅用 HW interrupt ID 已经不能唯一标识一 个外设中断,还需要知道该 HW interrupt ID 所属的 interrupt controller (HW interrupt ID 在不同的 Interrupt controller 上是会重复编码的) .

    这样,CPU和 interrupt controller 在标识中断上就有了一些不同的概念,但是,对于驱动工程师而言,我们和 CPU 视角是一样的,我们只希望得到一个 IRQ number,而不关心具体是哪个 interrupt controller 上的那个 HW interrupt ID。这样一个好处是在中断相关的硬件发生变化的时候, 驱动软件不需要修改。因此,linux kemel 中的中断子系统需要提供一个将 HW interrupt ID 映射到 IRQ number 上来的机制。

    最早的系统中,中断系统比较简单,可以认为静态的继续映射,可是随着系统的日趋复杂,就不得不采用一种新的方式来管理中断了,那就促使了 irq domain 的产生。

  • 映射方式

    映射方式有三种:

    线性映射:irq_domain_add_linear,

    树映射:irq_domain_create_tree

    不映射:irq_domain_add_nomap

    线性映射:维护固定大小的表,索引是硬件中断号(设备树配置的是硬件中断号) ,如果硬件中断最大数量固定,并且数值不大,可以选择线性映射。

    树映射:硬件中断号可能很大,可以选择树映射。

    不映射:硬件中断号就是 irq number。

    打开 CONFIG_SPARSE_IRQ 宏 (中断编号不连续) 中断描述符以 radix tree 来组织,在初始化时进行动态分配,然后再插入 radix-tree中。关闭 CONFIG_SPARSE_IRQ 宏(中断编号连续) ,中断描述符以数组的形式组织,并且已经分配好。

  • 映射过程
    创建中断映射表流程如下
    在这里插入图片描述

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值