Linux中断驱动

Linux 内核提供了完善的中断框架,我们只需要申请中断,然后注册中断处理函数即可。

一. API

1.申请/释放

request_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)

irq:要申请中断的中断号。
handler:中断处理函数,当中断发生以后就会执行此中断处理函数。
flags:中断标志,包括:
   IRQF_SHARED   多个设备共享一个中断线,共享的所有中断都必须指定此标志。
                如果使用共享中断的话,request_irq函数的 dev 参数就是唯一区分他们的标志。
    IRQF_ONESHOT          单次中断,中断执行一次就结束。
    IRQF_TRIGGER_NONE     无触发。
    IRQF_TRIGGER_RISING     上升沿触发。
    IRQF_TRIGGER_FALLING    下降沿触发。

name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
dev: 如果将 flags 设置为 IRQF_SHARED 的话, dev 用来区分不同的中断,
    一般情况下将dev 设置为设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数

2. 中断处理函数

irqreturn_t (*irq_handler_t) (int, void *)

第一个参数是要中断处理函数要相应的中断号。
第二个参数是一个指向 void 的指针,也就是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。

一般中断服务函数返回值使用如下形式:
return IRQ_RETVAL(IRQ_HANDLED)

 3.使能/禁止

void enable_irq(unsigned int irq)

void disable_irq(unsigned int irq)
void disable_irq_nosync(unsigned int irq)

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

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

4.中断触发方式 获取和设置

u32 irq_get_trigger_type(unsigned int irq)

int irq_set_irq_type(unsigned int irq, unsigned int type);

5.示例 


#include <linux/interrupt.h>


//int handler
irqreturn_t test_handler(int arg,void *dev_id)
{
    //todo
}

// 驱动入口
static int __init xxx_init(void)
{
    //int请求
    request_irq(xxx_irq,test_handler,0,"xxx",&xxx_dev);
    
    request_irq 函数会激活(使能)中断,所以不需要我们手动去使能中断
    //enable_irq(xxx_irq);

    ...

    disable_irq(xxx_irq);//或者 disable_irq_nosync(xxx_irq);
    return 0;
}


static void __exit xxx_exit(void)
{
    free_irq(xxx_irq,&xxx_dev);
    return;
}

二.中断上下部

1.上部

上半部:上半部就是中断处理函数,那些处理过程比较快,

         不会占用很长时间的处理就可以放在上半部完成。

request_irq 申请中断的时候注册的中断服务函数属于中断处理的上半部,

        只要中断触发,那么中断处理函数就会执行。

2.下部

下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,

         交给下半部去执行,这样中断处理函数就会快进快出。

3.区分 

①、如果要处理的内容不希望被其他中断打断, 那么可以放到上半部。
②、如果要处理的任务对时间敏感,可以放到上半部。
③、如果要处理的任务与硬件有关,可以放到上半部
④、除了上述三点以外的其他任务,优先考虑放到下半部 

 三. 下半部机制

1.软中断 

a.结构体定义 softirq_action

定义在 include/linux/interrupt.h 

struct softirq_action
{
    void (*action)(struct softirq_action *);
};

action 成员变量就是软中断的服务函数

在 kernel/softirq.c 文件中一共定义了 10 个软中断
static struct softirq_action softirq_vec[NR_SOFTIRQS];

enum
{
    HI_SOFTIRQ=0,
    TIMER_SOFTIRQ,
    NET_TX_SOFTIRQ,
    NET_RX_SOFTIRQ,
    BLOCK_SOFTIRQ,
    IRQ_POLL_SOFTIRQ,
    TASKLET_SOFTIRQ,
    SCHED_SOFTIRQ,
    HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on the numbering. Sigh! */
    RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
    NR_SOFTIRQS
};

b.注册

void open_softirq(int nr, void (*action)(struct softirq_action *))

nr:要开启的软中断
action:软中断对应的处理函数。

 c.触发

void raise_softirq(unsigned int nr)

 注意事项:

        软中断必须在编译的时候静态注册! Linux 内核使用 softirq_init 函数初始化软中断,softirq_init 函数定义在 kernel/softirq.c 文件里面。

2.tasklet

tasklet 是利用软中断来实现的另外一种下半部机制,在软中断和 tasklet 之间,用 tasklet 。

a.结构体定义

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

 b.初始化和定义

void tasklet_init(struct tasklet_struct *t, 
       void (*func)(unsigned long), unsigned long data);

t:要初始化的 tasklet
func: tasklet 的处理函数。
data: 要传递给 func 函数的参数

用宏DECLARE_TASKLET来一次性完成tasklet的定义和初始化
定义在 include/linux/interrupt.h 文件中,
定义如下:
    DECLARE_TASKLET(name, func, data)
    
    name 为要定义的 tasklet 名字,这个名字就是一个 tasklet_struct 类型的时候变量, 
    func 就是 tasklet 的处理函数, 
    data 是传递给 func 函数的参数。

c.调度

void tasklet_schedule(struct tasklet_struct *t)

t:要调度的 tasklet,也就是 DECLARE_TASKLET 宏里面的 name。

