Linux驱动开发中断

Posted XXX_UUU_XXX

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux驱动开发中断相关的知识,希望对你有一定的参考价值。

中断函数

中断号

中断号用来区分不同的中断,Linux内核使用一个int变量表示中断号。中断号也称为中断线。

中断API函数

request_irq

Linux内核使用中断需要申请,request_irq用于申请中断。request_irq会导致睡眠,不能再中断上下文或禁止睡眠的代码中使用。request_irq会使能中断

int request_irq(unsigned int  irq,
                irq_handler_t handler,
                unsigned long flags,
                const char    *name,
                void          *dev);
  • irq:申请中断的中断号。
  • handler:中断处理函数,中断发生后执行中断处理函数。
  • flags:中断标志。
  • name:中断名,设置完成后在/proc/interrupts中查看中断名。
  • dev:设备结构体,如果将flags设置为IRQF_SHARED共享中断,dev用于区分不同的中断,dev设备结构体传递给中断处理函数的第二个参数。
  • 返回值:0,中断申请成功;负值,中断申请失败,-EBUSY表示中断已经被申请
中断标志flags
标志描述
IRQF_SHARED多个设备共享一个中断号,共享的所有中断都必须指定IRQF_SHARED标志
IRQF_ONESHOT中断执行一次就结束
IRQF_TRIGGER_NONE无触发
IRQF_TRIGGER_RISING上升沿触发
IRQF_TRIGGER_FALLING下降沿触发
IRQF_TRIGGER_HIGH高电平触发
IRQF_TRIGGER_LOW低电平触发

free_irq

 中断使用完后释放相应的中断,如果中断不是共享中断,free_irq会删除中断处理函数禁止中断

void free_irq(unsigned int irq,
              void         *dev);
  • irq:释放的中断号。
  • dev:区分不同的共享中断,共享中断只有在释放最后中断处理函数时才会被禁止。
  • 返回值:无。

中断处理函数

 第一个参数:中断号。

第二个参数:指向void的指针,与request_irq函数的dev参数保持一直,用于区分共享中断的不同设备。

返回值:irqreturn_t类型,共有三种返回值,一般使用IRQ_HANDLED。

/* 中断处理函数 */
irqreturn_t(*irq_handler_t)(int, void *)

/* 返回值irqreturn_t类型定义 */
enum irqreturn 
    IRQ_NONE        = (0 << 0);
    IRQ_HANDLED     = (1 << 0);
    IRQ_WAKE_THREAD = (1 << 1);
;

typedef enum irqreturn irqreturn_t

中断使能/禁止函数 

disable_irq函数需要等到当前正在执行的中断处理函数执行完才返回,需要保证不会产生新的中断,并且所有已经开始执行的中断程序已经全部退出。 

/* 中断使能 */
void enable_irq(unsigned int irq);
/* 中断禁止 */
void disable_irq(unsigned int irq);
  • irq:中断号。 

 disable_irq_nosync函数调用后立即返回,不会等待当前中断处理程序执行完毕。

void disable_irq_nosync(unsigned int irq);

 使能全局中断和禁止全局中断

local_irq_enable();
local_irq_disable();

local_irq_save禁止中断,把中断状态保存在flags;local_irq_restore恢复中断,恢复至flags状态。

local_irq_save(flags);
local_irq_restore(flags);

中断上半部和下半部

上半部中断处理函数,对时间敏感、处理速度较快、不会占用很长时间的操作放在上半部进行。

下半部:中断处理过程中比较耗时的程序放在下半部进行,Linux内核提供多种下半部机制。

Linux内核将中断分为上半部和下半部的主要目的:实现中断处理函数的快进快出。

上半部和下半部举例:上半部将数据拷贝到内存,下半部对数据进行处理。

需要根据实际情况判断哪些代码属于上半部还是下半部,可参考如下情况进行判断:

  • 要处理的内容不希望被其他中断打断,放在上半部。
  • 要处理的任务对时间敏感,放在上半部。
  • 要处理的任务和硬件有关,放在上半部。
  • 其余的任务,放在下半部。

中断下半部常用处理方法

软中断(不推荐使用)

