天天看点

Linux中断子系统分析之(三):irq domain

Linux中断子系统分析之(一):整体框架

Linux中断子系统分析之(二):通用的中断处理

Linux中断子系统分析之(三):irq domain

Linux中断子系统分析之(四):驱动程序申请中断

irq domain的作用前面也要提到过,来张图:

Linux中断子系统分析之(三):irq domain

irq domain结构体的定义如下所示:

/* include/linux/irqdomain.h */
struct irq_domain {

  /* 系统中的irq_domain都会挂入全局链表irq_domain_list,link就是挂入该链表的节点 */
	struct list_head link; 
	
	//IRQ domain的name
	const char *name;  
  
	//irq_domain操作集              
	const struct irq_domain_ops *ops;  
	void *host_data;
	unsigned int flags;

	......
#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
	struct irq_domain *parent;  /* //支持级联的话,指向父设备 */
#endif

	/* 该domain中最大的硬件中断号 */
	irq_hw_number_t hwirq_max;

	//直接映射的最大hwirq,对于线性、Radix Tree map映射该域为0
	unsigned int revmap_direct_max_irq;

  /* 线性映射的size,对于Radix Tree map和no map,该值等于0 */
	unsigned int revmap_size;
  
  /* 对于Radix Tree map,revmap_tree指向Radix tree的root node */
	struct radix_tree_root revmap_tree;
  
  /* 线性映射使用的lookup table */
	unsigned int linear_revmap[];
};

struct irq_domain_ops {

  /* match是判断一个指定的device_node(node参数)是否和一个irq domain匹配(d参数),
     如果匹配的话,返回1 */
	int (*match)(struct irq_domain *d, struct device_node *node,
		     enum irq_domain_bus_token bus_token);

	//和match的功能是一样的,系统会优先使用select函数
	int (*select)(struct irq_domain *d, struct irq_fwspec *fwspec,
		      enum irq_domain_bus_token bus_token);
  
  /* 创建或者更新硬件中断号(hw参数)和软件中断号(virq参数)的映射关系 */
	int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);
  
    //map和unmap是操作相反的函数
	void (*unmap)(struct irq_domain *d, unsigned int virq);
  
  /* 在DTS文件中,各个使用中断的device node会通过一些属性(interrupts、interrupt-parent属性)
    来提供中断信息,以便kernel可以正确的进行driver的初始化动作。
    而xlate函数就是将指定的设备(node参数)上若干个(intsize参数)中断属性(intspec参数)翻译
   成硬件中断号(保存在out_hwirq参数)和中断trigger类型(保存在out_type) 
*/
	int (*xlate)(struct irq_domain *d, struct device_node *node,
		     const u32 *intspec, unsigned int intsize,
		     unsigned long *out_hwirq, unsigned int *out_type);

#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY  //支持中断控制器级联

	//分配IRQ描述符和中断控制器相关资源
	int (*alloc)(struct irq_domain *d, unsigned int virq,
		     unsigned int nr_irqs, void *arg);

	//释放IRQ描述符和中断控制器相关资源
	void (*free)(struct irq_domain *d, unsigned int virq,
		     unsigned int nr_irqs);

	......

	//和xlate的功能是一样的,系统会优先使用translate
	int (*translate)(struct irq_domain *d, struct irq_fwspec *fwspec,
			 unsigned long *out_hwirq, unsigned int *out_type);
#endif
};
           

每个中断控制器都对应一个irq domain。IRQ domain支持三种映射方式:linear map(线性映射),Radix tree map(树映射),no map(直接映射)。

根据不同的场景选择不同的映射方式:

  1. linear map:维护固定大小的表,索引是硬件中断号,如果硬件中断最大数量固定,并且数值不大,硬件中断号连续,可以选择线性映射;
  2. Radix tree map:硬件中断号可能很大,可以选择树映射;
  3. no map:硬件中断号直接就是Linux的中断号;

三种映射的方式如下图:

Linux中断子系统分析之(三):irq domain

图中描述了三个中断控制器,对应到三种不同的映射方式,各个控制器的硬件中断号可以一样,最终在Linux内核中映射的中断号是唯一的。

注册irq domain

在中断控制器的驱动程序中,都要注册irq domain,不同的映射方式,调用的api也不同。

对于线性映射,其接口API如下:

/* include/linux/irqdomain.h */
static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
					 unsigned int size,  //该interrupt domain支持多少IRQ
					 const struct irq_domain_ops *ops, 
					 void *host_data)
{
	return __irq_domain_add(of_node_to_fwnode(of_node), size, size, 0, ops, host_data);
}
           

对于Radix Tree map,其接口API如下:

static inline struct irq_domain *irq_domain_add_tree(struct device_node *of_node,
					 const struct irq_domain_ops *ops,
					 void *host_data)
{
	return __irq_domain_add(of_node_to_fwnode(of_node), 0, ~0, 0, ops, host_data);
}
           

在系统中只有一个中断控制器的情况下,可以不需要进行映射。对于这种类型的映射,其接口API如下:

