按键驱动
按键驱动和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) , 注册输入设备
-
在出口函数注销和释放输入设备相关内容