zoukankan      html  css  js  c++  java
  • 第一个驱动之字符设备驱动(三)按键查询

    总的来说这个驱动和之前的没有太大差别,只是熟悉并复习一下之前的知识,比如裸机的按键查询和前面的first_drv的构建过程:

    Linux操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,
    因为Linux使用的虚拟内存机制

    Code(可看备注回忆知识点):

    #include <linux/module.h>
    
    #include <linux/kernel.h>
    
    #include <linux/fs.h>
    
    #include <linux/init.h>
    
    #include <linux/delay.h>
    
    #include <asm/uaccess.h>
    
    #include <asm/irq.h>
    
    #include <asm/io.h>
    
    #include <asm/arch/regs-gpio.h>
    
    #include <asm/hardware.h>
    
    #include <linux/device.h>
    
    
    
    static struct class *seconddrv_class;
    
    static struct class_device    *seconddrv_class_dev;
    
    
    
    volatile unsigned long *gpfcon;
    
    volatile unsigned long *gpfdat;
    
    
    
    volatile unsigned long *gpgcon;
    
    volatile unsigned long *gpgdat;
    
    
    
    static int second_drv_open(struct inode *inode, struct file *file)
    
    {
    
        /* 配置GPF0,2为输入引脚 */
    
        *gpfcon &= ~((0x3<<(0*2)) | (0x3<<(2*2)));
    
    
    
        /* 配置GPG3,11为输入引脚 */
    
        *gpgcon &= ~((0x3<<(3*2)) | (0x3<<(11*2)));
    
    
    
        return 0;
    
    }
    
    
    
    ssize_t second_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
    
    {
    
        /* 返回4个引脚的电平 */
    
        unsigned char key_vals[4];
    
        int regval;
    
    
    
        if (size != sizeof(key_vals))
    
            return -EINVAL;//宏定义为22,代表的是invalid argument
    
    
    
        /* 读GPF0,2 */
    
        regval = *gpfdat;
    
        key_vals[0] = (regval & (1<<0)) ? 1 : 0;//只想有1或者0两种状态值而采取的方法
    
        key_vals[1] = (regval & (1<<2)) ? 1 : 0;
    
        
    
    
    
        /* 读GPG3,11 */
    
        regval = *gpgdat;
    
        key_vals[2] = (regval & (1<<3)) ? 1 : 0;
    
        key_vals[3] = (regval & (1<<11)) ? 1 : 0;
    
    
    
        //copy_to_user(buf, key_vals, sizeof(key_vals));//从内核空间拷贝数据到用户空间
    
        if(copy_to_user(buf, key_vals, sizeof(key_vals)))
    
        {
    
            return -EFAULT;//表示 bad address
    
        }
    
        return sizeof(key_vals);
    
    }
    
    
    
    
    
    static struct file_operations sencod_drv_fops = {
    
        .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    
        .open   =   second_drv_open,     
    
        .read    =    second_drv_read,       
    
    };
    
    
    
    
    
    int major;
    
    static int second_drv_init(void)
    
    {
    
        major = register_chrdev(0, "second_drv", &sencod_drv_fops);//注册驱动
    
    
    
        seconddrv_class = class_create(THIS_MODULE, "second_drv");//创建类
    
    
    
        seconddrv_class_dev = class_device_create(seconddrv_class, NULL, MKDEV(major, 10), NULL, "buttons"); //创建设备/* /dev/buttons 主设备号系统自动分配,次设备号为10 */
    
    
    
        gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);//物理地址重映射为虚拟地址
    
        gpfdat = gpfcon + 1;//指针+1,这里后移4byte
    
    
    
        gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
    
        gpgdat = gpgcon + 1;
    
    
    
        return 0;
    
    }
    
    
    
    static void second_drv_exit(void)
    
    {
    
        unregister_chrdev(major, "second_drv");//卸载驱动
    
        class_device_unregister(seconddrv_class_dev);//取消设备注册
    
        class_destroy(seconddrv_class);//清除类
    
        iounmap(gpfcon);//取消IO重映射
    
        iounmap(gpgcon);
    
        
    
    }
    
    
    
    
    
    module_init(second_drv_init);//驱动入口函数的修饰函数,该函数在do_initcalls()中被调用
    
    
    
    module_exit(second_drv_exit);
    
    
    
    MODULE_LICENSE("GPL");

    刚开始编译会有警告,说没有检查copy_to_user的返回值,这个函数如果成功返回的是0,失败返回有多少个Bytes未完成copy。所以做了如上更改。

    Makefile:

    KERN_DIR =/home/book/Documents/linux-2.6.22.6
    PWD       := $(shell pwd) 
    all:
        make -C $(KERN_DIR) M=$(PWD) modules 
    
    clean:
        make -C $(KERN_DIR) M=$(PWD) modules clean
        rm -rf modules.order
    
    obj-m    += second_drv.o

     测试程序:

    #include <sys/types.h>
    
    #include <sys/stat.h>
    
    #include <fcntl.h>
    
    #include <stdio.h>
    
    
    
    int main(int argc, char **argv)
    
    {
    
        int fd;
    
        unsigned char key_vals[4];
    
        int count = 0;
    
        fd=open("/dev/buttons",O_RDWR);
    
        if(fd<0)
    
        {
    
            printf("can't open!
    ");
    
        }
    
        while (1)
    
        {
    
            read(fd, key_vals, sizeof(key_vals));
    
            if (!key_vals[0] || !key_vals[1] || !key_vals[2] || !key_vals[3])
    
            {
    
                printf("%04d key pressed: %d %d %d %d
    ", count++, key_vals[0], key_vals[1], key_vals[2], key_vals[3]);
    
            }
    
        }
    
        return 0;
    
    }

    加载驱动,查看系统自动分配的主设备号和我们手动创建的次设备号10.

    运行应用测试程序:

     

    可见,我们的轮询按键的驱动函数正常工作了,此时在fs上输入top指令,查看cpu占有情况:

     

    由此可以看出,应用程序second_test几乎霸占所有cpu资源,所有查询方式太消耗cpu,一般不使用。

     原理图:

     以下转载于:http://blog.163.com/xinbuqianjin@126/blog/static/167563447201010221231507/

    在Linux底下写过driver模块的对这个宏一定不会陌生。module_init宏在MODULE宏有没有定义的情况下展开的内容是不同的,如果这个宏没有定义,基本上表明阁下的模块是要编译进内核的(obj-y)。
    1.在MODULE没有定义这种情况下,module_init定义如下:
    #define module_init(x) __initcall(x);
    因为
    #define __initcall(fn)                            device_initcall(fn)
    #define device_initcall(fn) __define_initcall("6",fn,6)
    #define __define_initcall(level,fn,id)
    static initcall_t __initcall_##fn##id __used
    __attribute__((__section__(".initcall" level ".init"))) = fn
    所以,module_init(x)最终展开为:
    static initcall_t __initcall_##fn##id __used
    __attribute__((__section__(".initcall" level ".init"))) = fn
    更直白点,假设阁下driver所对应的模块的初始化函数为int gpio_init(void),那么module_init(gpio_init)实际上等于:
    static initcall_t  __initcall_gpio_init_6 __used __attribute__((__section__(".initcall6.init"))) = gpio_init;
    就是声明一类型为initcall_t(typedef int (*initcall_t)(void))函数指针类型的变量__initcall_gpio_init_6并将gpio_init赋值与它。
    这里的函数指针变量声明比较特殊的地方在于,将这个变量放在了一名为".initcall6.init"节中。接下来结合vmlinux.lds中的
    .initcall.init : AT(ADDR(.initcall.init) - (0xc0000000 -0x00000000)) {
       __initcall_start = .;
       *(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init)
       __initcall_end = .;
       }
    以及do_initcalls:
    static void __init do_initcalls(void)
    {
    initcall_t *call;
    for (call = __initcall_start; call < __initcall_end; call++)
    do_one_initcall(*call);
    /* Make sure there is no pending stuff from the initcall sequence */
    flush_scheduled_work();
    }
    那么就不难理解阁下模块中的module_init中的初始化函数何时被调用了:在系统启动过程中start_kernel()->rest_init()->kernel_init()->do_basic_setup()->do_initcalls()。

     
     
       




    2.在MODULE被定义的情况下(大部分可动态加载的driver模块都属于此, obj-m),module_init定义如下:
    #define module_init(initfn)
    static inline initcall_t __inittest(void)
    { return initfn; }
    int init_module(void) __attribute__((alias(#initfn)));
    这段宏定义关键点是后面一句,通过alias将initfn变名为init_module。前面那个__inittest的定义其实是种技巧,用来对initfn进行某种静态的类型检查,如果阁下将模块初始化函数定义成,比如,void gpio_init(void)或者是int gpio_init(int),那么在编译时都会有类似下面的warning:
    GPIO/fsl-gpio.c: In function '__inittest':
    GPIO/fsl-gpio.c:46: warning: return from incompatible pointer type
    通过module_init将模块初始化函数统一别名为init_module,这样以后insmod时候,在系统内部会调用sys_init_module()去找到init_module函数的入口地址。
    如果objdump -t gpio.ko,就会发现init_module和gpio_init位于相同的地址偏移处。简言之,这种情况下模块的初始化函数在insmod时候被调用。
  • 相关阅读:
    [CodeForces]Codeforces Round #429 (Div. 2) ABC(待补)
    About Me
    2018-06-14
    Codeforces Codeforces Round #484 (Div. 2) E. Billiard
    Codeforces Codeforces Round #484 (Div. 2) D. Shark
    Codeforces Educational Codeforces Round 44 (Rated for Div. 2) F. Isomorphic Strings
    Codeforces Educational Codeforces Round 44 (Rated for Div. 2) E. Pencils and Boxes
    Codeforces Avito Code Challenge 2018 D. Bookshelves
    Codeforces Round #485 (Div. 2) D. Fair
    Codeforces Round #485 (Div. 2) F. AND Graph
  • 原文地址:https://www.cnblogs.com/yangguang-it/p/8763446.html
Copyright © 2011-2022 走看看