Linux内核定义softirq_action结构体表示软中断。

struct softirq_action 
    void (*action)(struct softirq_action *);
?;

在kernel/softirq.c中定义了10个软中断。softirq_action结构体中的action成员变量是软中断的服务函数。

static struct softirq_action softirq_vec[NR_SOFTRQS];

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
;

注册软中断处理函数

void open_softirq(int nr, void (*action)(struct softirq_action *));
  • nr:要开起的软中断,从10个软中断中选择。
  • action:软中断对应的处理函数。
  • 返回值:无。 

 触发中断

void raise_softirq(unsigned int nr);
  • nr:要开起的软中断,从10个软中断中选择。
  • 返回值:无。

软中断必须哎编译的时候静态注册

tasklet(建议使用)

Linux内核定义tasklet_struct结构体表示tasklet

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 的参数 */
;

 先定义一个tasklet,然后使用tasklet_init初始化

void tasklet_init(struct tasklet_struct *t,
                  void (*func)(unsigned long), 
                  unsigned long data);
  •  t:要初始化的tasklet。
  • func:tasklet 的处理函数。
  • data:要传递给func函数的参数
  • 返回值:无。

使用宏DECLARE_TASKLET一次性完成tasklet的定义和初始化

DECLARE_TASKLET(name, func, data);

  • name:tasklet的名字。
  • func:tasklet的处理函数。
  • data:传递给func的参数。

tasklet也要用到上半部,只是上半部的中断处理函数重点是调度tasklet_schedule使tasklet在合适的时间运行。

void tasklet_schedule(struct tasklet_struct *t);
  • t:要调用的tasklet,也是宏DECLARE_TASKLET里面的name。
  • 返回值:无。 

tasklet例程

// 定义tasklet
struct tasklet_struct test_tasklet;

// tasklet处理函数
void test_tasklet_func(unsigned long data) 
    // tasklet的具体处理代码


// 中断处理函数
irqreturn_t test_handler(int irq, void *dev_id) 
    
    //调度tasklet
    tasklet_schedule(&test_tasklet);



// 驱动入口函数
static int __init xxx_init(void) 
    // 初始化tasklet
    tasklet_init(&test_tasklet, test_tasklet_func, data);
    // 注册中断处理函数
    request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);

工作队列(需要睡眠的工作中使用)

工作队列在进程上下文执行,将要推后的工作交给一个内核线程去执行,允许睡眠或重新调度

Linux内核定义work_struct结构体表示一个工作

struct work_struct 
    atomic_long_t data;
    struct list_head entry;
    work_func_t func;    // 工作队列处理函数
;

 许多工作组成工作队列,定义workqueue_struct结构体表示工作队列

struct workqueue_struct 
    struct list_head pwqs;
    struct list_head list;
    struct mutex mutex;
    int work_color;
    int flush_color;
    atomic_t nr_pwqs_to_flush;
    struct wq_flusher *first_flusher;
    struct list_head flusher_queue;
    struct list_head flusher_overflow;
    struct list_head maydays;
    struct worker *rescuer;
    int nr_drainers;
    int saved_max_active;
    struct workqueue_attrs *unbound_attrs;
    struct pool_workqueue *dfl_pwq;
    char name[WQ_NAME_LEN];
    struct rcu_head rcu;
    unsigned int flags ____cacheline_aligned;
    struct pool_workqueue __percpu *cpu_pwqs;
    struct pool_workqueue __rcu *numa_pwq_tbl[];
;

 Linux内核使用工作者线程worker thread处理工作队列中的各个工作,Linux内核定义worker结构体表示工作者线程

struct worker 
    union 
        struct list_head entry;
        struct hlist_node hentry;
    ;
    struct work_struct *current_work;
    work_func_t current_func;
    struct pool_workqueue *current_pwq;
    bool desc_valid;
    struct list_head scheduled;
    struct task_struct *task;
    struct worker_pool *pool;
    struct list_head node;
    unsigned long last_active;
    unsigned int flags;
    int id;
    char desc[WORKER_DESC_LEN];
    struct workqueue_struct *rescue_wq;
;

