回答

收藏

【转】深度解析linux设备驱动中断程序

其他 其他 2485 人阅读 | 0 人回复 | 2017-08-09

1.ARM裸机中断程序流程分析
1.1.中断统一入口
1.2.事先注册中断处理程序
1.3.根据中断源的编号处理程序
2.linux系统中断处理流程分析2.1中断流程概述
Linux统irq.svc中断入口获得中断源的编号,根据中断号找到irq_desc结构,在irq_desc结构中找到action结构,执行用户注册中断处理函数
驱动程序支持中断应该做什么?
驱动程序实现中断处理程序,注册到中断号所对应irq_desc中
linux设备驱动程序中包含中断处理程序中断注册,中断处理函数实现,注销处理
2.2linux系统中断处理实现
2.2.1中断注册使用request_irq函数用于中断注册
int request_irq(unsigned int irq,void (handler)(int, void, structpt_regs *),unsigned long flags,const char *devname,void *dev_id)
返回0表示成功,或者返回一个错误码
参数:
unsigned int irq
中断号。
void (handler)(int,void )
中断处理函数。
unsigned long flags
与中断管理有关的各种选项。
const char * devname
设备名
void *dev_id
共享中断时使用
在flags参数中,可以选择一些与中断管理有关的选项,如:
IRQF_DISABLED(SA_INTERRUPT)
如果设置该位,表示是一个“快速”中断处理程序;如果没有设置这位,那么是一个“慢速”中断处理程
序。
IRQF_SHARED(SA_SHIRQ)
该位表明该中断号是多个设备共享的。
也可选择为中断触发标志:IRQF_TRIGGER_FALLING(下降沿)
快/慢速中断的主要区别在于:快速中断保证中断处理的原子性(不被打断),而慢速中断则不保证。换句话说,也就是“开启中断”标志位(处理器IF)
在运行快速中断处理程序时是关闭的,因此在服务该中断时,不会被其他类型的中断打断;而调用慢速中断处理时,其它类型的中断仍可以得到服务。
2.2.2中断处理程序
中断处理程序的特别之处是在中断上下文中运行的,它的行为受到某些限制:1.不能使用可能引起阻塞的函数,2.不能使用肯呢过引起调度的函数
中断处理程序流程
1.检查设备是否产生了中断
2.清除中断产生标志
3.相应的硬件操作
2.2.3.注销中断
void free_irq(unsigned int irq,void *dev_id)
3.按键中断处理框架代码实现
  1. #include<linux/module.h>
  2. #include<linux/init.h>
  3. #include<linux/miscdevice.h>
  4. #include<linux/fs.h>
  5. #include<linux/interrupt.h>
  6. #include<linux/irq.h>
  7. #include<linux/io.h>
  8. #include<linux/slab.h>
  9. #include<linux/vmalloc.h>
  10. MODULE_LICENSE("GPL");

  11. #define GPX1CON 0x11000C20
  12. //home按键的物理基地址
  13. unsigned int *gpx1con;
  14. irqreturn_t key_int(int irq,void *dev_id)
  15. {
  16.         //检测是否发生了按键中断



  17.         //清除按键中断


  18.         //打印键值
  19.           printk("press the key\n");
  20.          return 0;
  21. }
  22. void keyinit()
  23. {
  24.     gpx1con = ioremap(GPX1CON,4);//物理地址映射为虚拟地址
  25.     writel(readl(gpx1con)&~(0xf<<4)|(0xf<<4),gpx1con);//将GPIO功能设置为输入
  26. }
  27. int key_open(struct inode *node,struct file *filp)
  28. {
  29.     return 0;   
  30. }
  31. struct file_operations key_fops =
  32. {
  33.     .open = key_open,   
  34. };
  35. struct miscdevice key_miscdev = {

  36.     .minor = 200,
  37.     .name = "mykey",
  38.     .fops = &key_fops,
  39. };
  40. static int key_init()
  41. {

  42.     misc_register(&key_miscdev);//注册杂项设备
  43.     keyinit();按键的硬件初始化
  44.     request_irq(IRQ_EINT(9),key_int,IRQF_TRIGGER_FALLING,"mykey",0);//注册中断
  45.     return 0;
  46. }

  47. static void key_exit()
  48. {
  49.     misc_deregister(&key_miscdev);//注销杂项设备
  50.     free_irq(IRQ_EINT(9),0);//释放中断
  51. }

  52. module_init(key_init);
  53. module_exit(key_exit);
