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

文章目录

  • 5. 次级中断控制器驱动
    • 5.1 链式中断控制器
      • 5.1.1 irq_generic_chip_ops结构体
        • 5.1.1.1 xlate函数
        • 5.1.1.2 map 函数
        • 5.1.1.3 unmap 函数
      • 5.1.2 irq_chip
      • 5.1.3 rockchip_irq_demux
    • 5.2 层级中断控制器

5. 次级中断控制器驱动

中断控制器分为链式中断和层级中断,他们都是多个中断处理程序组成的中断处理链,链式中断适用于任务相对较简单、执行时间短的场景,而层级中断适用于需要更细粒度的控制和响应的场景。

链式中断是通过在中断处理程序中调用下一个中断处理程序来完成的。在链式中断中,每个中断处理程序负责部分工作,并将处理后的数据传递给下一个中断处理程序。当中断被触发时,中断处理器会按照它们在处理链中的顺序依次被调用,直到没有其他中断处理程序为止。链式中断是一种非常灵活的机制,因为它可以使用不同的中断处理程序来处理各种情况。在Linux内核中,在irq_desc结构体中的irqaction链表来维护中断处理程序的链式调用关系。

层级中断是一种多级中断处理机制,其中多个中断处理程序被分层处理。当中断被触发时,内核会按照嵌套的优先顺序,依次调用不同层级的中断处理程序。每个中断处理程序只负责其相应层级的处理,如果处理不了则将其传递给下一级中断处理程序,直到最高层级都无法处理才会返回中断处理结束。层级中断的优点在于能够进行更细粒度的控制,每个层级可以有特定的处理程序,每次中断都会按照优先级依次调用这些处理程序。但它也会造成中断响应时间超时过长,从而会严重影响系统的实时性能。在Linux内核中,使用了类似中断控制器的模型,在每个中断控制器中管理多个中断,其中每个中断对应不同的中断处理程序,比如IOAPIC和APIC都是中断控制器的实现。

5.1 链式中断控制器

链式中断驱动的初始化代码都在rockchip_interrupts_register中,其调用路径为rockchip_gpio_probe→rockchip_gpiolib_register→rockchip_interrupts_register。

static int rockchip_interrupts_register(struct rockchip_pin_bank *bank)
{
	unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
	struct irq_chip_generic *gc;
	int ret;

	bank->domain = irq_domain_create_linear(dev_fwnode(bank->dev), 32,
					&irq_generic_chip_ops, NULL);
	if (!bank->domain) {
		dev_warn(bank->dev, "could not init irq domain for bank %s\n",
			 bank->name);
		return -EINVAL;
	}

	ret = irq_alloc_domain_generic_chips(bank->domain, 32, 1,
					     "rockchip_gpio_irq",
					     handle_level_irq,
					     clr, 0, 0);
	if (ret) {
		dev_err(bank->dev, "could not alloc generic chips for bank %s\n",
			bank->name);
		irq_domain_remove(bank->domain);
		return -EINVAL;
	}

	gc = irq_get_domain_generic_chip(bank->domain, 0);
	if (bank->gpio_type == GPIO_TYPE_V2) {
		gc->reg_writel = gpio_writel_v2;
		gc->reg_readl = gpio_readl_v2;
	}

	gc->reg_base = bank->reg_base;
	gc->private = bank;
	gc->chip_types[0].regs.mask = bank->gpio_regs->int_mask;
	gc->chip_types[0].regs.ack = bank->gpio_regs->port_eoi;
	gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit;
	gc->chip_types[0].chip.irq_mask = irq_gc_mask_set_bit;
	gc->chip_types[0].chip.irq_unmask = irq_gc_mask_clr_bit;
	gc->chip_types[0].chip.irq_enable = rockchip_irq_enable;
	gc->chip_types[0].chip.irq_disable = rockchip_irq_disable;
	gc->chip_types[0].chip.irq_set_wake = irq_gc_set_wake;
	gc->chip_types[0].chip.irq_suspend = rockchip_irq_suspend;
	gc->chip_types[0].chip.irq_resume = rockchip_irq_resume;
	gc->chip_types[0].chip.irq_set_type = rockchip_irq_set_type;
	gc->wake_enabled = IRQ_MSK(bank->nr_pins);

	/*
	 * Linux assumes that all interrupts start out disabled/masked.
	 * Our driver only uses the concept of masked and always keeps
	 * things enabled, so for us that's all masked and all enabled.
	 */
	rockchip_gpio_writel(bank, 0xffffffff, bank->gpio_regs->int_mask);
	rockchip_gpio_writel(bank, 0xffffffff, bank->gpio_regs->port_eoi);
	rockchip_gpio_writel(bank, 0xffffffff, bank->gpio_regs->int_en);
	gc->mask_cache = 0xffffffff;

	irq_set_chained_handler_and_data(bank->irq,
					 rockchip_irq_demux, bank);

	return 0;
}

