• linux驱动学习笔记---实现中断下半部以及驱动编写规范(七)【转】


    转自:https://blog.csdn.net/weixin_42471952/article/details/81609141

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
    本文链接:https://blog.csdn.net/weixin_42471952/article/details/81609141
    中断下半部:
    tasklet :
    struct tasklet_struct
    {
        struct tasklet_struct *next;
        unsigned long state;
        atomic_t count;
        
        void (*func)(unsigned long); //下半部要执行的代码
        unsigned long data; // 传递给func的参数
    };
        
        1, 初始化tasklet 
            tasklet_init(struct tasklet_struct * t, void(* func)(unsigned long), unsigned long data)
        2, 在中断上半部,将tasklet加入到内核线程
        
            tasklet_schedule(struct tasklet_struct * t)
        
        
        3, 模块卸载的时候,需要从内核线程中移除tasklet
        
            tasklet_kill(struct tasklet_struct * t)
    ------------------------------------------------------------------
    typedef void (*work_func_t)(struct work_struct *work);
    struct work_struct {
        atomic_long_t data;
        struct list_head entry;
        work_func_t func;//下半部要执行的代码
    };

        
        1, 初始化work
        INIT_WORK(struct work_struct * work, work_func_t func);
        
        work_func_t func为结构体struct work_struct中的函数指针;
        2, 在中断上半部,将work加入到内核线程
        schedule_work(struct work_struct * work);
        
        
        3, 模块卸载的时候,需要从内核线程中移除work
        cancel_work_sync(struct work_struct * work)

    工作队列(work queue)是另外一种将工作推后执行的形式,它和前面讨论的tasklet有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。

    那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。

    引用: https://blog.csdn.net/av_geek/article/details/41278801

    一个为 struct tasklet_struct *next任务链表,一个为struct list_head entry;内核链表

    驱动编写规范
    设计一个对象描述所有的全局变量

    //设计一个对象类型--描述所有的全局的变量
    struct s5pv210_key{
    __u8 have_data; //用于描述是否有数据
    int major ; //记录主设备号
    struct class *cls; //用于创建 类
    struct device *dev; //用来创建设备文件
    wait_queue_head_t wq_head; //用于实现阻塞的等待队列头
    struct key_event event; //用于存放数据的包
    struct work_struct work;//用于实现中断下半部
    };
     声明一个对象

    //声明一个对象
    struct s5pv210_key *key_dev;
    初始化时统一申请空间(这也是好处之一) 

    // 0-为全局设备/数据对象分配空间
    //参数2--标志--分配内存方式, GFP_KERNEL(如果当前内存不够的时候会等待)
    key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL);
     错误判断

    if(key_dev == NULL)
    {
    printk(KERN_ERR"kmalloc error ");
    return -ENOMEM;
    }
    指针错误判断

    if(IS_ERR(key_dev->cls))
    {
    ret = PTR_ERR(key_dev->cls); //自动根据指针转换一个对应的出错码
    goto err_unregister;
    }
     统一下函数的最后做处理

    static int __init key_drv_init(void)
    {

    int ret;

    // 0-为全局设备/数据对象分配空间
    //参数2--标志--分配内存方式, GFP_KERNEL(如果当前内存不够的时候会等待)
    key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL);
    if(key_dev == NULL)
    {
    printk(KERN_ERR"kmalloc error ");
    return -ENOMEM;
    }


    // 1, 申请主设备号--动态申请主设备号
    key_dev->major = register_chrdev(0, "key_dev", &key_fops);
    if(key_dev->major < 0)
    {
    printk(KERN_ERR"register_chrdev error ");
    ret = key_dev->major;
    goto err_free;
    }


    // 2, 创建设备节点
    key_dev->cls = class_create(THIS_MODULE, "key_cls");
    if(IS_ERR(key_dev->cls))
    {
    ret = PTR_ERR(key_dev->cls); //自动根据指针转换一个对应的出错码
    goto err_unregister;
    }


    key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->major, 0), NULL, "key0");
    if(IS_ERR(key_dev->dev))
    {
    ret = PTR_ERR(key_dev->dev);
    goto err_destory_cls;
    }
    // 3, 硬件初始化-- 映射地址或者中断申请

    //参数1--中断号码
    //获取中断号码的方法: 1,外部中断IRQ_EINT(x)
    // 2, 头文件 #include <mach/irqs.h> #include <plat/irqs.h>
    //参数2--中断的处理方法
    //参数3--中断触发方式: 高电平,低电平,上升沿,下降沿,双边沿
    /*
    #define IRQF_TRIGGER_NONE 0x00000000
    #define IRQF_TRIGGER_RISING 0x00000001
    #define IRQF_TRIGGER_FALLING 0x00000002
    #define IRQF_TRIGGER_HIGH 0x00000004
    #define IRQF_TRIGGER_LOW 0x00000008
    */
    //参数4--中断的描述-自定义字符串
    //参数5--传递个参数2的任意数据
    //返回值0表示正确
    int i;
    int irqno;
    int flags;
    char *name;
    for(i=0; i<ARRAY_SIZE(all_keys); i++)
    {
    name = all_keys[i].name;
    irqno = all_keys[i].irqno;
    flags = all_keys[i].flags;
    ret = request_irq(irqno, key_irq_handler, flags, name, &all_keys[i]);
    if(ret != 0)
    {
    printk("request_irq error ");
    goto err_destroy_dev;
    }
    }


    // 定义一个等待队列头,并且初始化
    init_waitqueue_head(&key_dev->wq_head);

    //初始化work
    INIT_WORK(&key_dev->work, work_key_irq);




    return 0;

    err_destroy_dev:
    device_destroy(key_dev->cls, MKDEV(key_dev->major, 0));

    err_destory_cls:
    class_destroy(key_dev->cls);

    err_unregister:
    unregister_chrdev(key_dev->major, "key_dev");

    err_free:
    kfree(key_dev);
    return ret;

    }
     完整驱动代码
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/device.h>
    #include <linux/interrupt.h>
    #include <linux/gpio.h>
    #include <linux/input.h>
    #include <linux/wait.h>
    #include <linux/sched.h>
    #include <linux/poll.h>
    #include <linux/slab.h>

    #include <asm/uaccess.h>

    // 设计一个对象--描述按键产生的数据包
    struct key_event{
    int code; //什么按键: 左键, 回车键
    int value; // 按键的状态: 按下/抬起 (1/0)
    };

    //设计一个描述按键的对象: 名字, irqno, gpio, 按键值,触发方式
    struct key_desc{
    char *name;
    int irqno;
    int gpio;
    int code;
    int flags;// 触发方式
    };


    //设计一个对象类型--描述所有的全局的变量
    struct s5pv210_key{
    __u8 have_data; //用于描述是否有数据
    int major ; //记录主设备号
    struct class *cls; //用于创建 类
    struct device *dev; //用来创建设备文件
    wait_queue_head_t wq_head; //用于实现阻塞的等待队列头
    struct key_event event; //用于存放数据的包
    struct work_struct work;//用于实现中断下半部
    };

    //声明一个对象
    struct s5pv210_key *key_dev;


    struct key_desc all_keys[] = {
    [0] = {
    .name = "key1_up_eint0",
    .irqno = IRQ_EINT(0),
    .gpio = S5PV210_GPH0(0),
    .code = KEY_UP,
    .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,

    },
    [1] = {
    .name = "key2_down_eint1",
    .irqno = IRQ_EINT(1),
    .gpio = S5PV210_GPH0(1),
    .code = KEY_DOWN,
    .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,

    },
    [2] = {
    .name = "key3_left_eint2",
    .irqno = IRQ_EINT(2),
    .gpio = S5PV210_GPH0(2),
    .code = KEY_LEFT,
    .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,

    },
    [3] = {
    .name = "key4_right_eint3",
    .irqno = IRQ_EINT(3),
    .gpio = S5PV210_GPH0(3),
    .code = KEY_LEFT,
    .flags = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,

    },

    };




    int key_drv_open (struct inode *inode, struct file *filp)
    {
    printk("--------^_* %s------- ", __FUNCTION__);

    // 通过文件路径可以得到inode
    // 通过得到次设备号可以区分不同的设备
    //int major = MAJOR(filp->f_path.dentry->d_inode->i_rdev);
    int major = imajor(filp->f_path.dentry->d_inode);
    int major2 = imajor(inode);

    int minor = iminor(filp->f_path.dentry->d_inode);
    int minor2 = iminor(inode);

    printk("major = %d, minor = %d ", major, minor);
    printk("major2 = %d, minor2 = %d ", major2, minor2);

    memset(&key_dev->event, 0, sizeof(struct key_event));
    key_dev->have_data= 0; //为假--一开始都没有按键按下或者抬起



    return 0;
    }

    ssize_t key_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
    {

    int ret;
    //区分当前是阻塞还是非阻塞
    if((filp->f_flags & O_NONBLOCK) && !key_dev->have_data)
    {
    return -EAGAIN;
    }

    // 在资源不可达的时候,进行休眠
    // 参数1---当前驱动中的等待队列头
    // 参数2--休眠的条件: 假的话就休眠,真就不休眠
    wait_event_interruptible(key_dev->wq_head, key_dev->have_data);

    ret = copy_to_user(buf, &key_dev->event, count);
    if(ret > 0)
    {
    printk("copy_to_user error ");
    return -EFAULT;
    }
    //清零,以备下次充值
    memset(&key_dev->event, 0, sizeof(struct key_event));
    key_dev->have_data = 0; //没有数据了,等待下一次数据

    return count;
    }

    int key_drv_close(struct inode *inode, struct file *filp)
    {
    printk("--------^_* %s------- ", __FUNCTION__);


    return 0;
    }

    unsigned int key_drv_poll (struct file *filp, struct poll_table_struct *pts)
    {
    //返回一个整数: 没有数据的时候返回0, 有数据的时候,返回POLLIN

    unsigned int mask = 0;

    // 将当前的等待队列头,注册到vfs层, 注意: poll_wait该函数不会导致休眠
    //参数1-文件对象--当前file
    //参数2--当前的等待队列头
    //参数3--当前传递过来的第三个参数
    poll_wait(filp, &key_dev->wq_head, pts);

    if(key_dev->have_data)
    mask |= POLLIN;

    return mask;

    }

    // 4, 实现fops
    const struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .open = key_drv_open,
    .read = key_drv_read,
    .poll = key_drv_poll,
    .release =key_drv_close,
    };
    void work_key_irq(struct work_struct *work)
    {
    printk("-----------%s------- ", __FUNCTION__);
    key_dev->have_data = 1;//表示有数据了
    wake_up_interruptible(&key_dev->wq_head);
    }

    //中断处理方法
    //参数1--当前产生的中断的号码
    //参数2--request_irq传递过来的参数
    irqreturn_t key_irq_handler(int irqno, void *dev_id)
    {
    int *p = (int *)dev_id;
    //printk("-----------%s-------0x%x----- ", __FUNCTION__, *p);
    //区分当前是哪个按键
    struct key_desc *pdesc = (struct key_desc *)dev_id;

    //区分按下还是抬起
    int value = gpio_get_value(pdesc->gpio);
    if(value)
    {
    //抬起
    printk("<kernel>--%s : release ", pdesc->name);
    //填充值
    key_dev->event.code = pdesc->code;
    key_dev->event.value = 0;

    }else
    {
    //按下
    printk("<kernel>--%s : pressed ", pdesc->name);
    key_dev->event.code = pdesc->code;
    key_dev->event.value = 1;
    }

    //在中断上半部,将work加入到内核线程
    schedule_work(&key_dev->work);

    return IRQ_HANDLED;
    }
    static int __init key_drv_init(void)
    {

    int ret;

    // 0-为全局设备/数据对象分配空间
    //参数2--标志--分配内存方式, GFP_KERNEL(如果当前内存不够的时候会等待)
    key_dev = kzalloc(sizeof(struct s5pv210_key), GFP_KERNEL);
    if(key_dev == NULL)
    {
    printk(KERN_ERR"kmalloc error ");
    return -ENOMEM;
    }


    // 1, 申请主设备号--动态申请主设备号
    key_dev->major = register_chrdev(0, "key_dev", &key_fops);
    if(key_dev->major < 0)
    {
    printk(KERN_ERR"register_chrdev error ");
    ret = key_dev->major;
    goto err_free;
    }


    // 2, 创建设备节点
    key_dev->cls = class_create(THIS_MODULE, "key_cls");
    if(IS_ERR(key_dev->cls))
    {
    ret = PTR_ERR(key_dev->cls); //自动根据指针转换一个对应的出错码
    goto err_unregister;
    }


    key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->major, 0), NULL, "key0");
    if(IS_ERR(key_dev->dev))
    {
    ret = PTR_ERR(key_dev->dev);
    goto err_destory_cls;
    }
    // 3, 硬件初始化-- 映射地址或者中断申请

    //参数1--中断号码
    //获取中断号码的方法: 1,外部中断IRQ_EINT(x)
    // 2, 头文件 #include <mach/irqs.h> #include <plat/irqs.h>
    //参数2--中断的处理方法
    //参数3--中断触发方式: 高电平,低电平,上升沿,下降沿,双边沿
    /*
    #define IRQF_TRIGGER_NONE 0x00000000
    #define IRQF_TRIGGER_RISING 0x00000001
    #define IRQF_TRIGGER_FALLING 0x00000002
    #define IRQF_TRIGGER_HIGH 0x00000004
    #define IRQF_TRIGGER_LOW 0x00000008
    */
    //参数4--中断的描述-自定义字符串
    //参数5--传递个参数2的任意数据
    //返回值0表示正确
    int i;
    int irqno;
    int flags;
    char *name;
    for(i=0; i<ARRAY_SIZE(all_keys); i++)
    {
    name = all_keys[i].name;
    irqno = all_keys[i].irqno;
    flags = all_keys[i].flags;
    ret = request_irq(irqno, key_irq_handler, flags, name, &all_keys[i]);
    if(ret != 0)
    {
    printk("request_irq error ");
    goto err_destroy_dev;
    }
    }


    // 定义一个等待队列头,并且初始化
    init_waitqueue_head(&key_dev->wq_head);

    //初始化work
    INIT_WORK(&key_dev->work, work_key_irq);




    return 0;

    err_destroy_dev:
    device_destroy(key_dev->cls, MKDEV(key_dev->major, 0));

    err_destory_cls:
    class_destroy(key_dev->cls);

    err_unregister:
    unregister_chrdev(key_dev->major, "key_dev");

    err_free:
    kfree(key_dev);
    return ret;

    }



    static void __exit key_drv_exit(void)
    {
    //移除work
    cancel_work_sync(&key_dev->work);

    // 释放中断
    //参数1--中断号码
    //参数5--和request_irq第5个参数保持一致
    int i;
    int irqno;
    for(i=0; i<ARRAY_SIZE(all_keys); i++)
    {
    irqno = all_keys[i].irqno;
    free_irq(irqno, &all_keys[i]);
    }

    device_destroy(key_dev->cls, MKDEV(key_dev->major, 0));
    class_destroy(key_dev->cls);
    unregister_chrdev(key_dev->major, "key_dev");
    kfree(key_dev);
    }


    module_init(key_drv_init);
    module_exit(key_drv_exit);
    MODULE_LICENSE("GPL");








     应用层app代码

    #include <stdio.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <poll.h>

    #include <linux/input.h>

    // 设计一个对象--描述按键产生的数据包
    struct key_event{
    int code; //什么按键: 左键, 回车键
    int value; // 按键的状态: 按下/抬起 (1/0)
    };


    int main(int argc, char *argv[])
    {

    int on;
    int ret;
    char kbd_buf[128];
    struct key_event data;

    //直接将驱动模块当做文件来操作
    int fd = open("/dev/key0", O_RDWR);
    if(fd < 0)
    {
    perror("open");
    exit(1);
    }

    struct pollfd pfd[2];

    pfd[0].fd = fd; //自己写的按键设备
    pfd[0].events = POLLIN; //监控读 (POLLIN|POLLOUT|POLLERR)

    pfd[1].fd = 0; //监控标准输入
    pfd[1].events = POLLIN;

    while(1)
    {
    //参数1--你需要监控的文件描述符的集合
    //参数2--监控的文件的个数
    //参数3--监控的时间--毫秒为单位,如果是负数,永久监控
    ret = poll(pfd, 2, -1);
    if(ret < 0)
    {
    perror("poll");
    exit(1);
    }
    if(ret > 0)
    {

    //判断是哪个有数据
    if(pfd[1].revents & POLLIN)
    {
    //表示键盘是输入
    ret = read(0, kbd_buf, 128);
    //fgets(kbd_buf, 128, stdin);
    kbd_buf[ret] = '';
    printf("kbd_buf = %s ", kbd_buf);

    }
    if(pfd[0].revents & POLLIN)
    {
    //获取数据--不会阻塞
    ret = read(pfd[0].fd, &data, sizeof(struct key_event));
    //解析包
    switch(data.code)
    {
    case KEY_UP:
    if(data.value)
    {
    printf("<app>---KEY_UP pressed ");
    }else
    {
    printf("<app>---KEY_UP release ");
    }
    break;
    case KEY_DOWN:
    if(data.value)
    {
    printf("<app>---KEY_DOWN pressed ");
    }else
    {
    printf("<app>---KEY_DOWN release ");
    }
    break;
    case KEY_LEFT:
    if(data.value)
    {
    printf("<app>---KEY_LEFT pressed ");
    }else
    {
    printf("<app>---KEY_LEFT release ");
    }
    break;
    case KEY_RIGHT:
    if(data.value)
    {
    printf("<app>---KEY_RIGHT pressed ");
    }else
    {
    printf("<app>---KEY_RIGHT release ");
    }
    break;

    }

    }
    }

    }




    close(fd);


    }

     

     
    ————————————————
    版权声明:本文为CSDN博主「Moonright」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/weixin_42471952/article/details/81609141

  • 相关阅读:
    vue中select设置默认选中
    验证码
    JS图片src转义
    int main(int argc, char** argv) 以及CommandLineParser
    Visual Studio2013 配置opencv3.3.0 x64系统
    ubuntu16.04 下安装 visual studio code 以及利用 g++ 运行 c++程序
    第三次作业
    第二次作业
    作业一
    第四次作业
  • 原文地址:https://www.cnblogs.com/sky-heaven/p/11517207.html
Copyright © 2020-2023  润新知