复制代码
4.中断分层4.1.中断嵌套4.2.中断分层方式4.3.使用工作队列实现分层4.1中断嵌套
linux如何处理中断嵌套?
一种类型的中断发生的时候,又产生了其他的中断,其他的中断可以是同类型的,也可以是不同类型的。
不同的系统处理方式不同
慢速中断:在进行中断处理的时候,中断的总开关不关闭,允许其他类型中断产生
情况1:串口中断处理程序运行的时候,运行一段时间,又来一个网卡中断,LINUX系统暂停串口中断处理程序,转去执行网卡处理程序,执行完毕,又去执行串口中断处理程序。
情况2:串口中断处理程序运行的时候,运行一段时间,又来一个串口中断,不会执行新的串口中断,linux系统在处理同类型的中断时,不会处理新的中断,会忽略。
快速中断:在进行中断处理的时候,中断的总开关关闭,不允许其他类型中断产生
情况1:串口中断处理程序运行的时候,运行一段时间,又来一个网卡中断,LINUX系统继续串口中断处理程序,忽略网卡处理程序。
情况2:串口中断处理程序运行的时候,运行一段时间,又来一个串口中断,不会执行新的串口中断,linux系统在处理同类型的中断时,不会处理新的中断,会忽略。
4.2中断分层:
4.2.1linux如何处理中断丢失的问题?
方法:将中断处理程序时间减短,减小中断丢失的概率
如何将中断处理程序的时间尽量减短?
中断处理程序一般做两部分工作,一部分是和硬件相关的,另外一类部分和硬件不密切相关。于是把中断处理程序分成两部分,上半部分做与硬件相关的工作,因为硬件相关工作必须在中断处理程序上下文中进行。下半部分与硬件不相关的抛给内核处理。
上半部:当中断发生时,它进行相应地硬件读写,并“登记”该中断。通常由中断处理程序充当上半部。
下半部:在系统空闲的时候对上半部“登记”的中断进行后续处理。
4.2.2中断分层方式
中断分层的方式有三种
1软中断,2tasklet,3工作队列现在最常用的方式为工作队列
工作队列是一种将任务推后执行的形式,他把推后的任务交由一个内核线程去执行。这样下半部会在进程上下文执行,它允许重新调度甚至睡眠。每个被推后的任务叫做“工作”,由这些工作组成的队列称为工作队列。
创建一个工作队列,3核的CPU,每个CPU上都会挂再一个链表,链表上连的就是工作,将下半部的工作添加到链表中买,内核为每一个链表创建一个线程,当内核空闲时,就处理下半部工作。
4.3工作队列的使用
Linux内核使用workqueue_struct来描述一个工作队列
struct workqueue_struct {
struct cpu_workqueue_struct *cpu_wq;
struct list_head list;
const char name; /*workqueue name/
int singlethread;
int freezeable; /* Freeze threads during suspend */
int rt;
};
Linux内核使用struct work_struct来描述一个工作项:
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
};
typedef void (*work_func_t)(struct work_struct *work);
工作队列使用流程:
step1. 创建工作队列
create_workqueue
step2. 创建工作
INIT_WORK
step3. 提交工作
queue_work
4.4工作队列代码实现
  1. #include<linux/init.h>
  2. #include<linux/module.h>
  3. #include<linux/slab.h>
  4. MODULE_LICENSE("GPL");
  5. struct workqueue_struct *my_wq;
  6. struct work_struct *work1;
  7. struct work_struct *work2;
  8. static void work1_fun(struct work_struct *work)
  9. {

  10.     printk("this is work1\n");
  11. }
  12. static void work2_fun(struct work_struct *work)
  13. {
  14.     printk("this is work2\n");

  15. }
  16. MODULE_LICENSE("GPL");

  17. int init_queue(void)
  18. {
  19.     //创建工作队列
  20.     //my_wq = create_workqueue("mywq");

  21.     //创建工作
  22.     work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
  23.     INIT_WORK(work1,work1_fun);
  24.     //挂载工作
  25.     //queue_work(my_wq,work1);
  26.     schedule_work(work1);
  27.     //创建工作
  28.     work2 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
  29.     INIT_WORK(work2,work2_fun);
  30.     //挂载工作
  31.     //queue_work(my_wq,work2);
  32.     schedule_work(work2);
  33. }

  34. void clean_queue(void)
  35. {

  36. }
  37. module_init(init_queue);
  38. module_exit(clean_queue);