rockchip_interrupts_register函数主要做了以下几件事:

  1. 调用函数irq_domain_create_linear分配和初始化irq_domain数据
  2. 调用函数irq_alloc_domain_generic_chips分配和初始化irq_chip_generic数据
  3. 调用函数irq_get_domain_generic_chip获取irq_chip_generic指针,
  4. 填充irq_chip_generic->chip_types->chip的方法集
  5. 调用函数irq_set_chained_handler_and_data设置irq_desc[].handle_irq为rockchip_irq_demux,rockchip_irq_demux的功能是分辨是哪一个hwirq, 调用对应的irq_desc[].handle_irq。

gpio中断控制器的初始化就这么简单,这是因为很多工作在设备树被注册为平台设备的时候已经调用GIC的方法完成了初始化工作。当然,我们还要重点关注irq_generic_chip_ops结构体、irq_chip里面的方法和rockchip_irq_demux这个中断处理函数。

5.1.1 irq_generic_chip_ops结构体

irq_generic_chip_ops代码在kernel/irq/generic-chip.c文件中:

struct irq_domain_ops irq_generic_chip_ops = {
	.map	= irq_map_generic_chip,
	.unmap  = irq_unmap_generic_chip,
	.xlate	= irq_domain_xlate_onetwocell,
};
EXPORT_SYMBOL_GPL(irq_generic_chip_ops);

irq_domain_ops 结构体是 Linux 内核中 IRQ 控制界面的一组虚函数集合,用于支持不同类型的 IRQ 控制器,它的主要作用是为了解耦物理设备和CPU和IRQ中断控制器,使得内核能够更好地支持多处理器系统、设备树和ACPI功能等特性。这些函数通过和具体的中断控制器的交互,完成设备树或ACPI描述符和虚拟中断编号之间的映射,使得 IRQ domain 能够正确地分配和管理设备与CPU之间的中断,实现了中断管理的灵活性和可扩展性。

5.1.1.1 xlate函数

一般来说,在DTS文件中,各个使用中断的设备树节点会通过一些属性(例如interrupts和interrupt-parent属性)来提供中断信息给内核,以便内核可以正确的进行驱动的初始化动作。这里,interrupts属性所表示的interrupt specifier只能由具体的中断控制器来解析。而xlate函数就是将指定的设备节点上若干个中断属性解析成硬件中断号和触发类型。

int irq_domain_xlate_onetwocell(struct irq_domain *d,
				struct device_node *ctrlr,
				const u32 *intspec, unsigned int intsize,
				unsigned long *out_hwirq, unsigned int *out_type)
{
	if (WARN_ON(intsize < 1))
		return -EINVAL;
	*out_hwirq = intspec[0];
	if (intsize > 1)
		*out_type = intspec[1] & IRQ_TYPE_SENSE_MASK;
	else
		*out_type = IRQ_TYPE_NONE;
	return 0;
}
EXPORT_SYMBOL_GPL(irq_domain_xlate_onetwocell);

irq_domain_xlate_onetwocell函数可以把设备树的属性转化为硬件中断号和触发类型,硬件中断号放在out_hwirq 指针,触发类型放在out_type 指针。

5.1.1.2 map 函数

一般调用map函数的时机是在创建硬件中断号和软件中断号关系的时候:

