Skip to content

驱动第二天---中断


  1. 中断的基本概念
  2. 中断号的获取方式
  3. 中断申请
  4. 中断处理
  5. 上传数据给用户
  6. IO模型
  7. 异步信号
  8. 中断下半部

1. 中断的基本概念

参考裸机开发中的相关资料

2. 中断号的获取方式

设备树:dts 设备树源码 设备树里面一些通用的定义,提取出,形成类似c 头文件,dtsi 称之为:设备树的头文件,#include "xxx.dtsi" dtc 把dts编译成二进制文件(dtb) 给内核使用

1,中断号--就是一个号码,需要通过一定的方式去获取到

获取中断号到方法:
    1, 宏定义
            IRQ_EINT(号码) //eyxnos4412-mach.h//旧内核2.6.xxx

    2,设备树文件中  //在3.14.0内核中,从设备树中获取
        arch/arm/boot/dts/exynos4412-fs4412.dts
        --> arch/arm/boot/dts/exynos4412.dtsi
            -->arch/arm/boot/dts/exynos4x12.dtsi
                -->arch/arm/boot/dts/exynos4x12-pinctrl.dtsi

现在以key3为例: 1,看原理图 key3--->SIM_DET---->gpx1_2

2, 查看设备树文件:arch/arm/boot/dts/exynos4x12-pinctrl.dtsi

3,确定了一个中断号的源:以key3为例,对应datasheet(参考4412手册,752页关于中断号的描述)的中断号:26

4,在设备树中,关于gpx1_2的中断号也是26

在设备树头文件中:arch/arm/boot/dts/exynos4x12-pinctrl.dtsi

 gpx1: gpx1 {
                gpio-controller;
                #gpio-cells = <2>;

                interrupt-controller;
                interrupt-parent = <&gic>;
                interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,
                             <0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;
                #interrupt-cells = <2>;
        };

做一个设备树节目:描述Key3,并加载到开发板上面

vim  arch/arm/boot/dts/exynos4412-fs4412.dts

在编程过程中,需要定义自己的节点--描述当前设备用的中断号

arch/arm/boot/dts/exynos4412-fs4412.dts

    key3_int_nod{
                    compatible = "key3_int";
                    interrupt-parent = <&gpx1>;
                    interrupts = <2 4>
    };

这里的interrupts = <2 4>

第一项2 表示。GPX1-2
第二项4 表示 中断触发方式 具体的值参考: \Documentation\devicetree\bindings\pinctrl

编译设备树:make dtbs

烧录到开发板。。。。。

设备树调试信息: cat /proc/device-tree/

2.2 在驱动中通过设备树获取中断号

在驱动中,获取设备树的信息的APIs:

// 获取到设备树中到节点
//需要:linux/of.h
struct device_node *of_find_node_by_path(const char *path);
参数:path 就是设备树节点所在的路径
返回值:struct device_node *

// 通过节点去获取到中断号码
//需要:#include <linux/of_irq.h>
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
参数1:节点
参数2:索引号
返回值:中断号

3. 中断申请

3.1申请中断

获取到了中断号之后,我们需要做一个中断号,然后使用中断申请函数,去申请中断:

request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)


参数1:中断号
参数2:这个类型的函数指针:typedef irqreturn_t (*irq_handler_t)(int, void *);
      函数指针所指向的函数的返回值是一个枚举类型:
      typedef enum irqreturn irqreturn_t;

        具体有以下定义:
        enum irqreturn {
                    IRQ_NONE        = (0 << 0),
                    IRQ_HANDLED     = (1 << 0),
                    IRQ_WAKE_THREAD     = (1 << 1),
                };


参数3:flag表示 中断触发的方式:
        #define IRQF_TRIGGER_NONE    0x00000000//内部控制器触发中断的时候的标志
        #define IRQF_TRIGGER_RISING  0x00000001//上升沿1<<0
        #define IRQF_TRIGGER_FALLING 0x00000002//下降沿1<<1
        #define IRQF_TRIGGER_HIGH    0x00000004//高电平
        #define IRQF_TRIGGER_LOW     0x00000008//低电平
        #define IRQF_TRIGGER_MASK   (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
                         IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)

参数4:名字
    可以通过cat /proc/intterrupts
参数5:给中断处理函数的参数

3.2 中断释放

释放设备中断资源:

free_irq(unsigned int irq, void * dev_id);

4.中断处理