d.示例

/* 定义 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。

a.结构体定义

定义在 include/linux/workqueue.h 

work_struct 结构体表示一个工作
struct work_struct {
    atomic_long_t data;
    struct list_head entry;
    work_func_t func;   /* 工作处理函数 */
#ifdef CONFIG_LOCKDEP
    struct lockdep_map lockdep_map;
#endif
};


这些工作组织成工作队列,工作队列使用 workqueue_struct 结构体表示
定义在: kernel/workqueue.c 
struct workqueue_struct {
    。。。
};

Linux 内核使用工作者线程(worker thred)来处理工作队列中的各个工作,
用worker结构体表工作者线程,定义在kernel/workqueue_internal.h 
struct worker {
    。。。
};

         在实际的驱动开发中,我们只需要定义工作(work_struct)即可,关于工作队列和工作者线程我们基本不用去管。 

 b.API(初始化,调度)

初始化工作:
    #define INIT_WORK(_work, _func)

    work 表示要初始化的工作, _
    func 是工作对应的处理函数

创建和初始化:
    #define DECLARE_WORK(n, f)

    n 表示定义的工作(work_struct)
    f 表示工作对应的处理函数。

调度:
    bool schedule_work(struct work_struct *work)

c.示例


#include <linux/workqueue.h>
#include <linux/interrupt.h>
//定义
struct work_struct testwork;

//work todo
void testwork_func_t(struct work_struct *work)
{
    ...
}

//int handler
irqreturn_t test_handler(int arg,void *dev_id)
{
    schedule_work(&testwork);
}

// 驱动入口
static int __init xxx_init(void)
{
    //init
    INIT_WORK(&testwork, testwork_func_t);

    //int
    request_irq(xxx_irq,test_handler,0,"xxx",&xxx_dev);

    return 0;
}

四 设备树中断信息节点

 参考:Documentation/devicetree/bindings/arm/gic.txt

1.中断控制器描述

compatible 属性值为“ arm,cortex-a9-gic”

        在 Linux 内核源码中搜索“ arm,cortex-a9-gic”即可找到 GIC 中断控制器驱动文件。

#interrupt-cells 和#address-cells、 #size-cells 一样。

        表示此中断控制器下设备的 cells 大小。

        对于 ARM 处理的 GIC 来说,一共有 3 个 cells:
                第一个 cells:中断类型,

                         0 表示 SPI 中断(共享外设中断,中断号 32~92) 

                         1 表示 PPI 中断(私有外设中断,中断号 27~31) 

                第二个 cells:中断号(该 cell 描述的中断号是从 0 开始)
                第三个 cells:标志

                        bit[3:0]  中断触发类型(1:上升沿,2:下降沿,4:高电平,8:低电平)

                        bit[15:8]  PPI 中断的 CPU 掩码

interrupt-controller 属性来表示当前节点是中断控制器节点。

 2.具体设备中断描述

interrupts 属性来描述 gpio 中断源的信息,“ interrupts = <0 20 4>”表示如下:

        0 表示中断类型,也就是前面说到的 SPI 中断;

        20 对应的就是 ZYNQ 7010/7020 GPIO 外设所对应的中断号;

       (注意:GPIO 外设所对应的中断号为 52,

                将 52 减去 SPI 类型中断的起始编号 32 即可得出20)

        4 表示中断触发类型是高电平触发。

#interrupt-cells 为 2,在设备树中使用 gpio 中断需要提供 2 个参数,

        第一个表示 GPIO 编号,

        第二个表示该 GPIO 的中断触发类型。

在设备树中表示一个 GPIO 中断示例:

interrupt-parent 属性设置中断控制器,这里使用 gpio0 作为中断控制器。
interrupts 设置中断信息,

        12 表示一个具体的 GPIO 引脚编号,也就是对应GPIO0_12;

         2 表示该 GPIO 的中断触发类型为下降沿触发。

3. 总结 

4. 获取中断号

unsigned int irq_of_parse_and_map(struct device_node *dev, int index)

dev: 设备节点
index:索引号, interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。
返回值:中断号。


GPIO获取中断号方式:
int gpio_to_irq(unsigned int gpio)

gpio: 要获取的 GPIO 编号。
返回值: GPIO 对应的中断号

 五. 按键中断示例

1. 按键设备树


	key {
		compatible = "m0mo,key";
		status = "okay";
		key-gpio = <&gpio0 12 GPIO_ACTIVE_LOW>;

		interrupt-parent = <&gpio0>;
		interrupts = <12 IRQ_TYPE_EDGE_BOTH>;
	};

2.驱动编写

/*中断相关操作:1 申请中断;2获取触发方式或者设置触发方式;3注册使能中断*/
    key.irq = gpio_to_irq(key.gpio);

    key.irq_flag = irq_get_trigger_type(key.irq);
    if (IRQF_TRIGGER_NONE == key.irq_flag)
        key.irq_flag = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
    irq_set_irq_type(key.irq, key.irq_flag);  

    ret = request_irq(key.irq,key_handler, key.irq_flag ,"key_int",&key);

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值