20.3 中断控制器驱动
在Linux内核中,各个设备驱动可以调用request_irq()、enable_irq()、disable_irq()、local_irq_disable()、local_irq_enable()等通用API来完成中断申请、使能、禁止等功能。在将Linux移
植到新的SoC时,芯片供应商需要提供该部分API的底层支持。
local_irq_disable()、local_irq_enable()的实现与具体中断控制器无关,对于ARM v6以上的体系结
构,是直接调用CPSID/CPSIE指令进行,对于ARM v6以前的体系结构,则是通过MRS/MSR指令来读取和设置ARM的CPSR寄存器。由此可见,local_irq_disable()、local_irq_enable()针对的并不是外部的中断控制器,而是直接让CPU本身不响应中断请求。相关的实现位于arch/arm/include/asm/irqflags.h,如代码清单20.3所示。
代码清单20.3 ARM Linux local_irq_disable()/enable()底层实现(C内嵌汇编)
#if __LINUX_ARM_ARCH__ >= 6
static inline unsigned long arch_local_irq_save(void)
{
unsigned long flags;
asm volatile(
" mrs %0, " IRQMASK_REG_NAME_R " @ arch_local_irq_save\n"
" cpsid i"
: "=r" (flags) : : "memory", "cc");
return flags;
}
static inline void arch_local_irq_enable(void)
{
asm volatile(
" cpsie i @ arch_local_irq_enable"
:
:
: "memory", "cc");
}
static inline void arch_local_irq_disable(void)
{
asm volatile(
" cpsid i @ arch_local_irq_disable"
:
:
: "memory", "cc");
}
#else
/*
* Save the current interrupt enable state & disable IRQs
*/
static inline unsigned long arch_local_irq_save(void)
{
unsigned long flags, temp;
asm volatile(
" mrs %0, cpsr @ arch_local_irq_save\n"
" orr %1, %0, #128\n"
" msr cpsr_c, %1"
: "=r" (flags), "=r" (temp)
:
: "memory", "cc");
return flags;
}
/*
* Enable IRQs
*/
static inline void arch_local_irq_enable(void)
{
unsigned long temp;
asm volatile(
" mrs %0, cpsr @ arch_local_irq_enable\n"
" bic %0, %0, #128\n"
" msr cpsr_c, %0"
: "=r" (temp)
:
: "memory", "cc");
}
/*
* Disable IRQs
*/
static inline void arch_local_irq_disable(void)
{
unsigned long temp;
asm volatile(
" mrs %0, cpsr @ arch_local_irq_disable\n"
" orr %0, %0, #128\n"
" msr cpsr_c, %0"
: "=r" (temp)
:
: "memory", "cc");
}
#endif
与local_irq_disable()和local_irq_enable()不同,disable_irq()和enable_irq()针对的则是中断控制器,因此disable_irq()和enable_irq()适用的对象是某个中断。disable_irq()的字面意思是暂时屏蔽掉某中断(其实在内核的实现层面上做了延后屏蔽),直到enable_irq()后再执行ISR。实际上,屏蔽中断可以发生在外设、中断控制器、CPU三个位置,如图20.3所示。
对于外设端,是从源头上就不产生中断信号给中断控制器,由于外设端高度依赖于外设于本身,所以Linux不提供标准的API而是由外设的驱动直接读写自身的寄存器。
在内核中,使用irq_chip结构体来描述中断控制器。该结构体内部封装了中断mask、unmask、ack等成员函数,其定义于include/linux/irq.h中,如代码清单20.4所示。
代码清单20.4 中断控制器irq_chip结构体
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);
unsigned long flags;
};
备注:
各芯片公司会将芯片内部的中断控制器实现为irq_chip驱动的形式。受限于中断控制器硬件的能力,这些成员函数并不一定需要全部实现,有时只需要实现其中的部分函数即可。如
drivers/pinctrl/sirf/pinctrl-sirf.c驱动中的下面代码部分:
static struct irq_chip sirfsoc_irq_chip = {
.name = "sirf-gpio-irq",
.irq_ack = sirfsoc_gpio_irq_ack, // 清中断
.irq_mask = sirfsoc_gpio_irq_mask,// 中断屏蔽
.irq_unmask = sirfsoc_gpio_irq_unmask,// 取消中断屏蔽
.irq_set_type = sirfsoc_gpio_irq_type,// 设置中断触发方式,如高电平、低电平、上升沿、下降沿等
};
至于到enable_irq()时,虽然没有实现irq_enable()成员函数,但是内核会间接调用irq_unmask()成员函数,这点从kernel/irq/chip.c中可以看出:
void irq_enable(struct irq_desc *desc) 在芯片内部,中断控制器可能不止1个,多个中断控制器之间还很可能是级联的。假设芯片内部有一个中断控制器,支持32个中断源,其中有4个来源于GPIO控制器外围的4组GPIO,每组GPIO上又有32个中断(许多芯片的GPIO控制器也同时是一个中断控制器),其关系如图20.4所示。
图20.4 SoC中断控制器的典型分布
一般来讲,在实际操作中,gpio0_0~gpio0_31这些引脚本身在第1级会使用中断号28,而这些引脚本身的中断号在实现与GPIO控制器对应的irq_chip驱动时,我们又会把它映射到Linux系统的32~63号中断。同理,gpio1_0~gpio1_31这些引脚本身在第1级会使用中断号29,而这些引脚本身的中断号在实现与GPIO控制器对应的irq_chip驱动时,我们又会把它映射到Linux系统的64~95号中断,以此类推。对于中断号的使用者而言,无须看到这种2级映射关系。如果某设备想申请与gpio1_0这个引脚对应的中断,这个设备只需要申请64号中断即可。这个关系图看起来如图20.5所示。
图20.5 中断级联与映射
特别注意,上述图20.4和20.5中所涉及的中断号的数值,无论是base还是具体某个GPIO对应的中断号是多少,都不一定是如图20.4和图20.5所描述的简单线性映射。Linux使用IRQ Domain来描述一个中断控制器所管理的中断源。每个中断控制器都有自己的Domain。可以将IRQ Domain看作是IRQ控制器的软件抽象。在添加IRQ Domain的时候,内核中存在的映射方法有:irq_domain_add_legacy()、irq_domain_add_linear()、irq_domain_add_tree()等。
irq_domain_add_legacy()是一种过时的方法,irq_domain_add_legacy()一般是由IRQ控制器驱动直接指定中断源硬件意义上的偏移(一般称为hwirq)和Linux逻辑上的中断号的映射关系。
irq_domain_add_linear()则在中断源和irq_desc之间建立线性映射,内核针对这个IRQ Domain维护了一个hwirq和Linux逻辑IRQ之间关系的一个表,这个时候其实也完全不关心逻辑中断号了。
irq_domain_add_tree()则更加灵活,逻辑中断号和hwirq之间的映射关系是用一棵radix树来描述的,需要通过查找的方法来寻找hwirq和Linux逻辑IRQ之间的关系,一般适合某中断控制器支持非常多中断源的情况。
在当前的内核中,中断号更多的是一个逻辑概念,具体数值是多少不是很关键。更多的是关心在设备树中设置正确的interrupt_parrent和相对该interrupt_parent的偏移。
以drivers/pinctrl/sirf/pinctrl-sirf.c的irq_chip部分为例,在sirfsoc_gpio_probe()函数中,每组GPIO的中
断都通过gpiochip_set_chained_irqchip()级联到上一级中断控制器的中断。
代码清单20.5 二级GPIO中断级联到一级中断控制器
static int sirfsoc_gpio_probe(struct device_node *np)
{
int i, err = 0;
static struct sirfsoc_gpio_chip *sgpio;
struct sirfsoc_gpio_bank *bank;
void __iomem *regs;
struct platform_device *pdev;
u32 pullups[SIRFSOC_GPIO_NO_OF_BANKS], pulldowns[SIRFSOC_GPIO_NO_OF_BANKS];
pdev = of_find_device_by_node(np);
if (!pdev)
return -ENODEV;
sgpio = devm_kzalloc(&pdev->dev, sizeof(*sgpio), GFP_KERNEL);
if (!sgpio)
return -ENOMEM;
spin_lock_init(&sgpio->lock);
regs = of_iomap(np, 0);
if (!regs)
return -ENOMEM;
sgpio->chip.gc.request = sirfsoc_gpio_request;
sgpio->chip.gc.free = sirfsoc_gpio_free;
sgpio->chip.gc.direction_input = sirfsoc_gpio_direction_input;
sgpio->chip.gc.get = sirfsoc_gpio_get_value;
sgpio->chip.gc.direction_output = sirfsoc_gpio_direction_output;
sgpio->chip.gc.set = sirfsoc_gpio_set_value;
sgpio->chip.gc.base = 0;
sgpio->chip.gc.ngpio = SIRFSOC_GPIO_BANK_SIZE * SIRFSOC_GPIO_NO_OF_BANKS;
sgpio->chip.gc.label = kasprintf(GFP_KERNEL, "%pOF", np);
sgpio->chip.gc.of_node = np;
sgpio->chip.gc.of_xlate = sirfsoc_gpio_of_xlate;
sgpio->chip.gc.of_gpio_n_cells = 2;
sgpio->chip.gc.parent = &pdev->dev;
sgpio->chip.regs = regs;
err = gpiochip_add_data(&sgpio->chip.gc, sgpio);
if (err) {
dev_err(&pdev->dev, "%pOF: error in probe function with status %d\n",
np, err);
goto out;
}
err = gpiochip_irqchip_add(&sgpio->chip.gc,
&sirfsoc_irq_chip,
0, handle_level_irq,
IRQ_TYPE_NONE);
if (err) {
dev_err(&pdev->dev,
"could not connect irqchip to gpiochip\n");
goto out_banks;
}
for (i = 0; i < SIRFSOC_GPIO_NO_OF_BANKS; i++) {
bank = &sgpio->sgpio_bank[i];
spin_lock_init(&bank->lock);
bank->parent_irq = platform_get_irq(pdev, i);
if (bank->parent_irq < 0) {
err = bank->parent_irq;
goto out_banks;
}
gpiochip_set_chained_irqchip(&sgpio->chip.gc,&sirfsoc_irq_chip,
bank->parent_irq,// 与这一组GPIO对应的“上级”中断号
sirfsoc_gpio_handle_irq//与bank->parent_irq对应的“上级”中断服务程序
);
} 在sirfsoc_gpio_handle_irq()函数的入口处调用chained_irq_enter()暗示自身进入链式IRQ处理,在函数体内判断具体的GPIO中断,并通过generic_handle_irq()调用最终的外设驱动中的中断服务程序,最后调用chained_irq_exit()暗示自身退出链式IRQ处理,如代码清单20.6所示。
代码清单20.6 “上级”中断服务程序派生到下级
static void sirfsoc_gpio_handle_irq(struct irq_desc *desc)
{
unsigned int irq = irq_desc_get_irq(desc);
struct gpio_chip *gc = irq_desc_get_handler_data(desc);
struct sirfsoc_gpio_chip *sgpio = gpiochip_get_data(gc);
struct sirfsoc_gpio_bank *bank;
u32 status, ctrl;
int idx = 0;
struct irq_chip *chip = irq_desc_get_chip(desc);
int i;
for (i = 0; i < SIRFSOC_GPIO_NO_OF_BANKS; i++) {
bank = &sgpio->sgpio_bank[i];
if (bank->parent_irq == irq)
break;
}
BUG_ON(i == SIRFSOC_GPIO_NO_OF_BANKS);
chained_irq_enter(chip, desc);//进入链式IRQ处理
status = readl(sgpio->chip.regs + SIRFSOC_GPIO_INT_STATUS(bank->id));
if (!status) {
printk(KERN_WARNING
"%s: gpio id %d status %#x no interrupt is flagged\n",
__func__, bank->id, status);
handle_bad_irq(desc);
return;
}
while (status) {
ctrl = readl(sgpio->chip.regs + SIRFSOC_GPIO_CTRL(bank->id, idx));
/*
* Here we must check whether the corresponding GPIO's interrupt
* has been enabled, otherwise just skip it
*/
if ((status & 0x1) && (ctrl & SIRFSOC_GPIO_CTL_INTR_EN_MASK)) {
pr_debug("%s: gpio id %d idx %d happens\n",
__func__, bank->id, idx);
generic_handle_irq(irq_find_mapping(gc->irq.domain, idx +
bank->id * SIRFSOC_GPIO_BANK_SIZE));//调用最终的外设驱动中的中断服务程序
}
idx++;
status = status >> 1;
}
chained_irq_exit(chip, desc);//退出链式IRQ处理
}
用一个实例来呈现这个过程,假设GPIO0_0~31对应上级中断号28,而外设A使用了GPIO0_5(即第0组GPIO的第5个),并假定外设A的中断号为37,即32+5,中断服务程序为deva_isr()。那么,当GPIO0_5中断发生时,内核的调用顺序是:sirfsoc_gpio_handle_irq()->generic_handle_irq()->deva_isr()。如果硬件的中断系统有更深的层次,这种软件上的中断服务程序级联实际上可以有更深的级别。
在上述实例中,GPIO0_0~31的interrupt_parrent是上级中断控制器,而外设A的interrupt_parrent就
是GPIO0,这些都会在设备树中呈现。
很多中断控制器的寄存器定义呈现出简单的规律,如有一个mask寄存器,其中每1位可屏蔽1个中断等,在这种情况下,无须实现1个完整的irq_chip驱动,而可以使用内核提供的通用irq_chip驱动架构irq_chip_generic,这样只需要实现极少量的代码,如drivers/irqchip/irq-sirfsoc.c中,用于注册CSR SiRFprimaII内部中断控制器的代码(见代码清单20.7)。
代码清单20.7 使用generic的irq_chip(中断控制器)框架
static __init void sirfsoc_alloc_gc(void __iomem *base)
{
unsigned int clr = IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
unsigned int set = IRQ_LEVEL;
struct irq_chip_generic *gc;
struct irq_chip_type *ct;
int i;
irq_alloc_domain_generic_chips(sirfsoc_irqdomain, 32, 1, "irq_sirfsoc",
handle_level_irq, clr, set,
IRQ_GC_INIT_MASK_CACHE);
for (i = 0; i < SIRFSOC_NUM_BANKS; i++) {
gc = irq_get_domain_generic_chip(sirfsoc_irqdomain, i * 32);
gc->reg_base = base + i * SIRFSOC_INT_BASE_OFFSET;
ct = gc->chip_types;
ct->chip.irq_mask = irq_gc_mask_clr_bit;
ct->chip.irq_unmask = irq_gc_mask_set_bit;
ct->regs.mask = SIRFSOC_INT_RISC_MASK0;
}
}
irq_chip驱动的入口声明方法形如:
static int __init sirfsoc_irq_init(struct device_node *np,
struct device_node *parent)
{
void __iomem *base = of_iomap(np, 0);
if (!base)
panic("unable to map intc cpu registers\n");
sirfsoc_irqdomain = irq_domain_add_linear(np, SIRFSOC_NUM_IRQS,
&irq_generic_chip_ops, base);
sirfsoc_alloc_gc(base);
writel_relaxed(0, base + SIRFSOC_INT_RISC_LEVEL0);
writel_relaxed(0, base + SIRFSOC_INT_RISC_LEVEL1);
writel_relaxed(0, base + SIRFSOC_INT_RISC_MASK0);
writel_relaxed(0, base + SIRFSOC_INT_RISC_MASK1);
set_handle_irq(sirfsoc_handle_irq);
return 0;
}
IRQCHIP_DECLARE(sirfsoc_intc, "sirf,prima2-intc", sirfsoc_irq_init);
sirf,prima2-intc是设备树中中断控制器的compatible字段,sirfsoc_irq_init是匹配这个compatible字段后运行的初始化函数。
目前多数主流ARM芯片内部的一级中断控制器都使用了ARM公司的GIC,我们几乎不需要实现任何代码,只需要在设备树中添加相关的节点。
如在arch/arm/boot/dts/exynos5250.dtsi中即含有:
gic:interrupt-controller@10481000 {
compatible = "arm,cortex-a9-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0x10481000 0x1000>, <0x10482000 0x2000>;
};
在drivers/irqchip/irq-gic.c,发现GIC驱动的入口声明如下:
IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);
IRQCHIP_DECLARE(arm11mp_gic, "arm,arm11mp-gic", gic_of_init);
IRQCHIP_DECLARE(arm1176jzf_dc_gic, "arm,arm1176jzf-devchip-gic", gic_of_init);
这说明drivers/irqchip/irq-gic.c这个驱动可以兼容arm,gic-400、arm,arm11mp-gic、arm,arm1176jzf-devchip-gic等,但是初始化函数都是统一的gic_of_init。
include/linux/of.h
#define OF_DECLARE_2(table, name, compat, fn) \
_OF_DECLARE(table, name, compat, fn, of_init_fn_2)
include/linux/irqchip.h
/*
* This macro must be used by the different irqchip drivers to declare
* the association between their DT compatible string and their
* initialization function.
*
* @name: name that must be unique accross all IRQCHIP_DECLARE of the
* same file.
* @compstr: compatible string of the irqchip driver
* @fn: initialization function
*/
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)