Linux驱动笔记(三):中断相关知识

1、中断申请和释放

申请函数:int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)

成功返回 0

参数含义

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

释放:void free_irq(unsigned int irq, void  *dev);


 

中断处理函数格式:irqreturn_t (*irq_handler_t) (int, void *)

第一个参数是要中断处理函数要相应的中断号。第二个参数是一个指向 void 的指针,也就
是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备,
dev 也可以指向设备数据结构。中断处理函数的返回值为 irqreturn_t 类型,例子:

static irqreturn_t rtc_interrupt(int irq, void *dev_id)

激活当前CPU中断:

local_irq_enable();
禁止当前CPU中断:

local_irq_disable();
激活指定中断线:

void enable_irq(unsigned int irq);
禁止指定中断线:

void disable_irq(unsigned int irq);
禁止指定中断线:

void disable_irq_nosync(unsigned int irq);
注:此函数调用irq_chip中disable禁止指定中断线,所以不会保证中断线上执行的中断服务程序已经退出。
 

2、中断上下半部

这里重点关注:Tasklet

2.1下半部定义和初始化

DECLARE_TASKLET(name, func, data)
其中 name 为要定义的 tasklet 名字,这个名字就是一个 tasklet_struct 类型的时候变量, func
就是 tasklet 的处理函数, data 是传递给 func 函数的参数。

2.2下半部函数调用

在上半部,也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运
行, tasklet_schedule 函数原型如下:
void tasklet_schedule(struct tasklet_struct *t)
函数参数和返回值含义如下:
t:要调度的 tasklet,也就是 DECLARE_TASKLET 宏里面的 name。

 

 

3、设备树里的中断信息
 

3.1实例分析:I.MX6ULL 的中断控制器节点,节点内容如下所示

1 intc: interrupt-controller@00a01000 {
2 compatible = "arm,cortex-a7-gic";
3 #interrupt-cells = <3>;
4 interrupt-controller;

5 reg = <0x00a01000 0x1000>,
6 <0x00a02000 0x100>;
7 };
 

第 2 行, compatible 属性值为“arm,cortex-a7-gic”在 Linux 内核源码中搜索“arm,cortex-a7-
gic”即可找到 GIC 中断控制器驱动文件。
第 3 行, #interrupt-cells 和#address-cells、 #size-cells 一样。表示此中断控制器下设备的 cells
大小,对于设备而言,会使用 interrupts 属性描述中断信息, #interrupt-cells 描述了 interrupts 属性的 cells 大小,也就是一条信息有几个 cells。每个 cells 都是 32 位整形值,对于 ARM 处理的
GIC 来说,一共有 3 个 cells,这三个 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 掩码。

第 4 行, interrupt-controller 节点为空,表示当前节点是中断控制器
3.2实例分析:I.MX6ULL 的中断控制器节点,节点内容如下所示

1 gpio5: gpio@020ac000 {
2 compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
3 reg = <0x020ac000 0x4000>;
4 interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>,
5 <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
6 gpio-controller;
7 #gpio-cells = <2>;
8 interrupt-controller;
9 #interrupt-cells = <2>;
10 };第 4 行, interrupts 描述中断源信息,对于 gpio5 来说一共有两条信息,中断类型都是 SPI,
触发电平都是 IRQ_TYPE_LEVEL_HIGH。不同之处在于中断源,一个是 74,一个是 75,打开
可以打开《IMX6ULL 参考手册》的“Chapter 3 Interrupts and DMA Events”章节,找到表 3-1,
有如图 50.1.3.1 所示的内容:

 

 

 

图 50.1.3.1 中断表
从图 50.1.3.1 可以看出, GPIO5 一共用了 2 个中断号,一个是 74,一个是 75。其中 74 对
应 GPIO5_IO00~GPIO5_IO15 这低 16 个 IO, 75 对应 GPIO5_IO16~GPIOI5_IO31 这高 16 位 IO。
第 8 行, interrupt-controller 表明了 gpio5 节点也是个中断控制器,用于控制 gpio5 所有 IO
的中断。

第9行#interrupt-cells 描述了 interrupts 属性的个数,即指定了子中断描述

实例1(基于task机制)

   设备树文件修改

            gpio1: gpio@0209c000 {
                compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
                reg = <0x0209c000 0x4000>;
                interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
                         <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
                gpio-controller;
                #gpio-cells = <2>;
                interrupt-controller;
                #interrupt-cells = <2>;   /*这个2,代表它的中断子节点描述是2个参数,和下面key节点的interrupts里的参数对应 */
            };

