Linux驱动 | 按键驱动开发

按键驱动

按键驱动和LED驱动是最简单的驱动了,但是从这些最简单的驱动中可以学到不简单的东西,可以学习到Linux设备驱动的框架。

Linux驱动 = 软件框架 + 硬件操作

驱动只提供能力,具体需要怎样操作取决于应用程序。


设备树编写

根节点下添加按键相关节点

	xgj_key {
		compatible = "xgj-key";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_key>;
		xgj-key-gpios = <&gpio5 1 GPIO_ACTIVE_LOW>;     /* 默认低电平,按键按下高电平 */
		status = "okay";            
		// interrupt-parent = <&gpio5>;  
		// interrupts = <1 IRQ_TYPE_EDGE_RISING>;     /* 指定中断,触发方式为上升沿触发 */ 
	}; 

设备树编译:

ares@ubuntu:~/work/ebf_linux_kernel-ebf_4.19.35_imx6ul$ cat make_dtb.sh
#!/bin/sh

make ARCH=arm -j4 CROSS_COMPILE=arm-linux-gnueabihf- dtbs

将设备树拷贝系统目录:

debian@npi:~/nfs_root/driver$ cat cp_dtb_to_linux.sh
#!/bin/sh

sudo cp imx6ull-mmc-npi.dtb /usr/lib/linux-image-4.19.35-carp-imx6/
  • /usr/lib/linux-image-4.19.35-carp-imx6/ ,系统存放设备树的目录

重启系统设备树生效:

sudo reboot

按照基本框架实现按键驱动

头文件:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/timer.h>
#include <asm/spinlock.h>
#include <linux/jiffies.h>
#include <linux/platform_device.h>
#include <linux/of_irq.h>
#include <linux/wait.h>
#include <linux/sched/signal.h> 
#include <linux/poll.h>

设备树匹配节点属性:

#define ARES_KEY_DTS_NODE   "/xgj_key "       //red_led设备树节点路径
#define ARES_KEY_GPIO_NAME  "xgj-key-gpios"   //red led 节点中 gpio子系统相关属性名

自定义驱动数据结构:

struct keydev {
    struct cdev chrdev;               /* 字符设备结构体 */
    dev_t dev_no;                     /* 设备号 */
    struct class *class;
    struct device_node *dev_node;
    unsigned int key_gpio;
    struct timer_list *timer;
    unsigned long timeout;
    struct mutex  m_lock;;
    wait_queue_head_t r_wait_head;             //读等待队列
};

struct key_irq {
    int irqnum;                        /* 中断号 */
    char val;
    char is_press;
    char name[20];
    irqreturn_t (*irq_handler_t)(int, void *);
};

static struct keydev g_keydev;

static irqreturn_t irq_handler(int num, void *dev);   //中断
static struct key_irq my_key = {
    .irq_handler_t = irq_handler,
    .val = 0x00,
    .name = "mykey",
    .is_press = 0,
};

linux驱动框架&按键驱动 :


static void timeout_callback(struct timer_list *t);
DEFINE_TIMER(key_timer, timeout_callback);        /* 定义定时器并初始化 */

static int key_drv_open (struct inode *node, struct file *file)
{
    printk(KERN_INFO"open key dev\n");

    gpio_direction_input(g_keydev.key_gpio);    //设置为输入
    printk(KERN_INFO"%s success\n", __FUNCTION__);

    return 0;
}


static ssize_t key_drv_read(struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
    char status = 0x00;
    int ret;

    DECLARE_WAITQUEUE(wait, current);       /* 定义并初始化等待队列项 */
    add_wait_queue(&g_keydev.r_wait_head, &wait);      /* 将等待队列项加入等待队列 */

    mutex_lock(&g_keydev.m_lock);
    if (my_key.val == 0)                         /* 按键没有被按下 */
    {
        if (filp->f_flags & O_NONBLOCK)          /* 以非阻塞打开 */
        {
            ret = -EAGAIN;                     
            goto out;                            /* 直接返回 */
        }
		/* 能执行到下面表示以阻塞方式打开,没有数据将进程设置进入休眠状态 */
        __set_current_state(TASK_INTERRUPTIBLE);     /* 只是标记为未睡眠状态 */
        mutex_unlock(&g_keydev.m_lock);              /* 解锁 */
        schedule();                                  /* 让出CPU */
        
        if (signal_pending(current))               /* 判断是否是被信号唤醒 */
        {
            ret = -ERESTARTSYS;                  
            goto out2;                 /* 被信号唤醒,跳到out2,将进程唤醒并设置为运行态 */
        }                      
		
		/* 当进程被唤醒,会从断点处执行,进而执行到这,重新进行加锁保护 */
        mutex_lock(&g_keydev.m_lock);             /* 加锁 */
    }
   
    if (size >= 1)
        size = 1;

    status = my_key.val;   

    if (copy_to_user(buf, &status, size)) 
    {
        ret = -EFAULT;
    } 
    else
    {
        ret = size;
    }

out:
    mutex_unlock(&g_keydev.m_lock);
out2:
    remove_wait_queue(&g_keydev.r_wait_head, &wait);   /* 移出等待队列 */
    set_current_state(TASK_RUNNING);          /* 设置进程为运行态 */
	return ret;
}

