Skip to content

Latest commit

 

History

History
254 lines (207 loc) · 10 KB

中断.md

File metadata and controls

254 lines (207 loc) · 10 KB

中断

API

  1. 申请中断函数。该函数会引起线程的睡眠,调用之后,会使能中断。(但未触发)
    int request_irq( unsigned int irq,          //要申请的中断号
                     irq_handler_t handler,     //中断处理函数
                     unsigned long flags,       //中断触发标志(include/linux/interrupt.h),
                     const char *name,          //中断名字
                     void *dev)                 // flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断,一般情况下将dev 设置为设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数

    返回值0中断申请成功     <0 申请失败
  1. 释放中断
    void free_irq(  unsigned int irq,           //中断号
                    void *dev)                  //
  1. 中断处理函数 irq_handler_t 原型, 第二个参数[in]是一个指向 void 的指针,也就是个通用指针,需要与 request_irq 申请中断函数的 dev 参数保持一致。用于区分共享中断的不同设备,dev 也可以指向设备数据结构
    irqreturn_t (*irq_handler_t) (int, void *)      //第一个[in]是 要中断处理函数相应的中断号

    enum irqreturn {
                        IRQ_NONE = (0 << 0),
                        IRQ_HANDLED = (1 << 0),
                        IRQ_WAKE_THREAD = (1 << 1),
                    };
    typedef enum irqreturn irqreturn_t;     

    返回值一般是return IRQ_RETVAL(IRQ_HANDLED)            
  1. 中断 使能&&禁止
void enable_irq(unsigned int irq)   //使能
void disable_irq(unsigned int irq)  //禁止(要等到当前正在执行的中断处理函数执行完才返回)
void disable_irq_nosync(unsigned int irq)   //禁止(函数调用以后立即返回,不会等待当前中断处理程序执行完毕)

local_irq_enable()                  //使能当前处理器中断系统
local_irq_disable()                 //禁止当前处理器中断系统

//以下成对方式适用于多线程共同处理中断
local_irq_save(flags)               //禁止中断,并且将中断状态保存在 flags 中
local_irq_restore(flags)            //恢复中断,将中断到 flags 状态

