Linux中断申请流程

原创文章,转载请注明出处:

本文主要分析linux内核中设备驱动程序是如何申请中断的。中断的申请分为两个阶段,接下来分别

对两个阶段进行分析。

第一阶段:获取软件中断号,irq = platform_get_irq(pdev, 0)
->of_irq_get(dev->dev.of_node, num); drivers/base/platform.c
{
1. 解析dts中device node对中断的配置,rc = of_irq_parse_one(dev, index, &oirq);
{
以下是dts文件中i2c节点关于中断的定义, 这里主要看软解是如何解析中断的
i2c0: i2c@5a800000 
{
                interrupts = ;
                interrupt-parent = <&gic>;
        };

     解析的目的是初始化struct of_phandle_args结构体,该结构体定义如下:
struct of_phandle_args 
{
struct device_node *np;
int args_count;
uint32_t args[MAX_PHANDLE_ARGS];
};

解析的结果为:
out_irq->np = interrupt-parent = gic node
out_irq->args[0] = GIC_SPI;
out_irq->args[1] = 硬件中断号 = 220
out_irq->args[2] = 中断触发类型 = IRQ_TYPE_LEVEL_HIGH
}

2. 找到interrupt controller对应的irq domain,对于i2c来说,其对应的中断控制器为gic。
domain = irq_find_host(oirq.np);
{
irq_find_matching_fwspec() kernel/irq/irqdomain.c 
{
list_for_each_entry(h, &irq_domain_list, link)
{
rc = ((fwnode != NULL) && (h->fwnode == fwnode) &&
  ((bus_token == DOMAIN_BUS_ANY) ||
   (h->bus_token == bus_token)));
   
if (rc) {
                        found = h;
                        break;
                }
}
}

从上面的code可知linux kernel是通过匹配irq_domain_list中的fwnode参数来找当前终端控制器对应的
domain, 那么domain又是在什么时候加入irq_domain_list中的呢?

我们还是以跟终端控制器gic为例进行分析,gic在初始化的时候通过下面的函数创建irq domain。
gic->domain = irq_domain_create_linear(handle, gic_irqs,
&gic_irq_domain_hierarchy_ops, gic);
{
新建一个domain
struct irq_domain *domain;
domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
                              GFP_KERNEL, of_node_to_nid(of_node));
  
初始化新建的domain
INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
domain->ops = ops;
domain->host_data = host_data;
domain->fwnode = fwnode;
domain->hwirq_max = hwirq_max;
domain->revmap_size = size;
domain->revmap_direct_max_irq = direct_max;
irq_domain_check_hierarchy(domain);

将新建的domain添加到全局链表irq_domain_list上
list_add(&domain->link, &irq_domain_list);
}

找到gic终端控制器对应的domain之后,进行map, 将硬件中断号转化为软件中断号
return irq_create_of_mapping(&oirq);
{
1. 将结构体struct of_phandle_args转化为struct irq_fwspec
of_phandle_args_to_fwspec(irq_data, &fwspec);
{
fwspec->fwnode = irq_data->np ? &irq_data->np->fwnode : NULL;
fwspec->param_count = irq_data->args_count;


for (i = 0; i < irq_data->args_count; i++)
fwspec->param[i] = irq_data->args[i];
}

2. 建立映射,irq_create_fwspec_mapping(&fwspec);
{
1. 先找到终端控制器对应的domain
domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);

2. 解析结构体struct irq_fwspec, 获取hwirq和type
irq_domain_translate(domain, fwspec, &hwirq, &type)

3. 查看hwirq是否已经被映射,virq = irq_find_mapping(domain, hwirq)
{
如果virq存在, 说明hwirq已经被映射
}

4. hwirq还未被映射, 建立映射,virq = irq_domain_alloc_irqs(domain, ...);
{
1. 将hwirq和virq建立映射并且为虚拟中断号分配一个struct irq_desc结构体,中断响应
过程中调用的desc->handle_irq就是此处生成的。
virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL);
if (virq <= 0) {
pr_debug("-> virq allocation failed\n");
return 0;
}

2. alloc irq_data
irq_domain_alloc_irq_data(domain, virq, nr_irqs)

3. 调用domain->ops->alloc
irq_domain_alloc_irqs_recursive(domain, virq, nr_irqs, arg);
{
ret = domain->ops->alloc(domain, irq_base, nr_irqs, arg);
对于gic对应的irq domain来说,domain->ops = gic_irq_domain_hierarchy_ops

drivers/irqchip/irq-gic.c
static const struct irq_domain_ops gic_irq_domain_hierarchy_ops = {
.translate = gic_irq_domain_translate,
.alloc = gic_irq_domain_alloc,
.free = irq_domain_free_irqs_top,
};

继续分析gic_irq_domain_alloc()
{
解析struct irq_fwspec, 获取硬件中断号和中断触发类型
ret = gic_irq_domain_translate(domain, fwspec, &hwirq, &type);

总共映射nr_irqs个中断,virq是虚拟中断的base 
for (i = 0; i < nr_irqs; i++)
gic_irq_domain_map(domain, virq + i, hwirq + i);
{
case1: HWIRQ=[0,31], SGI和PPI
irq_set_percpu_devid(irq);
irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
handle_percpu_devid_irq, NULL, NULL);
irq_set_status_flags(irq, IRQ_NOAUTOEN);

case2: HWIRQ >= 32, SPI中断
irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
handle_fasteoi_irq, NULL, NULL);
}

接下里分析一下irq_domain_set_info()做了些什么事情:
irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data, ...);
{
irq_set_chip_and_handler_name(virq, chip, handler, handler_name);
{
irq_data->hwirq = hwirq;
irq_data->chip = chip ? chip : &no_irq_chip;
irq_data->chip_data = chip_data;
}
__irq_set_handler(virq, handler, 0, handler_name);
{
desc->handle_irq = handle = handle_fasteoi_irq;
}
irq_set_handler_data(virq, handler_data);
}
}
}
}