/{

......

key {        
        compatible = "fsl,my-key";
        #address-cells = <1>;
        #size-cells = <1>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_key>;
        gpios-key1 = <&gpio1 18 GPIO_ACTIVE_LOW>;
        interrupt-parent = <&gpio1>;
        interrupts = <18 IRQ_TYPE_EDGE_BOTH>;        
        status = "okay";


    };

....

}

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/timer.h>


#define KEY_CNT            1        /* 设备号个数     */
#define KEY_NAME        "key"    /* 名字         */

/* 定义按键值 */
#define KEY0VALUE        0X01    /* 按键值         */
#define INVAKEY            0X00    /* 无效的按键值  */

/* key设备结构体 */
struct key_dev{
    dev_t devid;            /* 设备号      */
    struct cdev cdev;        /* cdev     */
    struct class *class;    /* 类         */
    struct device *device;    /* 设备      */
    int major;                /* 主设备号      */
    int minor;                /* 次设备号   */
    struct device_node    *nd; /* 设备节点 */
    int key_gpio;            /* key所使用的GPIO编号        */
    atomic_t releasekey; /* 标记是否完成一次完成的按键 */
    atomic_t keyvalue;        /* 按键值         */    
    int key_irq;        /* 按键中断号*/
    struct tasklet_struct task_key;
    struct timer_list timer;
};

struct key_dev keydev;        /* key设备 */


/*定时器处理*/
void timer_fun(unsigned long arg)
{
    int value;
    struct key_dev *dev = (struct key_dev *)arg;
    value = gpio_get_value(dev->key_gpio); /* 读取 IO 值 */
    if(value == 0){ /* 按下按键 */
        atomic_set(&dev->keyvalue, KEY0VALUE);
        printk("key down\r\n");
    }
    else{ /* 按键松开 */
        if(atomic_read(&dev->keyvalue))
        {
//        atomic_set(&dev->keyvalue, 0x80 | KEY0VALUE);
        printk("key up\r\n");
        atomic_set(&dev->releasekey, 1);
        }
    }
}

/*中断下半部处理*/
void task_key_fun(unsigned long data)
{

    struct key_dev *dev = (struct key_dev *)data;
//    printk("task_key_fun %d\r\n",dev->key_irq);
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20));
    
}

irqreturn_t key_handler_t(int irq, void *dev_id)
{
    struct key_dev *dev = (struct key_dev*)dev_id;
//    printk("key_handler_t in %d\r\n",dev->key_irq);

#if 0
    dev->curkeynum = 0;
    dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
#endif
    tasklet_schedule(&dev->task_key);
    return IRQ_RETVAL(IRQ_HANDLED);

}

/*
 * @description    : 初始化按键IO,open函数打开驱动的时候
 *                   初始化按键所使用的GPIO引脚。
 * @param         : 无
 * @return         : 无
 */

static int keyio_init(void)
{
    int ret;
    keydev.nd = of_find_node_by_path("/key");
    if (keydev.nd== NULL) {
        return -EINVAL;
    }

    keydev.key_gpio = of_get_named_gpio(keydev.nd ,"gpios-key1", 0);
    if (keydev.key_gpio < 0) {
        printk("can't get key0\r\n");
        return -EINVAL;
    }
    printk("key_gpio=%d\r\n", keydev.key_gpio);
    
    /* 初始化key所使用的IO */
    ret=gpio_request(keydev.key_gpio, "key0");    /* 请求IO */
    if(ret < 0 )
    {
        printk("gpio_request erro!\r\n");        
        goto gpio_request_erro;
    }
    gpio_direction_input(keydev.key_gpio);    /* 设置为输入 */


    keydev.key_irq = gpio_to_irq(keydev.key_gpio);
    printk("the irq num = %d\r\n",keydev.key_irq);
#if 0
    keydev.key_irq = irq_of_parse_and_map(keydev.nd,0);
    printk("the irq num = %d\r\n",keydev.key_irq);
#endif

    ret = request_irq(keydev.key_irq,key_handler_t,IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,"xcmkey",&keydev);
    if(ret)
    {
        printk("request_irq erro!\r\n");
        goto request_irq_erro;
    }
        printk("request_irq ok!\r\n");

    tasklet_init(&keydev.task_key,task_key_fun,(unsigned long)&keydev);

    init_timer(&keydev.timer);
    keydev.timer.function = timer_fun; /* 设置定时处理函数 */
    //timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */
    keydev.timer.data = (unsigned long)&keydev; /* 将设备结构体作为参数 */

    add_timer(&keydev.timer);
    return 0;

request_irq_erro:
    gpio_free(keydev.key_gpio);
gpio_request_erro:
        return -EINVAL;
}