Linux驱动开发,只需要定义工作work_struct即可。定义work_struct结构体变量,然后INIT_WORK初始化。

#define INIT_WORK(_work, _func)
  • _work:要初始化的工作。
  • _func:工作处理函数 。

也可以使用宏DECLARE_WORK一次性完成工作的创建和初始化。

#define DECLARE_WORK(n, f)
  • n:定义的工作。
  • f:工作处理函数。 

工作的调度函数schedule_work

bool schedule_work(struct work_struct *work);
  • work:要调度的工作。
  • 返回值:0,成功;其他值,失败。 

工作队列例程

// 定义工作
struct work_struct test_work;

// 工作处理函数
void test_work_func_t(struct work_struct *work) 
    


// 中断处理函数
irqreturn_t test_handler(int irq, void *dev_id) 
    
    // 调度工作work
    schedule_work(&test_work);
    


// 驱动入口函数
static int __init xxx_init(void) 
    // 初始化工作
    INIT_WORK(&test_work, test_work_func_t);
    // 注册中断处理函数
    request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);

设备树设置中断信息

  • interrupt-controller,表示当前节点为中断控制器。
  • #interrupt-cells,指定interrupts属性的cells大小。
  • interrupts,指定中断号、触发方式。
  • interrupt-parent,指定父中断,即中断控制器。

Linux内核通过读取设备树中的中断属性信息配置中断。imx6ull.dtsi文件中intc节点表示如下:

intc: interrupt-controller@00a01000 
    compatible = "arm, cortex-a7-gic";
    #interrupt-cells = <3>;
    interrupt-controller;
    reg = <0x00a01000 0x1000>,
          <0x00a02000 0x100>;
;

#interrupt-cells描述interrupt属性的cells大小,对于ARM处理器的GIC中断控制器,共有3个cells。

  • 第一个cells表示中断类型0,SPI中断1,PPI中断
  • 第二个cells表示中断号,SPI中断的中断号范围0-987,PPI中断的中断号范围0-15。
  • 第三个cells表示标志,bit[3:0]表示中端触发类型,1,上升沿触发2,下降沿触发4,高电平触发8,低电平触发。bit[15:8]表示PPI中断的CPU掩码。

interrupt-controller节点为空表示当前节点是中断控制器

gpio节点也可作为中断控制器,imx6ull.dtsi文件中gpio5节点表示如下:

gpio5: gpio@0x0ac000 
    compatible = "fsl, imx6ul-gpio", "fsl, imx35-gpio";
    reg = <0x020ac000 0x4000>;
    interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
                 <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
;

interrupts描述中断源信息,中断类型都是SPI,触发电平是高电平,查看芯片手册gpio5的中断号一个是74,对应GPIO5_IO00~GPIO5_IO15低16位IO,一个是75,对应GPIO5_IO16~GPIO5_IO31高16位IO。 

Linux内核include/linux/irq.h文件中定义了中断号(线)的状态:

/*
 * IRQ line status.
 *
 * Bits 0-7 are the same as the IRQF_* bits in linux/interrupt.h
 *
 * IRQ_TYPE_NONE		- default, unspecified type
 * IRQ_TYPE_EDGE_RISING		- rising edge triggered
 * IRQ_TYPE_EDGE_FALLING	- falling edge triggered
 * IRQ_TYPE_EDGE_BOTH		- rising and falling edge triggered
 * IRQ_TYPE_LEVEL_HIGH		- high level triggered
 * IRQ_TYPE_LEVEL_LOW		- low level triggered
 * IRQ_TYPE_LEVEL_MASK		- Mask to filter out the level bits
 * IRQ_TYPE_SENSE_MASK		- Mask for all the above bits
 * IRQ_TYPE_DEFAULT		- For use by some PICs to ask irq_set_type
 *				  to setup the HW to a sane default (used
 *                                by irqdomain map() callbacks to synchronize
 *                                the HW state and SW flags for a newly
 *                                allocated descriptor).
 *
 * IRQ_TYPE_PROBE		- Special flag for probing in progress
 *
 * Bits which can be modified via irq_set/clear/modify_status_flags()
 * IRQ_LEVEL			- Interrupt is level type. Will be also
 *				  updated in the code when the above trigger
 *				  bits are modified via irq_set_irq_type()
 * IRQ_PER_CPU			- Mark an interrupt PER_CPU. Will protect
 *				  it from affinity setting
 * IRQ_NOPROBE			- Interrupt cannot be probed by autoprobing
 * IRQ_NOREQUEST		- Interrupt cannot be requested via
 *				  request_irq()
 * IRQ_NOTHREAD			- Interrupt cannot be threaded
 * IRQ_NOAUTOEN			- Interrupt is not automatically enabled in
 *				  request/setup_irq()
 * IRQ_NO_BALANCING		- Interrupt cannot be balanced (affinity set)
 * IRQ_MOVE_PCNTXT		- Interrupt can be migrated from process context
 * IRQ_NESTED_TRHEAD		- Interrupt nests into another thread
 * IRQ_PER_CPU_DEVID		- Dev_id is a per-cpu variable
 * IRQ_IS_POLLED		- Always polled by another interrupt. Exclude
 *				  it from the spurious interrupt detection
 *				  mechanism and from core side polling.
 */
