内核版本:linux2.6.22.6 硬件平台:JZ2440
a. 异常向量入口: archarmkernelentry-armv.S .section .vectors, "ax", %progbits .L__vectors_start: W(b) vector_rst W(b) vector_und W(ldr) pc, .L__vectors_start + 0x1000 W(b) vector_pabt W(b) vector_dabt W(b) vector_addrexcptn W(b) vector_irq W(b) vector_fiq b. 中断向量: vector_irq /* * Interrupt dispatcher */ vector_stub irq, IRQ_MODE, 4 // 相当于 vector_irq: ..., // 它会根据SPSR寄存器的值, // 判断被中断时CPU是处于USR状态还是SVC状态, // 然后调用下面的__irq_usr或__irq_svc .long __irq_usr @ 0 (USR_26 / USR_32) .long __irq_invalid @ 1 (FIQ_26 / FIQ_32) .long __irq_invalid @ 2 (IRQ_26 / IRQ_32) .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 c. __irq_usr/__irq_svc __irq_usr: usr_entry // 保存现场 kuser_cmpxchg_check irq_handler // 调用 irq_handler get_thread_info tsk mov why, #0 b ret_to_user_from_irq // 恢复现场 d. irq_handler: 将会调用C函数 handle_arch_irq .macro irq_handler #ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER ldr r1, =handle_arch_irq mov r0, sp badr lr, 9997f ldr pc, [r1] #else arch_irq_handler_default #endif 9997: .endm e. handle.c set_handle_irq( pointer_to_fun() ) handle_arch_irq = pointer_to_fun() f. handle_arch_irq的处理过程: (读取寄存器而分辨哪个中断,并调用对应的中断处理函数) 读取寄存器获得中断信息: hwirq 把hwirq转换为virq // hwirq为硬件中断号,virq为虚拟中断号 (两者间有偏移值) // #define S3C2410_CPUIRQ_OFFSET (16) #define S3C2410_IRQ(x) ((x) + S3C2410_CPUIRQ_OFFSET) 调用 1.irq_desc[virq].handle_irq //处理中断 2.irq_desc[virq].irq_data.irq_chip.fun() // 清中断 对于S3C2440, s3c24xx_handle_irq 是用于处理中断的C语言入口函数 set_handle_irq(s3c24xx_handle_irq); 注: irq_desc[nr_irqs] // 包含有多个irq_desc结构体,每个对应不同的中断 struct irq_desc { struct irq_data irq_data; // 带有具体处理中断函数 irq_flow_handler_t handle_irq; // 1.调用action链表中的handler(也就是具体的处理函数) 2.再清中断(使用irq_data->chip的函数) struct irqaction *action; // 指向irqaction链表 } struct irqaction { irq_handler_t handler; // 中断的具体处理函数 void *dev_id; struct irqaction *next; } struct irq_data { u32 mask; unsigned int irq; unsigned long hwirq; struct irq_common_data *common; struct irq_chip *chip; // 很多中断操作函数 struct irq_domain *domain; void *chip_data; }; struct irq_chip { 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_unmask)(struct irq_data *data); } 中断处理流程: 假设中断结构如下: sub int controller ---> int controller ---> cpu 发生中断时, cpu跳到"vector_irq", 保存现场, 调用C函数handle_arch_irq handle_arch_irq: a. 读 int controller, 得到hwirq b. 根据hwirq得到virq c. 调用 irq_desc[virq].handle_irq 如果该中断没有子中断, irq_desc[virq].handle_irq的操作: a. 取出irq_desc[virq].action链表中的每一个handler, 执行它 b. 使用irq_desc[virq].irq_data.chip的函数清中断 如果该中断是由子中断产生, irq_desc[virq].handle_irq的操作: a. 读 sub int controller, 得到hwirq' b. 根据hwirq'得到virq c. 调用 irq_desc[virq].handle_irq s3c24xx_handle_intc pnd = readl_relaxed(intc->reg_intpnd); handle_domain_irq(intc->domain, intc_offset + offset, regs); __handle_domain_irq(domain, hwirq, true, regs); irq = irq_find_mapping(domain, hwirq); generic_handle_irq(irq); struct irq_desc *desc = irq_to_desc(irq); generic_handle_irq_desc(desc); desc->handle_irq(desc); 中断的演变 以前中断号(virq)跟硬件密切相关, 现在的趋势是中断号跟硬件无关, 仅仅是一个标号而已 以前, 对于每一个硬件中断(hwirq)都预先确定它的中断号(virq), 这些中断号一般都写在一个头文件里, 比如archarmmach-s3c24xxincludemachirqs.h 使用时, a. 执行 request_irq(virq, my_handler) : 内核根据virq可以知道对应的硬件中断, 然后去设置、使能中断等 b. 发生硬件中断时, 内核读取硬件信息, 确定hwirq, 反算出virq, 然后调用 irq_desc[virq].handle_irq, 最终会用到my_handler 怎么根据hwirq计算出virq? 硬件上有多个intc(中断控制器), 对于同一个hwirq数值, 会对应不同的virq 所以在讲hwirq时,应该强调"是哪一个intc的hwirq", 在描述hwirq转换为virq时, 引入一个概念: irq_domain, 域, 在这个域里hwirq转换为某一个virq 当中断控制器越来越多、当中断越来越多,上述方法(virq和hwirq固定绑定)有缺陷: a. 增加工作量, 你需要给每一个中断确定它的中断号, 写出对应的宏, 可能有成百上千个 b. 你要确保每一个硬件中断对应的中断号互不重复 有什么方法改进? a. hwirq跟virq之间不再绑定 b. 要使用某个hwirq时, 先在irq_desc数组中找到一个空闲项, 它的位置就是virq 再在irq_desc[virq]中放置处理函数 新中断体系中, 怎么使用中断: a.以前是request_irq发起, 现在是先在设备树文件中声明想使用哪一个中断(哪一个中断控制器下的哪一个中断) b. 内核解析设备树时, 会根据"中断控制器"确定irq_domain, 根据"哪一个中断"确定hwirq, 然后在irq_desc数组中找出一个空闲项, 它的位置就是virq 并且把virq和hwirq的关系保存在irq_domain中: irq_domain.linear_revmap[hwirq] = virq; c. 驱动程序 request_irq(virq, my_handler) d. 发生硬件中断时, 内核读取硬件信息, 确定hwirq,确定中断控制器的域, 确定 virq = irq_domain.linear_revmap[hwirq]; 然后调用 irq_desc[virq].handle_irq, 最终会用到my_handler 假设要使用子中断控制器(subintc)的n号中断, 它发生时会导致父中断控制器(intc)的m号中断: a. 设备树表明要使用<subintc n> subintc表示要使用<intc m> b. 解析设备树时, 会为<subintc n>找到空闲项 irq_desc[virq'], sub irq_domain.linear_revmap[n] = virq'; 会为<intc m> 找到空闲项 irq_desc[virq], irq_domain.linear_revmap[m] = virq; 并且设置它的handle_irq为某个分析函数demux_func c. 驱动程序 request_irq(virq', my_handler) d. 发生硬件中断时, 内核读取intc硬件信息, 确定hwirq = m, 确定 virq = irq_domain.linear_revmap[m]; 然后调用 irq_desc[m].handle_irq, 即demux_func(分发函数) e. demux_func: 读取sub intc硬件信息, 确定hwirq = n, 确定 virq' = sub irq_domain.linear_revmap[n]; 然后调用 irq_desc[n].handle_irq, 即my_handler
驱动源码 int_key_drv.c :
#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<asm/arch/regs-gpio.h> #include<asm/hardware.h> #include <linux/irq.h> static struct class *key_int_class; static struct class_device *key_int_class_device; volatile unsigned long *GPFCON=NULL; volatile unsigned long *GPFDAT=NULL; volatile unsigned long *GPGCON=NULL; volatile unsigned long *GPGDAT=NULL; struct pin_desc { unsigned int pin; unsigned int key_val; }; static unsigned char key_val; static volatile int ev_press=0; static struct pin_desc pin_desc_array[4]={{S3C2410_GPF0,0x01},{S3C2410_GPF2,0X02},{S3C2410_GPG3,0x03},{S3C2410_GPG11,0x04}}; static DECLARE_WAIT_QUEUE_HEAD(wait_key); static irqreturn_t key_handler(int irq, void *dev_id) { struct pin_desc *pindesc = (struct pin_desc *)dev_id; unsigned int pinval=0; pinval = s3c2410_gpio_getpin(pindesc->pin); if(pinval) key_val = 0x08 | pindesc->key_val; else key_val = pindesc->key_val; wake_up_interruptible(&wait_key); ev_press = 1; return IRQ_RETVAL(IRQ_HANDLED); } static int key_drv_open(struct inode *inode,struct file *file) { request_irq(IRQ_EINT0, key_handler,IRQT_BOTHEDGE,"KEY1", &pin_desc_array[0]); request_irq(IRQ_EINT2, key_handler,IRQT_BOTHEDGE,"KEY2", &pin_desc_array[1]); request_irq(IRQ_EINT11, key_handler,IRQT_BOTHEDGE,"KEY3",&pin_desc_array[2]); request_irq(IRQ_EINT19, key_handler,IRQT_BOTHEDGE,"KEY4",&pin_desc_array[3]); return 0; } ssize_t key_drv_read(struct file *file,const char __user *buf,size_t count,loff_t *ppos) { if (count != 1) return -EINVAL; wait_event_interruptible(wait_key,ev_press); ev_press=0; copy_to_user(buf,&key_val,1); return 1; } static int key_drv_close(struct inode *inode,struct file *file) { free_irq(IRQ_EINT0, &pin_desc_array[0]); free_irq(IRQ_EINT2, &pin_desc_array[1]); free_irq(IRQ_EINT11, &pin_desc_array[2]); free_irq(IRQ_EINT19, &pin_desc_array[3]); return 0; } static struct file_operations key_drv_mode= { .owner = THIS_MODULE, .open = key_drv_open, .read = key_drv_read, .release = key_drv_close, }; int major=0; static int key_drv_init(void) { major = register_chrdev(0,"key_int_drv",&key_drv_mode); // /proc/devices key_int_class = class_create(THIS_MODULE,"key_int_class"); key_int_class_device = class_device_create(key_int_class,NULL,MKDEV(major,0),NULL,"key_int_drv"); // /dev/key_int_drv GPFCON=(volatile unsigned long *)ioremap(0x56000050,16); GPFDAT=GPFCON+1; GPGCON=(volatile unsigned long *)ioremap(0x56000060,16); GPGDAT=GPGCON+1; return 0; } static void key_drv_exit(void) { unregister_chrdev(major,"key_int_drv"); class_device_unregister(key_int_class_device); class_destroy(key_int_class); iounmap(GPFCON); iounmap(GPGCON); } module_init(key_drv_init); module_exit(key_drv_exit); MODULE_LICENSE("GPL");
测试应用程序 int_key_drv_test.c :
#include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<stdio.h> int main(int argc,char **argv) { int fd=0; unsigned char key_val=0; fd = open("/dev/key_int_drv",O_RDWR); if(fd <0) printf("error: can't open device :/dev/key_int_drv"); while(1) { read(fd,&key_val,1); printf("key_val=%d ",key_val); } return 0; }
Makefile文件:
KER_DIR=/work/systems/kernel/linux-2/linux-2.6.22.6 all: make -C $(KER_DIR) M=`pwd` modules clean: make -C $(KER_DIR) M=`pwd` modules clean rm -fr moudles.order obj-m +=key_int_drv.o