/*
 * @description        : 打开设备
 * @param - inode     : 传递给驱动的inode
 * @param - filp     : 设备文件,file结构体有个叫做private_data的成员变量
 *                       一般在open的时候将private_data指向设备结构体。
 * @return             : 0 成功;其他 失败
 */
static int key_open(struct inode *inode, struct file *filp)
{

    filp->private_data = &keydev;     /* 设置私有数据 */



    return 0;
}

/*
 * @description        : 从设备读取数据
 * @param - filp     : 要打开的设备文件(文件描述符)
 * @param - buf     : 返回给用户空间的数据缓冲区
 * @param - cnt     : 要读取的数据长度
 * @param - offt     : 相对于文件首地址的偏移
 * @return             : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    int value;
    struct key_dev *dev = filp->private_data;


    value = atomic_read(&dev->releasekey);
    if(value)
    {
            value = atomic_read(&dev->keyvalue);
            ret = copy_to_user(buf, &value, sizeof(value));
            atomic_set(&dev->keyvalue, 0);        /* 无效的按键值 */
            atomic_set(&dev->releasekey, 0);        /* 无效的按键值 */
    }
    else
    {
        return -EINVAL;
    }

    return value;
}

/*
 * @description        : 向设备写数据
 * @param - filp     : 设备文件,表示打开的文件描述符
 * @param - buf     : 要写给设备写入的数据
 * @param - cnt     : 要写入的数据长度
 * @param - offt     : 相对于文件首地址的偏移
 * @return             : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

/*
 * @description        : 关闭/释放设备
 * @param - filp     : 要关闭的设备文件(文件描述符)
 * @return             : 0 成功;其他 失败
 */
static int key_release(struct inode *inode, struct file *filp)
{

    return 0;
}

/* 设备操作函数 */
static struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .open = key_open,
    .read = key_read,
    .write = key_write,
    .release =     key_release,
};

/*
 * @description    : 驱动入口函数
 * @param         : 无
 * @return         : 无
 */
static int __init mykey_init(void)
{
    int ret;
    /* 初始化原子变量 */
    atomic_set(&keydev.keyvalue, INVAKEY);
    atomic_set(&keydev.releasekey, 0);
    /* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (keydev.major) {        /*  定义了设备号 */
        keydev.devid = MKDEV(keydev.major, 0);
        register_chrdev_region(keydev.devid, KEY_CNT, KEY_NAME);
    } else {                        /* 没有定义设备号 */
        alloc_chrdev_region(&keydev.devid, 0, KEY_CNT, KEY_NAME);    /* 申请设备号 */
        keydev.major = MAJOR(keydev.devid);    /* 获取分配号的主设备号 */
        keydev.minor = MINOR(keydev.devid);    /* 获取分配号的次设备号 */
    }
    
    /* 2、初始化cdev */
    keydev.cdev.owner = THIS_MODULE;
    cdev_init(&keydev.cdev, &key_fops);
    
    /* 3、添加一个cdev */
    cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);

    /* 4、创建类 */
    keydev.class = class_create(THIS_MODULE, KEY_NAME);
    if (IS_ERR(keydev.class)) {
        return PTR_ERR(keydev.class);
    }

    /* 5、创建设备 */
    keydev.device = device_create(keydev.class, NULL, keydev.devid, NULL, KEY_NAME);
    if (IS_ERR(keydev.device)) {
        return PTR_ERR(keydev.device);
    }
    ret = keyio_init();                /* 初始化按键IO */
    if (ret < 0) {
        return ret;
    }    
    return 0;
}

/*
 * @description    : 驱动出口函数
 * @param         : 无
 * @return         : 无
 */
static void __exit mykey_exit(void)
{
    del_timer(&keydev.timer);
    gpio_free(keydev.key_gpio);
    free_irq(keydev.key_irq,&keydev);
    /* 注销字符设备驱动 */
    cdev_del(&keydev.cdev);/*  删除cdev */
    unregister_chrdev_region(keydev.devid, KEY_CNT); /* 注销设备号 */

    device_destroy(keydev.class, keydev.devid);
    class_destroy(keydev.class);
}

module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

 

实例2(基于队列queue机制)

 

 

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/timer.h>