/* 多路复用IO实现,使按键在用户空间支持poll、select等系统调用 */
__poll_t key_drv_poll(struct file *filp, struct poll_table_struct *wait)
{
    __poll_t mask = 0;

    mutex_lock(&g_keydev.m_lock);

    poll_wait(filp, &g_keydev.r_wait_head, wait); 

    if (my_key.is_press)
    {
        my_key.is_press = 0;
        mask |= POLLIN | POLLRDNORM;
    }

    mutex_unlock(&g_keydev.m_lock);

    return mask;
}

static struct file_operations key_drv_ops = {
	.owner	= THIS_MODULE,
	.open   = key_drv_open,
    .read   = key_drv_read,
    .poll   = key_drv_poll,
};

/* 设备树的匹配列表 */
static struct of_device_id dts_match_table[] = {
    {.compatible = "xgj-key", },                     /* 设备树匹配属性 */
    {},
};

/* 中断服务函数 */
static irqreturn_t irq_handler(int num, void *dev)
{
    if (gpio_get_value(g_keydev.key_gpio))     /* 按键按下 */
    {
        mod_timer(g_keydev.timer,  jiffies + msecs_to_jiffies(10));    /* 启动定时器,20ms 进行消抖 */
    }
    
    return IRQ_RETVAL(IRQ_HANDLED); 
}
/* 在回调函数中再次判断按键是否按下 */
static void timeout_callback(struct timer_list *t)
{
    my_key.val = 0;
    my_key.is_press = 0;

    if (gpio_get_value(g_keydev.key_gpio))     /* 按键是否按下 */
    {
        my_key.val = 0xff;
        my_key.is_press = 1;
        wake_up_interruptible(&g_keydev.r_wait_head);      /* 唤醒等待队列中进入休眠的进程 */
    }
}

  • DECLARE_WAITQUEUE(wait, current),current在内核中是一个全局符号,代表是当前运行的进程。

驱动匹配成功、卸载:

static int key_driver_probe(struct platform_device *dev)
{
    int err;
    int ret;
    struct device *tmpdev;

    g_keydev.dev_node = of_find_node_by_path(ARES_KEY_DTS_NODE);         /* 找到red_led的设备树节点  */ 
    if (!g_keydev.dev_node) {          
        printk("key driver dts node can not found!\r\n"); 
        return -EINVAL; 
    }

    g_keydev.key_gpio = of_get_named_gpio(g_keydev.dev_node, ARES_KEY_GPIO_NAME, 0);   /* 获取按键gpio */
    if ( g_keydev.key_gpio < 0) {
        printk("key driver gpio can not found!\r\n"); 
        return -EINVAL;
    }
    my_key.irqnum = irq_of_parse_and_map(g_keydev.dev_node, 0);         /* 根据设备树节点获取中断号 */
    printk("irq number = %d\n", my_key.irqnum);
	/* 申请中断 */
    ret = request_irq(my_key.irqnum, my_key.irq_handler_t, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, my_key.name, NULL);  /*申请中断 */
    if (ret < 0) {
        printk("failed to request irq");
        return ret;
    }

    ret = alloc_chrdev_region(&g_keydev.dev_no, 0, 1, "xgj-key");
	if (ret < 0) {
		pr_err("Error: failed to register mbochs_dev, err: %d\n", ret);
		return ret;
	}

	cdev_init(&g_keydev.chrdev, &key_drv_ops);

	cdev_add(&g_keydev.chrdev, g_keydev.dev_no, 1);

    g_keydev.class = class_create(THIS_MODULE, "xgj-key");
	err = PTR_ERR(g_keydev.class);
	if (IS_ERR(g_keydev.class)) {
        goto failed1;
	}
	
	/* 辅助信息 */
    /* 在 /dev目录下创建red_led设备节点 */
    tmpdev = device_create(g_keydev.class , NULL, g_keydev.dev_no, NULL, "xgj-key"); 
    if (IS_ERR(tmpdev)) {
        ret = -EINVAL;
		goto failed2;
	}

    g_keydev.timer = &key_timer;
    init_waitqueue_head(&g_keydev.r_wait_head);     /* 初始化等待队列头  */
    mutex_init(&g_keydev.m_lock);   /* 初始化互斥锁 */

   	printk(KERN_INFO"key_driver_probe success\n");
    
    return 0;
failed2:
    device_destroy(g_keydev.class, g_keydev.dev_no);
    class_destroy(g_keydev.class);
failed1:
    cdev_del(&g_keydev.chrdev);
	unregister_chrdev_region(g_keydev.dev_no, 1);
    return ret;
}

