字符设备介绍
字符(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等高阶实现,将在未来的文章中做整理,总结出个人经验和理解哈。
完结撒花!!!!!!!!!!!