文章目录
- 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的属性,中断相关参数含义:
- interrupt-parent:表明该外设的interrupt request line物理的连接到了哪一个中断控制器上。
- interrupts:这个属性描述了具体该外设产生的中断的细节信息,包括硬件中断号和触发类型等。
- interrupt-controller:表明该device node就是一个中断控制器。
- #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);elseof_device_make_bus_id(&dev->dev);return dev;
}
of_device_alloc主要做了以下一些工作:
- 调用函数platform_device_alloc分配平台设备
- 调用函数of_irq_count统计节点使用irq的次数
- 遍历reg资源,调用of_address_to_resource函数把设备树地址转换为资源
- 调用函数of_irq_to_resource_table根据设备节点中的中断信息, 构造中断资源
- 设置平台设备的父节点、节点和名字等信息
我们主要关注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主要做了两件事:
- 调用函数of_irq_parse_one解析设备树中的中断信息, 保存在of_phandle_args结构体中
- 调用函数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找到对应的domianif (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, typeif (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);elseirq_dispose_mapping(virq);return 0;}//保存中断触发类型irqd_set_trigger_type(irq_data, type);return virq;
}
irq_create_fwspec_mapping函数主要做了一下几件事:
- 根据fwspec找到对应的domian
- 调用函数irq_domain_translate把设备节点里的中断信息解析出hwirq, type
- 调用函数irq_find_mapping查看这个hwirq是否已经映射,如果已经映射到某个虚拟中断号,则返回;否则往下走
- 调用函数irq_domain_is_hierarchy判断是否为级联中断,如果是则调用函数irq_domain_alloc_irqs根据domian分配虚拟中断号,
- 如果不是级联中断,说明是链式中断,则调用函数irq_create_mapping创建硬中断号和虚拟中断号的映射关系
- 调用函数irq_get_irq_data通过软件中断号查找irq_data,找不到就返回
- 调用函数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_HIERARCHYif (d->ops->translate)//如果translate方法集存在,就调用它return d->ops->translate(d, fwspec, hwirq, type);
#endifif (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函数做了一下几件事:
- /如果translate方法集存在,就调用translate方法后返回
- 如果xlate方法集存在,就调用xlate方法后返回
- 如果转换方法都不存在,则假定中断号
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函数做了以下几件事:
- 调用函数irq_domain_alloc_descs分配和初始化irq_des内存
- 调用函数irq_domain_alloc_irq_data把最外层的irq_data被嵌入到结构体irq_desc中
- 调用函数irq_domain_alloc_irqs_hierarchy调用domain->ops->alloc申请虚拟中断号
- 遍历每一个虚拟中断号,调用函数irq_domain_trim_hierarchy在IRQ域中修剪层次结构,并更新IRQ域的继承关系
- 遍历每一个虚拟中断号,调用函数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做了以下几件事:
- 调用函数irq_find_mapping通过硬中断号找软中断号,找到了就返回
- 调用函数irq_domain_alloc_descs申请软件中断号
- 调用函数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做了以下几件事:
- 初始化硬中断号和domian
- 调用调用map方法进行映射
- 调用函数irq_domain_set_mapping建立了软硬中断号之间的映射关系