参考文档
IHI0048B_b_gic_architecture_specification.pdf
IHI0069E_gic_architecture_specification.pdf
DUI0471M_software_development_guide.pdf
GIC400架构
中断的类型
SGI:software generatedinterrupt,软件出发产生的中断,相当于(Internal-Processor Interrupt,IPI),中断号范围0~15,用于核间通信
PPI:private peripheral interrupt,特定cpu独有,时钟,中断号范围16~31
SPI:shared peripheral interrupt,这是常见的外部设备中断,也定义为共享中断,中断号范围31~xxx
查看/proc/interrupts时,第一列是虚拟中断号,倒数第3列是硬件中断号,内核只关心虚拟中断号,通过irq_desc绑定虚拟中断号和虚拟中断号,发生中断时,可以直接读取硬件中断号,进而获取到虚拟中断号,从而跳到相应的中断处理函数。
内核初始化遍历所有的节点,对硬件中断号和虚拟中断号进行绑定。
const struct of_device_id of_default_bus_match_table[] = {
{ .compatible = "simple-bus", },
...
};
int of_platform_populate(struct device_node *root,const struct of_device_id *matches,const struct of_dev_auxdata *lookup,struct device *parent)
{
struct device_node *child;
root = root ? of_node_get(root) : of_find_node_by_path("/");
for_each_child_of_node(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true);
}
}
static int __init customize_machine(void)
{
of_platform_populate(NULL, of_default_bus_match_table,NULL, NULL);
}
arch_initcall(customize_machine);
把堆栈打印出来,查看调用关系。
[ 0.237489] [<c0195754>] (irq_create_fwspec_mapping) from [<c01958dc>] (irq_create_of_mapping+0x5c/0x7c)
[ 0.237501] [<c01958dc>] (irq_create_of_mapping) from [<c0737b58>] (irq_of_parse_and_map+0x30/0x50)
[ 0.237514] [<c0737b58>] (irq_of_parse_and_map) from [<c0737b9c>] (of_irq_to_resource+0x24/0xd0)
[ 0.237527] [<c0737b9c>] (of_irq_to_resource) from [<c0737c78>] (of_irq_to_resource_table+0x30/0x44)
[ 0.237540] [<c0737c78>] (of_irq_to_resource_table) from [<c0735294>] (of_device_alloc+0xe0/0x180)
[ 0.237552] [<c0735294>] (of_device_alloc) from [<c073537c>] (of_platform_device_create_pdata+0x48/0xb8)
[ 0.237564] [<c073537c>] (of_platform_device_create_pdata) from [<c073567c>] (of_platform_bus_create+0x284/0x388)
[ 0.237577] [<c073567c>] (of_platform_bus_create) from [<c07356e8>] (of_platform_bus_create+0x2f0/0x388)
一般硬件中断号,cpu规格书会提供,如
dts也有相应的信息
#define GIC_SPI 0
#define GIC_PPI 1
gic: interrupt-controller@12001000 {
compatible = "arm,gic-400";
reg = <0x12001000 0x1000>,
<0x12002000 0x2000>,
<0x12004000 0x2000>,
<0x12006000 0x2000>;
#interrupt-cells = <3>;
interrupt-controller;
interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>;
};
arch_timer: timer {
compatible = "arm,armv7-timer";
interrupts = <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>,
<GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_LOW)>;
clock-frequency = <26000000>;
};
interrupt-parent = <&gic>
compatible = "simple-bus";
spi0: spi@70a00000{
interrupts = <GIC_SPI 7 IRQ_TYPE_LEVEL_HIGH>;
};
spi1: spi@70b00000{
interrupts = <GIC_SPI 8 IRQ_TYPE_LEVEL_HIGH>;
};
spi2: spi@70c00000{
interrupts = <GIC_SPI 9 IRQ_TYPE_LEVEL_HIGH>;
};
i2c0: i2c@70500000 {
interrupts = <GIC_SPI 11 IRQ_TYPE_LEVEL_HIGH>;
};
i2c1: i2c@70600000 {
interrupts = <GIC_SPI 12 IRQ_TYPE_LEVEL_HIGH>;
};
i2c2: i2c@70700000 {
interrupts = <GIC_SPI 13 IRQ_TYPE_LEVEL_HIGH>;
};
i2c3: i2c@70800000 {
interrupts = <GIC_SPI 14 IRQ_TYPE_LEVEL_HIGH>;
};
i2c4: i2c@70900000 {
interrupts = <GIC_SPI 15 IRQ_TYPE_LEVEL_HIGH>;
};
这里的中断号会进行固定的运算,转换成真正的硬件中断号。以gic_400为例,
drivers/irqchip/irq-gic.c
如dts中的中断描述为<GIC_SPI 95 IRQ_TYPE_LEVEL_HIGH>;
参数0 值GIC_SPI(0)/GIC_PPI(1)
参数1 95
参数2 IRQ_TYPE_LEVEL_HIGH
默认+16,避开SGI(0~15)
如果是PPI,再+16
也就是
PPI_NEW=PPI_OLD+16
SPI_NEW=SPI_OLD+32
static int gic_irq_domain_translate(struct irq_domain *d,struct irq_fwspec *fwspec,unsigned long *hwirq,unsigned int *type)
{
if (is_of_node(fwspec->fwnode)) {
*hwirq = fwspec->param[1] + 16;
if (!fwspec->param[0])
*hwirq += 16;
*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
return 0;
}
}
static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,unsigned int nr_irqs, void *arg)
{
irq_hw_number_t hwirq;
struct irq_fwspec *fwspec = arg;
gic_irq_domain_translate(domain, fwspec, &hwirq, &type);
gic_irq_domain_map(domain, virq, hwirq);
}
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,
};
static void __init __gic_init_bases(unsigned int gic_nr, int irq_start,void __iomem *dist_base, void __iomem *cpu_base,u32 percpu_offset, struct fwnode_handle *handle)
{
gic->domain = irq_domain_create_linear(handle, gic_irqs,&gic_irq_domain_hierarchy_ops,gic);
gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
gic_irqs = (gic_irqs + 1) * 32;
if (gic_irqs > 1020)
gic_irqs = 1020;
gic->gic_irqs = gic_irqs;//获取硬件irq的数量
set_handle_irq(gic_handle_irq);//设置中断处理函数
}
static int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
__gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset,&node->fwnode);
}
IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);
分析下irq_create_fwspec_mapping函数
int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base,
unsigned int nr_irqs, int node, void *arg,
bool realloc)
{
virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node);//A分配virq和irq_desc结构体
ret = irq_domain_alloc_irqs_recursive(domain, virq, nr_irqs, arg);//hwirq转换成virq,并设定默认的中断handle_irq函数
irq_domain_insert_irq(virq);// C每个硬件irq保存相应的virq,中断过来后,根据硬件irq能直接获取virq
}
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);
}
unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
{
irq_domain_translate(domain, fwspec, &hwirq, &type);
virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
return virq;
}
A.分配virq和irq_desc结构体
struct irq_data {
unsigned int irq;
unsigned long hwirq;
struct irq_domain *domain;
};
struct irq_desc {
struct irq_data irq_data;
irq_flow_handler_t handle_irq;
} ____cacheline_internodealigned_in_smp;
static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node,
struct module *owner)
{
int cpu;
desc->irq_common_data.handler_data = NULL;
desc->irq_common_data.msi_desc = NULL;
desc->irq_data.common = &desc->irq_common_data;
desc->irq_data.irq = irq;
desc->irq_data.chip = &no_irq_chip;
desc->irq_data.chip_data = NULL;
irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS);
irqd_set(&desc->irq_data, IRQD_IRQ_DISABLED);
desc->handle_irq = handle_bad_irq;
desc->depth = 1;
desc->irq_count = 0;
desc->irqs_unhandled = 0;
desc->name = NULL;
desc->owner = owner;
for_each_possible_cpu(cpu)
*per_cpu_ptr(desc->kstat_irqs, cpu) = 0;
desc_smp_init(desc, node);
}
static struct irq_desc *alloc_desc(int irq, int node, struct module *owner)
{
struct irq_desc *desc;
desc = kzalloc_node(sizeof(*desc), gfp, node);
desc_set_defaults(irq, desc, node, owner);
return desc;
}
static int alloc_descs(unsigned int start, unsigned int cnt, int node,struct module *owner)
{
desc = alloc_desc(start + i, node, owner);
}
static void irq_insert_desc(unsigned int irq, struct irq_desc *desc)
{
radix_tree_insert(&irq_desc_tree, irq, desc);
}
int __ref __irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node,struct module *owner)
{
alloc_descs(start, cnt, node, owner);
irq_insert_desc(start, desc);
}
#define irq_alloc_descs(irq, from, cnt, node) \
__irq_alloc_descs(irq, from, cnt, node, THIS_MODULE)
#define irq_alloc_descs_from(from, cnt, node) \
irq_alloc_descs(-1, from, cnt, node)
static int irq_domain_alloc_descs(int virq, unsigned int cnt,
irq_hw_number_t hwirq, int node)
{
unsigned int hint;
virq = irq_alloc_descs_from(hint, cnt, node);
return virq;
}
B.hwirq转换成virq,并设定默认的中断handle_irq函数
static int irq_domain_alloc_irqs_recursive(struct irq_domain *domain,unsigned int irq_base,unsigned int nr_irqs, void *arg)
{
ret = domain->ops->alloc(domain, irq_base, nr_irqs, arg);
}
int irq_domain_set_hwirq_and_chip(struct irq_domain *domain, unsigned int virq,irq_hw_number_t hwirq, struct irq_chip *chip,void *chip_data)
{
struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq);
irq_data->hwirq = hwirq;
irq_data->chip = chip ? chip : &no_irq_chip;
irq_data->chip_data = chip_data;
}
void irq_domain_set_info(struct irq_domain *domain, unsigned int virq,irq_hw_number_t hwirq, struct irq_chip *chip,void *chip_data, irq_flow_handler_t handler,void *handler_data, const char *handler_name)
{
irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, chip_data);
__irq_set_handler(virq, handler, 0, handler_name);
}
static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,irq_hw_number_t hw)
{
struct irq_chip *chip = &gic_chip;
if (hw < 32) {
irq_set_percpu_devid(irq);
irq_domain_set_info(d, irq, hw, chip, d->host_data,
handle_percpu_devid_irq, NULL, NULL);
irq_set_status_flags(irq, IRQ_NOAUTOEN);
} else {
irq_domain_set_info(d, irq, hw, chip, d->host_data,
handle_fasteoi_irq, NULL, NULL);
irq_set_probe(irq);
}
return 0;
}
static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,unsigned int nr_irqs, void *arg)
{
irq_hw_number_t hwirq;
struct irq_fwspec *fwspec = arg;
gic_irq_domain_translate(domain, fwspec, &hwirq, &type);
gic_irq_domain_map(domain, virq, hwirq);
}
C.每个硬件irq保存相应的virq,中断过来后,根据硬件irq能直接获取virq
static void irq_domain_insert_irq(int virq)
{
struct irq_data *data;
for (data = irq_get_irq_data(virq); data; data = data->parent_data) {
struct irq_domain *domain = data->domain;
irq_hw_number_t hwirq = data->hwirq;
if (hwirq < domain->revmap_size) {
domain->linear_revmap[hwirq] = virq;
}
}
}
中断的处理过程
图来源DUI0471M_software_development_guide.pdf
CPSR:程序状态寄存器(current program status register)
SPSR:程序状态保存寄存器(Saved Program Status Register)
Disable interrupts. IRQs are disabled when any exception occurs异常时,硬件会关闭IRQ,所以不用担心中断嵌套
arch/arm/kernel/entry-armv.S
.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r1, =handle_arch_irq //中断处理函数
mov r0, sp
badr lr, 9997f
ldr pc, [r1]
#else
arch_irq_handler_default
#endif
__irq_usr: //从usr进入irq模式
usr_entry
kuser_cmpxchg_check
irq_handler
get_thread_info tsk
mov why, #0
b ret_to_user_from_irq
UNWIND(.fnend )
ENDPROC(__irq_usr)
__irq_svc: //从kernel进入irq模式
svc_entry
irq_handler
#ifdef CONFIG_PREEMPT
get_thread_info tsk
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
ldr r0, [tsk, #TI_FLAGS] @ get flags
teq r8, #0 @ if preempt count != 0
movne r0, #0 @ force flags to 0
tst r0, #_TIF_NEED_RESCHED
blne svc_preempt
#endif
svc_exit r5, irq = 1 @ return from exception
UNWIND(.fnend )
ENDPROC(__irq_svc)
.ltorg
#ifdef CONFIG_PREEMPT
svc_preempt:
mov r8, lr
1: bl preempt_schedule_irq @ irq en/disable is done inside
ldr r0, [tsk, #TI_FLAGS] @ get new tasks TI_FLAGS
tst r0, #_TIF_NEED_RESCHED
reteq r8 @ go again
b 1b
#endif
.macro svc_exit, rpsr, irq = 0
uaccess_restore
@ ARM mode SVC restore
msr spsr_cxsf, \rpsr
#if defined(CONFIG_CPU_V6) || defined(CONFIG_CPU_32v6K)
@ We must avoid clrex due to Cortex-A15 erratum #830321
sub r0, sp, #4 @ uninhabited address
strex r1, r2, [r0] @ clear the exclusive monitor
#endif
ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
* this is the entry point to schedule() from kernel preemption
* off of irq context.
* Note, that this is called and return with irqs disabled. This will
* protect us against recursive calling from irq.
*/
asmlinkage __visible void __sched preempt_schedule_irq(void)
{
enum ctx_state prev_state;
/* Catch callers which need to be fixed */
BUG_ON(preempt_count() || !irqs_disabled());
prev_state = exception_enter();
do {
preempt_disable();
local_irq_enable();
__schedule(true);
local_irq_disable();
sched_preempt_enable_no_resched();
} while (need_resched());
exception_exit(prev_state);
}
上面有将handle_arch_irq重新赋值成gic_handle_irq
irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
irqreturn_t retval = IRQ_NONE;
unsigned int flags = 0, irq = desc->irq_data.irq;
struct irqaction *action = desc->action;//request_irq里设置的action
while (action) {
irqreturn_t res;
res = action->handler(irq, action->dev_id);
switch (res) {
case IRQ_WAKE_THREAD:
__irq_wake_thread(desc, action);/
case IRQ_HANDLED:
flags |= action->flags;
break;
}
}
}
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
irqreturn_t ret;
desc->istate &= ~IRQS_PENDING;
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
raw_spin_unlock(&desc->lock);
ret = handle_irq_event_percpu(desc);
raw_spin_lock(&desc->lock);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
return ret;
}
非gpio方式的中断默认处理函数为handle_fasteoi_irq
void handle_fasteoi_irq(struct irq_desc *desc)
{
struct irq_chip *chip = desc->irq_data.chip;
if (!irq_may_run(desc))
goto out;
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
kstat_incr_irqs_this_cpu(desc);
if (desc->istate & IRQS_ONESHOT)
mask_irq(desc);
handle_irq_event(desc);
}
//根据硬件irq从数组里直接获取virq
unsigned int irq_find_mapping(struct irq_domain *domain,irq_hw_number_t hwirq)
{
/* Check if the hwirq is in the linear revmap. */
if (hwirq < domain->revmap_size)
return domain->linear_revmap[hwirq];
}
#define __irq_enter()
do {
preempt_count_add(HARDIRQ_OFFSET);
} while (0)
/*
* Enter an interrupt context.
*/
void irq_enter(void)
{
__irq_enter();
}
/*
* Exit an interrupt context. Process softirqs if needed and possible:
*/
void irq_exit(void)
{
preempt_count_sub(HARDIRQ_OFFSET);
}
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
desc->handle_irq(desc);
}
int generic_handle_irq(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;
generic_handle_irq_desc(desc);
return 0;
}
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
bool lookup, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);//保存中断寄存器
unsigned int irq = hwirq;
irq_enter();//进入中断上下文
irq = irq_find_mapping(domain, hwirq);//找出virq
generic_handle_irq(irq);//找到相应的handle函数
irq_exit();//退出中断上下文
set_irq_regs(old_regs);//恢复中断寄存器
static inline int handle_domain_irq(struct irq_domain *domain,
unsigned int hwirq, struct pt_regs *regs)
{
return __handle_domain_irq(domain, hwirq, true, regs);
}
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqstat, irqnr;
struct gic_chip_data *gic = &gic_data[0];
void __iomem *cpu_base = gic_data_cpu_base(gic);
do {
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
if (likely(irqnr > 15 && irqnr < 1021)) {
handle_domain_irq(gic->domain, irqnr, regs);
continue;
}
if (irqnr < 16) {
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SMP
smp_rmb();
handle_IPI(irqnr, regs);
#endif
continue;
}
break;
} while (1);
}
上面的描述中,非gpio方式的中断处理函数handle_irq为handle_fasteoi_irq,但我们经常交道是gpio方式的中断,在相应的gpio
平台驱动中会重新赋值,如
irq_set_chained_handler(irq, ap_irq_handler);
static inline void irq_set_chained_handler(unsigned int irq, irq_flow_handler_t handle)
{
__irq_set_handler(irq, handle, 1, NULL);
}
void __irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
const char *name)
{
unsigned long flags;
struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);
if (!desc)
return;
__irq_do_set_handler(desc, handle, is_chained, name);
irq_put_desc_busunlock(desc, flags);
}
__irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle,
int is_chained, const char *name)
{
desc->handle_irq = handle;
}
https://www.cnblogs.com/arnoldlu/p/8659981.html
http://www.wowotech.net/irq_subsystem/interrupt_subsystem_architecture.html
https://blog.youkuaiyun.com/sunsissy/article/details/73791470