Linux设备驱动——字符设备的基本驱动实现及关键要素(体系地认识文件与驱动)

字符设备介绍

字符(char)设备是个能够像字节流(类似文件)一样被访问的设备,比如字符终端(/dev/console)和串口(/dev/ttys0),它们可以通过文件系统节点来访问,大多数字符设备是一个个只能顺序访问的数据通道。

字符驱动设备程序适合于大多数简单的硬件设备,且比起块设备网络驱动设备程序的架构相对简洁。在深入了解设备驱动程序时,从字符设备开始剖析可以更好地理解内核的工作过程。

字符驱动的各要素

应用程序驱动程序的交互,内核控制流程的总体框图如下:

(调用关系中涉及的数据结构将在后续介绍)

设备号

对字符设备的访问是通过文件系统内的设备名称进行的。称为设备文件、文件系统树的节点,通常位于/dev目录;

ls -l

列出的第一列中"c"来识别,块设备由"b"识别。

主设备号标识设备对应的驱动程序,次设备号由内核使用,用于正确确定设备文件所指设备

dev_t类型是内核中用来表示设备编号的数据类型,包含在 <linux/types.h>文件中。

设备编号中获取主/次设备号的两个宏

int MAJOR(dev_t dev);
int MINOR(dev_t dev);

由主/次设备号构造一个dev_t数据项

dev_t MKDEV(unsigned int major, unsigned int minor);

内核数据结构

“文件系统” 头文件<linux/fs.h>,是编写设备驱动程序必要的头文件,其中声明了许多重要的函数和数据结构。

大多数设备驱动程序中,file_operations结构体保存了字符驱动程序的方法,file结构体表示一个打开的文件,inode结构体表示一个磁盘上的文件。

file_operations结构体

将任何驱动程序操作连接到设备编号,而这些操作主要是用来实现系统调用(open,read,write)

结构中每一个字段都必须指向驱动程序中实现特定操作的函数,对于不支持的操作,可以将对应字段置空。(__user字符串是一种形式的文档而已,表明指针是一个用户空间地址,因此不能被直接引用)

struct file_operations { 
 
    struct module *owner;//拥有该结构的模块的指针,一般为THIS_MODULES 
 
    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);//仅用于读取目录,对于设备文件,该字段为NULL 
 
    unsigned int (*poll) (struct file *, struct poll_table_struct *); //轮询函数,判断目前是否可以进行非阻塞的读写或写入 
 
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); //执行设备I/O控制命令 
 
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //不使用BLK文件系统,将使用此种函数指针代替ioctl 
 
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //在64位系统上,32位的ioctl调用将使用此函数指针代替 
 
 
    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); //通知设备FASYNC标志发生变化 
 
    int (*lock) (struct file *, int, struct file_lock *); 
 
    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 (*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); 
 
    int (*setlease)(struct file *, long, struct file_lock **);

    long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);

	void (*show_fdinfo)(struct seq_file *m, struct file *f);

#ifndef CONFIG_MMU
	unsigned (*mmap_capabilities)(struct file *);
#endif

	ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
			loff_t, size_t, unsigned int);

	int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
			u64);

	ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *,
			u64);
};

一般实现常用的设备方法,在创建结构体变量采用标准C的标记化结构初始化语法(允许对结构体成员进行重新排列)

struct file_operations my_fops = {
	.open = my_open,
    .release = my_release,
	.read = my_read,
    .write = my_write,
    .ioctl = my_ioctl,
};

file结构体

注意:file结构体与用户空间程序中的FILE没有任何关联(FILE在C库中定义且不会出现在内核代码中,file是内核结构,不会出现在用户空间程序中)

file代表打开的文件(不仅仅限定于设备驱动程序,系统中每个打开的文件在内核空间都有一个对应的file结构,并且在文件的所有实例都被关闭之后,内核会释放这个数据结构)

它在内核中的指针,通常称为“文件指针”(filp)

struct file{
    struct list_head f_list; /*所有打开的文件形成一个链表*/
    struct dentry *f_dentry; /*指向相关目录项的指针*/
    struct vfsmount *f_vfsmnt; /*指向VFS安装点的指针*/
    struct file_operations *f_op; /*指向文件操作表的指针*/
    mode_t f_mode; /*文件的打开模式*/
    loff_t f_pos; /*文件的当前位置*/
    unsigned short f_flags; /*打开文件时所指定的标志*/
    unsigned short f_count; /*使用该结构的进程数*/
    unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;
    /*预读标志、要预读的最多页面数、上次预读后的文件指针、预读的字节数以及
    预读的页面数*/
    int f_owner; /* 通过信号进行异步I/O数据的传送*/
    unsigned int f_uid, f_gid; /*用户的UID和GID*/
    int f_error; /*网络写操作的错误码*/

