linux驱动开发 - 09_中断

这篇具有很好参考价值的文章主要介绍了linux驱动开发 - 09_中断。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


链接: C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂

在裸机中使用中断需要做一大堆的工作,比如配置寄存器,使能 IRQ 等等。 Linux 内核提供了完善的中断框架,只需要申请中断,然后注册中断处理函数即可,使用非常方便,不需要一系列复杂的寄存器配置。

1 Linux 中断简介

1.1 Linux 中断 API 函数

裸机里面中断的处理方法:
①、使能中断,初始化相应的寄存器。
②、注册中断服务函数,也就是向 irqTable 数组的指定标号处写入中断服务函数
②、中断发生以后进入 IRQ 中断服务函数,在 IRQ 中断服务函数在数组 irqTable 里面查找具体的中断处理函数,找到以后执行相应的中断处理函数。

在 Linux 内核中也提供了大量的中断相关的 API 函数,这些跟中断有关的API 函数:

1、中断号

每个中断都有一个中断号,通过中断号即可区分不同的中断在 Linux 内核中使用一个 int 变量表示中断号

2、 request_irq 函数

在 Linux 内核中使用某个中断是需要申请的,request_irq 函数用于申请中断request_irq函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 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 设置为设备结构体, dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
    
返回值: 0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了。
  • flags:中断标志,可以在文件 include/linux/interrupt.h 里面查看所有的中断标志,几个常用的中断标志
标志 描述
IRQF_SHARED 多个设备共享一个中断线,共享的所有中断都必须指定此标志。 如果使用共享中断的话, request_irq 函数的 dev 参数就是唯一 区分他们的标志。
IRQF_ONESHOT 单次中断,中断执行一次就结束。
IRQF_TRIGGER_NONE 无触发。
IRQF_TRIGGER_RISING 上升沿触发。
IRQF_TRIGGER_FALLING 下降沿触发。
IRQF_TRIGGER_HIGH 高电平触发。
IRQF_TRIGGER_LOW 低电平触发。
3、free_irq 函数

使用中断的时候需要通过 request_irq 函数申请,使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会删除中断处理函数并且禁止中断。 free_irq函数原型如下所示:

void free_irq(unsigned int irq,void *dev)

函数参数和返回值含义如下:
    irq: 要释放的中断。
    dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。

返回值:无。  
4、中断处理函数

使用 request_irq 函数申请中断的时候需要设置中断处理函数,中断处理函数格式如下所示:

irqreturn_t (*irq_handler_t) (int, void *)
  • 第一个参数是要中断处理函数要相应的中断号。

  • 第二个参数是一个指向 void 的指针,也就是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备,dev 也可以指向设备数据结构。

  • 中断处理函数的返回值为 irqreturn_t 类型, irqreturn_t 类型定义如下所示:

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

typedef enum irqreturn irqreturn_t;

rqreturn_t 是个枚举类型,一共有三种返回值。一般中断服务函数返回值使用如下形式:

return IRQ_RETVAL(IRQ_HANDLED)  
5、中断使能与禁止函数

常用的中断使用和禁止函数如下所示:

void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)
  • enable_irq 和 disable_irq 用于使能和禁止指定的中断, irq 就是要禁止的中断号。

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

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

void disable_irq_nosync(unsigned int irq)
  • 关闭当前处理器的整个中断系统
local_irq_enable()
local_irq_disable()

local_irq_enable 用于使能当前处理器中断系统, local_irq_disable 用于禁止当前处理器中断系统。

local_irq_save(flags)
local_irq_restore(flags)

local_irq_save 函数用于禁止中断,并且将中断状态保存在 flags 中。
local_irq_restore 用于恢复中断,将中断到 flags 状态。

1.2 上半部与下半部

中断处理过程就分为了两部分:

  • **上半部:**上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。

  • **下半部:**如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出

因此, Linux 内核将中断分为上半部和下半部的主要目的就是实现中断处理函数的快进快出,**那些对时间敏感、执行速度快的操作可以放到中断处理函数中,也就是上半部。**剩下的所有工作都可以放到下半部去执行,比如在上半部将数据拷贝到内存中,关于数据的具体处理就可以放到下半部去执行。

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

上半部处理很简单,直接编写中断处理函数就行了,关键是下半部该怎么做呢? Linux 内核提供了多种下半部机制