int irq_map_generic_chip(struct irq_domain *d, unsigned int virq,
			 irq_hw_number_t hw_irq)
{
	struct irq_data *data = irq_domain_get_irq_data(d, virq);
	struct irq_domain_chip_generic *dgc = d->gc;
	struct irq_chip_generic *gc;
	struct irq_chip_type *ct;
	struct irq_chip *chip;
	unsigned long flags;
	int idx;

	gc = __irq_get_domain_generic_chip(d, hw_irq);
	if (IS_ERR(gc))
		return PTR_ERR(gc);

	idx = hw_irq % dgc->irqs_per_chip;

	if (test_bit(idx, &gc->unused))
		return -ENOTSUPP;

	if (test_bit(idx, &gc->installed))
		return -EBUSY;

	ct = gc->chip_types;
	chip = &ct->chip;

	/* We only init the cache for the first mapping of a generic chip */
	if (!gc->installed) {
		raw_spin_lock_irqsave(&gc->lock, flags);
		irq_gc_init_mask_cache(gc, dgc->gc_flags);
		raw_spin_unlock_irqrestore(&gc->lock, flags);
	}

	/* Mark the interrupt as installed */
	set_bit(idx, &gc->installed);

	if (dgc->gc_flags & IRQ_GC_INIT_NESTED_LOCK)
		irq_set_lockdep_class(virq, &irq_nested_lock_class,
				      &irq_nested_request_class);

	if (chip->irq_calc_mask)
		chip->irq_calc_mask(data);
	else
		data->mask = 1 << idx;

	irq_domain_set_info(d, virq, hw_irq, chip, gc, ct->handler, NULL, NULL);
	irq_modify_status(virq, dgc->irq_flags_to_clear, dgc->irq_flags_to_set);
	return 0;
}

irq_map_generic_chip 函数的作用是维护中断控制器映射表,其步骤为:

  1. 调用函数__irq_get_domain_generic_chip获取irq_chip_generic数据。
  2. 根据硬件中断号和每个芯片支持的最大中断数量计算出这个硬件中断在chip的序号。
  3. 标记芯片的这个中断已经被注册了。
  4. 调用函数irq_domain_set_info设置domain中虚拟中断号的具体信息,比如irq_data、irq_desc的handle函数和handle的参数。
  5. 调用函数irq_modify_status修改中断的触发类型和状态。

5.1.1.3 unmap 函数

unmap是map操作相反的函数,作用是接触硬件中断号和软件中断号的映射关系:

static void irq_unmap_generic_chip(struct irq_domain *d, unsigned int virq)
{
	struct irq_data *data = irq_domain_get_irq_data(d, virq);
	struct irq_domain_chip_generic *dgc = d->gc;
	unsigned int hw_irq = data->hwirq;
	struct irq_chip_generic *gc;
	int irq_idx;

	gc = irq_get_domain_generic_chip(d, hw_irq);
	if (!gc)
		return;

	irq_idx = hw_irq % dgc->irqs_per_chip;

	clear_bit(irq_idx, &gc->installed);
	irq_domain_set_info(d, virq, hw_irq, &no_irq_chip, NULL, NULL, NULL,
			    NULL);

}

irq_unmap_generic_chip主要做了以下两件事:

  1. 调用函数清除installed位,表示该硬件中断没有注册
  2. 调用函数irq_domain_set_info设置domain中虚拟中断号的具体信息,把这些设置为空。

5.1.2 irq_chip

struct irq_chip是Interrupt controller的描述符,包括了若干和具体Interrupt controller相关的callback函数,代码如下:

struct irq_chip {
	struct device	*parent_device;
	const char	*name;
	unsigned int	(*irq_startup)(struct irq_data *data);
	void		(*irq_shutdown)(struct irq_data *data);
	void		(*irq_enable)(struct irq_data *data);
	void		(*irq_disable)(struct irq_data *data);

	void		(*irq_ack)(struct irq_data *data);
	void		(*irq_mask)(struct irq_data *data);
	void		(*irq_mask_ack)(struct irq_data *data);
	void		(*irq_unmask)(struct irq_data *data);
	void		(*irq_eoi)(struct irq_data *data);

