字符设备是按照字节流进行读写操作的设备,读写数据是分先后顺序的。常见的点灯、按键、 IIC、 SPI和LCD 等都是字符设备 。
字符设备驱动开发步骤:
总体思路:
——定义并初始化一个字符设备——–
–1、定义一个字符设备—>struct cdev
2、定义并初始化字符设备的文件操作集—>struct file_operations
3、给字符设备申请一个设备号—>设备号=主设备号<<20 + 次设备号
4、初始化字符设备
5、将字符设备加入内核
——-自动生成设备文件———
6、创建class
7、创建device,其中device是属于class的
——-得到物理地址对应的虚拟地址——-
8、申请物理内存区,申请SFR的地址区。SFR — Special Function Register: GPIOEOUT
9、内存的动态映射,得到物理地址对应的虚拟地址 10、访问虚拟地址
1、驱动模块的加载和卸载
module_init(xxx_init); //注册模块加载函数module_exit(xxx_exit); //注册模块卸载函数
字符设备开发第一步需要向Linux内核注册模块加载函数和卸载函数。使用“ insmod”或“modprobe”命令加载驱动的时候 xxx_init这个函数就会被调用。使用“ rmmod”命令卸载具体驱动的时候 xxx_exit函数就会被调用。一般在加载函数里进行一些驱动的初始化工作,在卸载里面就需要对驱动程序的卸载做一些回收工作。
注:insmod命令不能解决模块的依赖关系 。modprobe 命令主要智能在提供了模块的依赖性分析、错误检查、错误报告等功能。
2、字符设备注册与注销
当驱动模块加载成功以后需要注册字符设备,卸载驱动模块的时候也需要注销掉字符设备。
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) static inline void unregister_chrdev(unsigned int major, const char *name)
register_chrdev函数用于注册字符设备,此函数共有三个参数: major:主设备号,linux每个设备都有一个设备号,设备号分为主设备号和次设备号。 name:设备名字,指向一串字符串。 fops:结构体file_operations类型指针。 unregister_chrdev函数用那个与注销字符设备,此函数共有两个参数: major:要注销的设备对应的主设备号。 name:要注销的设备对应的设备名。
2.1 创建设备号
如果没有定义了主设备号:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
如果给定了设备的主设备号和次设备号 :
int register_chrdev_region(dev_t from, unsigned count, const char *name)
注销设备号:
void unregister_chrdev_region(dev_t from, unsigned count)
2.2 初始化并添加cdev
在 Linux 中使用 cdev 结构体表示一个字符设备,定义好 cdev 变量以后就要使用 cdev_init 函数对其进行初始化并添加到内核。
void cdev_init(struct cdev *cdev, const struct file_operations *fops) int cdev_add(struct cdev *p, dev_t dev, unsigned count)
删除字符设备:
void cdev_del(struct cdev *p)
2.3 自动创建类创建设备
创建class和device的目的是在安装的驱动的时候,可以自动生成设备文件,在卸载驱动的时候,可以自动的删除设备文件。
类创建和删除。
struct class *class_create (struct module *owner, const char *name) void class_destroy(struct class *cls);
设备创建和删除。
struct device *device_create(struct class *class,struct device *parent,...)void device_destroy(struct class *class, dev_t devt)
2.4 申请物理内存区
linux驱动使用的虚拟地址,不能直接使用物理地址。在Linux中一般申请物理地址区作为一个资源,将物理内存区做内存的动态映射,得到虚拟地址。
注:资源—有限的,一旦一个物理内存区已经申请了,后面就不能再次申请。
struct resource * request_mem_region(resource_size_t start,resource_size_t n,const char *name)
释放物理内存区:
void release_mem_region(resource_size_t start, resource_size_t n)
2.5 物理地址内存映射
将一段物理地址内存区映射成一段虚拟地址内存区。
#include void __iomem *ioremap(phys_addr_t offset, unsigned long size)
解除IO内存动态映射
void iounmap(void __iomem *addr)
2.6使用虚拟地址
访问虚拟地址的方法:与访问物理地址的方法一样,使用内核提供的函数对虚拟地址进行操作。
3、用户空间和内核空间交互数据3.1 从用户空间获取数据
#include unsigned long __must_check copy_from_user(void *to, const void __user *from, unsigned long n)
在write函数对获取的数据进行写操作。
3.1 将数据拷贝给用户
unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n)
放在驱动程序的read()。