1、软中断

Linux 内核使用结构体 softirq_action 表示软中断, softirq_action结构体定义在文件 include/linux/interrupt.h 中,内容如下:

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

在 kernel/softirq.c 文件中一共定义了 10 个软中断

static struct softirq_action softirq_vec[NR_SOFTIRQS];

NR_SOFTIRQS 是枚举类型,定义在文件 include/linux/interrupt.h 中,定义如下:

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
};

数组 softirq_vec 有 10 个元素。softirq_action 结构体中的 action 成员变量就是软中断的服务函数,数组 softirq_vec 是个全局数组,所有的 CPU都可以访问到,每个 CPU 都有自己的触发和控制机制,并且只执行自己所触发的软中断。

  • 使用软中断,必须先使用 open_softirq 函数注册对应的软中断处理函数, open_softirq 函数原型如下:
void open_softirq(int nr, void (*action)(struct softirq_action *))
    
函数参数和返回值含义如下:
    nr:要开启的软中断,在示例代码 51.1.2.3 中选择一个。
    action:软中断对应的处理函数。
返回值: 没有返回值
  • 注册好软中断后需要通过 raise_softirq 函数触发, raise_softirq 函数原型如下:
void raise_softirq(unsigned int nr)

函数参数和返回值含义如下:
	nr:要触发的软中断。
返回值: 没有返回值。
2、tasklet

tasklet 是利用软中断来实现的另外一种下半部机制。Linux 内核使用 tasklet_struct 结构体来表示 tasklet:

484 struct tasklet_struct
485 {
486 	struct tasklet_struct *next; 	/* 下一个 tasklet */
487 	unsigned long state; 			/* tasklet 状态 */
488 	atomic_t count; 				/* 计数器,记录对 tasklet 的引用数 */
489 	void (*func)(unsigned long); 	/* tasklet 执行的函数 */
490 	unsigned long data; 			/* 函数 func 的参数 */
491 };

第 489 行的 func 函数就是 tasklet 要执行的处理函数,用户定义函数内容,相当于中断处理函数。

使用 tasklet,必须先定义一个 tasklet,然后使用 tasklet_init 函数初始化 tasklet,taskled_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 定义在 include/linux/interrupt.h 文件中,定义如下:
DECLARE_TASKLET(name, func, data)
    
name 为要定义的 tasklet 名字,这个名字就是一个 tasklet_struct 类型的时候变量
func就是 tasklet 的处理函数, data 是传递给 func 函数的参数。

在上半部,也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运行, tasklet_schedule 函数原型如下:

void tasklet_schedule(struct tasklet_struct *t)

函数参数和返回值含义如下:
t:要调度的 tasklet,也就是 DECLARE_TASKLET 宏里面的 name。
返回值: 没有返回值  
  • 关于 tasklet 的参考使用示例如下所示:
/* 定义 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

  • 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 结构体表示工作者线程, 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;
};

每个 worker 都有一个工作队列,工作者线程处理自己工作队列中的所有工作。

在实际的驱动开发中,只需要定义工作(work_struct)即可直接定义一个 work_struct 结构体变量即可,然后使用 INIT_WORK 宏来初始化工作, INIT_WORK 宏定义如下:

#define INIT_WORK(_work, _func)

_work 表示要初始化的工作, _func 是工作对应的处理函数。

  • 也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化,宏定义如下:
#define DECLARE_WORK(n, f)

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

和 tasklet 一样,工作也是需要调度才能运行的,工作的调度函数为 schedule_work,函数原型如下所示:

bool schedule_work(struct work_struct *work)
函数参数和返回值含义如下:
    work: 要调度的工作。
    返回值: 0 成功,其他值 失败。  

工作队列的参考使用示例如下所示:

/* 定义工作(work) */
struct work_struct testwork;

/* work 处理函数 */
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.3 设备树中断信息节点

如果使用设备树需要在设备树中设置好中断属性信息, Linux 内核通过读取设备树中 的中断属性信息来配置中断。

  • 中断有关的设备树属性信息:
    ①、 #interrupt-cells,指定中断源的信息 cells 个数。
    ②、 interrupt-controller,表示当前节点为中断控制器。
    ③、 interrupts,指定中断号,触发方式等。
    ④、 interrupt-parent,指定父中断,也就是中断控制器。