中断上 , 下 半部

  • 上半部的使用建议

    1. 要处理的内容不希望被其他中断打断,那么可以放到上半部
    2. 要处理的任务对时间敏感,可以放到上半部
    3. 要处理的任务与硬件有关,可以放到上半部

    中断底半部的实现方式

    1. 软中断

      1. 结构体定义 include/linux/interrupt.h
          struct softirq_action
          {
              void (*action)(struct softirq_action *)
          };
      1. 使用软中断,必须先使用 open_softirq 函数注册对应的软中断处理函数,软中断必须在编译的时候静态注册
          void open_softirq(  int nr,     // nr:要开启的软中断
                              void (*action)(struct softirq_action *))    // action:软中断对应的处理函数

      nr的枚举选项

          enum
          {
          HI_SOFTIRQ=0, /* 高优先级软中断 */
          TIMER_SOFTIRQ, /* 定时器软中断 */
          NET_TX_SOFTIRQ, /* 网络数据发送软中断 */
          NET_RX_SOFTIRQ, /* 网络数据接收软中断 */
          BLOCK_SOFTIRQ,
          BLOCK_IOPOLL_SOFTIRQ,
          TASKLET_SOFTIRQ, /* tasklet 软中断 */
          SCHED_SOFTIRQ, /* 调度软中断 */
          HRTIMER_SOFTIRQ, /* 高精度定时器软中断 */
          RCU_SOFTIRQ, /* RCU 软中断 */
          NR_SOFTIRQS
          };
    2. tasklet(更建议)

      1. 数据结构
          struct tasklet_struct
          {
              struct tasklet_struct *next;    /* 下一个 tasklet */
              unsigned long state;            /* tasklet 状态 */
              atomic_t count;                 /* 计数器,记录对 tasklet 的引用数 */
              void (*func)(unsigned long);    // tasklet 执行的  回调函数 
              unsigned long data;             /* 函数 func 的参数 */
          };

      使用的方法:

      1. 自行定义一个 tasklet 变量

      2. 使用 tasklet_init() 函数初始化 tasklet 变量

            void tasklet_init(  struct tasklet_struct *t,           //要初始化的tasklet
                                void (*func)(unsigned long),        //回调函数
                                unsigned long data);                // func 的传参
      3. 也可以使用宏DECLARE_TASKLET(name, func, data) 来完成tasklet的定义和初始化,该宏在 include/linux/interrupt.h

      4. 开启tasklet的调度 void tasklet_schedule(struct tasklet_struct *t)

      逻辑流程:

          /* 定义 taselet */
          struct tasklet_struct testtasklet;
      
          /* tasklet 处理函数 */
          void testtasklet_func(unsigned long data)
          {
              /* tasklet 具体处理内容 */
          }
      
          /* 中断处理函数*(即中断上半部) */
          irqreturn_t test_handler(int irq, void *dev_id)
          {
              ......
              /* !!!!!!!!!!!!!!!!!!调度 tasklet */
              tasklet_schedule(&testtasklet);
              ......
          }
      
          /* 驱动入口函数 */
          static int __init xxxx_init(void)
          {
              ......
              /* 初始化 tasklet */
              tasklet_init(&testtasklet, testtasklet_func, data);
              
              /* 注册中断处理函数 */
              request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
              ......
          }
    3. 工作队列 工作队列是在进程上下文执行,将要退后的工作交给一个内核线程去执行。因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet。

      • 表示一个工作的结构体
            struct work_struct {
                atomic_long_t data;
                struct list_head entry;
                work_func_t func;           /* 工作队列处理函数 */
            };
      • 表示一个工作队列
            struct workqueue_struct {
        
        
            };
      • 内核使用工作者线程(worker thread)来处理工作队列中的各个工作, Linux 内核使用worker 结构体表示工作者线程
            struct worker {
        
        
            };
        实际应用中,我们只需定义工作(work_struct),关于工作队列和工作者线程,基本上不用管。创建工作比较简单,使用
        1. #define INIT_WORK(_work, _func) 定义 ,_work 表示要初始化的工作, _func 是工作对应的处理函数。
        2. #define DECLARE_WORK(n, f) 一次性完成工作的创建和初始化,n 表示定义的工作(work_struct), f 表示工作对应的处理函数。
        3. 开启调度才能运行
              bool schedule_work(struct work_struct *work)
      • 逻辑流程
            /* 定义工作(work) */
            struct work_struct testwork;
        
            void testwork_func_t(struct work_struct *work);
            {
                /* work 具体处理内容 */
            }
        
            /* 中断处理函数 */
            irqreturn_t test_handler(int irq, void *dev_id)
            {
                ......
                /* 调度 work */
                schedule_work(&testwork);
                ......
            }
        
            /* 驱动入口函数 */
            static int __init xxxx_init(void)
            {
                ......
                /* 初始化 work */
                INIT_WORK(&testwork, testwork_func_t);
                /* 注册中断处理函数 */
                request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
                ......
            }

中断与设备树

  • 与中断有关的设备树属性信息
    1. #interrupt-cells, 指定中断源的信息 cells 个数
    2. interrupt-controller, 空节点,表示当前节点为中断控制器。
    3. interrupts, 指定中断号,触发方式等
    4. interrupt-parent, 指定父中断,也就是中断控制器。
  • API
    1. 获取中断号
          unsigned int irq_of_parse_and_map(  struct device_node *dev,        // 设备节点
                                              int index);                     // 索引号,interrupts属性含多条中断信息,通过index指定获取信息
          返回中断号
    2. 如果是gpio的话,可以使用 gpio_to_irq 函数获取 gpio 对应的中断号
          int gpio_to_irq(unsigned int gpio)      //gpio 获取GPIO对应的编号
          返回值GPIO对应的中断号

demo 笔记

  • 按键触发中断
    1. 按键 对应原理图 GPIO1_IO18
    2. 在设备树 .dts 文件中,添加该引脚的宏 imx6ull-pinfunc-snvs.h ==> #define MX6UL_PAD_KEY__GPIO1_IO18 0x008C 0x0318 0x0000 0x5 0x0

踩坑区

  1. free_irq时发生错误:Trying to free already-free IRQ
        int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
    
        void free_irq(unsigned int irq, void *dev)
    
    上两个函数,在使用的时候,最后一个参数必须保持一致,问题解决