linux中断申请之request_threaded_irq

转载:linux中断申请之request_threaded_irq

 

在linux里,中断处理分为顶半(top half),底半(bottom half),在顶半里处理优先级比较高的事情,要求占用中断时间尽量的短,在处理完成后,就激活底半,有底半处理其余任务。底半的处理方式主要有soft_irq, tasklet, workqueue三种,他们在使用方式和适用情况上各有不同。soft_irq用在对底半执行时间要求比较紧急或者非常重要的场合,主要为一些subsystem用,一般driver基本上用不上。 tasklet和work queue在普通的driver里用的相对较多,主要区别是tasklet是在中断上下文执行,而work queue是在process上下文,因此可以执行可能sleep的操作。

request_threaded_irq()是Linux kernel 2.6.30 之后新加的irq handler API

如何确定可以用到 request_threaded_irq() ?
Linux kernel config 需要定义CONFIG_GENERIC_HARDIQS
kernel config 才有支援threaded irq

    Moving interrupts to threads 介绍request_threaded_irq() 的由来
    http://lwn.net/Articles/302043/
    从realtime tree 移植而来,为了减少kernel 因为要等待每一个硬件中断处理的时间
    ,就另外交给kernel thread 处理中断后续工作。
    优点:
        1 减少 kernel 延迟时间
        2 避免处理中断时要分辨是在硬体中断或软体中断?
        3 更容易为kernel 中断处理除错,可能可完全取代tasklet
    原本的中断处理分上半部(硬体中断处理,必须关闭中断无法处理新的中断)跟下半部(
    软体中断处理),因此上半部的硬体中断处理必须尽可能简短,让系统反应速度更快。

    request_threaded_irq 是在将上半部的硬件中断处理缩短为只确定硬体中断来
    自我们要处理的装置,唤醒kernel thread 执行后续中断任务。

    缺点:
    对于非irq 中断的kernel threads ,需要在原本task_struct 新增struct
    irqaction 多占 4/8 bytes 记忆体空间
    linux kernel 2.6.29 之后(2.6.30)加入request_threaded_irq


    跟传统top/bottom havles 的差异是threaded_irq 受Linux kernel system
    的 process scheduling 控制,不会因为写错的bottom half 代码造成整个系统
    延迟的问题。

    也可以透过RT/non RT 跟nice 等工具调整各个thread 优先权,丢给使用率较低的
    cpu 以及受惠于kernel 原本可以对threads 做的各种控制,包括但不限于sleep,
    lock, allocate 新的记忆体区块。

    受惠最大的是shared irq line 的多个中断处理。除了可以加速共享中断造成的延迟
    ,threaded_irq 也可以降低在同一段程式码处理多个装置中断的复杂度。

    threaded irq 在使用性上也比tasklet(接着top half 直接执行,无法sleep)
    /workqueue(kernel context?) 等需要在top half 增加跟bottom half 连结与沟通
    的麻烦。

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)

IRQF_SHARED 共享中断时,dev_id不能为空,因为释放irq时要区分哪个共享中断
irq:中断号
handler:发生中断时首先要执行的硬中断处理函数,这个函数可以通过返回 IRQ_WAKE_THREADED唤醒中断线程,也可
返回IRQ_HANDLE不执行中断线程
thread_fn : 中断线程,类似于中断下半部
后三个参数与request_irq中的一致

关于IRQF_ONESHOT, 直到线程函数执行完毕才会开启该中断

IRQF_ONESHOT:Interrupt is not reenabled after the hardirq handler finished.
    Used by threaded interrupts which need to keep the irq line disabled until
the threaded handler has been run. 这里linus在邮件列表里指明IRQF_ONESHOT 的原因
Making the IRQF_ONESHOT explicit does two things:
 - it makes people who read the code *aware* of things
 - if/when you have irq conflicts and two drivers want to attach to
the same interrupt, at least you can see directly from the source what
flags they used (and again, not have to even *think* about it).

IRQF_ONESHOT 与 IRQF_SHARED 不能同时使用
当多个设备共享中断时,由于IRQF_ONESHOT会关闭中断线程的中断,而线程一般执行时间会比较长,所以是不允许的
当hardirq函数为NULL时,必须声明IRQF_ONESHOT, 表示threadirq线程中关闭该中断,在某些情况下,这个标志会非常有用
例如:设备是低电平产生中断,而硬中断函数为NULL,如果不使用IRQF_ONESHOT,就会一直产生中断执行NULL函数,中断线程
得不到执行,声明IRQF_ONESHOT后,会执行完线程才使能该中断  