打开 imx6ull-alientek-emmc.dts 文件,找到如下所示内容:

fxls8471@1e {
    compatible = "fsl,fxls8471";
    reg = <0x1e>;
    position = <0>;
    interrupt-parent = <&gpio5>;
    interrupts = <0 8>;
};
  • 第 5 行, interrupt-parent 属性设置中断控制器,这里使用 gpio5 作为中断控制器。

  • 第 6 行, interrupts 设置中断信息, 0 表示 GPIO5_IO00, 8 表示低电平触发。

1.4 获取中断号

编写驱动的时候需要用到中断号, 中断信息已经写到了设备树里面,可以通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号,函数原型如下:

unsigned int irq_of_parse_and_map(struct device_node *dev,
									int index)
函数参数和返回值含义如下:
    dev: 设备节点。
    index:索引号, interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。
返回值:中断号。

使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号,函数原型如下:

int gpio_to_irq(unsigned int gpio)
    
函数参数和返回值含义如下:
    gpio: 要获取的 GPIO 编号。
    返回值: GPIO 对应的中断号。

2 程序编写

以KEY0 按键,采用中断的方式,并且采用定时器来实现按键消抖,应用程序读取按键值并且通过终端打印出来。

2.1 修改设备树文件

使用到了按键 KEY0,按键 KEY0 使用中断模式,因此需要在“key”节点下添加中断相关属性,添加完成以后的“key”节点内容如下所示:

key {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "atkalpha-key";
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_key>;
    key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; 		/* KEY0 */
    interrupt-parent = <&gpio1>;
    interrupts = <18 IRQ_TYPE_EDGE_BOTH>; 			/* FALLING RISING */
    status = "okay";
};
  • 第 8 行,设置 interrupt-parent 属性值为“gpio1”,因为 KEY0 所使用的 GPIO 为GPIO1_IO18,也就是设置 KEY0 的 GPIO 中断控制器为 gpio1。
  • 第 9 行,设置 interrupts 属性,也就是设置中断源,第一个 cells 的 18 表示 GPIO1 组的 18号 IO。 IRQ_TYPE_EDGE_BOTH 定义在文件 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_EDGE_BOTH 表示上升沿和下降沿同时有效,相当于 KEY0 按下和释放都会触发中断。

设备树编写完成以后使用“ make dtbs”命令重新编译设备树,然后使用新编译出来的imx6ull-alientek-emmc.dtb 文件启动 Linux 系统。

2.2 按键中断驱动程序编写

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/interrupt.h>


#define IMX6UIRQ_CNT     1
#define IMX6UIRQ_NAME    "imx6uirq"
#define KEY_NUM            1
#define KEY0VALUE          0X01
#define INVAKEY            0XFF


/* key结构体 */
struct irq_keydesc{
    int gpio;               /* io编号 */
    int irqnum;             /* 中断号 */
    unsigned char value;    /* 键值 */
    char name[10];          /* 名字 */
    irqreturn_t (*handler) (int, void *);  /* 中断处理函数 */
    struct tasklet_struct tasklet;
};


/* imx6uirq设备结构体 */
struct imx6uirq_dev{
    dev_t devid;
    int major;
    int minor;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *nd;
    struct irq_keydesc irqkey[KEY_NUM];
    struct timer_list timer; 

    atomic_t keyvalue;
    atomic_t releasekey;


};

struct imx6uirq_dev imx6uirq; /* irq设备 */

static int imx6uirq_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &imx6uirq;
    return 0;
}


static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue;
    unsigned char releasekey;
    struct imx6uirq_dev *dev = filp->private_data;

    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    if(releasekey) {        /* 有效按键 */
        if(keyvalue & 0x80) {
            keyvalue &= ~0x80;
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        } else {
            goto data_error;
        }
        atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
    } else {
        goto data_error;
    }


    return ret;

data_error:
    return -EINVAL;
}

/* 操作集 */
static const struct file_operations imx6uirq_fops = {
    .owner		=	THIS_MODULE,
	.open		=	imx6uirq_open,
    .read       =   imx6uirq_read,
};

/* 按键中断处理函数 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{

    struct imx6uirq_dev *dev = dev_id;
#if 0
    dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); /* 20ms定时 */
#endif
    tasklet_schedule(&dev->irqkey[0].tasklet);

	return IRQ_HANDLED;
}