    unsigned long f_version; /*版本号*/
    void *private_data; /* tty驱动程序所需 */
};

inode结构体

内核用inode结构在内部表示具体的文件。对单个文件,可能会有许多个表示打开的文件描述符的file结构,它们都指向单个inode结构。

其中 i_rdev 对表示设备文件的inode结构,该字段包含了真正的设备编号。

i_cdev 表示inode指向一个字符设备文件,该字段包含了指向struct cdev结构的指针

这两个宏,可从一个inode中获得主设备号和次设备号
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);

 (开发过程中,应该避免直接操作i_rdev)

struct inode {
    umode_t         i_mode;//文件的访问权限(eg:rwxrwxrwx)
    unsigned short      i_opflags;
    kuid_t          i_uid;//inode拥有者id
    kgid_t          i_gid;//inode拥有者组id
    unsigned int        i_flags;//inode标志,可以是S_SYNC,S_NOATIME,S_DIRSYNC等

#ifdef CONFIG_FS_POSIX_ACL
    struct posix_acl    *i_acl;
    struct posix_acl    *i_default_acl;
#endif

    const struct inode_operations   *i_op;//inode操作
    struct super_block  *i_sb;//所属的超级快
    /*
        address_space并不代表某个地址空间,而是用于描述页高速缓存中的页面的一个文件对应一个address_space,一个address_space与一个偏移量能够确定一个一个也高速缓存中的页面。i_mapping通常指向i_data,不过两者是有区别的,i_mapping表示应该向谁请求页面,i_data表示被改inode读写的页面。
    */
    struct address_space    *i_mapping;

#ifdef CONFIG_SECURITY
    void            *i_security;
#endif

    /* Stat data, not accessed from path walking */
    unsigned long       i_ino;//inode号
    /*
     * Filesystems may only read i_nlink directly.  They shall use the
     * following functions for modification:
     *
     *    (set|clear|inc|drop)_nlink
     *    inode_(inc|dec)_link_count
     */
    union {
        const unsigned int i_nlink;//硬链接个数
        unsigned int __i_nlink;
    };
    dev_t           i_rdev;//如果inode代表设备,i_rdev表示该设备的设备号
    loff_t          i_size;//文件大小
    struct timespec     i_atime;//最近一次访问文件的时间
    struct timespec     i_mtime;//最近一次修改文件的时间
    struct timespec     i_ctime;//最近一次修改inode的时间
    spinlock_t      i_lock; /* i_blocks, i_bytes, maybe i_size */
    unsigned short          i_bytes;//文件中位于最后一个块的字节数
    unsigned int        i_blkbits;//以bit为单位的块的大小
    blkcnt_t        i_blocks;//文件使用块的数目

#ifdef __NEED_I_SIZE_ORDERED
    seqcount_t      i_size_seqcount;//对i_size进行串行计数
#endif

    /* Misc */
    unsigned long       i_state;//inode状态,可以是I_NEW,I_LOCK,I_FREEING等
    struct mutex        i_mutex;//保护inode的互斥锁

    //inode第一次为脏的时间 以jiffies为单位
    unsigned long       dirtied_when;   /* jiffies of first dirtying */

    struct hlist_node   i_hash;//散列表
    struct list_head    i_wb_list;  /* backing dev IO list */
    struct list_head    i_lru;      /* inode LRU list */
    struct list_head    i_sb_list;//超级块链表
    union {
        struct hlist_head   i_dentry;//所有引用该inode的目录项形成的链表
        struct rcu_head     i_rcu;
    };
    u64         i_version;//版本号 inode每次修改后递增
    atomic_t        i_count;//引用计数
    atomic_t        i_dio_count;
    atomic_t        i_writecount;//记录有多少个进程以可写的方式打开此文件
    const struct file_operations    *i_fop; /* former ->i_op->default_file_ops */
    struct file_lock    *i_flock;//文件锁链表
    struct address_space    i_data;
#ifdef CONFIG_QUOTA
    struct dquot        *i_dquot[MAXQUOTAS];//inode磁盘限额
#endif
    /*
        公用同一个驱动的设备形成链表,比如字符设备,在open时,会根据i_rdev字段查找相应的驱动程序,并使i_cdev字段指向找到的cdev,然后inode添加到struct cdev中的list字段形成的链表中
    */
    struct list_head    i_devices;,
    union {
        struct pipe_inode_info  *i_pipe;//如果文件是一个管道则使用i_pipe
        struct block_device *i_bdev;//如果文件是一个块设备则使用i_bdev
        struct cdev     *i_cdev;//如果文件是一个字符设备这使用i_cdev
    };

