问:内核的中断体系是怎么样的?
答:ARM架构Linux内核中,有5种常见的异常,其中中断异常是其一,Linux内核将所有中断统一编号,使用一个irq_desc结构体来描述这些中断,里面记录了中断名称、中断状态、中断标记、并提供了中断的底层硬件访问函数(如:清除、屏蔽、使能中断),提供了这个中断的处理函数入口,通过它还可以调用用户注册的的中断处理函数。
问:irq_desc结构体有哪些重要的成员?
-
-
-
-
-
-
-
-
-
-
- struct irq_desc {
- unsigned int irq;
- ......
- irq_flow_handler_t handle_irq;
- struct irq_chip *chip;
- ......
- struct irqaction *action;
- unsigned int status;
- ......
- unsigned int irq_count;
- ......
- const char *name;
- } ____cacheline_internodealigned_in_smp;
关于irq_desc结构体的具体描述,请参考《嵌入式Linux应用完全开发手册》的第20章的20.2.1节,即416页开始的描述。关于linux中断体系的详细讲解,我就不多说了,大家参考韦老师的视频教程:"第12课第4.1节.字符设备驱动程序之中断方式的按键驱动_Linux异常处理结构"
"第12课第4.2节.字符设备驱动程序之中断方式的按键驱动_Linux中断处理结构"
这二个视频,已经讲解的非常到位,如果还不理解的,大家可以参考韦老师的书的第20章内容。建议大家结合linux源码和书一起看,这样对你深入linux中断体系就不远了。
问:既然linux内核的中断体系已经 那么完善了,那么驱动工程师还需要做什么?
答:驱动工程师需要调用request_irq向内核注册中断,其原型为:
- request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char * devname, void * dev_id)
第一个参数irq:中断号,在irqs.h中定义,与架构相关;第二个参数handler: 用户中断处理函数;
第三个参数flags:中断标记;
第四个参数devname:中断名字;可以通过cat proc/interrupts查看;
第五个参数dev_id: 在free_irq中有用,也用作区分中断处理函数;
问:request_irq函数有什么作用?
答:
1)、irq_desc[irq]结构体中的action链表中已经链入了用户注册的中断处理函数。
2)、中断触发方式已经被设置好。
3)、中断已经被使能。
问:如何卸载中断处理函数?
答:中断是一种很稀缺的资源,当不再使用一个设备时,应该释放它占据的中断。驱动工程师通过free_irq函数来实现,其原型为:
- void free_irq(unsigned int irq, void *dev_id)
第一个参数irq:中断号,与request_irq中的irq一致,用于定位action链表;第二个参数dev_id:用于在action链表中找到要卸载的表项;同一个中断的不同中断处理函数必须使用不同的dev_id来区分,这就要求在注册共享中断时参数dev_id必须唯一。
问:free_irq有什么作用?
答:
1)、根据中断号irq、dev_id从action链表中找到表项,将它移除。
2)、如果它是唯一的表项,还要调用IRQ_DESC[IRQ].CHIP->SHUTDOWN或者IRQ_DESC[IRQ].CHIP->DISABLE来关闭中断。
前面说了那么多中断,暂且放下中断,来谈谈linux是如何睡觉的吧,睡觉?linux也会睡觉?没错,只不过在术语上不叫睡觉,人家那叫休眠。
问:linux内核如何进行休眠?
答:使用wait_event函数,其扩展型常用的函数为wait_event_interruptible(wq, condition),即可被中断打断的休眠。
wq是一个等待队列,condition是条件,如果condition = 0,则将会进行休眠,直到condition = 1,并且有唤醒函数唤醒它。
问:linux内核如果唤醒进程?
答:使用wait_up函数,其扩展型常用的函数为wake_up_interruptible(wq),wq与wait_event_interruptible的wq是一致的。
问:能直接使用wait_event_interruptible和wake_up_interruptible函数吗?
答:不能,必须先事先使用static DECLARE_WAIT_QUEUE_HEAD(wq)定义并初始化一个等待队列头,并设置condition条件变量。
详细请参考驱动源码:
- #include <linux/delay.h>
- #include <linux/irq.h>
- #include <asm/uaccess.h>
- #include <asm/irq.h>
- #include <asm/io.h>
- #include <linux/module.h>
- #include <linux/device.h> //class_create
- #include <mach/regs-gpio.h> //S3C2410_GPF1
-
- #include <mach/hardware.h>
-
- #include <linux/interrupt.h> //wait_event_interruptible
-
-
-
- static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
-
-
- static struct class *thirddrv_class;
- static struct device *thirddrv_device;
-
- static struct pin_desc{
- unsigned int pin;
- unsigned int key_val;
- };
-
- static struct pin_desc pins_desc[4] = {
- {S3C2410_GPF1,0x01},
- {S3C2410_GPF4,0x02},
- {S3C2410_GPF2,0x03},
- {S3C2410_GPF0,0x04},
- };
-
- static int ev_press = 0;
-
-
-
- static unsigned char key_val;
- int major;
-
-
- static irqreturn_t buttons_irq(int irq, void *dev_id)
- {
- struct pin_desc *pindesc = (struct pin_desc *)dev_id;
- unsigned int pinval;
- pinval = s3c2410_gpio_getpin(pindesc->pin);
-
- if(pinval)
- {
-
- key_val = 0x80 | (pindesc->key_val);
- }
- else
- {
-
- key_val = pindesc->key_val;
- }
-
- ev_press = 1;
- wake_up_interruptible(&button_waitq);
- return IRQ_HANDLED;
- }
- static int third_drv_open(struct inode * inode, struct file * filp)
- {
-
-
-
-
- request_irq(IRQ_EINT1, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K1",&pins_desc[0]);
- request_irq(IRQ_EINT4, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K2",&pins_desc[1]);
- request_irq(IRQ_EINT2, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K3",&pins_desc[2]);
- request_irq(IRQ_EINT0, buttons_irq, IRQ_TYPE_EDGE_BOTH, "K4",&pins_desc[3]);
- return 0;
- }
-
- static ssize_t third_drv_read(struct file *file, char __user *user, size_t size,loff_t *ppos)
- {
- if (size != 1)
- return -EINVAL;
-
-
-
-
-
-
-
- wait_event_interruptible(button_waitq, ev_press);
- copy_to_user(user, &key_val, 1);
-
-
- ev_press = 0;
- return 1;
- }
-
- static int third_drv_close(struct inode *inode, struct file *file)
- {
- free_irq(IRQ_EINT1,&pins_desc[0]);
- free_irq(IRQ_EINT4,&pins_desc[1]);
- free_irq(IRQ_EINT2,&pins_desc[2]);
- free_irq(IRQ_EINT0,&pins_desc[3]);
- return 0;
- }
-
-
- static const struct file_operations third_drv_fops = {
- .owner = THIS_MODULE,
- .open = third_drv_open,
- .read = third_drv_read,
- .release = third_drv_close,
- };
-
-
-
- static int third_drv_init(void)
- {
-
- major = register_chrdev(0, "third_drv", &third_drv_fops);
-
-
- thirddrv_class = class_create(THIS_MODULE, "thirddrv");
-
-
- thirddrv_device = device_create(thirddrv_class, NULL, MKDEV(major, 0), NULL, "buttons");
-
- return 0;
- }
-
-
- static void third_drv_exit(void)
- {
- unregister_chrdev(major, "third_drv");
- device_unregister(thirddrv_device);
- class_destroy(thirddrv_class);
- }
-
- module_init(third_drv_init);
- module_exit(third_drv_exit);
-
- MODULE_AUTHOR("LWJ");
- MODULE_DESCRIPTION("Just for Demon");
- MODULE_LICENSE("GPL");
应用测试程序源码:
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <unistd.h>
-
-
-
-
- int main(int argc ,char *argv[])
-
- {
- int fd;
- unsigned char key_val;
-
- fd = open("/dev/buttons",O_RDWR);
- if (fd < 0)
- {
- printf("open error\n");
- }
-
- while(1)
- {
- read(fd,&key_val,1);
- printf("key_val = 0x%x\n",key_val);
-
- }
- return 0;
- }
测试步骤1:
- [WJ2440]# ls
- Qt etc mnt second_drv.ko udisk
- TQLedtest first_drv.ko opt second_test usr
- app_test first_test proc sys var
- bin home root third_drv.ko web
- dev lib sbin third_test
- driver_test linuxrc sddisk tmp
- [WJ2440]# ls /dev/buttons -l
- ls: /dev/buttons: No such file or directory
- [WJ2440]# insmod third_drv.ko
- [WJ2440]# lsmod
- third_drv 3016 0 - Live 0xbf003000
- [WJ2440]# ls /dev/buttons -l
- crw-rw---- 1 root root 252, 0 Jan 2 02:12 /dev/buttons
- [WJ2440]# ./third_test
- key_val = 0x1
- key_val = 0x81
- key_val = 0x2
- key_val = 0x82
- key_val = 0x3
- key_val = 0x83
- key_val = 0x4
- key_val = 0x84
- key_val = 0x2
- key_val = 0x2
- key_val = 0x82
- key_val = 0x1
- key_val = 0x81
- key_val = 0x2
- key_val = 0x82
- key_val = 0x2
- key_val = 0x82
- key_val = 0x4
- key_val = 0x4
- key_val = 0x4
- key_val = 0x84
/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
测试步骤2:
- [WJ2440]# ./third_test &
- [WJ2440]# top
- Mem: 10912K used, 49252K free, 0K shrd, 0K buff, 8104K cached
- CPU: 0.0% usr 0.7% sys 0.0% nic 99.0% idle 0.0% io 0.1% irq 0.0% sirq
- Load average: 0.00 0.05 0.03 1/23 627
- PID PPID USER STAT VSZ %MEM CPU %CPU COMMAND
- 627 589 root R 2092 3.4 0 0.7 top
- 589 1 root S 2092 3.4 0 0.0 -/bin/sh
- 1 0 root S 2088 3.4 0 0.0 init
- 590 1 root S 2088 3.4 0 0.0 /usr/sbin/telnetd -l /bin/login
- 587 1 root S 1508 2.5 0 0.0 EmbedSky_wdg
- 626 589 root S 1428 2.3 0 0.0 ./third_test
- 573 2 root SW< 0 0.0 0 0.0 [rpciod/0]
- 5 2 root SW< 0 0.0 0 0.0 [khelper]
- 329 2 root SW< 0 0.0 0 0.0 [nfsiod]
- 2 0 root SW< 0 0.0 0 0.0 [kthreadd]
- 3 2 root SW< 0 0.0 0 0.0 [ksoftirqd/0]
- 4 2 root SW< 0 0.0 0 0.0 [events/0]
- 11 2 root SW< 0 0.0 0 0.0 [async/mgr]
- 237 2 root SW< 0 0.0 0 0.0 [kblockd/0]
- 247 2 root SW< 0 0.0 0 0.0 [khubd]
- 254 2 root SW< 0 0.0 0 0.0 [kmmcd]
- 278 2 root SW 0 0.0 0 0.0 [pdflush]
- 279 2 root SW 0 0.0 0 0.0 [pdflush]
- 280 2 root SW< 0 0.0 0 0.0 [kswapd0]
- 325 2 root SW< 0 0.0 0 0.0 [aio/0]
可发现,按键没有被按下时,third_test进程是处于睡眠状态的,并且几乎不占用CPU的利用率。
测试步骤3(如何卸载驱动):
- [WJ2440]# lsmod
- third_drv 3016 2 - Live 0xbf003000
- [WJ2440]# rmmod third_drv
- rmmod: remove 'third_drv': Resource temporarily unavailable
- [WJ2440]# kill -9 626
- [1]+ Killed ./third_test
- [WJ2440]# rmmod third_drv
- rmmod: module 'third_drv' not found
- [WJ2440]# lsmod
- [WJ2440]#
注意事项:1.楼主在编译驱动的时候,发现linux的头文件经常因版本不一致,导致路径不一致,很是苦恼。
2.有些中断共享标记,如:IRQ_TYPE_EDGE_BOTH,在韦老师那里是IRQT_BOTHEDGE,又是版本害的。
3.楼主使用的linux版本是2.6.30.4
4.楼主在测试驱动时,发现居然没有任何信息打印出来,调试了20分钟,最后发现原来是printf没有加'\n'