arm64架构的linux中断分析(三)

文章目录

    • 4. 中断的设备树及其处理
      • 4.1 设备树
      • 4.2 内核对设备树的处理
        • 4.2.1 irq_domain_translate
        • 4.2.2 irq_domain_alloc_irqs
        • 4.2.3 irq_create_mapping

4. 中断的设备树及其处理

4.1 设备树

		gpio0: gpio0@fdd60000 {
			compatible = "rockchip,gpio-bank";
			reg = <0x0 0xfdd60000 0x0 0x100>;
			interrupt-parent = <&gic>;
			interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
			clocks = <&pmucru PCLK_GPIO0>, <&pmucru DBCLK_GPIO0>;

			gpio-controller;
			#gpio-cells = <2>;
			interrupt-controller;
			#interrupt-cells = <2>;
		};

这里是瑞芯微3568的gpio0模块,瑞芯微每个gpio有32个引脚,对于Interrupt controller,我们需要定义interrupt-controller和#interrupt-cells的属性,中断相关参数含义:

  1. interrupt-parent:表明该外设的interrupt request line物理的连接到了哪一个中断控制器上。
  2. interrupts:这个属性描述了具体该外设产生的中断的细节信息,包括硬件中断号和触发类型等。
  3. interrupt-controller:表明该device node就是一个中断控制器。
  4. #interrupt-cells:该中断控制器用多少个cell(一个cell就是一个32-bit的单元)描述一个外设的interrupt request line。这个参数决定了使用这个中断控制器的设备的设备树的interrupts怎么写。

4.2 内核对设备树的处理

linux内核的设备树处理函数入口是drivers/of/platform.c文件的of_device_alloc函数,这个函数会在平台设备遍历设备树注册成平台设备的过程中被调用:

struct platform_device *of_device_alloc(struct device_node *np,
				  const char *bus_id,
				  struct device *parent)
{
	struct platform_device *dev;
	int rc, i, num_reg = 0, num_irq;
	struct resource *res, temp_res;

	//分配平台设备
	dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
	if (!dev)
		return NULL;

	/* count the io and irq resources */
	while (of_address_to_resource(np, num_reg, &temp_res) == 0)
		num_reg++;
	num_irq = of_irq_count(np);//统计节点使用irq的次数

	/* Populate the resource table */
	if (num_irq || num_reg) {
		res = kcalloc(num_irq + num_reg, sizeof(*res), GFP_KERNEL);
		if (!res) {
			platform_device_put(dev);
			return NULL;
		}

		dev->num_resources = num_reg + num_irq;
		dev->resource = res;
		for (i = 0; i < num_reg; i++, res++) {
			//把设备树地址转换为资源
			rc = of_address_to_resource(np, i, res);
			WARN_ON(rc);
		}
		//根据设备节点中的中断信息, 构造中断资源
		if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
			pr_debug("not all legacy IRQ resources mapped for %pOFn\n",
				 np);
	}

	dev->dev.of_node = of_node_get(np);//把node节点保存到deb中
	dev->dev.fwnode = &np->fwnode;
	dev->dev.parent = parent ? : &platform_bus;//保存父节点

	if (bus_id)
		dev_set_name(&dev->dev, "%s", bus_id);
	else
		of_device_make_bus_id(&dev->dev);

	return dev;
}

of_device_alloc主要做了以下一些工作:

  1. 调用函数platform_device_alloc分配平台设备
  2. 调用函数of_irq_count统计节点使用irq的次数
  3. 遍历reg资源,调用of_address_to_resource函数把设备树地址转换为资源
  4. 调用函数of_irq_to_resource_table根据设备节点中的中断信息, 构造中断资源
  5. 设置平台设备的父节点、节点和名字等信息

我们主要关注of_irq_to_resource_table函数:

int of_irq_to_resource_table(struct device_node *dev, struct resource *res,
		int nr_irqs)
{
	int i;

	for (i = 0; i < nr_irqs; i++, res++)
		if (of_irq_to_resource(dev, i, res) <= 0)
			break;

	return i;
}

of_irq_to_resource_table函数主要是遍历全部irq,然后调用of_irq_to_resource解析节点中的中断信息,和构造中断资源。我们看of_irq_to_resource这个函数:

int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)
{
	int irq = of_irq_get(dev, index);

	if (irq < 0)
		return irq;

	/* Only dereference the resource if both the
	 * resource and the irq are valid. */
	if (r && irq) {
		const char *name = NULL;

		memset(r, 0, sizeof(*r));
		/*
		 * Get optional "interrupt-names" property to add a name
		 * to the resource.
		 */
		of_property_read_string_index(dev, "interrupt-names", index,
					      &name);

		r->start = r->end = irq;
		r->flags = IORESOURCE_IRQ | irqd_get_trigger_type(irq_get_irq_data(irq));
		r->name = name ? name : of_node_full_name(dev);
	}

	return irq;
}

of_irq_to_resource主要是调用of_irq_get函数找到对应节点的中断号,同时找到其父节点,创建映射关系,最后保存为resource。我们看看of_irq_get:

int of_irq_get(struct device_node *dev, int index)
{
	int rc;
	struct of_phandle_args oirq;
	struct irq_domain *domain;

	//解析设备树中的中断信息, 保存在of_phandle_args结构体中
	rc = of_irq_parse_one(dev, index, &oirq);
	if (rc)
		return rc;

	domain = irq_find_host(oirq.np);
	if (!domain)
		return -EPROBE_DEFER;

	return irq_create_of_mapping(&oirq);//创建中断映射
}

of_irq_get主要做了两件事:

  1. 调用函数of_irq_parse_one解析设备树中的中断信息, 保存在of_phandle_args结构体中
  2. 调用函数irq_create_of_mapping创建中断映射

我们继续看irq_create_of_mapping:

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

	//根据irq_data的信息填充irq_fwspec结构体
	of_phandle_args_to_fwspec(irq_data->np, irq_data->args,
				  irq_data->args_count, &fwspec);

	//创建从fwspec到IRQ号的映射关系
	return irq_create_fwspec_mapping(&fwspec);
}

irq_create_of_mapping函数首先根据irq_data的信息填充irq_fwspec结构体,然后调用函数irq_create_fwspec_mapping创建从fwspec到IRQ号的映射关系。irq_create_fwspec_mapping函数:

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;

	//根据fwspec找到对应的domian
	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;
	}

	if (!domain) {
		pr_warn("no irq domain found for %s !\n",
			of_node_full_name(to_of_node(fwspec->fwnode)));
		return 0;
	}

	//调用irq_domain->ops的translate或xlate,把设备节点里的中断信息解析为hwirq, type
	if (irq_domain_translate(domain, fwspec, &hwirq, &type))
		return 0;

	/*
	 * WARN if the irqchip returns a type with bits
	 * outside the sense mask set and clear these bits.
	 */
	if (WARN_ON(type & ~IRQ_TYPE_SENSE_MASK))
		type &= IRQ_TYPE_SENSE_MASK;

	//看看这个hwirq是否已经映射, 如果virq非0,说明找到了就直接返回
	virq = irq_find_mapping(domain, hwirq);
	if (virq) {
		/*
		 * If the trigger type is not specified or matches the
		 * current trigger type then we are done so return the
		 * interrupt number.
		 */
		if (type == IRQ_TYPE_NONE || type == irq_get_trigger_type(virq))
			return virq;

		/*
		 * If the trigger type has not been set yet, then set
		 * it now and return the interrupt number.
		 */
		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;
		}

		pr_warn("type mismatch, failed to map hwirq-%lu for %s!\n",
			hwirq, of_node_full_name(to_of_node(fwspec->fwnode)));
		return 0;
	}

	//来到这里说明没有找到,需要创建映射
	if (irq_domain_is_hierarchy(domain)) {//如果这个是级联中断
		//根据domian分配虚拟中断号
		virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
		if (virq <= 0)
			return 0;
	} else {//否则就是链式中断
		//创建硬中断号和虚拟中断号的映射关系
		virq = irq_create_mapping(domain, hwirq);
		if (!virq)
			return virq;
	}

	//通过虚拟中断号查找irq_data,找不到就返回
	irq_data = irq_get_irq_data(virq);
	if (!irq_data) {
		if (irq_domain_is_hierarchy(domain))
			irq_domain_free_irqs(virq, 1);
		else
			irq_dispose_mapping(virq);
		return 0;
	}

	//保存中断触发类型
	irqd_set_trigger_type(irq_data, type);

	return virq;
}

irq_create_fwspec_mapping函数主要做了一下几件事:

  1. 根据fwspec找到对应的domian
  2. 调用函数irq_domain_translate把设备节点里的中断信息解析出hwirq, type
  3. 调用函数irq_find_mapping查看这个hwirq是否已经映射,如果已经映射到某个虚拟中断号,则返回;否则往下走
  4. 调用函数irq_domain_is_hierarchy判断是否为级联中断,如果是则调用函数irq_domain_alloc_irqs根据domian分配虚拟中断号,
  5. 如果不是级联中断,说明是链式中断,则调用函数irq_create_mapping创建硬中断号和虚拟中断号的映射关系
  6. 调用函数irq_get_irq_data通过软件中断号查找irq_data,找不到就返回
  7. 调用函数irqd_set_trigger_type保存中断触发类型,返回虚拟中断号