    __u32           i_generation;

#ifdef CONFIG_FSNOTIFY
   //目录通知事件掩码
    __u32           i_fsnotify_mask; /* all events this inode cares about */
    struct hlist_head   i_fsnotify_marks;
#endif

#ifdef CONFIG_IMA
    atomic_t        i_readcount; /* struct files open RO */
#endif
    //存储文件系统或者设备的私有信息
    void            *i_private; /* fs or device private pointer */
};

细节关联总结如下:

file结构体中包含有file_operations结构体,file_operations是file的一个域;

在使用系统调用open()打开一个设备节点inode时,会得到一个文件file,同时返回一个文件描述符,该文件描述符是一个整数,称之为句柄通过访问句柄能够访问设备文件file;

描述符是一个有着特殊含义的整数,特定位都有一定的意义或属性。
inode结构包含了大量有关文件的信息。而只有i_rdev 和 i_cdev与驱动程序代码有关用。
前者表示了设备文件的inode结构,包含了真正的设备编号,而后者表示字符设备的内核的内部结构,当其指向一个字符设备文件时,则包含了指向struct cdev结构的指针。

数据结构间的交互如下图:

一般步骤(可以采用创建设备类的方法来自动创建设备并初始化,将在例子中实现这种方法)

手动添加的方式:

(1)先找到一个设备号dev_t,可以动态申请,也可以静态设定,假设静态设定为major,minor,通过宏MKDEV(major,minor)来生成 dev_t数据项
(2)构建对设备的操作函数file_opreation结构体,包含系统调用的设备的操作:open、read、write、release、ioctl等
(3)构建cdev结构体,里面填充两个主要成员dev_t(设备号)、file_operations(对设备的操作)
(4)把cdev添加的cdev链表中:cdev_init、cdev_add

设备注册

内核内部使用 cdev 结构来表示字符设备 (内核调用设备的操作之前,必须分配并注册一个或多个此结构),其结构和一些辅助函数包含在 <linux/cdev.h>

struct cdev{
    struct kobject kobj;         /* 内嵌的 kobject 对象 */ 
    struct module *owner;        /* 所属模块 */  
    struct file_operations *ops;  /* 文件操作指针对象 */ 
    struct list_head list; 
    dev_t dev;                   /* 设备号 */ 
    unsigned int count; 
};

注意:有两种方法进行分配和初始化cdev,用来管理cdev结构的函数。

struct cdev * cdev_alloc(void);
void cdev_init(struct cdev *dev, struct file_operations *fops);
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
void cdev_del(struct cdev *dev);

第一套方法,如果打算在运行时获取一个独立的cdev结构,则可以这样做

struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
int my_dev_t = MKDEV(my_major, my_minor);

cdev_init(&my_cdev, &my_fops);
int err = cdev_add(&my_cdev, my_dev_t, 1);

其中cdev也有一个所有者字段,应该被设置为THIS_MODULE

(只要cdev_add返回了,设备就“活”了,它的操作就会被内核调用。因此,在驱动程序还没有完全准备好处理设备上的操作时,就不能调用cdev_add)

新接口

int register_chrdev_region(dev_t first, unsigned int count, char *name);
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);

void unregister_chrdev_region(dev_t first, unsigned int count);

其中name是和该编号范围关联的设备名称,它将出现在/proc/devices  和sysfs中。对于动态分配alloc,firstminor是要使用的被请求的第一个次设备号,通常是0。

(开发中推荐使用动态分配机制来获取主设备号,主设备号可以在 /proc/devices中读取到)

旧接口

第二套方法,使用旧的机制,可以避免使用cdev结构,以创建设备类设备的方式进行关联(register_chrdev的调用将为给定的主设备号注册0~255作为次设备号,并为每个设备建立一个对应的默认c_dev结构)

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
int unregister_chrdev(unsigned int major, const char *name);

