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

    1、字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后顺序。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。

    2、块设备:是指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。

      每一个字符设备或块设备都在/dev目录下对应一个设备文件。linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备和块设备。

    主设备号和次设备号(二者一起为设备号):
      一个字符设备或块设备都有一个主设备号和一个次设备号。主设备号用来标识与设备文件相连的驱动程序,用来反映设备类型。次设备号被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。

    驱动程序原理图:

    那么对于刚接触驱动的我们来说如何快速编写一个驱动程序呢?

    最好也是最快的方法是参考内核源代码中的demo。例如现在,我想编写我们的第一个字符驱动程序,那么我们可以看看别人是怎么实现的,在内核driver目录下找到led的驱动程序,参考别人是如何实现。还有就是厂家的参考demo。这是我们最快的学习方式。和STM32学习固件库函数一样的道理。

    先写出两个函数模型,打开(open)和写(write)函数:

    static int first_drv_open(struct inode *inode, struct file *file)
    {
        
        return 0;
    }
    
    static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
    {
        return 0;
    }

    然后是要告诉内核有这两个函数,怎样告诉内核呢?通过定义下面这样结构:

    /*
     * NOTE:
     * read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl
     * can be called without the big kernel lock held in all filesystems.
     */
    struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, struct dentry *, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*dir_notify)(struct file *filp, unsigned long arg);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    }

    然后通过一个函数告诉内核:

     那么谁来调用上面这个函数呢?调用上面这个函数的函数就叫做驱动入口函数(这里是first_drv_init):

    入口函数需要区分是哪个驱动,所以需要修饰一下,怎么修饰呢?就是调用一个函数:

     完整的myled.c函数如下:

    #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>
    
    
    
    static int first_drv_open(struct inode *inode, struct file *file)
    
    {
    
        printk("first_drv_open...
    ");
    
        return 0;
    
    }
    
    
    
    static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
    
    {
    
        printk("first_drv_write...
    ");
    
        return 0;
    
    }
    
    
    
    /* 这个结构是字符设备驱动程序的核心
    
     * 当应用程序操作设备文件时所调用的open、read、write等函数,
    
     * 最终会调用这个结构中指定的对应函数
    
     */
    
    static struct file_operations first_drv_fops = {
    
        .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    
        .open   =   first_drv_open,            
    
        .write    =    first_drv_write,       
    
    };
    
    int fisrt_drv_init(void)
    
    {
    
        register_chrdev(111, "first_drv", &first_drv_fops);
    
        return 0;
    
    }
    
    
    
    void fisrt_drv_exit(void)
    
    {
    
        unregister_chrdev(111, "first_drv");
    
    }
    
    
    
    module_init(fisrt_drv_init);
    
    module_exit(fisrt_drv_init);
    
    MODULE_AUTHOR("http://www.100ask.net");
    
    MODULE_VERSION("0.1.0");
    
    MODULE_DESCRIPTION("S3C2410/S3C2440 LED Driver");
    
    MODULE_LICENSE("GPL");
    View Code

    /*

    更正上面一个错误,由于粗心导致之后的程序出现bug:

    */

    Makefile如下:

      1 KERN_DIR =/home/book/Documents/linux-2.6.22.6
      2 PWD       := $(shell pwd)
      3 all:
      4         make -C $(KERN_DIR) M=$(PWD) modules 
      5 
      6 clean:
      7         make -C $(KERN_DIR) M=$(PWD) modules clean
      8         rm -rf modules.order
      9 
     10 obj-m   += myled.o

    上面的Makefile经过了一次更改,之前韦老师的Makefile如下:

    关键在于使用韦老师的`pwd`这个方式,我在ubuntu 16.04上make会失败,查询网上资料,改成$(PWD)之后,终于make成功了。特别注意一点,在make驱动函数之前,需要先构建内核树,其实就是保证在make驱动函数之前,先make一下内核。还有一点需要注意,想要加载驱动,在第一次make内核之后,把此次生成的uImage下载进入flash(如果无法生成uImage,执行sudo apt-get install u-boot-tools即可),然后才可以看到驱动被加载。还有就是,在使用不更改uboot参数的网络文件系统,即通过手动mount的方式,这种情况下insmod驱动的.ko文件,会比较耗时,甚至容易出现失败或者长时间卡死状态,所以建议选用set uboot参数的方式,这样insmod的时候可以快速响应:

    现在写个main函数测试这个驱动:

    #include <sys/types.h>
    
    #include <sys/stat.h>
    
    #include <fcntl.h>
    
    #include <stdio.h>
    
    
    
    int main(int argc, char **argv)
    
    {
    
        int fd;
    
        int val=1;
    
        fd=open("/dev/xxx",O_RDWR);
    
        if(fd<0)
    
            printf("can't open!
    ");
    
        write(fd,&val,4);
    
        return 0;
    
    }

    在nfs共享目录下编译一下这个源文件:

    生成可执行文件之后,在开发板上运行:

    首先执行的时候,显示不能打开,因为我们还没有创建这样的设备,使用mknod创建设备节点之后,可以看到应用程序的open和write会触发我们驱动函数的open和write,证明我们的入门测试成功了。创建设备采用/dev/xxx是为了展示这个设备的名字,其实无关紧要,但是最好能有意义。

    当然,这里只是我们第一个测试程序,存在不足,我们在驱动函数中是写死了主设备号为111,而且还需要手动创建节点,在之后的随笔中,将对其进行改进。

    (现在我是使用的经过uboot更改了参数的nfs网络文件系统,这样的方式insmod更快)

    但是,现在有个问题,LCD此时的驱动是有问题的,现在只是在测试学习驱动阶段,可暂且不管,现在我屏幕没有企鹅了,花屏~~~继续往后学习!

  • 相关阅读:
    [CQOI2015]选数
    [AHOI2009]中国象棋
    [ZJOI2012]灾难
    [NOI2018]屠龙勇士
    [APIO2016]划艇
    [ZJOI2011]礼物
    cent 7 识别exfat
    C语言风格的 for 循环(SHELL的循环写法 已验证20200517)
    系统安装时间
    单用户模式修改root密码
  • 原文地址:https://www.cnblogs.com/yangguang-it/p/8681579.html
Copyright © 2011-2022 走看看