Linux异常处理体系结构
更新记录
version | status | description | date | author |
---|---|---|---|---|
V1.0 | C | Create Document | 2019.1.1 | John Wan |
V2.0 | A | 添加案例 | 2019.1.13 | John Wan |
status:
C―― Create,
A—— Add,
M—— Modify,
D—— Delete。
注:内核版本 3.0.15
1、异常处理概述
1.1 异常的作用
异常:就是可以打断CPU正常运行流程的一些事情,比如外部中断、未定义的指令、试图修改只读的数据、执行 swi 指令(Software Interrupt Instruction, 软件中断指令)等。当这些事情发生时,CPU暂停当前的程序,先处理异常事件,然后再继续执行被中断的程序。
1.2 常见的异常类型
2、异常处理流程
2.1 异常处理框架
当异常来临时,处理流程是这样:1)保护现场;2)异常处理;3)恢复现场。
那么 LInux内核为应对这么多的硬件环境,是如何找到对应的异常处理函数?
硬件环境:exynos4412
内核版本:linux-kernel 3.0.15
内核要进行异常处理,那么首先就要配置对应硬件的异常向量表。
2.1.1 设置异常向量表
在内核的初始化时,就应配置好异常向量表。
内核的初始化函数:init/main.c
中的 start_kernel()
。
板级的初始化函数:内核初始化中的setup_arch(&command_line)
;
向量表的初始化函数:板级初始化中的early_trap_init()
;
early_trap_init()
被用来设置各种异常的处理向量,包括中断向量。所谓的“向量”,就是一些被安放在固定位置的代码,当发生异常时,CPU 会自动执行这些固定位置上的指令。ARM 架构 CPU 的异常向量基址可以是 0x00000000,也可以是0xffff0000,LInux内核使用后者。该函数将异常向量表复制到 0xffff0000处,部分代码如下:
void __init early_trap_init(void)
{
#if defined(CONFIG_CPU_USE_DOMAINS)
unsigned long vectors = CONFIG_VECTORS_BASE;
#else
unsigned long vectors = (unsigned long)vectors_page;
#endif
......
/*
* 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);
......
}
其中 变量vectors
等于 CONFIG_VECTORS_BASE
,等于 0xffff0000
。地址 __vectors_end ~ __vectors_start
之间就是异常向量。
2.1.2 寻找异常处理函数(C函数)
异常向量的代码很简单,它们只是一些跳转指令。发生异常时,CPU 自动执行这些指令,跳转去执行更复杂的代码,比如保存被中断程序的执行环境,调用异常处理函数,恢复被中断程序的执行环境并重新运行。这些"更复杂的代码"在地址_stubs_end ~ __stubs_start
之间,它们被复制到0xffff0000 + 0x200处
。
arch/arm/kernel/entry-armv.S
中:
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
.globl __vectors_start
__vectors_start:
ARM( swi SYS_ERROR0 ) /*复位时,CPU将执行这条指令*/
THUMB( svc #0 )
THUMB( nop )
W(b) vector_und + stubs_offset /*未定义异常时,CPU执行这*/
W(ldr) pc, .LCvswi + stubs_offset /*swi异常*/
W(b) vector_pabt + stubs_offset /*指令预取中止*/
W(b) vector_dabt + stubs_offset /*数据访问中止*/
W(b) vector_addrexcptn + stubs_offset /*没有用到*/
W(b) vector_irq + stubs_offset /*中断*/
W(b) vector_fiq + stubs_offset /*快速中断*/
.globl __vectors_end
__vectors_end:
其中的"stubs_offset"用来重新定位跳转的位置(向量被复制到地址0xffff0000
处,跳转的目的代码被复制到地址0xffff0000 + 0x200
处)。
其中 vector_und、vector_pabt
等表示要跳转去执行的代码。以vector_irq
为例,它仍处于该文件中,通过vector_stub宏
来定义,代码如下:
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32),在用户模式执行了未定义指令
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32),在FIQ模式执行了未定义指令
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32),在IRQ模式执行了未定义指令
.long __irq_svc @ 3 (SVC_26 / SVC_32),在管理模式执行了未定义指令
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
vector_stub
是一个宏,它根据后面的参数"irq, IRQ_MODE, 4"
定义了以vector_irq
为标号的一段代码,代入以下代码。
/*
* Vector stubs.
*
* This code is copied to 0xffff0200 so we can use branches in the
* vectors, rather than ldr's. Note that this code must not
* exceed 0x300 bytes.
*
* Common stub entry macro:
* Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
*
* SP points to a minimal amount of processor-private memory, the address
* of which is copied into r0 for the mode specific abort handler.
*/
.macro vector_stub, name, mode, correction=0
.align 5
vector_\name:
.if \correction
sub lr, lr, #\correction
.endif
@
@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f
THUMB( adr r0, 1f )
THUMB( ldr lr, [r0, lr, lsl #2] )
mov r0, sp
ARM( ldr lr, [pc, lr, lsl #2] )
movs pc, lr @ branch to handler in SVC mode
ENDPROC(vector_\name)
vector_stub宏
的功能:
1)计算处理完异常后的返回地址;
2)保存一些寄存器(比如:r0,lr,spsr);
3)进入管理模式;
4)最后根据被中断的工作模式调用vector_irq
中的某个跳转分支。
vector_irq
中的代码表示在各个工作模式下执行中断指令时,发生的异常的处理分支。比如"__irq_usr"
表示在用户模式下执行了中断指令时,所发生的中断异常将由它来处理,在其它工作模式下不可能发生中断指令异常,否则使用"__irq_invalid"
来处理错误。
对应的vector_und
中的代码表示在各个工作模式下执行未定义指令时,发生的异常的处理分支。
ARM架构CPU中使用4位数据来表示工作模式(目前只有7种工作模式),所以共有16个跳转分支。不同的跳转分支(比如 __irq_usr、__irq_svc
)只是在它们的入口处(比如保存被中断程序的寄存器)稍有差别,后续的处理大体相同,都是调用C函数。
以外部中断为例:
通过前面的说明调用"__irq_usr"
:
__irq_usr:
usr_entry
kuser_cmpxchg_check
#ifdef CONFIG_IRQSOFF_TRACER
bl trace_hardirqs_off
#endif
get_thread_info tsk
#ifdef CONFIG_PREEMPT
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
add r7, r8, #1 @ increment it
str r7, [tsk, #TI_PREEMPT]
#endif
irq_handler
#ifdef CONFIG_PREEMPT
ldr r0, [tsk, #TI_PREEMPT]
str r8, [tsk, #TI_PREEMPT]
teq r0, r7
ARM( strne r0, [r0, -r0] )
THUMB( movne r0, #0 )
THUMB( strne r0, [r0] )
#endif
mov why, #0
b ret_to_user_from_irq
UNWIND(.fnend )
ENDPROC(__irq_usr)
其中"usr_entry"
:保存相关寄存器;
其中"irq_handler"
:就是异常处理函数入口;
其中"b ret_to_user_from_irq"
:就是返回。
那么通过"irq_handler"
找到"arch_irq_handler_default"
,该函数的原型在"arch/arm/include/asm/entry-macro-multi.S"
中:
/*
* Interrupt handling. Preserves r7, r8, r9
*/
.macro arch_irq_handler_default
get_irqnr_preamble r5, lr
1: get_irqnr_and_base r0, r6, r5, lr
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne lr, BSYM(1b)
bne asm_do_IRQ
其中"asm_do_IRQ"
就是该中断对应的异常处理函数(C函数)。
注意:上面的图是基于linux-kernel 2.6,原理相同,但所处文件位置有差异
小结:"early_trap_init()"
函数搭建了 Linux 异常的处理框架,当异常发生时,内核是如何找到对应异常类型的C处理函数(比如上面的中断,最终找到"asm_do_IRQ"
)。
前面了解了内核是如何找到对应异常的异常处理函数(C函数),那么该函数是如何进行处理的呢?
2.1.3 “asm_do_IRQ()”的作用
"arch/arm/kernel/irq.c"
中函数的原型:
/*
* 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_irq_entry
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()"
:
/*
* Architectures call this to let the generic IRQ layer
* handle an interrupt. If the descriptor is attached to an
* irqchip-style controller then we call the ->handle_irq() handler,
* and it calls __do_IRQ() if it's attached to an irqtype-style controller.
*/
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc);
}
最终是根据中断号,调用以该中断号为下标的数组数据类型成员中的handle_irq:irq_desc[irq].handle_irq(irq, &irq_desc[irq])
。那么 struct irq_desc
是什么东西?
2.1.5 irq_desc
结构数组
Linux 内核将所有的中断统一编号,使用一个irq_desc
结构数组来描述这些中断:每个数组项对应一个中断(也可能是一组中断,共用相同的中断号),里面记录了中断的名称、中断状态、中断标记(比如中断类型,是否共享中断等),并提供了中断的低层硬件访问函数(清除、屏蔽、使能中断)、提供了这个中断的处理函数入口,通过它可以调用用户注册的中断处理函数。原型在include/linux/irqdesc.h
:
/**
* struct irq_desc - interrupt descriptor
* @irq_data: per irq and chip data passed down to chip functions
* @timer_rand_state: pointer to timer rand state struct
* @kstat_irqs: irq stats per cpu
* @handle_irq: highlevel irq-events handler
* @preflow_handler: handler called before the flow handler (currently used by sparc)
* @action: the irq action chain
* @status: status information
* @core_internal_state__do_not_mess_with_it: core internal status information
* @depth: disable-depth, for nested irq_disable() calls
* @wake_depth: enable depth, for multiple irq_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_hint: hint to user space for preferred irq affinity
* @affinity_notify: context for notification of affinity changes
* @pending_mask: pending rebalanced interrupts
* @threads_oneshot: bitfield to handle shared oneshot threads
* @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 {
struct irq_data irq_data;
struct timer_rand_state *timer_rand_state;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
irq_preflow_handler_t preflow_handler;
#endif
struct irqaction *action; /* IRQ action list */
unsigned int status_use_accessors;
unsigned int core_internal_state__do_not_mess_with_it;
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
const struct cpumask *affinity_hint;
struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask;
#endif
#endif
unsigned long threads_oneshot;
atomic_t threads_active;
wait_queue_head_t wait_for_threads;
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
const char *name;
} ____cacheline_internodealigned_in_smp;
内核与驱动程序关于中断的联系就是通过中断对应的irq_desc
结构来沟通的。
2.1.5.1 成员irq_data
include/linux/irq.h
:
/**
* struct irq_data - per irq and irq chip data passed down to chip functions
* @irq: interrupt number
* @node: node index useful for balancing
* @state_use_accessors: status information for irq chip functions.
* Use accessor functions to deal with it
* @chip: low level interrupt hardware access
* @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
* @msi_desc: MSI descriptor
* @affinity: IRQ affinity on SMP
*
* The fields here need to overlay the ones in irq_desc until we
* cleaned up the direct references and switched everything over to
* irq_data.
*/
struct irq_data {
unsigned int irq;
unsigned int node;
unsigned int state_use_accessors;
struct irq_chip *chip;
void *handler_data;
void *chip_data;
struct msi_desc *msi_desc;
#ifdef CONFIG_SMP
cpumask_var_t affinity;
#endif
};
其中irq_chip
结构类型中的成员大多用于操作底层硬件,比如设置寄存器以屏蔽中断、使能中断、清除中断等:
/**
* struct irq_chip - hardware interrupt chip descriptor
*
* @name: name for /proc/interrupts
* @irq_startup: start up the interrupt (defaults to ->enable if NULL)
* @irq_shutdown: shut down the interrupt (defaults to ->disable if NULL)
* @irq_enable: enable the interrupt (defaults to chip->unmask if NULL)
* @irq_disable: disable the interrupt
* @irq_ack: start of a new interrupt
* @irq_mask: mask an interrupt source
* @irq_mask_ack: ack and mask an interrupt source
* @irq_unmask: unmask an interrupt source
* @irq_eoi: end of interrupt
* @irq_set_affinity: set the CPU affinity on SMP machines
* @irq_retrigger: resend an IRQ to the CPU
* @irq_set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
* @irq_set_wake: enable/disable power-management wake-on of an IRQ
* @irq_bus_lock: function to lock access to slow bus (i2c) chips
* @irq_bus_sync_unlock:function to sync and unlock slow bus (i2c) chips
* @irq_cpu_online: configure an interrupt source for a secondary CPU
* @irq_cpu_offline: un-configure an interrupt source for a secondary CPU
* @irq_suspend: function called from core code on suspend once per chip
* @irq_resume: function called from core code on resume once per chip
* @irq_pm_shutdown: function called from core code on shutdown once per chip
* @irq_print_chip: optional to print special chip info in show_interrupts
* @flags: chip specific flags
*
* @release: release function solely used by UML
*/
struct irq_chip {
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_print_chip)(struct irq_data *data, struct seq_file *p);
unsigned long flags;
/* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
void (*release)(unsigned int irq, void *dev_id);
#endif
};
2.1.5.2 成员*action
include/linux/interrupt.h
:用户注册的每个中断处理函数用一个irqaction
结构来表示,一个中断(比如共享中断)可以有多个处理函数,它们的irqaction
结构链接成一个链表,以action
为表头。
/**
* struct irqaction - per interrupt action descriptor
* @handler: interrupt handler function
* @flags: flags (see IRQF_* above)
* @name: name of the device
* @dev_id: cookie to identify the device
* @next: pointer to the next irqaction for shared interrupts
* @irq: interrupt number
* @dir: pointer to the proc/irq/NN/name entry
* @thread_fn: interrupt handler function for threaded interrupts
* @thread: thread pointer for threaded interrupts
* @thread_flags: flags related to @thread
* @thread_mask: bitmask for keeping track of @thread activity
*/
struct irqaction {
irq_handler_t handler;
unsigned long flags;
void *dev_id;
struct irqaction *next;
int irq;
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned long thread_flags;
unsigned long thread_mask;
const char *name;
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;
2.1.5.3 成员handle_irq
typedef void (*irq_flow_handler_t)(unsigned int irq, struct irq_desc *desc);
handle_irq
是这个或这组中断的处理函数入口。发生中断时,总入口函数asm_do_IRQ
将根据中断号调用相应irq_desc
数组项中的handle_irq
。handle_irq
使用chip
结构中的函数来清除、屏蔽或重新使能中断,还一一调用用户在action
链表中注册的中断处理函数。
2.1.5.4 小结
irq_desc结构数组
、它的成员"struct irq_chip *chip"
、"struct irqaction *action"
,这3种数据结构构成了异常处理体系的框架。3者的关系如下:
2.1.6 exynos_irq_eint()
外部中断初始化
前面了解整个架构,及其架构中重要结构、重要函数的功能,那么内核是在哪里对这些进行初始化的呢?
1)由kernel/irq/chip.c
中的__irq_set_handler()
中向该中断对应的irq_desc
结构成员handle_irq
赋值了传入handle
参数。
2)由kernel/irq/chip.c
中的irq_set_chip_and_handler_name()
调用了__irq_set_handler()
,并且还调用了irq_set_chip()
,而该函数是向该中断对应的irq_desc
结构成员chip
赋值了传入chip
参数。
3)由include/linux/irq.h
中的irq_set_chip_and_handler()
调用irq_set_chip_and_handler_name()
。
4)由arch/arm/mach-exynos/irq-eint.c
中的exynos_init_irq_eint()
调用了irq_set_chip_and_handler()
。
那么分析exynos_init_irq_eint()
函数:
1)arch_initcall(exynos_init_irq_eint);
与驱动加载的类似,有了这句,那么在内核进行初始化时,自动调用exynos_init_irq_eint()
函数。
2)调用irq_set_chip_and_handler()
初始化外部中断的irq_desc
结构成员.chip
为exynos_irq_eint
,其为外部中断对应的底层硬件操作。
3)调用irq_set_chip_and_handler()
初始化外部中断的irq_desc
结构成员.handle_irq
为handle_level_irq()
,其为中断入口函数。
static struct irq_chip exynos_irq_eint = {
.name = "exynos-eint",
.irq_mask = exynos_irq_eint_mask,
.irq_unmask = exynos_irq_eint_unmask,
.irq_mask_ack = exynos_irq_eint_maskack,
.irq_ack = exynos_irq_eint_ack,
.irq_set_type = exynos_irq_eint_set_type,
#ifdef CONFIG_PM
.irq_set_wake = s3c_irqext_wake,
#endif
};
int __init exynos_init_irq_eint(void)
{
int irq;
for (irq = 0 ; irq <= 31 ; irq++) {
irq_set_chip_and_handler(IRQ_EINT(irq), &exynos_irq_eint,
handle_level_irq);
set_irq_flags(IRQ_EINT(irq), IRQF_VALID);
}
......
}
arch_initcall(exynos_init_irq_eint);
由handle_level_irq()
函数找到handle_irq_event()
函数,进而找到handle_irq_event_percpu()
函数(kernel/irq/handle.c
)。
handle_irq_event()
函数:1)清中断;2)设置中断状态;3)调用handle_irq_event_percpu()
函数;4)清除中断状态。
handle_irq_event_percpu()
函数:用一个循环,遍历中断对应irq_desc
结构成员action
链表中的所有节点,节点即用户注册的中断服务函数。
irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
unsigned int random = 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);
retval |= res;
action = action->next;
} while (action);
......
}
dev_id
的功能:一个中断号,可以是一个中断,也可以是一组中断(例如外部中断16~31),那么只使用中断号,不能区分同一组中断中的哪个,因此引入dev_id
。例如在action
时需传入dev_id
,用来区分同一中断号中挂载在action
链表中的的多个中断服务函数。
2.2 中断处理流程
1)发生中断时,CPU执行异常向量 vector_irq
的代码。
2)在vector_irq
里面,最终会调用中断处理的总入口函数asm_do_IRQ
。
3)asm_do_IRQ
根据中断号调用irq_desc
数组项中的handle_irq
。
4)handle_irq
会使用chip
成员中的函数来设置硬件,比如清除中断、禁止中断、重新使能中断等。
5)handle_irq
逐个调用用户在action
链表中注册的处理函数。
可见,中断体系结构的初始化就是构造这些数据结构,比如irq_desc
数组项中的handle_irq、chip
等成员。用户注册中断时就是构造action
链表;用户卸载时就是从action
链表中去除不需要的项。
2.3 中断的注册与卸载
2.3.1 “request_irq()”
注册中断
include/linux/interrupt.h
:
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);
}
kernel/irq/manage.c
:
/**
* 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)
return -EINVAL;
desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;
if (!irq_settings_can_request(desc))
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(desc);
retval = __setup_irq(irq, desc, action);
chip_bus_sync_unlock(desc);
if (retval)
kfree(action);
#ifdef CONFIG_DEBUG_SHIRQ_FIXME
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;
}
传入的参数:
1)irq
:中断号,寻找对应的irq_desc
结构。
2)handler
:中断服务函数,申请新的irqaction
,并添加到action
的链表中。
3)flags
:标志,是否共享、触发方式等等。
4)name
:
5)dev
:dev_id
,用来区分同一中断中的不同中断服务函数(action
节点)。
__setup_irq()
函数:
1)将新建的irqaction
结构链入irq_desc[irq]
结构成员的action
链表中,有链表为空不为空两种可能。
2)设置irq_desc[irq]
结构中chip
成员的还没设置的指针,让它们指向一些默认函数。
3)设置中断的触发方式。
4)启动中断。
2.3.2 "free_irq()"
卸载中断
中断是一种很稀缺的资源,当不再使用一个设备时,应该释放它占据的中断。
kernel/irq/manage.c
:
/**
* free_irq - free an interrupt allocated with request_irq
* @irq: Interrupt line to free
* @dev_id: Device identity to free
*
* Remove an interrupt handler. The handler is removed and if the
* interrupt line is no longer in use by any driver it is disabled.
* On a shared IRQ the caller must ensure the interrupt is disabled
* on the card it drives before calling this function. The function
* does not return until any executing interrupts for this IRQ
* have completed.
*
* This function must not be called from interrupt context.
*/
void free_irq(unsigned int irq, void *dev_id)
{
struct irq_desc *desc = irq_to_desc(irq);
if (!desc)
return;
#ifdef CONFIG_SMP
if (WARN_ON(desc->affinity_notify))
desc->affinity_notify = NULL;
#endif
chip_bus_lock(desc);
kfree(__free_irq(irq, dev_id));
chip_bus_sync_unlock(desc);
}
需要两个参数:irq
和dev_id
,使用中断号irq
定位action
链表,再使用dev_id
在action
链表中找到要卸载的表项,将其移除。
3、案例:按键
3.1 轮询方式
3.2 中断方式
驱动源码:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
/*驱动注册的头文件,包含驱动的结构体和注册和卸载的函数*/
#include <linux/platform_device.h>
/*Linux中申请GPIO的头文件*/
#include <linux/gpio.h>
/*三星平台的GPIO配置函数头文件*/
/*三星平台EXYNOS系列平台,GPIO配置参数宏定义头文件*/
#include <plat/gpio-cfg.h>
#include <mach/gpio.h>
/*三星平台4412平台,GPIO宏定义头文件*/
#include <mach/gpio-exynos4.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#define DEVICE_NAME "buttons_irq"
static struct class *buttons_irq_class;
static struct device *buttons_irq_class_dev;
struct pin_desc {
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[5] = {
{EXYNOS4_GPX1(1), 1},
{EXYNOS4_GPX1(2), 2},
{EXYNOS4_GPX3(3), 3},
{EXYNOS4_GPX2(1), 4},
{EXYNOS4_GPX2(0), 5},
};
static unsigned char key_val = 0;
static DECLARE_WAIT_QUEUE_HEAD(button_waitq); //声明一个队列
/* 中断事件标志, 中断服务程序将它置1,third_drv_read将它清0 */
static volatile int ev_press = 0;
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
struct pin_desc *pindesc = (struct pin_desc *)dev_id;
unsigned int pinval;
pinval = gpio_get_value(pindesc->pin);
if (pinval)
key_val = 0x80 | pindesc->key_val;
else
key_val = pindesc->key_val;
printk(DEVICE_NAME " key press1\n");
ev_press = 1; //中断发生
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
printk(DEVICE_NAME " key press2\n");
return IRQ_RETVAL(IRQ_HANDLED);
}
static int buttons_irq_open(struct inode *pinode, struct file *pfile)
{
/* 配置各按键引脚为外部中断 */
request_irq(IRQ_EINT(9), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S1_Home", &pins_desc[0]);
request_irq(IRQ_EINT(10), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S2_Back", &pins_desc[1]);
request_irq(IRQ_EINT(27), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S3_Sleep", &pins_desc[2]);
request_irq(IRQ_EINT(17), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S4_Vol+", &pins_desc[3]);
request_irq(IRQ_EINT(16), buttons_irq, IRQ_TYPE_EDGE_BOTH, "S5_Vol-", &pins_desc[4]);
printk(DEVICE_NAME " I'm open!\n");
return 0;
}
static ssize_t buttons_irq_read(struct file *pfile, char __user *pbuf,
size_t count, loff_t *ploff)
{
if (count != 1)
return -EINVAL;
//如果没有按键动作,休眠
wait_event_interruptible(button_waitq, ev_press);
//如果有按键动作,返回键值
copy_to_user(pbuf, &key_val, 1);
ev_press = 0;
printk(DEVICE_NAME " I'm read key_val %d!\n", key_val);
return 1;
}
static int buttons_irq_release(struct inode *pinode, struct file *pfile)
{
free_irq(IRQ_EINT(9), &pins_desc[0]);
free_irq(IRQ_EINT(10), &pins_desc[1]);
free_irq(IRQ_EINT(27), &pins_desc[2]);
free_irq(IRQ_EINT(17), &pins_desc[3]);
free_irq(IRQ_EINT(16), &pins_desc[4]);
printk(DEVICE_NAME " I'm release\n");
return 0;
}
static struct file_operations buttons_irq_fpos = {
.owner = THIS_MODULE,
.open = buttons_irq_open,
.read = buttons_irq_read,
.release = buttons_irq_release,
};
int major;
static int __init buttons_irq_init(void)
{
/*注册主设备号*/
major = register_chrdev(0, "buttons_irq", &buttons_irq_fpos);
/*注册次设备号*/
buttons_irq_class = class_create(THIS_MODULE, "buttons_irq");
if (IS_ERR(buttons_irq_class))
return PTR_ERR(buttons_irq_class);
buttons_irq_class_dev = device_create(buttons_irq_class, NULL,
MKDEV(major, 0), NULL, "buttons_irq_minor");
printk(DEVICE_NAME " initialized\n");
return 0;
}
static void __exit buttons_irq_exit(void)
{
unregister_chrdev(major, "buttons_irq");
device_unregister(buttons_irq_class_dev);
class_destroy(buttons_irq_class);
//return 0;
}
module_init(buttons_irq_init);
module_exit(buttons_irq_exit);
MODULE_LICENSE("GPL");
疑问:
外部中断引脚的配置 IRQ_EINT(16)
原型处于哪个文件?猜测:需要详细了解 linux 移植的过程,了解各文件的功能。这里也牵涉到一个大问题,如何使用、查找硬件对应的文件。
测试源码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
int main(int argc, char **argv)
{
int fd;
unsigned char key_val;
fd = open("/dev/buttons_irq_minor", O_RDWR);
if (fd < 0)
printf("can't open is!\n");
while (1) {
read(fd, &key_val, sizeof(key_val));
printf("key_val = 0x%x\n", key_val);
}
return 0;
}
测试:
[root@iTOP-4412]# insmod buttons_irq.ko
[ 28.776606] buttons_irq initialized
[root@iTOP-4412]# lsmod
buttons_irq 2690 0 - Live 0xbf000000
[root@iTOP-4412]# ./buttons_irq_test
[ 38.455867] buttons_irq I'm open!
[ 46.710833] buttons_irq key press1
[ 46.712806] buttons_irq key press2
[ 46.716404] buttons_irq I'm read key_val 1!
key_val = 0x1
[ 46.929165] buttons_irq key press1
[ 46.931133] buttons_irq key press2
[ 46.934565] buttons_irq I'm read key_val 129!
key_val = 0x81
[ 56.005297] buttons_irq key press1
[ 56.007275] buttons_irq key press2
[ 56.010717] buttons_irq I'm read key_val 2!
key_val = 0x2
[ 56.216146] buttons_irq key press1
[ 56.218121] buttons_irq key press2
[ 56.221510] buttons_irq key press1
[ 56.224874] buttons_irq key press2
[ 56.228491] buttons_irq I'm read key_val 130!
key_val = 0x82
[ 58.030606] buttons_irq key press1
[ 58.032585] buttons_irq key press2
[ 58.036019] buttons_irq I'm read key_val 3!
key_val = 0x3
[ 58.255054] buttons_irq key press1
[ 58.257035] buttons_irq key press2
[ 58.260490] buttons_irq I'm read key_val 131!
key_val = 0x83
[ 60.100599] buttons_irq key press1
[ 60.102590] buttons_irq key press2
[ 60.106080] buttons_irq I'm read key_val 4!
key_val = 0x4
[ 60.301443] buttons_irq key press1
[ 60.303423] buttons_irq key press2
[ 60.306873] buttons_irq I'm read key_val 132!
key_val = 0x84
[ 61.937725] buttons_irq key press1
[ 61.939703] buttons_irq key press2
[ 61.943151] buttons_irq I'm read key_val 5!
key_val = 0x5
[ 62.161527] buttons_irq key press1
[ 62.163515] buttons_irq key press2
[ 62.167010] buttons_irq I'm read key_val 133!
key_val = 0x85
^C
[ 84.825125] buttons_irq I'm release
双边沿触发,按下对应按键的 15,松开将最高位置位0x810x85。
也可以:
① 加载驱动 "insmod buttons_irq.ko"
;
② 后台运行测试程序 "./buttons_irq_test &"
;
③ 通过命令 “ps”
,查看运行的进程;
④ 通过命令 "cat /proc/interrupts"
,查看注册的中断;
⑤ 然后,通过命令 "kill -9 PID"
,杀死进程,PID通过 ③ 查看。
⑥ 最后,通过命令 "rmmod buttons_irq"
,卸载驱动模块。
参考
- 嵌入式Linux应用开发完全手册 - 韦东山,20章
- 韦东山第一期视频,第十二课
- 迅为iTop4412资料