点击(此处)折叠或打开

  1. /*
  2.  * gpio_irqTest.c
  3.  * PB27 receive this signal as IRQ and make the LED linking on PB17 turn on or turn off
  4.  *
  5.  */
  6.   
  7. #include <linux/types.h>
  8. #include <linux/kernel.h>
  9. #include <linux/module.h>
  10. #include <linux/init.h>
  11. #include <linux/platform_device.h>
  12. #include <linux/cdev.h>
  13. #include <linux/ioctl.h>
  14. #include <linux/fs.h>
  15. #include <linux/gpio.h>
  16. #include <linux/delay.h>
  17. #include <linux/cdev.h>
  18. #include <linux/interrupt.h>

  19. #include <asm/io.h>
  20. #include <asm/io.h>

  21. #include <mach/gpio.h>
  22. #include <mach/hardware.h>
  23. #include <mach/board.h>
  24. #include <mach/gpio.h>
  25. #include <mach/at91_pio.h>
  26. #include <mach/at91_aic.h>
  27. #include <mach/at91_pmc.h>

  28. void led_on()
  29. {
  30. // at91_set_gpio_output(AT91_PIN_PB17,1);
  31.     printk("led on\n");
  32. }
  33.   
  34. void led_off()
  35. {
  36. // at91_set_gpio_output(AT91_PIN_PB17 ,0);
  37.     printk("led off.\n");
  38. }
  39.   
  40. struct light_dev *light_devp;
  41. int light_major = 200;
  42.   
  43. struct light_dev
  44. {
  45.     struct cdev cdev;
  46.     unsigned char value;
  47. };
  48.   

  49.   
  50. static void io_init(void)
  51. {
  52.     at91_set_GPIO_periph(AT91_PIN_PB27, 0);
  53.     at91_set_gpio_input(AT91_PIN_PB27, 1);
  54.     at91_set_deglitch(AT91_PIN_PB27, 1);

  55. }
  56.   
  57.   
  58. struct gpio_irq_desc
  59. {
  60.     int pin;
  61.     int irq;
  62.     unsigned long flags;
  63.     char *name;
  64. };
  65.   
  66. static struct gpio_irq_desc gpio_irq={AT91_PIN_PB27, AT91_PIN_PB27,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING|IRQF_ONESHOT,"PB27"};
  67. static irqreturn_t gpio_irqhandler(int irq, void *dev_id)
  68. {
  69.     printk(KERN_INFO "In hard irq handler.\n");
  70.     return IRQ_WAKE_THREAD;
  71. }

  72. static irqreturn_t gpio_threadhandler(int irq, void *dev_id)
  73. {
  74.     int rst;
  75.     rst = at91_get_gpio_value(gpio_irq.pin);
  76.     printk(KERN_INFO "gpio stat: %d\n", rst);
  77.     if(rst == 0){
  78.         led_on();
  79.     }else{
  80.         led_off();
  81.     }
  82.     printk(KERN_INFO "sleep 3000ms\n");
  83.     msleep(3000);
  84.     printk(KERN_INFO "awake after sleep\n");
  85.     return IRQ_HANDLED;
  86. }
  87.   
  88. int light_open(struct inode *inode,struct file *filp)
  89. {
  90.     int err;
  91.     struct light_dev *dev;
  92.     dev = container_of(inode->i_cdev,struct light_dev,cdev);
  93.     filp->private_data = dev;
  94.     printk(KERN_DEBUG "%s", __FUNCTION__);
  95.     io_init();
  96. // err = request_threaded_irq(gpio_irq.irq,gpio_irqhandler,gpio_threadhandler,gpio_irq.flags,gpio_irq.name,(void*)0);
  97.     err = request_threaded_irq(gpio_irq.irq,NULL,gpio_threadhandler,gpio_irq.flags,gpio_irq.name,(void*)0);
  98.     if(err)
  99.     {
  100. // free_irq(gpio_irq.irq,(void*)0);
  101.         printk(KERN_DEBUG "request irq failed.\n");
  102.         return -EBUSY;
  103.     }
  104.       
  105.     return 0;
  106. }
  107.   
  108. int light_release(struct inode *inode,struct file *filp)
  109. {
  110.     free_irq(gpio_irq.irq,(void*)0);
  111.     return 0;
  112. }
  113.   
  114. int light_ioctl(struct inode *inode,struct file *filp,unsigned int cmd, unsigned long arg)
  115. {
  116.     struct light_dev *dev = filp->private_data;
  117.   
  118.     switch(cmd)
  119.     {
  120.         case 0:
  121.             at91_set_gpio_output(AT91_PIN_PB19,0);
  122.         break;
  123.   
  124.         case 1:
  125.           at91_set_gpio_output(AT91_PIN_PB19,1);
  126.        led_off();
  127.         break;
  128.   
  129.     default:
  130.   
  131.             return -ENOTTY;
  132.         // break;
  133.     }
  134.     return 0;
  135. }
  136.   
  137. struct file_operations light_fops =
  138. {
  139.     .owner = THIS_MODULE,
  140.     .open = light_open,
  141.     .release = light_release,
  142.     .unlocked_ioctl = light_ioctl,
  143. };
  144.   
  145.   
  146. static void light_setup_cdev(struct light_dev *dev,int index)
  147. {
  148.     int err,devno = MKDEV(light_major,index);
  149.   
  150.     cdev_init(&dev->cdev,&light_fops);
  151.     dev->cdev.owner = THIS_MODULE;
  152.     dev->cdev.ops = &light_fops;
  153.   
  154.     err = cdev_add(&dev->cdev,devno,1);
  155.   
  156.     if(err)
  157.     {
  158.         printk(KERN_NOTICE "Error %d adding LED%d",err,index);
  159.     }
  160. }
  161.   
  162.   
  163. int __init light_init(void)
  164. {
  165.     int result;
  166.   
  167.     dev_t dev = MKDEV(light_major,0);
  168.     if(light_major)
  169.     {
  170.           
  171.         result = register_chrdev_region(dev,1,"gpio");
  172.     }
  173.   
  174.     if(result < 0)
  175.     {
  176.         printk(KERN_DEBUG "%s: register char dev failed.\n", __FUNCTION__);
  177.         return result;
  178.     }
  179.   
  180.     light_devp = kmalloc(sizeof(struct light_dev),GFP_KERNEL);
  181.     if(!light_devp)
  182.     {
  183.         result = - ENOMEM;
  184.         goto fail_malloc;
  185.     }
  186.   
  187.     memset(light_devp,0,sizeof(struct light_dev));
  188.     light_setup_cdev(light_devp,0);
  189.       
  190.     printk(KERN_DEBUG "%s done\n", __FUNCTION__);
  191.     return 0;
  192.   
  193.     fail_malloc:unregister_chrdev_region(dev,light_devp);
  194.     return result;
  195.       
  196. }
  197.   
  198. void __exit light_cleanup(void)
  199. {
  200.     cdev_del(&light_devp->cdev);
  201.     kfree(light_devp);
  202.     unregister_chrdev_region(MKDEV(light_major,0),1);
  203. }
  204.   
  205. module_init(light_init);
  206. module_exit(light_cleanup);

  207. MODULE_AUTHOR("Enzo Fang");
  208. MODULE_LICENSE("Dual BSD/GPL");