将在按键控制,这一实际案例中展示旧接口的详细操作,自动创建设备节点,声明在<linux/device.h>

/* This is a #define to keep the compiler from merging different
 * instances of the __key variable */
#define class_create(owner, name)       \
({                      \
    static struct lock_class_key __key; \
    __class_create(owner, name, &__key);    \
})

/**
 * class_create - create a struct class structure
 * @owner: pointer to the module that is to "own" this struct class
 * @name: pointer to a string for the name of this class.
 * @key: the lock_class_key for this class; used by mutex lock debugging
 *
 * This is used to create a struct class pointer that can then be used
 * in calls to device_create().
 *
 * Returns &struct class pointer on success, or ERR_PTR() on error.
 *
 * Note, the pointer created here is to be destroyed when finished by
 * making a call to class_destroy().
 */
struct class *__class_create(struct module *owner, const char *name,struct lock_class_key *key);

/*
class:给哪个类创建设备
parent:父设备,一般为NULL
devt:设备号
drvdata:设备可能会使用到的数据
fmt:设备名字

*/
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);

必要的系统调用接口函数

在基本驱动中,主要实现这四个功能,在高阶驱动中,还将实现异步和poll及ioctl等功能

open && release

一般完备的open函数应该完成,1.检查设备特定的错误(诸如设备未就绪或类似的硬件问题)2.如果设备是首次打开,则对其进行初始化,如有必要,更新f_op指针3.分配并填写置于filp->private_data里的数据结构。

一个方便使用的宏,它可用于从包含在某个结构中的指针获得结构本身的指针。

int (*open)(struct inode *inode, struct file *filp);
#include <linux/kernel.h>
container_of(pointer, container_type, container_field);

简单示例

struct scull_dev *dev;
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev;

开发注意:利用register_chrdev注册自己的设备,则必须使用该技术,使用iminor宏从inode结构中获得次设备号,并确保它对应于驱动程序真正准备打开的设备。

release方法的作用与open相反,这个设备方法应该完成 1.释放由open分配的、保存在filp->private_data中的所有内容。2.在最后一次关闭操作时关闭设备。

(只有那些真正释放设备数据结构的close调用才会调用这个方法,release方法和close系统调用间的关系保证了对于每次open驱动程序只会看到对应的一次release调用)

当内核在进程退出的时候,通过在内部使用close系统调用自动关闭所有相关的文件。

int (*release) (struct inode *, struct file *);

read  && write

read和write方法完成的任务是相似的,拷贝数据到应用程序空间或从应用程序空间拷贝数据到内核空间。

返回值等于count,则表示传输成功,是最理想情况。

返回值小count的正数,则表示只传输了部分数据,程序可能再次试图读取或写入,直到完毕。

返回值为0,在read中表示到达文件尾,在write中意味着什么也没有写入(不算错误,标准库会重复调用write,并且在阻塞写入时,有对应的处理情形)。

返回值为负值,意味着发生了错误。

ssize_t (*read)(struct file *filp, char __user *buffer, size_t count, loff_t *offp); 
ssize_t (*write)(struct file *filp, const char __user *buffer, size_t count, loff_t *offp); 

其中filp是文件指针,count是请求传输的数据长度,offp为读的位置相对于文件开头的偏移,在读取信息后,这个指针一般都会移动,移动的值为要读取信息的长度值;在写入时,为当前的偏移位置,这个值通常是用来判断写文件是否越界,buffer为用户空间缓冲区指针(内核代码不能直接引用其中的内容)。

因此,在内核空间和用户空间之间拷贝数据用下列内核函数(字节序列)

这些函数使用了一些特殊的,架构相关的方法来确保在内核和用户空间之间安全,正确地交换数据

#include <asm/uaccess.h>

unsigned long copy_from_user(void *to, const void *from, unsigned long count);
unsigned long copy_to_user(void *to, const void *from, unsigned long count);

注意:访问用户空间的任何函数都必须是可重入的,并且必须能和其他驱动程序函数并发执行,更特别的是,必须处于能够合法休眠的状态。

read方法负责从设备拷贝数据到用户空间(copy_to_user),而write方法负责从用户空间拷贝数据到设备上(copy_from_user)。出错时,read和write方法都返回一个负值(这种实现惯例要求驱动程序必须记住错误的发生,这样才能在将来把错误状态返回给应用程序,用户空间的程序必须访问errno变量进而得知(源于POSIX标准))

