zoukankan      html  css  js  c++  java
  • 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资料
  • 相关阅读:
    使用shc加密bash脚本程序
    shell加密工具shc的安装和使用
    cgi程序报 Premature end of script headers:
    gearmand安装过程
    解决Gearman 报sqlite3错误
    gearman安装实录
    PHP APC安装与使用
    在Centos上面用yum不能安装redis的朋友看过来
    CentOS 5
    CentOS安装配置MongoDB
  • 原文地址:https://www.cnblogs.com/wanjianjun777/p/10483946.html
Copyright © 2011-2022 走看看