• 驱动开发,基于中断子系统完成按键的中断驱动,引入中断底半部


    一.引入linux内核中断目的

            引入linux内核中断之前,内核访问设备要不断轮询访问;

            引入linux内核中断便于内核对设备的访问,当设备事件发生后主动通知内核,内核再去访问设备;

    二.linux内核中断实现过程框图      

     根据软中断号回调当前中断的 中断函数 过程:

            中断注册进内核之后,中断信息会保存在一个struct irq_desc对象中,内核中存在一个struct irq_desc类型的数组,软中断号就是数组的下标,数组中每一个成员都是保存了一个注册进内核的设备中断信息,类型为struct irqaction,struct irqaction对象里有中断处理函数的函数指针,指向自定义中断处理函数;

    三.添加按键的设备树节点

     在stm32mp157a-fsmp1a.dts文件的根节点内部添加如下内容:

    myirq

    {

    compatible="myirq"; interrupt-parent=<&gpiof>; interrupts=<9 0>,<7 0>,<8 0>;

    };

    四.中断底半部

    1.引入目的(解决问题)

            当一个中断被触发以后,会关闭抢占,一个单核CPU处理当前中断任务时,当前CPU无法处理其他任务,所有的CPU都会关闭当前中断线。在这种情况下,如果一个中断中有延时、耗时甚至休眠操作,最终会导致整个系统功能的延迟。所以一般在中断处理过程中不允许有延时、耗时甚至休眠的操作。但是有的时候又必须在中断的处理程序中进行一些耗时任务。

            这样就会产生一个冲突:中断不允许有耗时但是有时候需要耗时的冲突;

    2.解决上面冲突,引入中断底半部

           将一个中断处理得分过程分为了中断顶半部和中断底半部,中断顶半部就是通过 request_irq注册的中断处理函数,在顶半部中主要进行一些重要的、不耗时的任务;中断底半部则是区进行一些耗时,不紧急的任务。在执行中断底半部时,会将执行中断顶半部时关闭的中断线启用以及抢占开启,这样进程以及其他的中断就可以正常的工作了。

    3.中断底半部的实现机制

            实现机制有softirq(软中断)、tasklet以及工作队列;

    1)软中断机制

            当顶半部即将执行结束时开启软中断,在软中断处理函数中取处理当前中断里的耗时任务。软中断存在数量限制(32个),一般留给内核开发者使用。

    2)tasklet机制

    •         基于软中断工作原理进行的;
    •         tasklet没有使用数量的限制;
    •         中断底半部可以进行耗时任务,但不可以进行休眠操作;
    •         工作于中断上下文,不用于进程上下文;

    3)工作队列机制

            内核中存在工作队列对应的内核线程,这个线程从内核启动就存在,处于休眠态。当有任务需要执行时,只需要将任务提交到工作队列中,然后唤醒休眠的内核线程,由内核线程去处理对应的任务即可。工作队列既可以用于中断,也可以用于进程。

    4)tasklet机制驱动代码

    1. #include <linux/init.h>
    2. #include <linux/module.h>
    3. #include <linux/of.h>
    4. #include <linux/of_irq.h>
    5. #include <linux/interrupt.h>
    6. struct device_node *dnode;
    7. unsigned int irqno[3];
    8. int i;
    9. struct tasklet_struct tasklet; //分配tasklet对象
    10. //定义底半部处理函数
    11. void key_callback(struct tasklet_struct *t)
    12. {
    13. int i;
    14. //耗时事件
    15. for(i=0; i<100; i++)
    16. {
    17. printk("i=%d\n",i);
    18. }
    19. }
    20. // 定义中断处理函数
    21. irqreturn_t key_handler(int irq, void *dev)
    22. {
    23. int witch = (int)dev;
    24. switch (witch)
    25. {
    26. case 0:
    27. printk("KEY1_INTERRUPT\n");
    28. break;
    29. case 1:
    30. printk("KEY2_INTERRUPT\n");
    31. break;
    32. case 2:
    33. printk("KEY3_INTERRUPT\n");
    34. break;
    35. }
    36. //开启底半部
    37. tasklet_schedule(&tasklet);
    38. return IRQ_HANDLED;
    39. }
    40. static int __init mycdev_init(void)
    41. {
    42. // 1解析按键的设备树节点
    43. dnode = of_find_compatible_node(NULL, NULL, "myirq");
    44. if (dnode == NULL)
    45. {
    46. printk("解析设备树节点失败\n");
    47. return -ENXIO;
    48. }
    49. printk("解析设备树节点成功\n");
    50. // 2解析按键的软中断号
    51. for (i = 0; i < 3; i++)
    52. {
    53. irqno[i] = irq_of_parse_and_map(dnode, i);
    54. if (!irqno[i])
    55. {
    56. printk("解析按键%d软中断号失败\n", i);
    57. return -ENXIO;
    58. }
    59. printk("解析按键%d软中断号成功%d\n", i, irqno[i]);
    60. // 3注册按键1中断
    61. int ret = request_irq(irqno[i], key_handler, IRQF_TRIGGER_FALLING, "key_int", (void *)i);
    62. if (ret < 0)
    63. {
    64. printk("注册按键%d中断失败\n", i);
    65. return ret;
    66. }
    67. }
    68. printk("注册按键中断成功\n");
    69. //初始化底半部
    70. tasklet_setup(&tasklet,key_callback);
    71. return 0;
    72. }
    73. static void __exit mycdev_exit(void)
    74. {
    75. // 注销中断
    76. int i;
    77. for (i = 0; i < 3; i++)
    78. {
    79. free_irq(irqno[i], (void *)i);
    80. }
    81. }
    82. module_init(mycdev_init);
    83. module_exit(mycdev_exit);
    84. MODULE_LICENSE("GPL");

    现象:

    5)工作队列机制驱动代码

    1. #include <linux/init.h>
    2. #include <linux/module.h>
    3. #include <linux/of.h>
    4. #include <linux/of_irq.h>
    5. #include <linux/interrupt.h>
    6. struct device_node *dnode;
    7. unsigned int irqno[3];
    8. int i;
    9. struct work_struct work; //分配工作队列对象
    10. //定义底半部处理函数
    11. void key_work(struct work_struct *t)
    12. {
    13. int i;
    14. //耗时事件
    15. for(i=0; i<100; i++)
    16. {
    17. printk("i=%d\n",i);
    18. }
    19. }
    20. // 定义中断处理函数
    21. irqreturn_t key_handler(int irq, void *dev)
    22. {
    23. int witch = (int)dev;
    24. switch (witch)
    25. {
    26. case 0:
    27. printk("KEY1_INTERRUPT\n");
    28. break;
    29. case 1:
    30. printk("KEY2_INTERRUPT\n");
    31. break;
    32. case 2:
    33. printk("KEY3_INTERRUPT\n");
    34. break;
    35. }
    36. //开启底半部
    37. schedule_work(&work);
    38. return IRQ_HANDLED;
    39. }
    40. static int __init mycdev_init(void)
    41. {
    42. // 1解析按键的设备树节点
    43. dnode = of_find_compatible_node(NULL, NULL, "myirq");
    44. if (dnode == NULL)
    45. {
    46. printk("解析设备树节点失败\n");
    47. return -ENXIO;
    48. }
    49. printk("解析设备树节点成功\n");
    50. // 2解析按键的软中断号
    51. for (i = 0; i < 3; i++)
    52. {
    53. irqno[i] = irq_of_parse_and_map(dnode, i);
    54. if (!irqno[i])
    55. {
    56. printk("解析按键%d软中断号失败\n", i);
    57. return -ENXIO;
    58. }
    59. printk("解析按键%d软中断号成功%d\n", i, irqno[i]);
    60. // 3注册按键1中断
    61. int ret = request_irq(irqno[i], key_handler, IRQF_TRIGGER_FALLING, "key_int", (void *)i);
    62. if (ret < 0)
    63. {
    64. printk("注册按键%d中断失败\n", i);
    65. return ret;
    66. }
    67. }
    68. printk("注册按键中断成功\n");
    69. //初始化队列项
    70. INIT_WORK(&work,key_work);
    71. return 0;
    72. }
    73. static void __exit mycdev_exit(void)
    74. {
    75. // 注销中断
    76. int i;
    77. for (i = 0; i < 3; i++)
    78. {
    79. free_irq(irqno[i], (void *)i);
    80. }
    81. }
    82. module_init(mycdev_init);
    83. module_exit(mycdev_exit);
    84. MODULE_LICENSE("GPL");

    现象:

  • 相关阅读:
    Java自学Day1-java语法-变量
    基于velero及minio实现etcd数据备份与恢复
    IP和MAC的作用区别
    Java 使用 JDBI 库访问MySQL 数据库
    SQL关联 NULL 值的处理
    腾讯云服务器与普通服务器区别在哪?如何选择?
    uniapp/微信小程序 项目day03
    HandlerThread使用
    简单聊下STM32F103的串口
    PyQt5_股票最近均线状态工具
  • 原文地址:https://blog.csdn.net/weixin_46260677/article/details/133044420