下面我们分小结讲解irq_domain_translate、irq_domain_alloc_irqs和irq_create_mapping这几个函数

4.2.1 irq_domain_translate

static int irq_domain_translate(struct irq_domain *d,
				struct irq_fwspec *fwspec,
				irq_hw_number_t *hwirq, unsigned int *type)
{
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
	if (d->ops->translate)//如果translate方法集存在,就调用它
		return d->ops->translate(d, fwspec, hwirq, type);
#endif
	if (d->ops->xlate)//如果xlate方法集存在,就调用它
		return d->ops->xlate(d, to_of_node(fwspec->fwnode),
				     fwspec->param, fwspec->param_count,
				     hwirq, type);

	//如果转换方法都不存在,则假定中断号
	*hwirq = fwspec->param[0];
	return 0;
}

irq_domain_translate函数做了一下几件事:

  1. /如果translate方法集存在,就调用translate方法后返回
  2. 如果xlate方法集存在,就调用xlate方法后返回
  3. 如果转换方法都不存在,则假定中断号

translate和xlate都是中断控制器驱动注册的时候写好的,后面会详细说。

4.2.2 irq_domain_alloc_irqs

static inline int irq_domain_alloc_irqs(struct irq_domain *domain,
			unsigned int nr_irqs, int node, void *arg)
{
	return __irq_domain_alloc_irqs(domain, -1, nr_irqs, node, arg, false,
				       NULL);
}

int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base,
			    unsigned int nr_irqs, int node, void *arg,
			    bool realloc, const struct irq_affinity_desc *affinity)
{
	int i, ret, virq;

	if (domain == NULL) {
		domain = irq_default_domain;
		if (WARN(!domain, "domain is NULL; cannot allocate IRQ\n"))
			return -EINVAL;
	}

	if (realloc && irq_base >= 0) {
		virq = irq_base;
	} else {
		//分配和初始化irq_des内存
		virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node,
					      affinity);
		if (virq < 0) {
			pr_debug("cannot allocate IRQ(base %d, count %d)\n",
				 irq_base, nr_irqs);
			return virq;
		}
	}

	//最外层的irq_data被嵌入到结构体irq_desc中
	if (irq_domain_alloc_irq_data(domain, virq, nr_irqs)) {
		pr_debug("cannot allocate memory for IRQ%d\n", virq);
		ret = -ENOMEM;
		goto out_free_desc;
	}

	mutex_lock(&irq_domain_mutex);
	//调用domain->ops->alloc申请虚拟中断号
	ret = irq_domain_alloc_irqs_hierarchy(domain, virq, nr_irqs, arg);
	if (ret < 0) {
		mutex_unlock(&irq_domain_mutex);
		goto out_free_irq_data;
	}

	for (i = 0; i < nr_irqs; i++) {
		//在IRQ域中修剪层次结构,并更新IRQ域的继承关系
		ret = irq_domain_trim_hierarchy(virq + i);
		if (ret) {
			mutex_unlock(&irq_domain_mutex);
			goto out_free_irq_data;
		}
	}
	
	for (i = 0; i < nr_irqs; i++)
		//将新的中断描述符加入到IRQ域中,以及将中断描述符与对应的中断控制器进行关联
		irq_domain_insert_irq(virq + i);
	mutex_unlock(&irq_domain_mutex);

	return virq;

out_free_irq_data:
	irq_domain_free_irq_data(virq, nr_irqs);
out_free_desc:
	irq_free_descs(virq, nr_irqs);
	return ret;
}

irq_domain_alloc_irqs直接调用函数__irq_domain_alloc_irqs,__irq_domain_alloc_irqs函数做了以下几件事:

  1. 调用函数irq_domain_alloc_descs分配和初始化irq_des内存
  2. 调用函数irq_domain_alloc_irq_data把最外层的irq_data被嵌入到结构体irq_desc中
  3. 调用函数irq_domain_alloc_irqs_hierarchy调用domain->ops->alloc申请虚拟中断号
  4. 遍历每一个虚拟中断号,调用函数irq_domain_trim_hierarchy在IRQ域中修剪层次结构,并更新IRQ域的继承关系
  5. 遍历每一个虚拟中断号,调用函数irq_domain_insert_irq将新的中断描述符加入到IRQ域中,以及将中断描述符与对应的中断控制器进行关联

