1中断寄存器map
在中断来临时,必然要读写相应的中断寄存器,来判断中断源,调用不同的中断处理函数,所以在系统初始化的时候,就给中断寄存器做了相应的静态映射。
start_kernel
-------------->setup_arch
-------------->paging_init
---------------->devicemaps_init
---------------- >mdesc->map_io
------------------>smdk2440_map_io
------------------>iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc));
static struct map_desc s3c_iodesc[] __initdata = {
IODESC_ENT(GPIO),
IODESC_ENT(IRQ),
IODESC_ENT(MEMCTRL),
IODESC_ENT(UART)
};
IODESC_ENT(IRQ),是个宏定义,里面包含了中断寄存器的物理地址以及需要静态映射的虚拟地址,至此,中断寄存器页表建立完毕。
2 中断处理函数初始化
2.1 重要数据结构介绍
前面一篇文章介绍过,irq的中断处理总入口是handle_arch_irq,那么如何设置handle_arch_irq呢,以及在这个函数里面具体都做什么,在详细分析前,先看一下几个关键的数据结构。
Linux内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断;每个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称、中断状态、中断标记(比如中断类型、是否共享中断等),并提供了中断的低层硬件访问函数(清除、屏蔽、使能中断),提供了这个中断的处理函数入口,通过它可以调用用户注册的中断处理函数。通过irq_desc结构数组就可以了解中断处理体系结构,irq_desc结构的数据类型include/linux/irq.h中定义:
struct irq_desc {
unsigned int irq;
struct timer_rand_state *timer_rand_state;
unsigned int *kstat_irqs;
#ifdef CONFIG_INTR_REMAP
struct irq_2_iommu *irq_2_iommu;
#endif
irq_flow_handler_t handle_irq; // 当前中断的处理函数入口
struct irq_chip *chip; //低层的硬件访问
struct msi_desc *msi_desc;
void *handler_data;
void *chip_data;
struct irqaction *action; // 用户提供的中断处理函数链表
unsigned int status; //IRQ状态
........
const char *name; //中断的名称
}
handle_irq是这个或这组中断的处理函数入口。发生中断时,总入口函数handle_arch_irq将根据中断号调用相应irq_desc数组项中handle_irq
,
handle_irq使用chip结构中的函数清除、屏蔽或者重新使能中断,还要调用用户在action链表中注册的中断处理函数。irq_chip结构类型也是在include/linux/irq.h中定义,其中的成员大多用于操作底层硬件,比如设置寄存器以屏蔽中断,使能中断,清除中断等。
struct irq_chip {
const char *name;
unsigned int (*startup)(unsigned int irq);//启动中断,如果不设置,缺省为“enable
void (*shutdown)(unsigned int irq);/*关闭中断,如果不设置,缺省为"disable"*/
void (*enable)(unsigned int irq);// 使用中断,如果不设置,缺省为"unmask"
void (*disable)(unsigned int irq);//禁止中断,如果不设置,缺省为“mask”
void (*ack)(unsigned int irq);/*响应中断,通常是清除当前中断使得可以接收下一个中断*/
void (*mask)(unsigned int irq); //屏蔽中断源
void (*mask_ack)(unsigned int irq);//屏蔽和响应中断
void (*unmask)(unsigned int irq);//开启中断源
void (*eoi)(unsigned int irq);
........
const char *typename;
};
irq_desc结构中的irqaction结构类型在include/linux/iterrupt.h中定义。用户注册的每个中断处理函数用一个irqaction结构来表示,一个中断比如共享中断可以有多个处理函数,它们的irqaction结构链接成一个链表,以action为表头。irqation结构定义如下:
struct irqaction {
irq_handler_t handler; //用户注册的中断处理函数
unsigned long flags; //中断标志,比如是否共享中断,电平触发还是边沿触发
const char *name; //用户注册的中断名字
void *dev_id; //用户传给上面的handler的参数,还可以用来区分共享中断
struct irqaction *next; //指向下一个用户注册函数的指针
int irq; //中断号
struct proc_dir_entry *dir;
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned long thread_flags;
};
irq_desc结构数组、它的成员“struct irq_chip *chip” "struct irqaction *action",这3种数据结构构成了中断处理体系的框架。下图中描述了Linxu中断处理体系结构的关系图:
2.2 中断处理函数初始化
start_kernel
----------->early_irq_init
----------->init_IRQ
early_irq_init函数中先初始化中断描述符数组:
int __init early_irq_init(void)
{
int count, i, node = first_online_node;
struct irq_desc *desc;
init_irq_default_affinity();
printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS);
desc = irq_desc;
count = ARRAY_SIZE(irq_desc);
for (i = 0; i < count; i++) {
desc[i].kstat_irqs = alloc_percpu(unsigned int);
alloc_masks(&desc[i], GFP_KERNEL, node);
raw_spin_lock_init(&desc[i].lock);
lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
desc_set_defaults(i, &desc[i], node, NULL);
}
return arch_early_irq_init();
}
接着初始化各个描述符的中断处理函数:
void __init init_IRQ(void)
{
if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
irqchip_init();
else
machine_desc->init_irq();
}
可以看到如果定义了device tree,但是没有定义machine_desc->init_irq,则利用device tree相关的方式来处理中断处理函数,我们这边定义了machine_desc->init_irq,该函数是s3c2440_init_irq:
void __init s3c2440_init_irq(void)
{
pr_info("S3C2440: IRQ Support\n");
#ifdef CONFIG_FIQ
init_FIQ(FIQ_START);
#endif
设置主中断源相关的中断
s3c_intc[0] = s3c24xx_init_intc(NULL, &init_s3c2440base[0], NULL,
0x4a000000);
if (IS_ERR(s3c_intc[0])) {
pr_err("irq: could not create main interrupt controller\n");
return;
}
设置扩展的外部中断源中断,外部中断4-7,8-13在主中断源中只用一位表示,用0x560000a4
地址开始的外部中断寄存器进一步描述中断位
s3c24xx_init_intc(NULL, &init_eint[0], s3c_intc[0], 0x560000a4);
初始化子中断相关的中断
s3c_intc[1] = s3c24xx_init_intc(NULL, &init_s3c2440subint[0],
s3c_intc[0], 0x4a000018);
}
上面函数初始化每个中断描述符,并为其设置相应的处理中断处理函数。子中断和外部扩展中断,都依赖于主中断寄存器中的位,再看s3c24xx_init_intc:
static struct s3c_irq_intc * __init s3c24xx_init_intc(struct device_node *np,
struct s3c_irq_data *irq_data,
struct s3c_irq_intc *parent,
unsigned long address)
{
struct s3c_irq_intc *intc;
void __iomem *base = (void *)0xf6000000; /* static mapping */中断寄存器静态映射地址
int irq_num;
int irq_start;
int ret;
intc = kzalloc(sizeof(struct s3c_irq_intc), GFP_KERNEL);
if (!intc)
return ERR_PTR(-ENOMEM);
intc->irqs = irq_data;
if (parent) 如果有父中断控制器,则设置父中断控制器
intc->parent = parent;
/* select the correct data for the controller.
* Need to hard code the irq num start and offset
* to preserve the static mapping for now
*/
switch (address) {
case 0x4a000000: 主中断源
pr_debug("irq: found main intc\n");
intc->reg_pending = base;
intc->reg_mask = base + 0x08;
intc->reg_intpnd = base + 0x10;
irq_num = 32;
irq_start = S3C2410_IRQ(0);
break;
case 0x4a000018: 子中断源
pr_debug("irq: found subintc\n");
intc->reg_pending = base + 0x18;
intc->reg_mask = base + 0x1c;
irq_num = 29;
irq_start = S3C2410_IRQSUB(0);
break;
case 0x4a000040:
pr_debug("irq: found intc2\n");
intc->reg_pending = base + 0x40;
intc->reg_mask = base + 0x48;
intc->reg_intpnd = base + 0x50;
irq_num = 8;
irq_start = S3C2416_IRQ(0);
break;
case 0x560000a4: 外部扩展中断源
pr_debug("irq: found eintc\n");
base = (void *)0xfd000000;
intc->reg_mask = base + 0xa4;
intc->reg_pending = base + 0xa8;
irq_num = 24;
irq_start = S3C2410_IRQ(32);
break;
default:
pr_err("irq: unsupported controller address\n");
ret = -EINVAL;
goto err;
}
/* now that all the data is complete, init the irq-domain */
s3c24xx_clear_intc(intc);清楚中断寄存器的相应位
intc->domain = irq_domain_add_legacy(np, irq_num, irq_start,
0, &s3c24xx_irq_ops,
intc);
if (!intc->domain) {
pr_err("irq: could not create irq-domain\n");
ret = -EINVAL;
goto err;
}
set_handle_irq(s3c24xx_handle_irq);设置irq总中断入口程序为s3c24xx_handle_irq
return intc;
err:
kfree(intc);
return ERR_PTR(ret);
}
主要看一下irq_domain_add_legacy函数:
struct irq_domain *irq_domain_add_legacy(struct device_node *of_node,
unsigned int size,
unsigned int first_irq,
irq_hw_number_t first_hwirq,
const struct irq_domain_ops *ops,
void *host_data)
{
struct irq_domain *domain;
unsigned int i;
domain = irq_domain_alloc(of_node, IRQ_DOMAIN_MAP_LEGACY, ops, host_data);
if (!domain)
return NULL;
domain->revmap_data.legacy.first_irq = first_irq;当前寄存器的中断号起始地址
domain->revmap_data.legacy.first_hwirq = first_hwirq;在当前中断寄存器中的偏移
domain->revmap_data.legacy.size = size; 当前中断寄存器中断的数量
mutex_lock(&irq_domain_mutex);
/* Verify that all the irqs are available */
for (i = 0; i < size; i++) {
int irq = first_irq + i;
struct irq_data *irq_data = irq_get_irq_data(irq);
if (WARN_ON(!irq_data || irq_data->domain)) {
mutex_unlock(&irq_domain_mutex);
irq_domain_free(domain);
return NULL;
}
}
/* Claim all of the irqs before registering a legacy domain */
for (i = 0; i < size; i++) {
struct irq_data *irq_data = irq_get_irq_data(first_irq + i);
irq_data->hwirq = first_hwirq + i;
irq_data->domain = domain;
}
mutex_unlock(&irq_domain_mutex);
for (i = 0; i < size; i++) {
int irq = first_irq + i;
int hwirq = first_hwirq + i;
/* IRQ0 gets ignored */
if (!irq)
continue;
/* Legacy flags are left to default at this point,
* one can then use irq_create_mapping() to
* explicitly change them
*/
if (ops->map) 调用map函数,为每个中断描述符设置中断处理函数,以及硬件相关的函数
ops->map(domain, irq, hwirq);
/* Clear norequest flags */
irq_clear_status_flags(irq, IRQ_NOREQUEST);
}
把domain挂载到irq_domain_list链表上
irq_domain_add(domain);
return domain;
}
在ops->map中,为每个中断描述符设置中断处理函数,其实就是s3c24xx_irq_map函数:
static int s3c24xx_irq_map(struct irq_domain *h, unsigned int virq,
irq_hw_number_t hw)
{
struct s3c_irq_intc *intc = h->host_data;
struct s3c_irq_data *irq_data = &intc->irqs[hw];
struct s3c_irq_intc *parent_intc;
struct s3c_irq_data *parent_irq_data;
unsigned int irqno;
/* attach controller pointer to irq_data */
irq_data->intc = intc;
irq_data->offset = hw;
parent_intc = intc->parent;
/* set handler and flags */
switch (irq_data->type) {
case S3C_IRQTYPE_NONE:
return 0;
case S3C_IRQTYPE_EINT:
/* On the S3C2412, the EINT0to3 have a parent irq
* but need the s3c_irq_eint0t4 chip
*/
if (parent_intc && (!soc_is_s3c2412() || hw >= 4))
irq_set_chip_and_handler(virq, &s3c_irqext_chip,
handle_edge_irq);
else
irq_set_chip_and_handler(virq, &s3c_irq_eint0t4,
handle_edge_irq);
break;
case S3C_IRQTYPE_EDGE:
if (parent_intc || intc->reg_pending == S3C2416_SRCPND2)
irq_set_chip_and_handler(virq, &s3c_irq_level_chip,
handle_edge_irq);
else
irq_set_chip_and_handler(virq, &s3c_irq_chip,
handle_edge_irq);
break;
case S3C_IRQTYPE_LEVEL:
if (parent_intc)
irq_set_chip_and_handler(virq, &s3c_irq_level_chip,
handle_level_irq);
else
irq_set_chip_and_handler(virq, &s3c_irq_chip,
handle_level_irq);
break;
default:
pr_err("irq-s3c24xx: unsupported irqtype %d\n", irq_data->type);
return -EINVAL;
}
irq_set_chip_data(virq, irq_data);
set_irq_flags(virq, IRQF_VALID);
如果当前设置的中断具有主中断,则需要修改主中断的处理函数为s3c_irq_demux
if (parent_intc && irq_data->type != S3C_IRQTYPE_NONE) {
if (irq_data->parent_irq > 31) {
pr_err("irq-s3c24xx: parent irq %lu is out of range\n",
irq_data->parent_irq);
goto err;
}
parent_irq_data = &parent_intc->irqs[irq_data->parent_irq];
parent_irq_data->sub_intc = intc;
parent_irq_data->sub_bits |= (1UL << hw);
/* attach the demuxer to the parent irq */
获取主中断的中断号
irqno = irq_find_mapping(parent_intc->domain,
irq_data->parent_irq);
if (!irqno) {
pr_err("irq-s3c24xx: could not find mapping for parent irq %lu\n",
irq_data->parent_irq);
goto err;
}
设置主中断的处理函数为s3c_irq_demux
irq_set_chained_handler(irqno, s3c_irq_demux);
}
return 0;
err:
set_irq_flags(virq, 0);
/* the only error can result from bad mapping data*/
return -EINVAL;
}
上面根据不同的中断出发类型,设置不同的处理函数,同时如果设置了主中断控制器的情况下parent_intc,则需要做一些不同的处理,在中断来临时,如果是主中断发生的中断,则可以直接调用相应的而函数处理,而如果是子中断,则需要去读取子中断,获得真正的中断号来处理,处理上会有一些区别。举个例子,如果只是设置外部中断0-3,则其就是主中断,中断处理设置函数为irq_set_chip_and_handler(virq, &s3c_irq_eint0t4,handle_edge_irq);中断描述符的handle为handle_edge_irq,但是如果外部中断号大于3,则需要先设置当前子中断描述符处理函数为handle_edge_irq,设置中断描述符的handle为s3c_irq_demux,在这边处理子中断。
2.3 中断处理函数调用
设置完中断处理函数以后,看一下中断如何调用。中断总入口函数:
asmlinkage void __exception_irq_entry s3c24xx_handle_irq(struct pt_regs *regs)
{
do {
if (likely(s3c_intc[0]))
if (s3c24xx_handle_intc(s3c_intc[0], regs, 0))
continue;
if (s3c_intc[2])
if (s3c24xx_handle_intc(s3c_intc[2], regs, 64))
continue;
break;
} while (1);
}
s3c_intc[2] 这边没有使用,传入的struct pt_regs *regs参数其实就是之前保存现场的那些寄存器,可以参考第一章。
static inline int s3c24xx_handle_intc(struct s3c_irq_intc *intc,
struct pt_regs *regs, int intc_offset)
{
int pnd;
int offset;
int irq;
pnd = __raw_readl(intc->reg_intpnd);
if (!pnd)
return false;
/* non-dt machines use individual domains */
if (!intc->domain->of_node)
intc_offset = 0;
/* We have a problem that the INTOFFSET register does not always
* show one interrupt. Occasionally we get two interrupts through
* the prioritiser, and this causes the INTOFFSET register to show
* what looks like the logical-or of the two interrupt numbers.
*
* Thanks to Klaus, Shannon, et al for helping to debug this problem
*/
offset = __raw_readl(intc->reg_intpnd + 4);
/* Find the bit manually, when the offset is wrong.
* The pending register only ever contains the one bit of the next
* interrupt to handle.
*/
if (!(pnd & (1 << offset)))
offset = __ffs(pnd);
irq = irq_find_mapping(intc->domain, intc_offset + offset);
handle_IRQ(irq, regs);
return true;
}
s3c24xx_handle_intc先处理主中断寄存器,根据寄存器请求位,找到主中断寄存器的中断号,然后调用handle_IRQ。
void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
irq_enter();
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (unlikely(irq >= nr_irqs)) {
if (printk_ratelimit())
printk(KERN_WARNING "Bad IRQ%u\n", irq);
ack_bad_irq(irq);
} else {
generic_handle_irq(irq); 主要的中断处理函数
}
irq_exit(); 退出的时候会检查是否有软中断,如果有,则调用软中断
set_irq_regs(old_regs);
}
handle_IRQ
----------->generic_handle_irq
------------>generic_handle_irq_desc
------------>desc->handle_irq
这边分两种情况,如果该中断号下面不再包含子中断,则根据触发方式不同,中断处理函数类型应该如下:
(1)handle_edge_irq
(2)handle_level_irq
如果中断号下面包含子中断,例如主中断为EINT4_7,则还包含4,5,6,7 4个子中断,则其主中断处理函数为s3c_irq_demux,先看该函数:
static void s3c_irq_demux(unsigned int irq, struct irq_desc *desc)
{
struct irq_chip *chip = irq_desc_get_chip(desc);
struct s3c_irq_data *irq_data = irq_desc_get_chip_data(desc);
struct s3c_irq_intc *intc = irq_data->intc;
struct s3c_irq_intc *sub_intc = irq_data->sub_intc;
unsigned long src;
unsigned long msk;
unsigned int n;
unsigned int offset;
/* we're using individual domains for the non-dt case
* and one big domain for the dt case where the subintc
* starts at hwirq number 32.
*/
offset = (intc->domain->of_node) ? 32 : 0;
chained_irq_enter(chip, desc);调用chip操作函数集清除主中断位,并设置该中断屏蔽位
src = __raw_readl(sub_intc->reg_pending);
msk = __raw_readl(sub_intc->reg_mask);
src &= ~msk;
src &= irq_data->sub_bits;
while (src) {
n = __ffs(src);
src &= ~(1 << n); 根据子中断寄存器,计算出子中断号,调用generic_handle_irq
irq = irq_find_mapping(sub_intc->domain, offset + n);
generic_handle_irq(irq);
}
chained_irq_exit(chip, desc);清除该中断屏蔽位
}
可以看到s3c_irq_demux函数会根据子中断寄存器计算出子中断号,然后再一次调用generic_handle_irq,即generic_handle_irq_desc,再看一下handle_edge_irq如何处理。
void
handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
/*
* If we're currently running this IRQ, or its disabled,
* we shouldn't process the IRQ. Mark it pending, handle
* the necessary masking and go out
*/
if (unlikely(irqd_irq_disabled(&desc->irq_data) ||
irqd_irq_inprogress(&desc->irq_data) || !desc->action)) {
if (!irq_check_poll(desc)) {
desc->istate |= IRQS_PENDING;
mask_ack_irq(desc);
goto out_unlock;
}
}
kstat_incr_irqs_this_cpu(irq, desc);
/* Start handling the irq */
desc->irq_data.chip->irq_ack(&desc->irq_data);
do {
if (unlikely(!desc->action)) {
mask_irq(desc);
goto out_unlock;
}
/*
* When another irq arrived while we were handling
* one, we could have masked the irq.
* Renable it, if it was not disabled in meantime.
*/
if (unlikely(desc->istate & IRQS_PENDING)) {
if (!irqd_irq_disabled(&desc->irq_data) &&
irqd_irq_masked(&desc->irq_data))
unmask_irq(desc);
}
handle_irq_event(desc);具体中断处理函数
} while ((desc->istate & IRQS_PENDING) &&
!irqd_irq_disabled(&desc->irq_data));
out_unlock:
raw_spin_unlock(&desc->lock);
}
中断处理的核心函数是handle_irq_event
handle_irq_event
----------->handle_irq_event_percpu
irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
irqreturn_t retval = IRQ_NONE;
unsigned int flags = 0, irq = desc->irq_data.irq;
do {
irqreturn_t res;
trace_irq_handler_entry(irq, action);
res = action->handler(irq, action->dev_id);
trace_irq_handler_exit(irq, action, res);
if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
irq, action->handler))
local_irq_disable();
switch (res) {
case IRQ_WAKE_THREAD:
/*
* Catch drivers which return WAKE_THREAD but
* did not set up a thread function
*/
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}
irq_wake_thread(desc, action);
/* Fall through to add to randomness */
case IRQ_HANDLED:
flags |= action->flags;
break;
default:
break;
}
retval |= res;
action = action->next;
} while (action);
add_interrupt_randomness(irq, flags);
if (!noirqdebug)
note_interrupt(irq, desc, retval);
return retval;
}
可以看到,最终的执行处理函数是action->handler,而action->handler是在驱动程序调用request_irq的时候注册的,当使用共享中断的时候,会有多个action。最后总结一下中断处理的调用过程:
(1)中断向量调用总入口函数s3c24xx_handle_irq
(2)根据中断寄存器,计算出中断号调用不同的中断处理函数,对于电平触发的中断,这个入口函数通常为handle_level_irq,对于边沿触发的中断,这个入口通常为handle_edge_irq。而对于有子中断的中断号,则需要先调用s3c_irq_demux处理获取子中断的中断号
(3)逐个调用用户在irq_desc[irq].aciton链表中注册的中断处理函数网上找到一张不错的结构图:
引用博文:https://www.cnblogs.com/hoys/archive/2011/04/13/2015322.html