enum 
	IRQ_TYPE_NONE		= 0x00000000,
	IRQ_TYPE_EDGE_RISING	= 0x00000001,
	IRQ_TYPE_EDGE_FALLING	= 0x00000002,
	IRQ_TYPE_EDGE_BOTH	= (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING),
	IRQ_TYPE_LEVEL_HIGH	= 0x00000004,
	IRQ_TYPE_LEVEL_LOW	= 0x00000008,
	IRQ_TYPE_LEVEL_MASK	= (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH),
	IRQ_TYPE_SENSE_MASK	= 0x0000000f,
	IRQ_TYPE_DEFAULT	= IRQ_TYPE_SENSE_MASK,

	IRQ_TYPE_PROBE		= 0x00000010,

	IRQ_LEVEL		= (1 <<  8),
	IRQ_PER_CPU		= (1 <<  9),
	IRQ_NOPROBE		= (1 << 10),
	IRQ_NOREQUEST		= (1 << 11),
	IRQ_NOAUTOEN		= (1 << 12),
	IRQ_NO_BALANCING	= (1 << 13),
	IRQ_MOVE_PCNTXT		= (1 << 14),
	IRQ_NESTED_THREAD	= (1 << 15),
	IRQ_NOTHREAD		= (1 << 16),
	IRQ_PER_CPU_DEVID	= (1 << 17),
	IRQ_IS_POLLED		= (1 << 18),
;

具体开发板中fxls8471磁力计的中断属性信息示例如下:

fxls8471@1e 
    compatible = "fsl, fxls8471";
    reg = <0x1e>;
    position = <0>;

    interrupt-parent = <&gpio5>;
    interrupts = <0 8>;
;

 具体开发板的设备树一般只需要添加两行:

  • interrupt-parent属性设置gpio5为中断控制器 。
  • interrupts设置中断信息,0,表示GPIO5_IO00,8表示低电平触发。

获取中断号

 irq_of_parse_and_map

unsigned int irq_of_parse_and_map(struct device_node *dev,
                                  int index);
  • dev:设备节点。
  • index:interrupts属性可能包含多条中断信息,index指定要获取的信息。
  • 返回值:中断号。

gpio_to_irq

获取gpio对应的中断号

inT gpio_to_irq(unsigned int gpio);
  • gpio:GPIO编号。
  • 返回值:GPIO对应的中断号。

以上是关于Linux驱动开发中断的主要内容,如果未能解决你的问题,请参考以下文章

Linux——Linux驱动之GPIO中断的应用实战(下)(中断下文之工作队列的使用,处理非常复杂且耗时的操作)

Linux驱动开发-内核共享工作队列

Linux驱动开发-内核共享工作队列

Linux驱动实践:中断处理中的工作队列 workqueue 是什么鬼?

Linux——Linux驱动之GPIO中断的应用实战(中断概述及相关函数设备树中指定中断驱动中获取中断)

Linux驱动开发-中断处理模型笔记 5