注意:使用标准I/O库的fread函数读数据,这个库函数就会不断调用系统调用,直到所请求的数据传输完毕为止

开发中:有这种情况,“现在还没有数据,但以后可能会有”,read系统调用应该阻塞,即阻塞读取

readv && writev

作为Unix很早就支持的可选系统调用,这些“向量”型的函数具有一个结构数组,每个结构包含一个指向缓冲区的指针和一个长度值。

readv调用可用于将指定数量的数据依次读入每个缓冲区。writev则可以把每个缓冲区的内容收集起来,并将它们在一次写入操作中进行输出。

如果驱动程序没有提供用于处理向量的操作方法,那么readv和writev会通过对read和write方法的多次调用来实现。

ssize_t (*readv)(struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos);
ssize_t (*writev)(struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos);

struct iovec {
    void __user *iov_base;      /* Starting address */
    __kernel_size_t iov_len;     /* Number of bytes to transfer */
};

 函数中的count指明要操作多少个iovec结构,这些结构由应用程序创建,而内核在调用驱动程序之前会把它们拷贝到内核空间。

注意:在很多情况下,直接在驱动程序中实现readv和writev可以获得更高的效率。

同时这个接口在网络编程中,可以为数据传递带来便捷,即分散输入集中输出的优势。

按键控制

这里以 NXP公司的IMX6ULL开发板为例子

采用封装寄存器资源接口,并自定义操作结构体,来关联字符设备驱动接口

board_my_imx6ull.c  (驱动资源程序)

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <asm/io.h>

#include "button_drv.h"

struct iomux {
    volatile unsigned int unnames[23];
    volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00; /* offset 0x5c */
    volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO01;
    volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO02;
    volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03;
    volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04;
    volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO05;
    volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO06;
    volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO07;
    volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO08;
    volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO09;
    volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA;
    volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_UART1_RX_DATA;
    volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B;
};

struct imx6ull_gpio {
    volatile unsigned int dr;
    volatile unsigned int gdir;
    volatile unsigned int psr;
    volatile unsigned int icr1;
    volatile unsigned int icr2;
    volatile unsigned int imr;
    volatile unsigned int isr;
    volatile unsigned int edge_sel;
};

/* enable GPIO4 */
static volatile unsigned int *CCM_CCGR3; 

/* enable GPIO5 */
static volatile unsigned int *CCM_CCGR1; 

/* set GPIO5_IO03 as GPIO */
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1;

/* set GPIO4_IO14 as GPIO */
static volatile unsigned int *IOMUXC_SW_MUX_CTL_PAD_NAND_CE1_B;

static struct iomux *iomux;

static struct imx6ull_gpio *gpio4;
static struct imx6ull_gpio *gpio5;

static void board_imx6ull_button_init (int which) /* 初始化button, which-哪个button */      
{
    if (!CCM_CCGR1)
    {
        CCM_CCGR1 = ioremap(0x20C406C, 4);
        CCM_CCGR3 = ioremap(0x20C4074, 4);
        IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1 = ioremap(0x229000C, 4);
		IOMUXC_SW_MUX_CTL_PAD_NAND_CE1_B        = ioremap(0x20E01B0, 4);

        iomux = ioremap(0x20e0000, sizeof(struct iomux));
        gpio4 = ioremap(0x020A8000, sizeof(struct imx6ull_gpio));
        gpio5 = ioremap(0x20AC000, sizeof(struct imx6ull_gpio));
    }

    if (which == 0)
    {
        /* 1. enable GPIO5 
         * CG15, b[31:30] = 0b11
         */
        *CCM_CCGR1 |= (3<<30);
        
        /* 2. set GPIO5_IO01 as GPIO 
         * MUX_MODE, b[3:0] = 0b101
         */
        *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1 = 5;

        /* 3. set GPIO5_IO01 as input 
         * GPIO5 GDIR, b[1] = 0b0
         */
        gpio5->gdir &= ~(1<<1);
    }
    else if(which == 1)
    {
        /* 1. enable GPIO4 
         * CG6, b[13:12] = 0b11
         */
        *CCM_CCGR3 |= (3<<12);
        
        /* 2. set GPIO4_IO14 as GPIO 
         * MUX_MODE, b[3:0] = 0b101
         */
        IOMUXC_SW_MUX_CTL_PAD_NAND_CE1_B = 5;

        /* 3. set GPIO4_IO14 as input 
         * GPIO4 GDIR, b[14] = 0b0
         */
        gpio4->gdir &= ~(1<<14);
    }
    
}

