linux驱动开发(2)开发流程

1.驱动

驱动其实就是获取外设数据或者控制外设,将数据给应用层,应用层进行处理,显示等等操作。

驱动写好了,注入到内核之后,我们还需要去写简单的测试应用程序去检测程序是否正确。裸机开发,驱动和测试是写在一起的,这里有可以体现出驱动开发和裸机开发的不同之处了。

 驱动程序表现为就是一个/dev/xx文件,这个文件就是控制外设的文件。

比如led ,通过write写入1,点亮,写0关闭。

1.1.开发流程

linux驱动开发(2)开发流程_第1张图片

         在 Linux 中一切皆为文件,驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应
用程序通过对这个名为“/dev/xxx”(xxx 是具体的驱动文件名字)的文件进行相应的操作即可实
现对硬件的操作。简单理解,驱动开发可以将硬件变为文件,提供给上层应用去操控。

        好了,明白了驱动开发是干嘛,接下来该学习一下驱动开发了,驱动开发其实就是一个模板开发,很多都是操作系统写好的,我们只需要去按照模板去修改,满足自己的要求。本质就是自己实现file_operations里面的方法。

struct file_operations {
	struct module *owner;
	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 (*read_iter) (struct kiocb *, struct iov_iter *);
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
	int (*iterate) (struct file *, struct dir_context *);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*mremap)(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 *, loff_t, loff_t, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	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 **, void **);
	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
};

上面就是 file_operations 的结构体。里面有很多我们常见的方法,比如open函数,read/write函数等等,有没有一点点熟悉的感觉了,但是这里的函数和我们应用层使用的不一样。

2.驱动 模块的加载和卸载

        Linux 驱动有两种运行方式,第一种就是将驱动编译进 Linux 内核中,这样当 Linux 内核启
动的时候就会自动运行驱动程序。第二种就是将驱动编译成模块(Linux 下模块扩展名为.ko),在
Linux 内核启动以后使用“insmod/modprobe(modprobe 会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中,因此modprobe 命令相比 insmod 要智能一些)”命令加载驱动模块。在调试驱动的时候一般都选择将其编译为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。而且在调试的时候只需要加载或者卸载驱动模块即可,不需要重启整个系统。总之,将驱动编
译为模块最大的好处就是方便开发,当驱动开发完成,确定没有问题以后就可以将驱动编译进
Linux 内核中,当然也可以不编译进 Linux 内核中,具体看自己的需求。模块有加载和卸载两种操作,我们在编写驱动的时候需要注册这两种操作函数,模块的加载和卸载注册函数如下:

module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

这两个函数是比较重要的,加载函数里面,我们一般要注册设备,注册设备就需要使用到刚刚上面提及到的file_operations 的结构体了;卸载的时候进行注销,下面会有讲这两个函数的实现。

/* 驱动入口函数 */
static int __init xxx_init(void)
{
/* 入口函数具体内容 */
 return 0;
}

/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
/* 出口函数具体内容 */
}

/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);
module_exit(xxx_exit);

linux源码里面很多案例可以去看看.里面有一些函数看不懂没关系,后面会有博客会讲,也会有博客进行总结常见api使用。

linux驱动开发(2)开发流程_第2张图片

 3.字符设备注册与注销

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 :要注销的设备对应的设备名。

3.1.设备号

为了方便管理,Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分
组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。Linux 提供了
一个名为 dev_t 的数据类型表示设备号,dev_t 定义在文件 include/linux/types.h 里面,这个其实是一个宏,在源码里面可以看到就是一个 unsigned int 类型,这 32 位的数据构成了主设备号和次设备号两部分,其中高 12 位为主设备号,低 20 位为次设备号。因此 Linux系统中主设备号范围为 0~4095,所以大家在选择主设备号的时候一定不要超过这个范围,在文件 include/linux/kdev_t.h 中提供了几个关于设备号的操作函数(本质是宏),如下所示:

#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)

#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

宏 MINORBITS 表示次设备号位数,一共是 20 位。
宏 MINORMASK 表示次设备号掩码。
宏 MAJOR 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。
宏 MINOR 用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。
宏 MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号

后面三个函数我们比较常用。

3.1.1 、静态分配设备号

注册字符设备的时候需要给设备指定一个设备号,这个设备号可以是驱动开发者静态的指定一个
设备号,比如选择 200 这个主设备号。有一些常用的设备号已经被 Linux 内核开发者给分配掉
了,具体分配的内容可以查看文档 Documentation/devices.txt。并不是说内核开发者已经分配掉
的主设备号我们就不能用了,具体能不能用还得看我们的硬件平台运行过程中有没有使用这个
主设备号,使用“cat /proc/devices”命令即可查看当前系统中所有已经使用了的设备号。

3.1.2动态分配设备号

静态分配设备号需要我们检查当前系统中所有被使用了的设备号,然后挑选一个没有使用
的。而且静态分配设备号很容易带来冲突问题,Linux 社区推荐使用动态分配设备号,在注册字
符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。
卸载驱动的时候释放掉这个设备号即可,设备号的申请函数如下:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

dev:保存申请到的设备号。
baseminor :次设备号起始地址,alloc_chrdev_region 可以申请一段连续的多个设备号,这
些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递
增。一般 baseminor 为 0,也就是说次设备号从 0 开始。
count :要申请的设备号数量。
name:设备名字。

 注销字符设备之后要释放掉设备号,设备号释放函数如下:

void unregister_chrdev_region(dev_t from, unsigned count)

 from:要释放的设备号。
count :表示从 from 开始,要释放的设备号数量。

4.添加 LICENSE 和作者信息

我们需要在驱动中加入 LICENSE 信息和作者信息,其中 LICENSE 是必须添加的,否
则的话编译的时候会报错,作者信息可以添加也可以不添加。LICENSE 和作者信息的添加使用
如下两个函数:

MODULE_LICENSE() //添加模块 LICENSE 信息
MODULE_AUTHOR() //添加模块作者信息

 如:

MODULE_LICENSE("GPL");  //LICENSE 采用 GPL 协议。
63 MODULE_AUTHOR("your name"); //作者名字

5.物理内存和虚拟内存之间的转换

ioremap 和 iounmap。

5.1 、ioremap 函数

ioremap 函 数 用 于 获 取 指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间 , 定 义 在
arch/arm/include/asm/io.h 文件中

#define ioremap(cookie,size) __arm_ioremap((cookie), (size),
MT_DEVICE)

void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size,
unsigned int mtype)
{
     return arch_ioremap_caller(phys_addr, size, mtype,__builtin_return_address(0));
}

ioremap 是个宏,有两个参数:cookie 和 size,真正起作用的是函数__arm_ioremap,此函
数有三个参数和一个返回值,这些参数和返回值的含义如下:
phys_addr:要映射的物理起始地址。
size:要映射的内存空间大小。
mtype:ioremap 的类型,可以选择 MT_DEVICE、MT_DEVICE_NONSHARED、
MT_DEVICE_CACHED 和 MT_DEVICE_WC,ioremap 函数选择 MT_DEVICE。
返回值:__iomem 类型的指针,指向映射后的虚拟空间首地址。

5.2 、iounmap 函数

卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射,iounmap 函数原
型如下:

void iounmap (volatile void __iomem *addr)

iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址。这里虚拟地址空间首地址其实就是ioremap宏返回值。

到目前为止模板里面的函数介绍差不多了,该介绍 file_operations 的结构体里面函数的实现了,这些函数介绍会先通过一些案例,进行介绍,到最后会汇总到一起,方便大家使用查看。

你可能感兴趣的:(嵌入式开发,linux驱动开发,驱动开发)