栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

【Linux驱动开发】中断

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【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 *)


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, 
    SCHED_SOFTIRQ,   
    HRTIMER_SOFTIRQ, 
    RCU_SOFTIRQ,     
    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; 
    unsigned long state;         
    atomic_t count;              
    void (*func)(unsigned long); 
    unsigned long data;          
};

 先定义一个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 = ,
                 ;
    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文件中定义了中断号(线)的状态:

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对应的中断号。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/831104.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号