static int key_driver_remove(struct platform_device *dev)
{
    device_destroy(g_keydev.class, g_keydev.dev_no);
	class_destroy(g_keydev.class);
	unregister_chrdev_region(g_keydev.dev_no, 1);
    cdev_del(&g_keydev.chrdev);
    del_timer(g_keydev.timer);
    free_irq(my_key.irqnum, NULL);
    printk(KERN_INFO"key_driver_remove success\n");

    return 0;
}

驱动出口、入口函数: 平台总线

static struct platform_driver key_platform_driver = {
      .probe = key_driver_probe,
      .remove = key_driver_remove,
      .driver = {
        .name = "my_key",
        .owner = THIS_MODULE,
        .of_match_table = dts_match_table,         //通过设备树匹配
      },
};

static int __init key_driver_init(void)
{
    int ret;
    printk(" %s\n", __FUNCTION__);
    ret = platform_driver_register(&key_platform_driver);   //注册platform驱动
    return ret;
}

static void __exit key_driver_exit(void)
{
    printk(" %s\n", __FUNCTION__);
    platform_driver_unregister(&key_platform_driver);
}

module_init(key_driver_init);
module_exit(key_driver_exit);

MODULE_AUTHOR("Ares");
MODULE_LICENSE("GPL");

测试app程序:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <poll.h>