/* tasklet */
static void key_tasklet(unsigned long data)
{
    struct imx6uirq_dev *dev = (struct imx6uirq_dev*)data;

    dev->timer.data = data;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20)); /* 20ms定时 */

}

/* 定时器处理函数 */
static void timer_func(unsigned long arg) {
    int value = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev*)arg;

    value = gpio_get_value(dev->irqkey[0].gpio);
    if(value == 0)   {          /* 按下 */
        atomic_set(&dev->keyvalue, dev->irqkey[0].value);
    } else if(value == 1) {     /* 释放 */
        atomic_set(&dev->keyvalue, 0X80 | (dev->irqkey[0].value));
        atomic_set(&dev->releasekey, 1);  /* 完成的按键过程 */
    }
}


/* 按键初始化 */
static int keyio_init(struct imx6uirq_dev *dev)
{
    int ret  = 0;
    int i = 0;

    /* 1,按键初始化 */
    dev->nd = of_find_node_by_path("/key");
    if(dev->nd == NULL) {
        ret = -EINVAL;
        goto fail_nd;
    }

    for(i = 0; i < KEY_NUM; i++) {
        dev->irqkey[i].gpio = of_get_named_gpio(dev->nd, "key-gpio", i);
    }
    for(i = 0; i < KEY_NUM; i++) {
        memset(dev->irqkey[i].name, 0, sizeof(dev->irqkey[i].name));
        sprintf(dev->irqkey[i].name, "KEY%d", i);
        gpio_request(dev->irqkey[i].gpio, dev->irqkey[i].name);
        gpio_direction_input(dev->irqkey[i].gpio);

        dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio); /* 获取中断号 */
#if 0
        dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd, i);
#endif

    }

    dev->irqkey[0].handler = key0_handler;
    dev->irqkey[0].value  = KEY0VALUE;
   // dev->irqkey[0].tasklet = key_tasklet;

    /* 2,按键中断初始化 */
    for(i = 0; i < KEY_NUM; i++) {
        ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[i].handler, 
                         IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, 
                         dev->irqkey[i].name, &imx6uirq);
        if(ret) {
            printk("irq %d request failed!\r\n", dev->irqkey[i].irqnum);
            goto fail_irq;
        }

        tasklet_init(&dev->irqkey[i].tasklet, key_tasklet, (unsigned long)dev);
    }

    /* 3、初始化定时器 */
    init_timer(&imx6uirq.timer);
    imx6uirq.timer.function = timer_func;
    return 0;

fail_irq:
    for(i = 0; i < KEY_NUM; i++) {
        gpio_free(dev->irqkey[i].gpio);
    }
fail_nd:
    return ret;
}

/* 驱动入口函数 */
static int __init imx6uirq_init(void)
{

    int ret = 0;

    /* 注册字符设备驱动 */
    imx6uirq.major = 0;
    if(imx6uirq.major) { /* 给定主设备号 */
        imx6uirq.devid = MKDEV(imx6uirq.major, 0);
        ret = register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
    } else {            /* 没给定设备号 */
        ret = alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);
        imx6uirq.minor = MINOR(imx6uirq.devid);
    }
    if(ret < 0) {
        goto fail_devid;
    }
    printk("imx6uirq major = %d, minor = %d\r\n", imx6uirq.major, imx6uirq.minor);

    /* 2,初始化cdev */
    imx6uirq.cdev.owner = THIS_MODULE;
    cdev_init(&imx6uirq.cdev, &imx6uirq_fops);

    /* 3,添加cdev */
    ret = cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
    if (ret)
		goto fail_cdevadd;

    /* 4、创建类 */
    imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.class)) {
        ret = PTR_ERR(imx6uirq.class);
        goto fail_class;
    }

    /* 5,创建设备  */
    imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.device)) {
        ret = PTR_ERR(imx6uirq.device);
        goto fail_device;
    }

    /* 初始化IO */
    ret = keyio_init(&imx6uirq);
    if(ret < 0) {
        goto fail_keyinit;
    }


    /* 初始化原子变量 */
    atomic_set(&imx6uirq.keyvalue, INVAKEY);
    atomic_set(&imx6uirq.releasekey, 0);

    return 0;

fail_keyinit:
fail_device:
    class_destroy(imx6uirq.class);
