字符设备是按照字节流进行读写操作的设备,读写数据是分先后顺序的。常见的点灯、按键、 IIC、 SPI和LCD 等都是字符设备 。

image-20221124203105697

字符设备驱动开发步骤:

总体思路:

——定义并初始化一个字符设备——–

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()。