int main(int argc, char **argv)
{
	int fd;
	int ret;
	char status = 0x00;
	int timeid = 0;

	struct pollfd fds[1];

	/* 1. 判断参数 */
	if (argc != 2)
	{
		printf("Usage: %s <dev> \n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open(argv[1], O_RDWR);

	if (fd < 0)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	fds[0].fd = fd;
	fds[0].events = POLLIN | POLLRDNORM;

	while (1)
	{
		ret = poll(fds, 1, 5000);
		if (ret == 1 && (fds[0].events & POLLIN))
		{
			read(fd, &status, 1);
			timeid++;
			printf("%d key press val = %d\r\n", timeid, status);
			//        status = 0x00;
		}
		if (ret == 0)
		{
			printf("poll timeout\r\n");
		}
	}

	close(fd);

	return 0;
}

使用输入子系统框架实现按键驱动

针对使用输入子系统相应的修改

#define XGJ_KEY_DTS_PATH   "/xgj_key"        /* 设备树节点路径,在根目录下*/
#define XGJ_KEY_COMPATILE  "xgj-key"
#define XGJ_KEY_GPIO_NAME  "xgj-key-gpios"   /* 设备树中描述按键GPIO的属性字段 */

struct keydevice {
    int irq;                           /* GPIO */
    int gpio;
    unsigned long timeout;
    dev_t dev_no;                      /* 设备号 */
    struct cdev chrdev;                  
    struct class *class;
    struct device_node *dev_node;
    struct timer_list timer;
	struct input_dev *inputdev;    /* input 结构体 */ 
};
  • 增加struct input_dev *inputdev输入设备结构
static struct keydevice *keydev;

static void timeout_callback(struct timer_list *t);
DEFINE_TIMER(key_timer, timeout_callback);

static irqreturn_t key_isr(int irq, void *dev_id)
{
    mod_timer(&key_timer,  jiffies + msecs_to_jiffies(10));    /* jiffies全局变量,内核记录心跳时钟变量,启动定时器消抖,10ms */
    return IRQ_RETVAL(IRQ_HANDLED); 
}

static void timeout_callback(struct timer_list *t)
{
    if (gpio_get_value(keydev->gpio))     /* 再次判断按键是否按下 */
    {
        /* 向输入子系统上报按键按下 */
        input_report_key(keydev->inputdev, KEY_0, 1);  
    }
    else
    {
        /* 向输入子系统上报按键弹起 */
        input_report_key(keydev->inputdev, KEY_0, 0);  
    }
    input_sync(keydev->inputdev);          /* 上报同步事件,告诉输入子系统上报结束 */
}

static int key_device_io_init(void)
{
    int ret = 0;

    keydev->dev_node = of_find_node_by_path(XGJ_KEY_DTS_PATH);         /* 找到按键的设备树节点 */
    if (!keydev->dev_node) {          
        printk("key driver dts node can not found!\r\n"); 
        return -EINVAL; 
    }

    keydev->gpio = of_get_named_gpio(keydev->dev_node, XGJ_KEY_GPIO_NAME, 0);   /* 获取gpio的编号 */
    if ( keydev->gpio < 0) {
        printk("key driver gpio can not found!\r\n"); 
        return -EINVAL;
    }

    printk("key-gpio %d\n", keydev->gpio);

    ret = gpio_request(keydev->gpio, "xgj-key-gpio");
    if (ret) return ret;
       
	gpio_direction_input(keydev->gpio);    /* 设置为输入 */
	
    keydev->irq = gpio_to_irq(keydev->gpio);

    printk("key-irq %d\n",  keydev->irq);

    ret = request_irq(keydev->irq, key_isr, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "xgj-key-irq", NULL);    /* 申请中断 */
    if (ret) {
        printk("failed to request key's irq");
    }

    return ret;
}
  • input_report_key(keydev->inputdev, KEY_0, 1), 向输入子系统上报按键按下
  • input_report_key(keydev->inputdev, KEY_0, 0), 向输入子系统上报按键弹起
  • input_sync(keydev->inputdev), 上报同步事件,告诉输入子系统上报结束

出口入口:

static int __init key_driver_init(void)
{
    int ret = 0;
    printk(" %s\n", __FUNCTION__);

    keydev = (struct keydevice*)kzalloc(sizeof(struct keydevice), GFP_KERNEL);
	if (!keydev) 
    {
        ret = -EINVAL;
        return ret;
    }

    ret = key_device_io_init();
    if (ret) 
    {
        printk(KERN_ERR "%s: Failed to regist key device.\n", __func__);
        goto out2;
    }

	keydev->inputdev = input_allocate_device();    /* 申请输入设备结构 */
	if (!keydev->inputdev) 
    {
        printk(KERN_ERR "%s: Failed to allocate input device.\n", __func__);
        ret = -ENOMEM;
        goto out2;
    }
    keydev->inputdev->name = "xgj-key";

    set_bit(EV_KEY, keydev->inputdev->evbit);     /* 设置产生按键事件    */ 
    set_bit(EV_REP, keydev->inputdev->evbit);     /* 设置重复事件  */ 
    set_bit(KEY_0, keydev->inputdev->keybit);     /* 设置产生的按键值 */

    ret = input_register_device(keydev->inputdev);             /* 注册输入设备 */
    if (ret) 
    {
        printk(KERN_ERR "%s: Failed to regist key device.\n", __func__);
        goto out1;
    }

    goto out;

out1:
    input_unregister_device(keydev->inputdev);              /* 卸载输入设备 */
    input_free_device(keydev->inputdev);                    /* 释放输入设备 */
out2:
    kfree(keydev);
    keydev = NULL;
out:
    return ret;
}

static void __exit key_driver_exit(void)
{
    printk(" %s\n", __FUNCTION__);

    free_irq(keydev->irq, NULL);        /* 释放中断 */

    gpio_free(keydev->gpio);

    del_timer_sync(&key_timer);

    input_unregister_device(keydev->inputdev);   /* 注销输入设备 */
    input_free_device(keydev->inputdev);         /* 释放输入设备 */

    if (keydev)
    {
        kfree(keydev);                          /* 释放内存 */
        keydev = NULL;
    }
}

module_init(key_driver_init);
module_exit(key_driver_exit);

MODULE_AUTHOR("Ares");
MODULE_LICENSE("GPL");
  • 使用输入子系统后,不再需要自己注册设备、创建class、设备节点以及open、read、write等函数,也不需要去考虑处理阻塞非阻塞等问题,input子系统框架已经帮我们处理。

  • keydev->inputdev = input_allocate_device(), 申请输入设备

  • 设置按键事件

    set_bit(EV_KEY, keydev->inputdev->evbit);     /* 设置产生按键事件    */ 
    set_bit(EV_REP, keydev->inputdev->evbit);     /* 设置重复事件  */ 
    set_bit(KEY_0, keydev->inputdev->keybit);     /* 设置产生的按键值 */
    
  • input_register_device(keydev->inputdev) , 注册输入设备

  • 在出口函数注销和释放输入设备相关内容

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

欲盖弥彰1314

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

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

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

打赏作者

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

抵扣说明:

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

余额充值