/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名        : keyqueue.c
作者          : 左忠凯
版本           : V1.0
描述           : Linux按键输入中断+定时器+下半部队列驱动实验
其他           : 无
论坛            : www.openedv.com
日志           : 初版V1.0 2019/7/18 左忠凯创建
***************************************************************/
#define KEY_CNT            1        /* 设备号个数     */
#define KEY_NAME        "key_queue"    /* 名字         */

/* 定义按键值 */
#define KEY0VALUE        0X01    /* 按键值         */
#define INVAKEY            0X00    /* 无效的按键值  */

/* key设备结构体 */
struct key_dev{
    dev_t devid;            /* 设备号      */
    struct cdev cdev;        /* cdev     */
    struct class *class;    /* 类         */
    struct device *device;    /* 设备      */
    int major;                /* 主设备号      */
    int minor;                /* 次设备号   */
    struct device_node    *nd; /* 设备节点 */
    int key_gpio;            /* key所使用的GPIO编号        */
    atomic_t releasekey; /* 标记是否完成一次完成的按键 */
    atomic_t keyvalue;        /* 按键值         */    
    int key_irq;        /* 按键中断号*/
    //struct tasklet_struct task_key;
    struct work_struct key_work;  /*队列queue结构体*/
    struct timer_list timer;
};

struct key_dev keydev;        /* key设备 */


/*定时器处理*/
void timer_fun(unsigned long arg)
{
    int value;
    struct key_dev *dev = (struct key_dev *)arg;
    value = gpio_get_value(dev->key_gpio); /* 读取 IO 值 */
    if(value == 0){ /* 按下按键 */
        atomic_set(&dev->keyvalue, KEY0VALUE);
        printk("key down\r\n");
    }
    else{ /* 按键松开 */
        if(atomic_read(&dev->keyvalue))
        {
//        atomic_set(&dev->keyvalue, 0x80 | KEY0VALUE);
        printk("key up\r\n");
        atomic_set(&dev->releasekey, 1);
        }
    }
}

void key_work_fun(struct work_struct *work)
{
    mod_timer(&keydev.timer, jiffies + msecs_to_jiffies(20));
}
/*中断下半部处理*/
void task_key_fun(unsigned long data)
{

    struct key_dev *dev = (struct key_dev *)data;
//    printk("task_key_fun %d\r\n",dev->key_irq);
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20));
    
}

irqreturn_t key_handler_t(int irq, void *dev_id)
{
    struct key_dev *dev = (struct key_dev*)dev_id;
//    printk("key_handler_t in %d\r\n",dev->key_irq);

#if 0
    dev->curkeynum = 0;
    dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
#endif
    //tasklet_schedule(&dev->task_key);/*task调度*/
    schedule_work(&dev->key_work); /*队列调度*/
    return IRQ_RETVAL(IRQ_HANDLED);

}

/*
 * @description    : 初始化按键IO,open函数打开驱动的时候
 *                   初始化按键所使用的GPIO引脚。
 * @param         : 无
 * @return         : 无
 */

static int keyio_init(void)
{
    int ret;
    keydev.nd = of_find_node_by_path("/key");
    if (keydev.nd== NULL) {
        return -EINVAL;
    }

    keydev.key_gpio = of_get_named_gpio(keydev.nd ,"gpios-key1", 0);
    if (keydev.key_gpio < 0) {
        printk("can't get key0\r\n");
        return -EINVAL;
    }
    printk("key_gpio=%d\r\n", keydev.key_gpio);
    
    /* 初始化key所使用的IO */
    ret=gpio_request(keydev.key_gpio, "key0");    /* 请求IO */
    if(ret < 0 )
    {
        printk("gpio_request erro!\r\n");        
        goto gpio_request_erro;
    }
    gpio_direction_input(keydev.key_gpio);    /* 设置为输入 */


    keydev.key_irq = gpio_to_irq(keydev.key_gpio);
    printk("the irq num = %d\r\n",keydev.key_irq);

    keydev.key_irq = irq_of_parse_and_map(keydev.nd,0);
    printk("the irq num = %d\r\n",keydev.key_irq);

    ret = request_irq(keydev.key_irq,key_handler_t,IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,"xcmkey",&keydev);
    if(ret)
    {
        printk("request_irq erro!\r\n");
        goto request_irq_erro;
    }
        printk("request_irq ok!\r\n");

//    tasklet_init(&keydev.task_key,task_key_fun,(unsigned long)&keydev);

    INIT_WORK(&keydev.key_work, key_work_fun);/*初始化中断下半部的队列*/

    /*内核定时器初始化*/
    init_timer(&keydev.timer);
    keydev.timer.function = timer_fun; /* 设置定时处理函数 */
    //timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */
    keydev.timer.data = (unsigned long)&keydev; /* 将设备结构体作为参数 */

    add_timer(&keydev.timer);
    return 0;

request_irq_erro:
    gpio_free(keydev.key_gpio);
gpio_request_erro:
        return -EINVAL;
}

