LED驱动代码托管在 gitee
https://gitee.com/maziot-samsung/jz2440v3.drivers/blob/master/leds/myleds.c
字符设备的核心就是 file_operations 结构体,不管是现在的LED驱动还是什么驱动,不管是最普通的写法还是总线设备驱动模型。只要他是一个字符设备,那他的核心就一定是 file_operations 结构体。
作为一个驱动程序,我们应该从入口函数开始看,入口函数就是被 module_init() 修饰过的函数,出口函数同理
在入口函数里面首先调用了ioremap函数,此函数是将物理地址映射成虚拟地址,这样我们就可以像操作内存中的地址一样操作物理地址,参数为 ioremap(起始地址,映射长度) 然后就是向内核注册一个字符设备,也就是 file_operations 结构体 最后分创建类和设备。
ioremap不讲就是一个映射,STM32中GPIOA就是等于0x80000080一样,只不过STM32标准库做好了映射,而Linux需要调用函数来映射而已
file_operations (非常重要)
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 **);
};
结构体中包含非常多的函数指针,并不是每个驱动程序都要有所有的函数指针,此LED驱动程序就只用了open read write三个而已。给函数指针赋值,当应用程序调用read write的时候实际上就会调用你赋值的那个函数了。
用register_chrdev来注册,参数为register_chrdev(主设备号,名字,结构体指针),值得注意一下的是当主设备号为0时系统自动分配一个暂时没用的数字作为书设备号。名字不重要。结构体指针才是关键,目的是为了可以找到驱动程序的read write函数。
用 class_create先创建一个类。然后再用class_device_create创建类下面的设备。怎么理解呢?
先从设备讲起吧,我们这是个LED的驱动。一个LED灯就是一个设备,但是他们都属于LED这个类
出口函数和入口函数恰好相反,有注册就有注销 unregister_chrdev 有创建类就有消除类 class_destroy 有创建设备就有消除设备class_device_unregister 有映射就有取消映射 iounmap
到这里驱动程序的框架差不多就写好了,接下来就是填充结构体里面的函数指针来具体实现open read wirte
open:
当应用程序调用open时传到驱动程序就会调用这个open,open很简单,只需要将GPIO初始化就行,比如设置为输入还是输出等。
read:
read函数是应用程序想要知道当前设备状态,就去读,需要把当前状态发送给应用程序,首先根据主设备号通过MINOR宏取得此设备号以进一步取得设备led1、led2... 然后获取相应的状态值。由于3个LED的状态值保存于一个变量,我们需要读出具体某一个的状态,因此需要对leds_status进行读操作,读的时候先获得原子锁,以防读取了一个错误的状态最后通过copy_to_user将状态值发送给应用程序
write:
通过copy_from_user获得应用程序发过来的数据,通过MINOR获得需要操作的设备,最后将引脚电平拉高或者拉低