3. 存储中断的触发类型,irqd_set_trigger_type(irq_data, type);
}
}
}
}
platform_get_irq()终于分析完成, 至此我们已经获得了一个虚拟中断号。


第二阶段主要是为上文中获得的中断号分配中断处理函数,现在开始分析第二阶段。
->devm_request_irq(&pdev->dev, irq, i2c_imx_isr, IRQF_NO_SUSPEND, pdev->name, i2c_imx);
->devm_request_threaded_irq(dev, irq, handler, NULL, irqflags, devname, dev_id); kernel/irq/devres.c
->request_threaded_irq(unsigned int irq, irq_handler_t handler,                         kernel/irq/manage.c
   irq_handler_t thread_fn, unsigned long irqflags,
                       const char *devname, void *dev_id) 
{
1. 定义变量struct irqaction *action 和 struct irq_desc *desc;

2. 判断irqflags的合法性, 如果不合法,直接return -EINVAL;


3. 将虚拟的irq转化为desc, desc = irq_to_desc(irq); 上文分析过每一个virq对应一个struct irq_desc


4. 如果handler和thread_fn都不存在,直接return;如果只是handler不存在,使用default handler直接唤醒thread
{
if (!handler) 
{
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}
}


5. 为action分配内存,然后初始化action
{
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
}

6. 使用runtime机制给中断控制器的上电。比如i2c的中断控制器为gic,那么irq_chip_pm_get()给gic上电。

7. __setup_irq(irq, desc, action); kernel/irq/manage.c
{
1. 判断参数的合法性
{
if (!desc)
return -EINVAL;


if (desc->irq_data.chip == &no_irq_chip)
return -ENOSYS;
}

2. struct irqaction *new对应着上文中初始化的action。
new->irq = irq,new->flags |= irqd_get_trigger_type(&desc->irq_data);

3. 如果irq可以设置为theaded, 强制thread
{
irq_setup_forced_threading(new);
{
new->flags |= IRQF_ONESHOT;


if (!new->thread_fn) 
{
set_bit(IRQTF_FORCED_THREAD, &new->thread_flags);
new->thread_fn = new->handler;
new->handler = irq_default_primary_handler;
}
}
}

4. 如果中断处理为thread的方式,创建线程,
{
struct task_struct *t;
static const struct sched_param param = 
{
.sched_priority = MAX_USER_RT_PRIO/2,
};

t = kthread_create(irq_thread, new, "irq/%d-%s", irq, new->name);
->kthread_create_on_node() kernel/kthread.c
{
定义一个struct kthread_create_info并初始化
struct kthread_create_info *create = kmalloc(sizeof(*create),  GFP_KERNEL);

create->threadfn = threadfn;
create->data = data;
create->node = node;
create->done = &done;

将create结构体加入全局的kthread_create_list中
list_add_tail(&create->list, &kthread_create_list);

唤醒kthreadd_task, 创建kthread_create_list中的线程
wake_up_process(kthreadd_task);
{
kthreadd(void *unused) kernel/kthread.c
create_kthread(create);
pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
{
_do_fork(flags|CLONE_VM|CLONE_UNTRACED, kernel/fork.c 
(unsigned long)fn,
(unsigned long)arg, NULL, NULL, 0);
}
}

等待创建过程的完成
if (unlikely(wait_for_completion_killable(&done))) 
{
if (xchg(&create->done, NULL))
return ERR_PTR(-EINTR);

wait_for_completion(&done);
}

获取线程创建的结果, 并free掉结构体task
task = create->result;
kfree(create);
}

sched_setscheduler_nocheck(t, SCHED_FIFO, ¶m);
get_task_struct(t);
new->thread = t;
set_bit(IRQTF_AFFINITY, &new->thread_flags)
}

5. 如果老的desc->action存在,但是和新的action在flag上不match直接退出; 如果match,shared=1

6. 如果不是shared中断
{
1.  ret = irq_request_resources(desc);

2. init_waitqueue_head(&desc->wait_for_threads);

3. 如果new->flag有关于TRIGGER类型的flag, 设置trigger类型
{
if (new->flags & IRQF_TRIGGER_MASK)
ret = __irq_set_trigger(desc, irq,new->flags & IRQF_TRIGGER_MASK);

获得中断控制器对应的chip,struct irq_chip *chip = desc->irq_data.chip;  


chip->irq_set_type(&desc->irq_data, flags), 对于gic来说就是设置cpu interface的寄存器
}

4. 设置desc->istate


5. 如果可以autoenable,使能中断
if (irq_settings_can_autoenable(desc))
irq_startup(desc, true); kernel/irq/chip.c
}

7. 将action续在了desc的action list上,new->irq = irq; *old_ptr = new;

8. irq_pm_install_action(desc, new);

9. 注册proc接口,register_irq_proc(irq, desc);

10 register_handler_proc(irq, new);
}
}

你可能感兴趣的:(Linux)