fail_class:
    cdev_del(&imx6uirq.cdev);
fail_cdevadd:
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
fail_devid:
    return ret;

}

/* 驱动出口函数 */
static void __exit imx6uirq_exit(void)
{
    int i = 0;
    /*1、释放中断 */
    for(i = 0; i < KEY_NUM; i++) {
        free_irq(imx6uirq.irqkey[i].irqnum, &imx6uirq);
    }
    /* 2,释放IO */
    for(i = 0; i < KEY_NUM; i++) {
        gpio_free(imx6uirq.irqkey[i].gpio);
    }

    /* 3,删除定时器 */
    del_timer_sync(&imx6uirq.timer);

    /* 注销字符设备驱动 */
    cdev_del(&imx6uirq.cdev);
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);

    device_destroy(imx6uirq.class, imx6uirq.devid);
    class_destroy(imx6uirq.class);

}

module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("kaka");

2.3 编写测试 APP

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
/*
 *argc:应用程序参数个数
 *argv[]:具体的参数内容,字符串形式 
 *./imx6uirqAPP  <filename>  
 * ./imx6uirqAPP /dev/mx6uirq
 */


int main(int argc, char *argv[])
{
    int fd, ret;
    char *filename;
    unsigned char data;

    if(argc != 2) {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    fd = open(filename, O_RDWR);
    if(fd < 0) {
        printf("file %s open failed!\r\n", filename);
        return -1;
    }

    /* 循环读取 */
    while(1) {
        ret = read(fd, &data, sizeof(data));
        if(ret < 0) {

        } else {
            if(data)
                printf("key value = %#x\r\n", data);
        }

    }

    close(fd);

    return 0;
}

4 编译驱动程序和测试 APP

1、编译驱动程序

编写 Makefile 文件

KERNELDIR := /home/kaka/linux/IMX6ULL/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)

obj-m := tasklet.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

输入如下命令编译出驱动模块文件:

make -j32

编译成功以后就会生成一个名为“dtsled.ko ”的驱动模块文件。

2、编译测试 APP
arm-linux-gnueabihf-gcc keyApp.c -o keyApp

5运行测试

将编译出来的tasklet.ko 和 keyApp 这两个文件拷贝到rootfs/lib/modules/4.1.15目录中 ,输入如下命令加载 led.ko 驱动模块:

depmod //第一次加载驱动的时候需要运行此命令
modprobe tasklet.ko //加载驱动

驱动加载成功以后可以通过查看/proc/interrupts 文件来检查一下对应的中断有没有被注册上,输入如下命令:

/lib/modules/4.1.15 # cat /proc/interrupts
           CPU0
 16:       9567       GPC  55 Level     i.MX Timer Tick
 18:          0       GPC  33 Level     2010000.ecspi
 19:        326       GPC  26 Level     2020000.serial
 46:         11  gpio-mxc  18 Edge      KEY0
 47:          0  gpio-mxc  19 Edge      2190000.usdhc cd
196:          0       GPC   4 Level     20cc000.snvs:snvs-powerkey
197:      11026       GPC 120 Level     20b4000.ethernet
198:          0       GPC 121 Level     20b4000.ethernet

看出 tasklet.c 驱动文件里面的 KEY0 中断已经存在了,触发方式为跳边沿(Edge),中断号为 46

使用如下命令来测试中断:

./imx6uirqApp /dev/imx6uirq

按下开发板上的 KEY0 键,终端就会输出按键

/lib/modules/4.1.15 # ./imx6uirqApp /dev/imx6uirq
key value = 0X1
key value = 0X1
key value = 0X1
key value = 0X1

卸载驱动的话输入如下命令即可:文章来源地址https://www.toymoban.com/news/detail-436224.html

rmmod tasklet.ko

