学习LDD 中断处理
中断初始化:
mx53中,通过
ACHINE_START(MX53_LOCO, "Freescale MX53 LOCO Board")
/* Maintainer: Freescale Semiconductor, Inc. */
.fixup = fixup_android_board,
.map_io = mx5_map_io,
.init_irq = mx5_init_irq,
.init_machine = mxc_board_init,
.timer = &mxc_timer,
MACHINE_END
并在setup_arch函数中调用mx5_init_irq,最终调用到:
/*!
* This function initializes the TZIC hardware and disables all the
* interrupts. It registers the interrupt enable and disable functions
* to the kernel for each interrupt source.
*/
void __init mxc_tzic_init_irq(unsigned long base)
{
int i;
tzic_base = ioremap(base, SZ_4K);
/* put the TZIC into the reset value with
* all interrupts disabled
*/
i = __raw_readl(TZIC_INTCNTL);
__raw_writel(0x80010001, TZIC_INTCNTL);
i = __raw_readl(TZIC_INTCNTL);
__raw_writel(0x1f, TZIC_PRIOMASK);
i = __raw_readl(TZIC_PRIOMASK);
__raw_writel(0x02, TZIC_SYNCCTRL);
i = __raw_readl(TZIC_SYNCCTRL);
for (i = 0; i < 4; i++) {
__raw_writel(0xFFFFFFFF, TZIC_INTSEC0 + i * 4);
}
/* disable all interrupts */
for (i = 0; i < 4; i++) {
__raw_writel(0xFFFFFFFF, TZIC_ENCLEAR0 + i * 4);
}
/* all IRQ no FIQ Warning :: No selection */
for (i = 0; i < TZIC_NUM_IRQS; i++) {
set_irq_chip(i, &mxc_tzic_chip); /*设置irq_desc中断处理结构体数组的chip,包括mask,unmask等函数*/
set_irq_handler(i, handle_level_irq); /*设置中断处理函数入口*/
set_irq_flags(i, IRQF_VALID); /*设置标志位,是valid*/
}
mxc_register_gpios();
printk(KERN_INFO "MXC IRQ initialized\n");
}
desc定义如下:
/**
* struct irq_desc - interrupt descriptor
* @irq: interrupt number for this descriptor
* @timer_rand_state: pointer to timer rand state struct
* @kstat_irqs: irq stats per cpu
* @irq_2_iommu: iommu with this irq
* @handle_irq: highlevel irq-events handler [if NULL, __do_IRQ()]
* @chip: low level interrupt hardware access
* @msi_desc: MSI descriptor
* @handler_data: per-IRQ data for the irq_chip methods
* @chip_data: platform-specific per-chip private data for the chip
* methods, to allow shared chip implementations
* @action: the irq action chain
* @status: status information
* @depth: disable-depth, for nested irq_disable() calls
* @wake_depth: enable depth, for multiple set_irq_wake() callers
* @irq_count: stats field to detect stalled irqs
* @last_unhandled: aging timer for unhandled count
* @irqs_unhandled: stats field for spurious unhandled interrupts
* @lock: locking for SMP
* @affinity: IRQ affinity on SMP
* @node: node index useful for balancing
* @pending_mask: pending rebalanced interrupts
* @threads_active: number of irqaction threads currently running
* @wait_for_threads: wait queue for sync_irq to wait for threaded handlers
* @dir: /proc/irq/ procfs entry
* @name: flow handler name for /proc/interrupts output
*/
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; /* IRQ action list */
unsigned int status; /* IRQ status */
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned long last_unhandled; /* Aging timer for unhandled count */
unsigned int irqs_unhandled;
raw_spinlock_t lock;
#ifdef CONFIG_SMP
cpumask_var_t affinity;
const struct cpumask *affinity_hint;
unsigned int node;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask;
#endif
#endif
atomic_t threads_active;
wait_queue_head_t wait_for_threads;
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
const char *name;
}
中断注册函数:
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
/**
* request_threaded_irq - allocate an interrupt line
* @irq: Interrupt line to allocate
* @handler: Function to be called when the IRQ occurs.
* Primary handler for threaded interrupts
* If NULL and thread_fn != NULL the default
* primary handler is installed
* @thread_fn: Function called from the irq handler thread
* If NULL, no irq thread is created
* @irqflags: Interrupt type flags
* @devname: An ascii name for the claiming device
* @dev_id: A cookie passed back to the handler function
*
* This call allocates interrupt resources and enables the
* interrupt line and IRQ handling. From the point this
* call is made your handler function may be invoked. Since
* your handler function must clear any interrupt the board
* raises, you must take care both to initialise your hardware
* and to set up the interrupt handler in the right order.
*
* If you want to set up a threaded irq handler for your device
* then you need to supply @handler and @thread_fn. @handler ist
* still called in hard interrupt context and has to check
* whether the interrupt originates from the device. If yes it
* needs to disable the interrupt on the device and return
* IRQ_WAKE_THREAD which will wake up the handler thread and run
* @thread_fn. This split handler design is necessary to support
* shared interrupts.
*
* Dev_id must be globally unique. Normally the address of the
* device data structure is used as the cookie. Since the handler
* receives this value it makes sense to use it.
*
* If your interrupt is shared you must pass a non NULL dev_id
* as this is required when freeing the interrupt.
*
* Flags:
*
* IRQF_SHARED Interrupt is shared
* IRQF_SAMPLE_RANDOM The interrupt can be used for entropy
* IRQF_TRIGGER_* Specify active edge(s) or level
*
*/
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{
struct irqaction *action;
struct irq_desc *desc;
int retval;
/*
* Sanity-check: shared interrupts must pass in a real dev-ID,
* otherwise we'll have trouble later trying to figure out
* which interrupt is which (messes up the interrupt freeing
* logic etc).
*/
if ((irqflags & IRQF_SHARED) && !dev_id) /*共享中断状态下,dve_id不能为null*/
return -EINVAL;
desc = irq_to_desc(irq); /*获取desc结构体数组中对于中断号的数组指针,通过此指针初始化并注册中断处理函数*/
if (!desc)
return -EINVAL;
if (desc->status & IRQ_NOREQUEST) /*IRQ can't be requested*/
return -EINVAL;
if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
action->handler = handler; /*初始化中断相关结构体的函数等*/
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
chip_bus_lock(irq, desc);
retval = __setup_irq(irq, desc, action); /*register irq action, irq--> desc --> desc->action*/
chip_bus_sync_unlock(irq, desc);
if (retval)
kfree(action);
#ifdef CONFIG_DEBUG_SHIRQ
if (!retval && (irqflags & IRQF_SHARED)) {
/*
* It's a shared IRQ -- the driver ought to be prepared for it
* to happen immediately, so let's make sure....
* We disable the irq to make sure that a 'real' IRQ doesn't
* run in parallel with our fake.
*/
unsigned long flags;
disable_irq(irq);
local_irq_save(flags);
handler(irq, dev_id);
local_irq_restore(flags);
enable_irq(irq);
}
#endif
return retval;
}
函数参数意义:
irq: 中断号
handle:中断处理函数指针
flags:位掩码,标志位。(SA_INTERRUPT:快速中断,应用在中断禁用状态下,SA_SHAIRQ:设备间共享中断)
dev_name:设备名,用来在/proc/interrupts中显示中断拥有者
dev_id:用于共享的中断信号线,没有强制使用共享方式时,可以设置为null
thread_fn:解释是用于被处理中断的线程调用的函数
__setup_irq:
1.添加irqaction结构到irq_desc的action链表中
2.设置一些irq_chip结构中的函数指针指向默认函数
3.设置中断的触发方式和启动中断中断处理流程:
1. setup_arch->early_trap_init函数
void __init early_trap_init(void)
{
unsigned long vectors = CONFIG_VECTORS_BASE;
extern char __stubs_start[], __stubs_end[];
extern char __vectors_start[], __vectors_end[];
extern char __kuser_helper_start[], __kuser_helper_end[];
int kuser_sz = __kuser_helper_end - __kuser_helper_start;
/*
* Copy the vectors, stubs and kuser helpers (in entry-armv.S)
* into the vector page, mapped at 0xffff0000, and ensure these
* are visible to the instruction stream.
*/
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
/*
* Copy signal return handlers into the vector page, and
* set sigreturn to be a pointer to these.
*/
memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
sizeof(sigreturn_codes));
memcpy((void *)KERN_RESTART_CODE, syscall_restart_code,
sizeof(syscall_restart_code));
flush_icache_range(vectors, vectors + PAGE_SIZE);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}
其中的memcpy函数等,用来将异常向量表拷贝到0xffff0000地址处
以stubs举例,在linux的entry-armV.S函数中,如下:
.globl __stubs_start /*声明全局变量,地址就是下面的__stubs_start*/
__stubs_start:
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32) /*USR模式下中断函数入口*/
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32) /*SVC模式下中断函数入口*/
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
........
__irq_usr和__irq_svc函数,最终都会进入irq_handle函数,在进入asm_do_IRQ函数
/*
* do_IRQ handles all hardware IRQ's. Decoded IRQs should not
* come via this function. Instead, they should provide their
* own 'handler'
*/
asmlinkage void __exception asm_do_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); /*处理函数*/
}
/* AT91 specific workaround */
irq_finish(irq);
irq_exit(); /*退出中断*/
set_irq_regs(old_regs); /*恢复中断寄存器配置*/
}
generic_handle_irq --> generic_handle_irq_desc --> (desc->handle_irq 或者__do_irq)
desc->handle_irq就是在mx5_irq_init中配置的handle_level_irq, 在指向handle_irq_event
/**
* handle_IRQ_event - irq action chain handler
* @irq: the interrupt number
* @action: the interrupt action chain for this irq
*
* Handles the action chain of an irq event
*/
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
irqreturn_t ret, retval = IRQ_NONE;
unsigned int status = 0;
do {
trace_irq_handler_entry(irq, action);
ret = action->handler(irq, action->dev_id); /*执行action*/
trace_irq_handler_exit(irq, action, ret);
switch (ret) {
case IRQ_WAKE_THREAD:
/*
* Set result to handled so the spurious check
* does not trigger.
*/
ret = IRQ_HANDLED;
/*
* 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;
}
/*
* Wake up the handler thread for this
* action. In case the thread crashed and was
* killed we just pretend that we handled the
* interrupt. The hardirq handler above has
* disabled the device interrupt, so no irq
* storm is lurking.
*/
if (likely(!test_bit(IRQTF_DIED,
&action->thread_flags))) {
set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
wake_up_process(action->thread);
}
/* Fall through to add to randomness */
case IRQ_HANDLED:
status |= action->flags;
break;
default:
break;
}
retval |= ret;
action = action->next; /*当中断号是共享模式下,循环执行*/
} while (action);
if (status & IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
local_irq_disable();
return retval;
}
irq_exit
/*
* Exit an interrupt context. Process softirqs if needed and possible:
*/
void irq_exit(void)
{
account_system_vtime(current);
trace_hardirq_exit();
sub_preempt_count(IRQ_EXIT_OFFSET);
if (!in_interrupt() && local_softirq_pending()) /*执行软中断*/
invoke_softirq();
rcu_irq_exit();
#ifdef CONFIG_NO_HZ
/* Make sure that timer wheel updates are propagated */
if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
tick_nohz_stop_sched_tick(0);
#endif
preempt_enable_no_resched();
}
函数执行结束后,会返回到__irq_usr或者__irq_svc两种模式的处理函数,继续执行
1.内核态,只有在preempt_count为0时,schedule()才会被调用
2.内核态,preempt_count不为0,则直接返回至被中断的内核代码。
3.中断前处于用户态时,统一跳转到ret_to_user。
顶半部和底半部:
linux将中断分为顶半部和底半部
顶半部:linux中断实际相应函数,即request_irq注册函数,要求执行时间快,这样,不会使得CPU丢失其他中断
底半部:是被顶半部调用,并在稍后执行的例程,此时,与顶半部不同,所有中断都是打开的。
中断处理流程大体学习完,详细的,如中断号是如何从CPU或者并匹配,处理中断时堆栈的应用,中断标志位的开关闭,中断的嵌套等等,还有待学习。