Linux驱动学习记录-20.Demo_Pro

针对之前的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.编译测试

还没来得及编译测试,只是把全部流程写出来了

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值