复制代码
4.5内核定时器
linux内核使用struct timer_list来描述定时器:
  1. struct timer_list {
  2. /*
  3. * All fields that change during normal runtime grouped to the
  4. * same cacheline
  5. */
  6. struct list_head entry;
  7. unsigned long expires;
  8. struct tvec_base *base;

  9. void (*function)(unsigned long);
  10. unsigned long data;
  11. };
复制代码
定时器使用流程
1.定义定时器变量struct timer_list
2初始化定时器
2.1init_timer的初始化
2.2设置超时函数
3.add_timer注册定时器
4.mod_timer启动定时器
按键驱动的分层实现
  1. #include<linux/module.h>
  2. #include<linux/init.h>
  3. #include<linux/miscdevice.h>
  4. #include<linux/fs.h>
  5. #include<linux/interrupt.h>
  6. #include<linux/io.h>
  7. #include<linux/irq.h>
  8. #include<linux/slab.h>
  9. #include<linux/vmalloc.h>
  10. #include<linux/uaccess.h>
  11. MODULE_LICENSE("GPL");
  12. #define GPX1CON 0x11000C20
  13. #define GPX1DAT 0x11000C24
  14. unsigned int *gpx1con;
  15. unsigned int *gpx1dat;
  16. unsigned int key_num;

  17. struct work_struct *work1;

  18. struct work_struct *work1;
  19. struct timer_list key_timer;//定义定时器变量struct timer_list
  20. void work1_fun(struct work_struct *work)
  21. {
  22.     mod_timer(&key_timer,jiffies+HZ/10);//mod_timer启动定时器
  23.     //  printk("press the key\n");
  24. }
  25. void key_timer_func(unsigned long data)//超时函数
  26. {   unsigned int key_val;
  27.     key_val = readl(gpx1dat)&(0x1<<1);
  28.     if(key_val==0)
  29.     {
  30.         key_num = 1;
  31.     }
  32.         //printk("\nhome key down\n");

  33.     key_val = readl(gpx1dat)&(0x1<<2);
  34.     if(key_val==0)
  35.     {
  36.         key_num = 2;
  37.     }      
  38. //printk("\n back key down\n");

  39. }
  40. irqreturn_t key_int(int irq,void *dev_id)
  41. {
  42.         //检测是否发生了按键中断
  43.         //清除按键中断
  44.         //提交下半部分   
  45.         schedule_work(work1);
  46.         //printk("press the key\n");

  47.         return 0;
  48. }
  49. void keyinit()
  50. {
  51.     gpx1con = ioremap(GPX1CON,4);

  52.     writel(readl(gpx1con)&~(0xff<<4)|(0xff<<4),gpx1con);
  53.     gpx1dat = ioremap(GPX1DAT,4);


  54. }
  55. ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
  56. {
  57.     printk("in kernel :key num is %d\n",key_num);   
  58.     copy_to_user(buf, &key_num, 4);

  59.     return 4;
  60. }
  61. int key_open(struct inode *node,struct file *filp)
  62. {
  63.     return 0;   
  64. }

  65. struct file_operations key_fops =
  66. {
  67.     .open = key_open,
  68.     .read = key_read,   
  69. };
  70. struct miscdevice key_miscdev = {

  71.     .minor = 200,
  72.     .name = "mykey",
  73.     .fops = &key_fops,
  74. };
  75. static int key_init()
  76. {

  77.     misc_register(&key_miscdev);
  78.      keyinit();

  79.     request_irq(IRQ_EINT(9),key_int,IRQF_TRIGGER_FALLING,"mykey",0);

  80.     request_irq(IRQ_EINT(10),key_int,IRQF_TRIGGER_FALLING,"mykey",0);

  81.     //创建工作
  82.     work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
  83.     INIT_WORK(work1,work1_fun);

  84.     init_timer(&key_timer);//init_timer的初始化
  85.     key_timer.function = key_timer_func;//设置超时函数   
  86.     add_timer(&key_timer);//add_timer注册定时器
  87.     return 0;
  88. }

  89. static void key_exit()
  90. {
  91.     misc_deregister(&key_miscdev);
  92.     free_irq(IRQ_EINT(9),0);
  93.     free_irq(IRQ_EINT(10),0);
  94. }

  95. module_init(key_init);
  96. module_exit(key_exit);  