static int board_imx6ull_button_read (int which) /* 读button, which-哪个 */
{
    //printk("%s %s line %d, button %d, 0x%x\n", __FILE__, __FUNCTION__, __LINE__, which, *GPIO1_DATAIN);
    if (which == 0)
        return (gpio5->psr & (1<<1)) ? 1 : 0;
    else
        return (gpio4->psr & (1<<14)) ? 1 : 0;
}
    
static struct button_operations my_buttons_ops = {
    .count = 2,
    .init = board_imx6ull_button_init,
    .read = board_imx6ull_button_read,
};

int board_imx6ull_button_drv_init(void)
{
    register_button_operations(&my_buttons_ops);
    return 0;
}

void board_imx6ull_button_drv_exit(void)
{
    unregister_button_operations();
}

module_init(board_imx6ull_button_drv_init);
module_exit(board_imx6ull_button_drv_exit);

MODULE_LICENSE("GPL");

button_drv.h (驱动程序头文件)

#ifndef _BUTTON_DRV_H
#define _BUTTON_DRV_H

struct button_operations {
	int count;
	void (*init) (int which);
	int (*read) (int which);
};

void register_button_operations(struct button_operations *opr);
void unregister_button_operations(void);

#endif

button_drv.c (驱动程序)

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/signal.h>
#include <linux/mutex.h>
#include <linux/mm.h>
#include <linux/timer.h>
#include <linux/wait.h>
#include <linux/skbuff.h>
#include <linux/proc_fs.h>
#include <linux/poll.h>
#include <linux/capi.h>
#include <linux/kernelcapi.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/moduleparam.h>

#include "button_drv.h"


static int major = 0;

static struct button_operations *p_button_opr;
static struct class *button_class;

static int button_open (struct inode *inode, struct file *file)
{
	int minor = iminor(inode);
	p_button_opr->init(minor);
	return 0;
}

static ssize_t button_read (struct file *file, char __user *buf, size_t size, loff_t *off)
{
	unsigned int minor = iminor(file_inode(file));
	char level;
	int err;
	
	level = p_button_opr->read(minor);
	err = copy_to_user(buf, &level, 1);
	return 1;
}


static struct file_operations button_fops = {
	.open = button_open,
	.read = button_read,
};

void register_button_operations(struct button_operations *opr)
{
	int i;

	p_button_opr = opr;
	for (i = 0; i < opr->count; i++)
	{
		device_create(button_class, NULL, MKDEV(major, i), NULL, "my_button%d", i);
	}
}

void unregister_button_operations(void)
{
	int i;

	for (i = 0; i < p_button_opr->count; i++)
	{
		device_destroy(button_class, MKDEV(major, i));
	}
}


EXPORT_SYMBOL(register_button_operations);
EXPORT_SYMBOL(unregister_button_operations);


int button_init(void)
{
	major = register_chrdev(0, "my_button", &button_fops);

	button_class = class_create(THIS_MODULE, "my_button");
	if (IS_ERR(button_class))
		return -1;
	
	return 0;
}

void button_exit(void)
{
	class_destroy(button_class);
	unregister_chrdev(major, "my_button");
}

module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");

button_test.c (应用程序)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

/*
 * ./button_test /dev/my_button0
 *
 */
int main(int argc, char **argv)
{
	int fd;
	char val;
	
	/* 1. 判断参数 */
	if (argc != 2) 
	{
		printf("Usage: %s <dev>\n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open(argv[1], O_RDWR);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	/* 3. 写文件 */
	read(fd, &val, 1);
	printf("get button : %d\n", val);
	
	close(fd);
	
	return 0;
}

Makefile文件

KERN_DIR = /home/usr/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules
	$(CROSS_COMPILE)gcc -o button_test button_test.c

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f button_test


obj-m	+= button_drv.o
obj-m	+= board_my_imx6ull.o

(按键例子中所使用的是比较离散的控制方式,安全性和稳定性相对较低(不便于维护与移植),未来的文章中将展示内核子系统,这一成体系的接口来控制硬件)

关于字符设备驱动,这仅仅只是实现基本功能,对于异步、阻塞型I/O、poll&select等高阶实现,将在未来的文章中做整理,总结出个人经验和理解哈。

完结撒花!!!!!!!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值