针对之前的Demo,结合最近学习的知识,再做一次改进,改进版功能如下:
1.整体改用input子系统设备结构
2.做设备树下的platform总线驱动
3.加入异步通知,按键1按下,窗口打印占空比
4.加入非阻塞,按键2按下,窗口打印占空比
改:写完后发现不合适使用input子系统。因为input子系统自动注册设备,也不需要设置fops操作函数集。只是针对设备输入,上报通知的功能。但是此Demo不仅有设备输入还有终端控制输出,涉及到了.unlocked_ioctl、.write等函数,稍微复杂些,因此还是采用了普通字符设备驱动的结构来写。 找到一个比较详细的input子系统和platform总线设备结合的例子,博客链接如下 :
链接: Input输入子系统–按键.
目录标题
一、设备树
完全参考第13节的即可,无需更改。
二、驱动程序
大致结构和13.demo差不多,这次要添加更多的注释,方便回忆。
1.两个结构体
struct zchirq_desc {
int gpio; //gpio
int irqnum; //中断号
int flag; //按键标志
unsigned char value; //按键值
char name[10]; //名字
irqreturn_t (*handler) (int, void *); //中断服务函数
};
struct irq_dev {
dev_t = devid; //设备号
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd; //节点号
int led_gpio; //led的gpio号
atomic_t keyvalue; //原子变量,储存周期值
spinlock_t spinlock; //自旋锁
struct timer_list timer[2]; //两个定时器
int period;
int key1_flag;
struct zchirq_desc keydesc[2]; //两个按键
struct input_dev *inputdev; //input结构体,没用
wait_queue_head_t r_wait; //等待队列头
struct fasync_struct *async_queue; //异步相关结构体
};
2.入口出口函数
//platform注册,匹配成功执行probe函数
static int __init zch_init(void)
{
return platform_device_register(&zch_driver);
}
//注销platform驱动
static void __exit zch_exit(void)
{
platform_device_unregister(&zch_driver);
}
module_init(zch_init);
module_exit(zch_exit);
MODULE_LISCENS("GPL");
MODULE_AUTHOR("Gordon");
3.probe函数
就是将之前放在init的程序搬到probe里面。
static int zch_probe(struct platform_device *dev)
{
int ret = 0;
spin_lock_init(&irqled.spinlock); //初始化自旋锁
#if 0 //不采用input系统
//此处不需要各种注册,因为有input_dev结构体
irqled.inputdev = input_allocate_device();
irqled.inputdev->name = KEYINPUT_NAME;
//按键事件、重复事件
irqled.inputdev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REF);
//input_set_capacity()一次只能设置一个具体事件,上报多个需要多次调用。
input_set_capacity(irqled.inputdev, EV_KEY, KEY_0);
input_set_capacity(irqled.inputdev, EV_KEY, KEY_1);
/* 注册输入设备 */
ret = input_register_device(irqled.inputdev);
if (ret) {
printk("register input device failed!\r\n");
return ret;
}
#endif
#if 1 //采用普通的字符设备注册
/*设备号*/
if (irqled.major)
{
irqled.devid = MKDEV(irqled.major, 0);
register_chrdev_region(irqled.devid, IRQ_CNT, IRQ_NAME);
}
else
{
alloc_chrdev_region(&irqled.devid, 0, IRQ_CNT, IRQ_NAME);
irqled.major = MAJOR(irqled.devid);
irqled.minor = MINOR(irqled.devid);
}
irqled.cdev.owner = THIS_MODULE;
cdev_init(&irqled.cdev, &irq_fops);
cdev_add(&irqled.cdev, irqled.devid, IRQ_CNT);
irqled.class = class_create(THIS_MODULE, IRQ_NAME);
irqled.device = device_create(irqled.class, NULL, irqled.devid, NULL, IRQ_NAME);
#endif
//初始化
atomic_set(&irqled.keyvalue, 5);
//设置按键中断、定时器等。
ret = gpio_init();
if(ret < 0){
printk("gpioinit error");
return -1;
}
printk("gpioinit success\r\n");
return 0;
}
//设置按键中断、定时器等。
static int gpio_init(void)
{
int ret = 0;
unsigned char i = 0;
//先设置ledgpio
irqled.nd = of_find_node_by_path("/gpio_led"); //找gpio节点
if(irqled.nd == NULL){
printk("ledgpio node not find");
return -1;
}
irqled.led_gpio = of_get_named_gpio(irqled.nd, "led-gpio", 0); //找gpio编号
if(irqled.led_gpio < 0){
printk("ledgpio not get");
return -1;
}
gpio_request(irqled.led_gpio, "led"); //申请gpio 使用之前要申请
ret = gpio_direction_output(irqled.led_gpio, 1); //设置ledgpio输出模式,初始值1
if(ret < 0)
printk("not set ledgpio");
else
printk("gpioled set success");
//再设置keygpio1
irqled.nd = of_find_node_by_path("/gpio_key1"); //找key1节点
if(irqled.nd == NULL){
printk("keygpio1 node not find");
return -1;
}
irqled.keydesc[0].gpio = of_get_named_gpio(irqled.nd, "key1-gpio", 0); //编号
if(irqled.keydesc[0].gpio < 0){
printk("key1 not get");
return -1;
}
irqled.keydesc[0].value = KEY_0;
sprintf(irqled.keydesc[0].name, "key1");//设置名字label
irqled.keydesc[0].handler = key1_handler; //中断函数句柄
//再设置keygpio2
irqled.nd = of_find_node_by_path("/gpio_key2"); //找key2节点
if(irqled.nd == NULL){
printk("keygpio2 node not find");
return -1;
}
irqled.keydesc[1].gpio = of_get_named_gpio(irqled.nd, "key1-gpio", 0); //编号
if(irqled.keydesc[1].gpio < 0){
printk("key2 not get");
return -1;
}
irqled.keydesc[1].value = KEY_1;
sprintf(irqled.keydesc[1].name, "key2");//设置名字label
irqled.keydesc[1].handler = key2_handler; //中断函数句柄
//key设置中断,两个一起
for(i=0; i < 2; ++i){
irqled.keydesc[i].flag = 0; //标志位清零
gpio_request(irqled.keydesc[i].gpio, irqled.keydesc[i].name); //申请
gpio_direction_input(irqled.keydesc[i].gpio); //设置方向
irqled.keydesc[i].irqnum = gpio_to_irq(irqled.keydesc[i].gpio); //获取中断号
//申请中断
ret = request_irq(irqled.keydesc[i].irqnum, //中断号
irqled.keydesc[i].handler, //中断函数句柄
IRQF_TRIGGER_FALLING, //中断触发标志
irqled.keydesc[i].name, //中断名字
&irqled); //设备结构体地址
if(ret < 0){
printk("irq %d request faild ", irqled.keydesc[i].irqnum);
return -1;
}
}
//两个定时器初始化,还未设置周期,不会激活
init_timer(&irqled.timer[0]);
init_timer(&irqled.timer[1]);
irqled.timer[0].function = timer1_func;
irqled.timer[1].function = timer2_func;
irqled.timer[0].data = (unsigned long) &irqled;
irqled.timer[1].data = (unsigned long) &irqled;
return 0;
}
4.remove函数
卸载驱动会执行remove函数,和之前的exit函数如出一辙。
static int zch_remove(struct platform_device *dev)
{
del_timer_sync(&irqled.timer[0]);
del_timer_sync(&irqled.timer[1]);
free_irq(irqled.keydesc[0].irqnum, &irqled);
free_irq(irqled.keydesc[1].irqnum, &irqled);
//input_unregister_device(irqled.inputdev);
//input_free_device(irqled.inputdev);
cdev_del(&irqled.cdev);
unregister_chrdev_region(irqled.devid, IRQ_CNT);
device_destroy(irqled.class, irqled.devid);
class_destroy(irqled.class);
return 0;
}
5.platform匹配
static const struct of_device_id led_of_match[] = {
{ .compatible = "gpio-led" },
{ /*Sentinel*/ },
};
static struct platform_device zch_driver = {
.driver = {
.name = "imx6ul-led", //这是非设备树方式,自己建立device,名字要和这个一样
.of_match_table = led_of_match,
},
.probe = zch_probe,
.remove = zch_remove,
};
6.两个外部中断服务函数
static irqreturn_t key1_handler(int irqnum, void *dev_id)
{
struct irq_dev *dev = (struct irq_dev *)dev_id;
irqled.keydesc[0].flag = 1; //key1按下
printk("key1 push\r\n");
mod_timer(&dev->timer[1], jiffies + msecs_to_jiffies(10)); //10ms,启动定时器2
return IRQ_RETVAL(IRQ_HANDLED);
}
static irqreturn_t key2_handler(int irqnum, void *dev_id)
{
struct irq_dev *dev = (struct irq_dev *)dev_id;
irqled.keydesc[1].flag = 1; //key2按下
printk("key2 push\r\n");
mod_timer(&dev->timer[1], jiffies + msecs_to_jiffies(10)); //10ms,启动定时器2
return IRQ_RETVAL(IRQ_HANDLED);
}
7.两个定时器服务函数
在按键确认的中断函数里,分别添加了异步通知和非阻塞的程序。
static void timer1_func(unsigned long dev_id)
{
struct irq_dev *dev = (struct irq_dev *)dev_num;
int value = atomic_read(&dev->keyvalue);
if(dev->period > value)
gpio_set_value(dev->led_gpio, 0);
else
gpio_set_value(dev->led_gpio, 1);
spin_lock(&dev->spinlock); //加锁
dev->period--; //保护
if(dev->period < 1){
dev->period = 10; //范围1-10
}
spin_unlock(&dev->spinlock); //解锁
//重启定时器
mod_timer(&dev->timer[0], jiffies + msecs_to_jiffies(100));
}
//按键延迟确认
static void timer2_func(unsigned long dev_id)
{
struct irq_dev *dev = (struct irq_dev *)dev_num;
//key1,异步通知功能
if(dev->keydesc[0].flag == 1) //判断哪个按键
if(gpio_get_value(dev->keydesc[0].gpio) == 0) //确认按下
{
if(atomic_inc_return(&dev->keyvalue) > 10)//value自加1,并返回判断
atomic_set(&dev->keyvalue, 10); //最大为10
if(dev->async_queue) //异步通知
kill_fasync(&dev->async_queue, SIGIO, POLL_IN); //上报
input_report_key(&dev->inputdev, KEY_0, 1); //按键事件上报
input_sync(&dev->inputdev); //同步事件
}
dev->keydesc[0].flag = 0; //标志清零
//key2,非阻塞IO,轮询检查,falg置一,可以read
if(dev->keydesc[1].flag == 1)
if(gpio_get_value(dev->keydesc[1].gpio) == 0) //确认按下
{
if(atomic_dec_return(&dev->keyvalue) < 0) //value自减1,并返回判断
atomic_set(&dev->keyvalue, 0); //最小为0
spin_lock(&dev->spinlock); //加锁
dev->key1_flag = 1; //按下标志,轮询查到
spin_unlock(&dev->spinlock);
input_report_key(&dev->inputdev, KEY_1, 1); //按键事件上报
input_sync(&dev->inputdev); //同步事件
}
dev->keydesc[1].flag = 0;
}
8.开、写、放函数
static int zch_open(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
filp->private_data = &irqled;
//初始化period,从10开始减,未激活定时器
mod_timer(&irqled.timer[0], jiffies + msecs_to_jiffies(100));
irqled.period = 10;
irqled.key1_flag = 0; //按键标志清零
return 0;
}
static ssize_t zch_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int ret;
unsigned char databuf[1];
struct irq_dev *dev = filp->private_data;
//取值
ret = copy_from_user(databuf, buf, cnt);
if(ret < 0){
printk("Error");
dev->timer[0].expires = jiffies + msecs_to_jiffies(100);
}
return 0;
}
static int zch_release (struct inode *inode, struct file *filp)
{
return zch_fasync(-1, filp, 0);
}
9.读函数
较为特殊,涉及了非阻塞
static ssize_t zch_read (struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
int value;
struct irq_dev *dev = filp->private_data;
if(filp->flags & O_NONBLOCK){ //如果非阻塞读
if(dev->key1_flag == 1){ //确认按键有值
//获取原子变量,并输出到用户空间
value = atomic_read(&dev->keyvalue);
ret = copy_to_user(buf, &value, sizeof(value));
//标志清零
dev->key1_flag = 0;
}
}
return ret;
}
10.ioctl函数
static long zch_unlockled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct irq_dev *dev = filp->private_data;
switch(cmd){
case CLOSE_CMD: //关
del_timer_sync(&dev->timer[0]);
gpio_set_value(dev->led_gpio, 0);
break;
case OPEN_CMD: //开 10ms
mod_timer(&dev->timer[0], jiffies + msecs_to_jiffies(100));
break;
case SET_CMD:
if(arg > 10) //最大10
atomic_set(&dev->keyvalue, 10);
else if(arg < 0) //最小0
atomic_set(&dev->keyvalue, 0);
else //中间数 直接赋值
atomic_set(&dev->keyvalue, arg);
break;
default:
break;
}
printk("ioctl success\r\n");
return 0;
}
11.poll和fasync函数
poll用于非阻塞设置。sync是异步通知
static unsigned int zch_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct irq_dev *dev = (struct irq_dev *)filp->private_data;
poll_wait(filp, &dev->r_wait, wait);
if(dev->key1_flag == 1)
mask = POLLIN | POLLRDNORM;
return mask;
}
static int zch_fasync(int fd, struct file *filp, int on)
{
struct irq_dev *dev = (struct irq_dev *)filp->private_data;
return fasync_helper(fd, filp, on, &dev->async_queue);
}
12.fops函数集
static struct file_operationgs zch_fops = {
.owner = THIS_MODULE,
.open = zch_open,
.read zch_read,
.write = zch_read,
.unlocked_ioctl = zch_unlockled_ioctl,
.fasync = zch_fasync,
.poll = zch_poll,
.release = zch_release,
};
三、测试
1.App
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "linux/ioctl.h"
#include "signal.h"
#include "poll.h"
#include "sys/select.h"
#include "sys/time.h"
#define SET_CMD (_IO(0XEF, 0x01))
#define OPEN_CMD (_IO(0XEF, 0x02))
#define CLOSE_CMD (_IO(0XEF, 0x03))
static int fd = 0;
static struct input_event inputevent; //input结构体,没用
//异步通知处理函数
static void sigio_signal_func(int signum)
{
int err = 0;
int value = 0;
read(fd, &value, sizeof(value));
printf("pwm is %d \r\n", value);
}
int main (int argc, char *argv[])
{
int ret, value, flags;
char *filename;
unsigned char databuf[1];
unsigned int cmd, arg;
fd_set readfds;
struct timeval timeout;
if(argc != 3){
printf("error\r\n");
return (-1);
}
filename = argv[1];
fd = open(filename, O_RDWR | O_NONBLOCK);
if(fd < 0){
printf("error file");
return -1;
}
databuf[0] = atoi(argv[2]);
ret = write(fd, databuf, sizeof(databuf));
if(ret < 0){
printf("error write");
close(fd);
return -1;
}
signal(SIGIO, sigio_signal_func); //关联信号和处理函数
fcntl(fd, F_SETOWN, getpid()); //将进程号告诉内核
flags = fcntl(fd, F_FETFL); //获取当前的进程状态
fcntl(fd, flags | FASYNC); //开启当前进程的异步通知功能
while(1) {
FD_ZERO(&readfds); //所有位清零
FD_SET(fd, &readfds); //置一
timeout.tv_sec = 10;// 10s没有读取就输出一次超时
timeout.tv_usec = 0;
ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
switch(ret){
case 0:
printf("Time is out\r\n");
break;
case -1;
printf("error\r\n");
break;
default:
if(FD_ISSET(fd, &readfds)){
ret = read(fd, &value, sizeof(value));
printf("pwm is %d \r\n", value);
}
break;
}
printf("Input CMD:\r\n");
printf("1 is set; 2 is open, 3 is close, 4 is printf\r\n");
ret = scanf("%d", &cmd);
if(cmd == 3) //关闭
cmd = CLOSE_CMD;
else if(cmd == 2) //打开
cmd = OPEN_CMD;
else if(cmd == 1){//直接设置占空比
cmd = SET_CMD;
printf("input pwm 0-10\r\n");
ret = scanf("%d",&arg);
}
else if(cmd == 4){ //打印占空比
read(fd, &value, sizeof(value));
printf("pwm is %d \r\n", value);
}
ioctl(fd, cmd, arg); //控制
}
close(fd);
return 0;
}
2.编译测试
还没来得及编译测试,只是把全部流程写出来了