复制代码
5.阻塞型驱动设计5.1.阻塞的必要性
当应用程序调用read读取数据时,设备没有数据提供,但以后可能会有,或者进程正在试图去写,但设备暂时没有准备好去接收数据。当以上情况发生时。驱动程序应当(缺省地)阻塞进程,使它进入等待(睡眠)状态,直到请求得到满足。内核等待队列像是候车室
1.读数据2.睡眠3.唤醒
5.2.内核等待队列
进程在哪里睡眠,就是内核等待队列
5.2.1、定义等待队列
wait_queue_head_t my_queue
5.2.2、初始化等待队列
init_waitqueue_head(&my_queue)
5.2.3、定义+初始化等待队列
DECLARE_WAIT_QUEUE_HEAD(my_queue)
5.2.4、进入等待队列,睡眠
wait_event(queue,condition)
当condition(布尔表达式)为真时,立即返回;否则让进程进入TASK_UNINTERRUPTIBLE模式的睡眠,并挂在queue参数所指定的等待队列上。
wait_event_interruptible(queue,condition)当condition(布尔表达式)为真时,立即返回;否则让进程进入TASK_INTERRUPTIBLE的睡眠,并挂在queue参数所指定的等待队列上。
5.2.5从等待队列中唤醒进程
wake_up(wait_queue_t *q)
从等待队列q中唤醒状态为TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,TASK_KILLABLE 的所有进程。wake_up_interruptible(wait_queue_t *q)从等待队列q中唤醒状态为TASK_INTERRUPTIBLE 的进程
5.3.阻塞优化驱动
  1. #include<linux/module.h>
  2. #include<linux/init.h>
  3. #include<linux/miscdevice.h>
  4. #include<linux/fs.h>
  5. #include<linux/interrupt.h>
  6. #include<linux/io.h>
  7. #include<linux/irq.h>
  8. #include<linux/slab.h>
  9. #include<linux/vmalloc.h>
  10. #include<asm/uaccess.h>
  11. #include <linux/sched.h>
  12. MODULE_LICENSE("GPL");
  13. #define GPX1CON 0x11000C20
  14. #define GPX1DAT 0x11000C24
  15. unsigned int *gpx1con;
  16. unsigned int *gpx1dat;
  17. unsigned int key_num=0;
  18. wait_queue_head_t key_queue;//定义等待队列
  19. struct work_struct *work1;

  20. struct timer_list key_timer;
  21. void work1_fun(struct work_struct *work)
  22. {
  23.     mod_timer(&key_timer,jiffies+HZ/10);
  24.     //  printk("press the key\n");
  25. }
  26. void key_timer_func(unsigned long data)
  27. {   unsigned int key_val;
  28.     key_val = readl(gpx1dat)&(0x1<<1);
  29.     if(key_val==0)
  30.     {
  31.         key_num = 1;
  32.     }
  33.         //printk("\nhome key down\n");
  34.     key_val = readl(gpx1dat)&(0x1<<2);
  35.     if(key_val==0)
  36.     {
  37.         key_num = 2;
  38.     }      
  39.     wake_up(&key_queue);//按键按下则唤醒进程
  40. //printk("\n back key down\n");
  41. }
  42. irqreturn_t key_int(int irq,void *dev_id)
  43. {
  44.         //检测是否发生了按键中断



  45.         //清除按键中断


  46.         //提交下半部分   
  47.         schedule_work(work1);
  48.         //printk("press the key\n");

  49.         return 0;
  50. }
  51. void keyinit()
  52. {
  53.     gpx1con = ioremap(GPX1CON,4);

  54.     writel(readl(gpx1con)&~(0xff<<4)|(0xff<<4),gpx1con);
  55.     gpx1dat = ioremap(GPX1DAT,4);


  56. }
  57. ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
  58. {   
  59.     wait_event(key_queue,key_num);//进入等待队列,key_num是进入等待队列的条件
  60.     printk("in kernel :key num is %d\n",key_num);   
  61.     copy_to_user(buf, &key_num, 4);
  62.     key_num = 0;
  63.     return 4;
  64. }
  65. int key_open(struct inode *node,struct file *filp)
  66. {
  67.     return 0;   
  68. }
  69. struct file_operations key_fops =
  70. {
  71.     .open = key_open,
  72.     .read = key_read,   
  73. };
  74. struct miscdevice key_miscdev = {

  75.     .minor = 200,
  76.     .name = "mykey",
  77.     .fops = &key_fops,
  78. };
  79. static int mykey_init()
  80. {

  81.     misc_register(&key_miscdev);
  82.      keyinit();

  83.     request_irq(IRQ_EINT(9),key_int,IRQF_TRIGGER_FALLING,"mykey",0);

  84.     request_irq(IRQ_EINT(10),key_int,IRQF_TRIGGER_FALLING,"mykey",0);

  85.     //创建工作
  86.     work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
  87.     INIT_WORK(work1,work1_fun);
  88.     //初始化定时器
  89.     init_timer(&key_timer);
  90.     //添加超时函数
  91.     key_timer.function = key_timer_func;
  92.     //向内核注册
  93.     add_timer(&key_timer);
  94.     //初始化等待队列
  95.     init_waitqueue_head(&key_queue);
  96.     return 0;
  97. }

  98. static void mykey_exit()
  99. {
  100.     misc_deregister(&key_miscdev);
  101.     free_irq(IRQ_EINT(9),0);
  102.     free_irq(IRQ_EINT(10),0);
  103. }

  104. module_init(mykey_init);
  105. module_exit(mykey_exit);  
复制代码
应用程序调用
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include <errno.h>

  4. int main(int argc,char **argv)
  5. {
  6.     int fd;
  7.     int key_num;
  8.     //1.打开设备
  9.     fd = open("/dev/mykey",0);
  10.     if(fd<0)
  11.         printf("open device fail\n");

  12.     //读取设备
  13.     read(fd,&key_num,4);
  14.     printf("key is %d\n",key_num);


  15.     //关闭设备
  16.     close(fd);
  17.     return 0;
  18. }
复制代码
当应用程序调用驱动程序读取按键值,此时按键没有按下,测试驱动程序使其应用程序进入等待状态,当有按键按下,则唤醒程序读取键值。


关注下面的标签,发现更多相似文章
分享到:
回复

使用道具 举报

您需要登录后才可以回帖 注册/登录

本版积分规则

369 积分
28 主题
+ 关注
热门推荐
关闭

站长推荐上一条 /2 下一条