/*
 * @description        : 打开设备
 * @param - inode     : 传递给驱动的inode
 * @param - filp     : 设备文件,file结构体有个叫做private_data的成员变量
 *                       一般在open的时候将private_data指向设备结构体。
 * @return             : 0 成功;其他 失败
 */
static int key_open(struct inode *inode, struct file *filp)
{

    filp->private_data = &keydev;     /* 设置私有数据 */



    return 0;
}

/*
 * @description        : 从设备读取数据
 * @param - filp     : 要打开的设备文件(文件描述符)
 * @param - buf     : 返回给用户空间的数据缓冲区
 * @param - cnt     : 要读取的数据长度
 * @param - offt     : 相对于文件首地址的偏移
 * @return             : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    int value;
    struct key_dev *dev = filp->private_data;


    value = atomic_read(&dev->releasekey);
    if(value)
    {
            value = atomic_read(&dev->keyvalue);
            ret = copy_to_user(buf, &value, sizeof(value));
            atomic_set(&dev->keyvalue, 0);        /* 无效的按键值 */
            atomic_set(&dev->releasekey, 0);        /* 无效的按键值 */
    }
    else
    {
        return -EINVAL;
    }

    return value;
}

/*
 * @description        : 向设备写数据
 * @param - filp     : 设备文件,表示打开的文件描述符
 * @param - buf     : 要写给设备写入的数据
 * @param - cnt     : 要写入的数据长度
 * @param - offt     : 相对于文件首地址的偏移
 * @return             : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t key_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    return 0;
}

/*
 * @description        : 关闭/释放设备
 * @param - filp     : 要关闭的设备文件(文件描述符)
 * @return             : 0 成功;其他 失败
 */
static int key_release(struct inode *inode, struct file *filp)
{

    return 0;
}

/* 设备操作函数 */
static struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .open = key_open,
    .read = key_read,
    .write = key_write,
    .release =     key_release,
};

/*
 * @description    : 驱动入口函数
 * @param         : 无
 * @return         : 无
 */
static int __init mykey_init(void)
{
    int ret;
    /* 初始化原子变量 */
    atomic_set(&keydev.keyvalue, INVAKEY);
    atomic_set(&keydev.releasekey, 0);
    /* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (keydev.major) {        /*  定义了设备号 */
        keydev.devid = MKDEV(keydev.major, 0);
        register_chrdev_region(keydev.devid, KEY_CNT, KEY_NAME);
    } else {                        /* 没有定义设备号 */
        alloc_chrdev_region(&keydev.devid, 0, KEY_CNT, KEY_NAME);    /* 申请设备号 */
        keydev.major = MAJOR(keydev.devid);    /* 获取分配号的主设备号 */
        keydev.minor = MINOR(keydev.devid);    /* 获取分配号的次设备号 */
    }
    
    /* 2、初始化cdev */
    keydev.cdev.owner = THIS_MODULE;
    cdev_init(&keydev.cdev, &key_fops);
    
    /* 3、添加一个cdev */
    cdev_add(&keydev.cdev, keydev.devid, KEY_CNT);

    /* 4、创建类 */
    keydev.class = class_create(THIS_MODULE, KEY_NAME);
    if (IS_ERR(keydev.class)) {
        return PTR_ERR(keydev.class);
    }

    /* 5、创建设备 */
    keydev.device = device_create(keydev.class, NULL, keydev.devid, NULL, KEY_NAME);
    if (IS_ERR(keydev.device)) {
        return PTR_ERR(keydev.device);
    }
    ret = keyio_init();                /* 初始化按键IO */
    if (ret < 0) {
        return ret;
    }    
    return 0;
}

/*
 * @description    : 驱动出口函数
 * @param         : 无
 * @return         : 无
 */
static void __exit mykey_exit(void)
{
    del_timer(&keydev.timer);
    gpio_free(keydev.key_gpio);
    free_irq(keydev.key_irq,&keydev);
    /* 注销字符设备驱动 */
    cdev_del(&keydev.cdev);/*  删除cdev */
    unregister_chrdev_region(keydev.devid, KEY_CNT); /* 注销设备号 */

    device_destroy(keydev.class, keydev.devid);
    class_destroy(keydev.class);
}

module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小薛1988

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值