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 常见的异常类型

  01_ARM架构Linux中常见的异常

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_
ame:
	.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_
ame)

  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函数)。

02_ARM架构 Linux内核的异常处理体系结构

注意:上面的图是基于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
", 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_irqhandle_irq使用chip结构中的函数来清除、屏蔽或重新使能中断,还一一调用用户在action链表中注册的中断处理函数。

2.1.5.4 小结

  irq_desc结构数组、它的成员"struct irq_chip *chip""struct irqaction *action",这3种数据结构构成了异常处理体系的框架。3者的关系如下:

03_Linux中断处理体系结构

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结构成员.chipexynos_irq_eint,其为外部中断对应的底层硬件操作。

  3)调用irq_set_chip_and_handler()初始化外部中断的irq_desc结构成员.handle_irqhandle_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)devdev_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);
}

  需要两个参数:irqdev_id,使用中断号irq定位action链表,再使用dev_idaction链表中找到要卸载的表项,将其移除。

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
");

	ev_press = 1; 	//中断发生
	wake_up_interruptible(&button_waitq);	/* 唤醒休眠的进程 */
  
	printk(DEVICE_NAME " key press2
");

	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!
");

	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!
", 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
");

	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
");

	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!
");

	while (1) {
		read(fd, &key_val, sizeof(key_val));
		printf("key_val = 0x%x
", 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",卸载驱动模块。

参考

  1. 嵌入式Linux应用开发完全手册 - 韦东山,20章
  2. 韦东山第一期视频,第十二课
  3. 迅为iTop4412资料
原文地址:https://www.cnblogs.com/wanjianjun777/p/10483946.html