到了这里,关于linux驱动开发 - 09_中断的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【IMX6ULL驱动开发学习】14.Linux驱动开发 - GPIO中断(设备树 + GPIO子系统)

    代码自取 【14.key_tree_pinctrl_gpios_interrupt】: https://gitee.com/chenshao777/imx6-ull_-drivers 主要接口函数: 1. of_gpio_count (获得GPIO的数量) 2. kzalloc (向内核申请空间) 3. of_get_gpio (获取GPIO子系统标号) 4. gpio_to_irq (根据GPIO子系统标号得到软件中断号) 5. request_irq (根据软件中断号

    2024年02月12日
    浏览(49)
  • SecureCRT链接linux服务器提示Key exchange failed的解决办法

    今天重新购买了腾讯云服务器,不得不说现在的优惠力度太小了,服务器基本找不到3年的,都是1年或者6个月的,但是因为服务器要到期了,等不到双十一,就直接买了,购买之后首当其冲就是链接服务器,安装宝塔控制系统,但是当使用SecureCRT链接服务器的时候提示: 如图

    2024年02月13日
    浏览(33)
  • 服务器,Linux,centos7成功安装显卡驱动(超详细)

    在某些情况下我们可能会遇到重装服务器显卡驱动的需求,比如重装操作系统之后需要安装一个显卡驱动,旧版的显卡驱动需要升级,需要换一个显卡驱动等等。 如果你需要重装服务器操作系统,请戳这里: 服务器重装centos7系统 https://blog.csdn.net/qq_51570094/article/details/124133

    2024年01月23日
    浏览(52)
  • 【Linux驱动】Linux的中断系统 | 中断的重要数据结构

    🐱作者:一只大喵咪1201 🐱专栏:《Linux驱动》 🔥格言: 你只管努力,剩下的交给时间! 如上图所示,本喵使用的 IMX6ULL 也是ARM架构,中断也是异常的一种,CPU在运行的过程中,会被各种异常打断,包括: 未定义指令异常 指令、数据访问异常 SWI(软中断) 快中断 中断 导致

    2024年02月02日
    浏览(33)
  • 【Linux驱动】设备树中指定中断 | 驱动中获得中断 | 按键中断实验

    🐱作者:一只大喵咪1201 🐱专栏:《Linux驱动》 🔥格言: 你只管努力,剩下的交给时间! 继续拿这个中断流程图来说话。 在硬件上,中断控制器只有 GIC 这一个,但是我们在软件上可以把 GPIO 也归类为中断控制器。 芯片会有多个GPIO模块,所以软件上的中断控制器就会有很

    2024年01月17日
    浏览(51)
  • linux驱动学习3-外部中断

    在做中断试验时,发现中断驱动总是insmod失败,之后定位到 gpio_request 失败,之后是想到使用的野火做好的系统,在uEnv.txt中会加载大量设备树插件,将key相关的设备树插件屏蔽即可。 中断号 每个中断都有一个中断号,通过中断号即可区分不同的中断,在 Linux 内核中使用一个

    2024年02月11日
    浏览(40)
  • 在windows系统中映射网络驱动器时,如何通过非455端口远程访问Linux服务器的Samba服务

    通常情况下,在windows中通过Linux服务器的Samba服务去映射网络驱动器时候,一般默认就是直接填入内网Linux服务器ip即可,它会默认445端口。 若是我的windows并不和Linux服务器在一个网段时,该如何操作呢? 是 pid 为 4 的进程进行监听。 右键 电脑–管理–服务和应用程序–服务

    2024年02月11日
    浏览(47)
  • linux驱动之中断(一) --- 设备树中描述中断

    本文仅介绍如何在Linux驱动中使用中断 linux中的中断不可嵌套(中断不可打断其他中断) 不宜在中断中做耗时长的事情 耗时的事情应当放在中断下半部中 中断下半部是可以被其他中断打断的, 而且可以用线程来执行 内核对中断下半部有三种方式 1,软中断 2, tasklet(用软中断实现

    2024年02月03日
    浏览(29)
  • 【Linux后端服务器开发】管道设计

    目录 一、管道通信 二、匿名管道 1. 匿名管道通信 2. 匿名管道设计 三、命名管道 comm.hpp client.cc serve.cc 进程通信 数据传输:一个进程需要将它的数据发送给另一个进程 资源共享:多个进程之间共享同样的资源 通知事件:一个进程向另一个(一组)进程发送信息,通知它们发

    2024年02月13日
    浏览(55)
  • FTP服务器移植到Linux开发板

    提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 提示:这里可以添加本文要记录的大概内容: 将Linux开发板作为一个小型的 FTP 服务器,这样就可以通过 FileZilla 软件直接在开发板和 windows 之间通过网络进行文件互传。在开发板上搭建 FTP 服务器很简单

    2024年02月14日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包