	int		(*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
	int		(*irq_retrigger)(struct irq_data *data);
	int		(*irq_set_type)(struct irq_data *data, unsigned int flow_type);
	int		(*irq_set_wake)(struct irq_data *data, unsigned int on);

	void		(*irq_bus_lock)(struct irq_data *data);
	void		(*irq_bus_sync_unlock)(struct irq_data *data);

	void		(*irq_cpu_online)(struct irq_data *data);
	void		(*irq_cpu_offline)(struct irq_data *data);

	void		(*irq_suspend)(struct irq_data *data);
	void		(*irq_resume)(struct irq_data *data);
	void		(*irq_pm_shutdown)(struct irq_data *data);

	void		(*irq_calc_mask)(struct irq_data *data);

	void		(*irq_print_chip)(struct irq_data *data, struct seq_file *p);
	int		(*irq_request_resources)(struct irq_data *data);
	void		(*irq_release_resources)(struct irq_data *data);

	void		(*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg);
	void		(*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg);

	int		(*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state);
	int		(*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state);

	int		(*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info);

	void		(*ipi_send_single)(struct irq_data *data, unsigned int cpu);
	void		(*ipi_send_mask)(struct irq_data *data, const struct cpumask *dest);

	int		(*irq_nmi_setup)(struct irq_data *data);
	void		(*irq_nmi_teardown)(struct irq_data *data);

	unsigned long	flags;
};

整理成表格:

成员 描述
name 该中断控制器的名字,用于/proc/interrupts中的显示
irq_startup start up 指定的irq domain上的HW interrupt ID。如果不设定的话,default会被设定为enable函数
irq_shutdown shutdown 指定的irq domain上的HW interrupt ID。如果不设定的话,default会被设定为disable函数
irq_enable enable指定的irq domain上的HW interrupt ID。如果不设定的话,default会被设定为unmask函数
irq_disable disable指定的irq domain上的HW interrupt ID。
irq_ack 和具体的硬件相关,有些中断控制器必须在Ack之后(清除pending的状态)才能接受到新的中断。
irq_mask mask指定的irq domain上的HW interrupt ID
irq_mask_ack mask并ack指定的irq domain上的HW interrupt ID。
irq_unmask mask指定的irq domain上的HW interrupt ID
irq_eoi 有些interrupt controler(例如GIC)提供了这样的寄存器接口,让CPU可以通知interrupt controller,它已经处理完一个中断
irq_set_affinity 在SMP的情况下,可以通过该callback函数设定CPU affinity
irq_retrigger 重新触发一次中断,一般用在中断丢失的场景下。如果硬件不支持retrigger,可以使用软件的方法。
irq_set_type 设定指定的irq domain上的HW interrupt ID的触发方式,电平触发还是边缘触发
irq_set_wake 和电源管理相关,用来enable/disable指定的interrupt source作为唤醒的条件。
irq_bus_lock 有些interrupt controller是连接到慢速总线上(例如一个i2c接口的IO expander芯片),在访问这些芯片的时候需要lock住那个慢速bus(只能有一个client在使用I2C bus)
irq_bus_sync_unlock unlock慢速总线
irq_suspend 电源管理相关的callback函数
irq_resume 电源管理相关的callback函数
irq_pm_shutdown 电源管理相关的callback函数
irq_print_chip /proc/interrupts中的信息显示
	gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit;
	gc->chip_types[0].chip.irq_mask = irq_gc_mask_set_bit;
	gc->chip_types[0].chip.irq_unmask = irq_gc_mask_clr_bit;
	gc->chip_types[0].chip.irq_enable = rockchip_irq_enable;
	gc->chip_types[0].chip.irq_disable = rockchip_irq_disable;
	gc->chip_types[0].chip.irq_set_wake = irq_gc_set_wake;
	gc->chip_types[0].chip.irq_suspend = rockchip_irq_suspend;
	gc->chip_types[0].chip.irq_resume = rockchip_irq_resume;
	gc->chip_types[0].chip.irq_set_type = rockchip_irq_set_type;

5.1.3 rockchip_irq_demux

5.2 层级中断控制器

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