static inline struct irq_domain *irq_domain_add_nomap(struct device_node *of_node,
					 unsigned int max_irq,
					 const struct irq_domain_ops *ops,
					 void *host_data)
{
	return __irq_domain_add(of_node_to_fwnode(of_node), 0, max_irq, max_irq, ops, host_data);
}
           

注册irq domain这类接口的逻辑很简单,根据自己的映射类型,初始化struct irq_domain中的各个成员,最后将该irq domain挂入irq_domain_list的全局列表。

下面来分析外设在设备树上描述的中断信息怎么被解析的。

一个外设想使用中断,得说明它接在哪个中断控制器上,硬件中断号是多少,中断触发类型等信息。

比如下列这个例子(下面的源码分析,也是以这个为例):

Linux中断子系统分析之(三):irq domain

interrupt-parent属性描述的信息是该外设的interrupt request line连接到了哪一个中断控制器上;interrupts属性描述了该外设的中断信息,如硬件中断号、中断触发类型等,可以描述多个中断的信息。

来看看中断控制器的设备节点:

Linux中断子系统分析之(三):irq domain

有nterrupt-controller属性,表明是一个中断控制器;#interrupt-cells属性为2,用2个整数描述一个中断。

在设备树上描述号中断信息后,驱动程序可以调用of_irq_get函数获得软件中断号了。

先来张of_irq_get的执行流程图:

Linux中断子系统分析之(三):irq domain

of_irq_get函数定义如下:

int of_irq_get(struct device_node *dev, int index) //index获取哪个中断信息
{
	int rc;
	struct of_phandle_args oirq;
	struct irq_domain *domain;

	//解析设备节点的interrupt-parent、interrupts属性,解析结果保存在oirq
	rc = of_irq_parse_one(dev, index, &oirq);
	if (rc)
		return rc;

	/* 根据中断控制器的device_node,在irq_domain_list链表里找该控制器的irq_domain
     会调用irq_domain->irq_domain_ops->select/match函数,判断device_node是否与控制器的
     irq_domain匹配
   */ 
	domain = irq_find_host(oirq.np);
	if (!domain)
		return -EPROBE_DEFER;



	//重点要分析的函数,函数返回软件中断号
	return irq_create_of_mapping(&oirq);
}

struct of_phandle_args {
	struct device_node *np;  //指向中断控制器的device_node 
	int args_count;          
	uint32_t args[MAX_PHANDLE_ARGS];  
};
           

of_irq_parse_one函数可以总结如下:

Linux中断子系统分析之(三):irq domain

irq_create_of_mapping函数会返回软件中断号,里面涉及硬件中断号到软件中断号的映射等,下面就来分析该函数:

unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{
	struct irq_fwspec fwspec;

	of_phandle_args_to_fwspec(irq_data, &fwspec); //fwspec由irq_data初始化
 
	/*
		struct irq_fwspec {
		struct fwnode_handle *fwnode;         //中断控制器节点np->fwnode
		int param_count;                      // 2
		u32 param[IRQ_DOMAIN_IRQ_SPEC_PARAMS];//param[0] = 14, param[1] = IRQ_TYPE_EDGE_FALLING
	}; 
*/
	return irq_create_fwspec_mapping(&fwspec);
}


unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
{
	struct irq_domain *domain;
	struct irq_data *irq_data;
	irq_hw_number_t hwirq;
	unsigned int type = IRQ_TYPE_NONE;
	int virq;

	//通过irq_fwspec找到中断控制器的irq_domain,在irq_domain_list链表里找 
	if (fwspec->fwnode) {
		domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);
		if (!domain)
			domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_ANY);
	} else {
		domain = irq_default_domain;
	}
	......

	/* 调用irq_domain->irq_domain_ops->translate/xlate,解析fwspec->param
     解析得到的硬件中断号保存在hwirq,中断触发类型保存在type
     如果驱动程序未提供irq_domain->irq_domain_ops->translate/xlate函数,则hwirq为第一个
     参数fwspec->param[0] (14)
   */
	if (irq_domain_translate(domain, fwspec, &hwirq, &type))
		return 0;
	......

	/* 通过硬件中断号找到软件中断号,函数返回0表示硬件中断号与软件中断号还未映射,需要建立映
     射关系
   */
	virq = irq_find_mapping(domain, hwirq);
	if (virq) {
	
		if (type == IRQ_TYPE_NONE || type == irq_get_trigger_type(virq))
			return virq;  
	
	
		if (irq_get_trigger_type(virq) == IRQ_TYPE_NONE) {
			irq_data = irq_get_irq_data(virq);
			if (!irq_data)
				return 0;
	
				irqd_set_trigger_type(irq_data, type); //设置中断触发类型
				return virq;                           //返回软件中断号
		}
	
		......
	
	}

	/* 执行到这里说明还未建立映射关系 */


	if (irq_domain_is_hierarchy(domain)) {
   
		/* 申请一个未使用的软件中断号,内核通过位图记录系统中软件中断号的使用情况;
       会调用到irq_domain->irq_domain_ops->alloc函数,设置软件中断号应的中断描述符
       之后建立映射关系,对于线性映射:irq_domain->linear_revmap[hwirq] = virq
    */
		virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
		......
	} else {
		......
	}

	......

	return virq;
}
           

继续阅读