4.2.3 irq_create_mapping

static inline unsigned int irq_create_mapping(struct irq_domain *host,
					      irq_hw_number_t hwirq)
{
	//创建映射
	return irq_create_mapping_affinity(host, hwirq, NULL);
}

unsigned int irq_create_mapping_affinity(struct irq_domain *domain,
				       irq_hw_number_t hwirq,
				       const struct irq_affinity_desc *affinity)
{
	struct device_node *of_node;
	int virq;

	pr_debug("irq_create_mapping(0x%p, 0x%lx)\n", domain, hwirq);

	/* Look for default domain if nececssary */
	if (domain == NULL)
		domain = irq_default_domain;
	if (domain == NULL) {
		WARN(1, "%s(, %lx) called with NULL domain\n", __func__, hwirq);
		return 0;
	}
	pr_debug("-> using domain @%p\n", domain);

	of_node = irq_domain_get_of_node(domain);

	//通过硬中断号找软中断号
	virq = irq_find_mapping(domain, hwirq);
	if (virq) {
		pr_debug("-> existing mapping on virq %d\n", virq);
		return virq;
	}

	//申请软件中断号
	virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node),
				      affinity);
	if (virq <= 0) {
		pr_debug("-> virq allocation failed\n");
		return 0;
	}

	//建立软中断号和硬中断号的映射关系
	if (irq_domain_associate(domain, virq, hwirq)) {
		irq_free_desc(virq);
		return 0;
	}

	pr_debug("irq %lu on domain %s mapped to virtual irq %u\n",
		hwirq, of_node_full_name(of_node), virq);

	return virq;
}

irq_create_mapping函数直接调用irq_create_mapping_affinity,irq_create_mapping_affinity做了以下几件事:

  1. 调用函数irq_find_mapping通过硬中断号找软中断号,找到了就返回
  2. 调用函数irq_domain_alloc_descs申请软件中断号
  3. 调用函数irq_domain_associate建立软中断号和硬中断号的映射关系

irq_domain_associate主要是通过线性映射或radix tree映射,以硬中断号为索引,建立硬中断号与软中断号的映射关系:

int irq_domain_associate(struct irq_domain *domain, unsigned int virq,
			 irq_hw_number_t hwirq)
{
	struct irq_data *irq_data = irq_get_irq_data(virq);
	int ret;

	if (WARN(hwirq >= domain->hwirq_max,
		 "error: hwirq 0x%x is too large for %s\n", (int)hwirq, domain->name))
		return -EINVAL;
	if (WARN(!irq_data, "error: virq%i is not allocated", virq))
		return -EINVAL;
	if (WARN(irq_data->domain, "error: virq%i is already associated", virq))
		return -EINVAL;

	mutex_lock(&irq_domain_mutex);
	//初始化硬中断号,软中断号在alloc_desc->desc_set_defaults中初始化
	irq_data->hwirq = hwirq;
	irq_data->domain = domain;
	//调用map方法进行映射
	if (domain->ops->map) {
		ret = domain->ops->map(domain, virq, hwirq);
		if (ret != 0) {
			/*
			 * If map() returns -EPERM, this interrupt is protected
			 * by the firmware or some other service and shall not
			 * be mapped. Don't bother telling the user about it.
			 */
			if (ret != -EPERM) {
				pr_info("%s didn't like hwirq-0x%lx to VIRQ%i mapping (rc=%d)\n",
				       domain->name, hwirq, virq, ret);
			}
			irq_data->domain = NULL;
			irq_data->hwirq = 0;
			mutex_unlock(&irq_domain_mutex);
			return ret;
		}

		/* If not already assigned, give the domain the chip's name */
		if (!domain->name && irq_data->chip)
			domain->name = irq_data->chip->name;
	}

	domain->mapcount++;
	//建立了软硬中断号之间的映射关系
	irq_domain_set_mapping(domain, hwirq, irq_data);
	mutex_unlock(&irq_domain_mutex);

	irq_clear_status_flags(virq, IRQ_NOREQUEST);

	return 0;
}

irq_domain_associate做了以下几件事:

  1. 初始化硬中断号和domian
  2. 调用调用map方法进行映射
  3. 调用函数irq_domain_set_mapping建立了软硬中断号之间的映射关系

你可能感兴趣的:(架构,linux,运维)