irqreturn_t key_drv_irq_handler(int v,void *arg)
{
    printk("-------------%s-----------\n",__FUNCTION__);
    if(readl(reg_addr+4) &(1<<2) )
    {

        printk("key3 up!\n");
    }
    else
    {
        printk("key3 press down!\n");

    }
    return IRQ_HANDLED;
}

5. 上传数据给用户

    static ssize_t key_drv_read(struct file *file, char __user *buf,
        size_t nbytes, loff_t *ppos)
    {
        int usr_ret;
        //printk("--------------%s--------------\n",__FUNCTION__);

        usr_ret =copy_to_user(buf,&key_dev->key_t,sizeof(struct key_desc));
        if(usr_ret >0)
        {
            printk("read error\n");
            return usr_ret;
        }
        memset(&key_dev->key_t,0,sizeof(struct key_desc));
        return 0;
    }

6.IO模型

文件io模型:

1,非阻塞: 立即返回数据给用户,读写之间的时候段很短,非常浪费资源。

2,阻塞 :当进程在读取数据,如果资源/数据没准备好,进程就进行休眠,让出系统调度,可以减少资源开支

3,多路复用--select/poll:poll可用于多路设备,资源的监控,用于多种设备都是阻塞的情况下,可以实现轮循的动作。

4, 异步信号通知faync :不需要同步读写数据,负责监控一个可见的信号,当信号发生时才做处理,不影响其他任务的正常执行。

6.1 阻塞

当进程在读取外部设备的资源(数据),资源没有准备好,进程就会休眠

linux应用中,大部分的函数接口都是阻塞
scanf();
read();
write();
accept();

休眠在驱动的实现:

1,将当前进程加入到等待队列头中
    add_wait_queue(wait_queue_head_t * q, wait_queue_t * wait)
2,将当前进程状态设置成TASK_INTERRUPTIBLE (可中断的状态)
    set_current_state(TASK_INTERRUPTIBLE)
3,让出调度--休眠
    schedule(void)

更加智能的接口,等同于上面的三个接口:
    wait_event_interruptible(wait_queue_head_t *q, condition)

参数1:typedef struct __wait_queue_head wait_queue_head_t;
        等待头 指针
参数2:为0的时候 就等待数据
        为1的时候 ,就不等

驱动如何去写代码

1,等待队列头
    wait_queue_head_t wq_head;

    init_waitqueue_head(wait_queue_head_t *q);

2,在需要等待(没有数据)的时候,进行休眠
    wait_event_interruptible(wait_queue_head_t *q, condition)


3,在一个合适的时候(有数据),会将进程唤醒
    wake_up_interruptible

   // 表示有数据,需要去唤醒整个进程/等待队列
    wake_up_interruptible(wq_head);
    //同时设置标志位
    key_dev->key_state  = 1;

6.2 非阻塞

在读写的时候,如果没有数据,立刻返回,并且返回一个出错码 用的会比较少,因为比较耗资源

在打开(open)一个设备节点文件的时候,加入O_NONBLOCK这个标志,那么可以实现非阻塞

在fcntl.h 中的定义:

#ifndef O_NONBLOCK
#define O_NONBLOCK  00004000

对于应用程序代码来说:

open("/dev/key0", O_RDWR|O_NONBLOCK);

对于驱动程序来说:

//驱动中需要去区分,当前模式是阻塞还是非阻塞
//如果当前是非阻塞模式,并且没有数据,立马返回一个出错码
if(   (file->f_flags & O_NONBLOCK) && (!key_dev->key_status))//非阻塞的情况
{
    return -EAGAIN;
}

6.3 多路复用 (poll)

在ubuntu 主机里面,输入man poll 可以看到相应的使用方法

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数1:struct pollfd *fds

  struct pollfd {
               int   fd;         /* 打开的设备对应的fd */
               short events;     /* 请求的事件 */
               short revents;    /* 返回的事件 */
               //对于events 和 revents  常用的无非就以下几种
               //POLLIN 读
               //POLLOUT 写
               //POLLIN 出错
               };

参数2:监控的 fds 的个数 参数3:超时的时间,单位为ms(毫秒)

    如果是正数,表示等待n ms
    如果是0的时候就马上返回,非阻塞
    如果是负数,-1 表示无限等待

返回值: 为正数的时候表示成功,表示fd中有数据 返回为-1的时候 表示出错 且会设定errno 可以使用perror( ".....") 返回为0的时候 表示超时出错且fd没准备好

驱动里面代码怎么样写? 1、在file_operation 里面有这个成员:

unsigned int (*poll) (struct file *, struct poll_table_struct *);

其中,关于struct poll_table_struct的定义如下:

typedef struct poll_table_struct {
    poll_queue_proc _qproc;
    unsigned long _key;
} poll_table;

2、在file_operations myfops中添加注册成员:

const struct file_operations myfops={
    .open = key_drv_open,
    .write=  key_drv_write,
    .read = key_drv_read,
    .release = key_drv_close,
    .poll = key_poll;
};

3、实现这个成员函数:

unsigned int key_poll(struct file *filep, struct poll_table_struct * poll_t)
{
    unsigned int mask;
    printk("--------------%s--------------\n",__FUNCTION__);

    poll_wait(filp,      key_dev->wq_head,     poll_t  );

    if(key_dev->key_status)
        mask |= POLLIN;
    else
        mask = 0;

    return mask;
}

7 异步信号

异步信号通知: 当有数据到时候,驱动会发送信号(SIGIO)给应用,就可以异步去读写数据,不用主动去读写

7.1 应用--处理信号,主要是读写数据

第一步:

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

 void catch_signale(int signo)
{
    if(signo == SIGIO)
    {
        printf("we got sigal SIGIO");
        // 读取数据
        read(fd, &event, sizeof(struct key_event));
        if(event.code == KEY_ENTER)
        {
            if(event.value)
            {
                printf("APP__ key enter pressed\n");
            }else
            {
                printf("APP__ key enter up\n");
            }
        }
    }

}

第二步:

int fcntl(int fd, int cmd, ... /* arg */ );

把SIGIO的信号关联到本进程(属主进程)

把fd改成异步的模式:F_flag |= FASYNC

#define FASYNC      00020000    /* fcntl, for BSD compatibility */

//first signed SIGIO to handler func
signal(SIGIO, signal_handler);

//second  set process id to caught SIGIO
fcntl(fd,   F_SETOWN,   getid());

//last,set fd to async
flags = fcntl(fd, F_GETFL);
flags |= FASYNC;
fcntl(fd, F_SETFL, flags);

第三步:

驱动--发送信号

1,需要和进程进行关联--记录信号该发送给谁

实现一个fasync的接口:

//int (*fasync) (int, struct file *, int);
int key_fasync(int fd, struct file *filp, int on)
{
    return fasync_helper(fd, filp, on, &key_dev->fasync);   
}
const struct file_operations myfops={
        。。。。。
    .fasync = key_fsync,
};

//只需要调用这个接口:

int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp);

参数1-3从:int key_fasync(int fd, struct file *filp, int on) 传入即可

7.2 在某个特定的时候去发送信号,在有数据的时候

//发送信号
kill_fasync(&event->fasync, SIGIO,  POLLIN);//read

8 中断下半部

中断上下文是什么概念?

中断下半部的实现的方法有哪些?

1,softirq :处理速度快,使用复杂,需要修改内核源码,对于我们用户来说呢,用得比较少也不推荐大家使用

2, tasklet :使用了sotfirq做为二次封装,处理速度较快,但是它工作于中断上下文,里面不能使用阻塞型接口,延时函数

3, workqueue :并没有使用了sotfirq做为二次封装,处理速度稍慢,对于用户来说无影响,灵活之处:工作在进程上下文,可以使用阻塞型函数,也可以延时。。。

一、tasklet的使用:

首先是,tasklet 的数据结构:

struct tasklet_struct
{
    struct tasklet_struct *next;
    unsigned long state;
    atomic_t count;
    void (*func)(unsigned long);
    unsigned long data;
};

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

tasklet_schedule(struct tasklet_struct *t);

tasklet_kill(struct tasklet_struct *t);//释放资源

二、工作 队列 workqueue的实现

    struct work_struct {
        atomic_long_t data;
        struct list_head entry;
        work_func_t func;
    };

    typedef void (*work_func_t)(struct work_struct *work);



    a, 初始化

        void work_irq_half(struct work_struct *work)
        {
            printk("-------%s-------------\n", __FUNCTION__);
            // 表示有数据,需要去唤醒整个进程/等待队列
            wake_up_interruptible(&key_dev->wq_head);
            //同时设置标志位
            key_dev->key_state  = 1;

            //发送信号
            kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);

        }
        struct work_struct mywork;

        INIT_WORK(struct work_struct *work, work_func_t func);

    b, 在上半部中放入到内核线程中--启动

        schedule_work(&key_dev->mywork);