结论:
使用
request_threaded_irq(gpio_irq.irq,gpio_irqhandler,gpio_threadhandler,gpio_irq.flags,gpio_irq.name,(void*)0);
hardirq和thread_fn同时出现时,处理thread_fn时该中断是打开的

  err = request_threaded_irq(gpio_irq.irq,NULL,gpio_threadhandler,gpio_irq.flags,gpio_irq.name,(void*)0);
但hardirq和thread_fn只有一个存在时,处理thread_fn时,中断是关闭的

转载于:https://www.cnblogs.com/pengdonglin137/p/3984649.html

<think>我们正在讨论的是Linux内核中的中断处理函数。用户之前询问了关于设备树中配置GPIO中断的pinctrl状态问题,现在转向了解`devm_request_threaded_irq`函数的用法和解释。根据引用内容,我们已知:-`devm_request_threaded_irq`用于请求并注册一个线程化的中断处理程序[^1]。-使用此函数时需要注意传递有效的中断号,否则会返回错误(如-22)[^2]。-该函数是支持中断线程化的接口,与传统的`request_irq`相比,可以简化设计,避免使用tasklet/workqueue等机制[^3]。因此,我们应详细解释该函数的作用、参数、使用方法和注意事项。</think> ### Linux 驱动中 `devm_request_threaded_irq` 函数详解 `devm_request_threaded_irq` 是 Linux 内核中用于管理线程化中断处理的核心函数[^1][^3],与传统 `request_irq` 相比,它实现了两大创新: 1. **资源自动管理**:使用 `devm_` 前缀确保驱动卸载时自动释放中断资源 2. **中断线程化**:支持将耗时中断任务转移到内核线程执行,避免阻塞整个系统 #### 一、函数原型与参数详解 ```c int devm_request_threaded_irq(struct device *dev, // 关联的设备结构体 unsigned int irq, // 硬件中断号[^2] irq_handler_t handler, // 顶半部函数 irq_handler_t thread_fn, // 底半部线程函数 unsigned long irqflags, // 中断标志 const char *devname, // 设备标识名 void *dev_id); // 共享中断ID ``` **关键参数解析**: 1. `handler`(顶半部): - 在硬中断上下文中运行(原子上下文) - 应小于 100μs,仅执行关键操作(如寄存器读取、状态标记) - 返回: - `IRQ_WAKE_THREAD`:唤醒底半部线程 - `IRQ_HANDLED`:无后续处理 ```c irqreturn_t handler(int irq, void *dev_id) { if (check_hardware_status()) return IRQ_WAKE_THREAD; // 触发线程处理 return IRQ_HANDLED; // 处理完成 } ``` 2. `thread_fn`(底半部): - 在内核线程中运行(可睡眠、调度) - 处理耗时任务(如数据传输、复杂计算) ```c irqreturn_t thread_fn(int irq, void *dev_id) { process_data_from_hardware(); // 可调用kmalloc/schedule_timeout等 return IRQ_HANDLED; } ``` 3. `irqflags` 标志组合: | 标志 | 说明 | 适用场景 | |---|---|---| | `IRQF_TRIGGER_RISING` | 上升沿触发 | GPIO按键[^2] | | `IRQF_ONESHOT` | 执行完成后才开中断 | 避免中断嵌套 | | `IRQF_SHARED` | 共享中断线 | 多个设备共享同中断号 | #### 二、典型使用流程 ```c // 驱动探测函数 static int probe(struct platform_device *pdev) { struct my_device *dev = alloc_device(); // 获取设备树中配置的中断号 int irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; // 错误处理[^2] // 注册线程化中断 int ret = devm_request_threaded_irq(&pdev->dev, irq, top_half_handler, bottom_half_thread, IRQF_TRIGGER_RISING | IRQF_ONESHOT, "my-device", dev); if (ret) { dev_err(&pdev->dev, "中断注册失败: %d\n", ret); return ret; } } ``` #### 三、与传统方案的对比 | 方案 | 上下文 | 可睡眠 | 资源管理 | 适用场景 | |---|---|---|---|---| | `request_irq` + Tasklet | 软中断 | ❌ | 手动释放 | 简单实时任务 | | `request_irq` + Workqueue | 进程 | ✅ | 手动释放 | 复杂非实时任务 | | `devm_request_threaded_irq` | 线程 | ✅ | 自动释放 | 推荐通用方案[^3] | #### 四、高频错误处理 1. **返回 -22 (EINVAL)**: - 原因:无效中断号(如 `irq=0` 或超出范围)[^2] - 解决:用 `platform_get_irq()` 替代硬编码 2. **返回 -16 (EBUSY)**: - 原因:共享中断未设置 `IRQF_SHARED` 标志 - 解决:添加共享标志或检查设备树配置 3. **内核崩溃 (oops)**: - 原因:在 `handler` 中使用睡眠函数(如 `msleep()`) - 解决:将耗时操作移至 `thread_fn` > 注意:`IRQF_ONESHOT` 必须与线程化中断配合使用,确保顶半部执行期间不会产生二次中断[^1]。 #### 五、设备树集成示例 ```dts // 设备树配置 my_device { compatible = "vendor,my-device"; interrupt-parent = <&gpio1>; interrupts = <3 IRQ_TYPE_EDGE_RISING>; // GPIO1_IO03上升沿触发 }; ``` 驱动通过 `platform_get_irq()` 自动解析该配置,无需硬编码中断号。 --- ### 相关问题 1. 如何验证 `devm_request_threaded_irq` 的顶半部和底半部执行时间? 2. 共享中断场景下 `dev_id` 参数应如何设置? 3. 线程化中断的实时性如何保障? 4. 设备树配置的 GPIO 中断与标准硬件中断在函数使用上有何区别?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值