本文共 3217 字,大约阅读时间需要 10 分钟。
话说,从事嵌入式或者相关开发的,最难搞的就是驱动了,其实说实在的,做任何的事情都不是那么的简单,你得到的回报大都是小于等于你相应的能力的,个别幸运的就不说了,所以只有学会大都都不会的,你才能脱颖而出,获得更大的价值。
仔细的想下,很多人很是迷茫的做着自己的事情,甚至干了很长的时间,也没有真正的认清楚自己到底为什么这样做,以及这样做的目的,何为驱动,简单的概括,就是为了给上层提供一个统一的接口,我们都知道在上层有很多的函数,都是通用的,你可以随便的使用,但是函数名字都是一样的,这是因为如果不这样做的话,估计全世界都乱套了,这是这个行业的规范(GPL),那么针对不同的开发平台,虽然上面是一样的,但是下面肯定是不一样的,这就是我们驱动工程师的事情了,我们要为上层服务。
所以对驱动工程师的要求一般很多,既要了解整个平台的架构,又要理解底层的相关硬件的原理,上层的话,你要知道整个应用层的具体实现,这样拿到高的回报也就理所当然啦。
最近一直涉及驱动方面的学习还谈不上研究,在我看来,相对于应用层的千变万化来说,驱动层很少变化,很多都是相似的开发套路,下面结合最近所学,介绍几个经常使用到的函数,以及关于驱动的相关知识,希望对正在从事本行业的友人,提供点滴参考,当然,如果哪里写的不是很恰当,欢迎批评指出。
1. 初始化设备cdev结构体函数
void cdev_init(struct cdev * cdev,const struct file_operations * fops)
函数功能: 初始化cdev 结构体
参数:
cdev : cdev 结构体
fops : 操作函数的结构体
2. 申请设备号的函数
Int register_chrdev_region(dev_t from ,unsigned count ,const char * name)
功能: 申请设备的设备号
参数:
from : 包含主设备号的数字
count : 设备号的个数
name : 设备号的名字,注意,我们可以在/proc/devices中看到我们所设置的名字
成功返回 0 ,失败返回负的错误码
3. 添加字符设备函数
int cdev_add(struct cdev * p,dev_t dev,unsigned count);
参数:
p : cdev结构体
dev : 第一个设备号
count : 次设备号的个数
成功返回0 ,失败返回负的错误码
4. 动态注册设备号的函数
int alloc_chrdev_region(dev_t * dev , unsigned baseminor ,unsigned count,const char * name)
参数:
dev : 获得的设备号
baseminor : 第一个次设备号
count : 次设备号的个数
name : 设备号的名字
成功返回0 ,失败返回负的错误码
5. 通过主 次设备生成设备号的宏
MKDEV( major, minor)
参数:
major:主设备号
minor:次设备号
6. 通过设备号,获得主设备号,或次设备号
获得主设备号的宏:MAJOR(dev_num) 获得设备号的前12位
获得此设备号的宏:MINOR (dev_num) 获得设备号的后20位
7.如何在添加驱动的时候自动生成设备节点
1》创建类 在/sys/class目录下创建一个子目录
struct class * class_create(struct module *owner ,char *name)
参数:
owner : 这个是固定死了的,THIS_MODULE
name: 子目录的名字
成功返回有效的指针,失败返回负的错误码
2》 在sysfs文件中注册设备(导出主设备号和次设备号)
struct device * device_create(struct class *class ,struct device *parent, dev_t devt,void *drvdata, const char *fmt,...)
参数:
class: 类结构体的首地址
parent: NULL
devt : 设备号
drvdata:NULL
fmt: 格式化字符串 "mycdev" or "mycdev_%d",i
成功返回有效指针,失败返回负的错误码
8.那么,我们如何判断是有效的指针还是负的错误码呢?
提供了一个宏:
IS_ERR(指针) PRT_ERR(指针)
返回值:若是负的错误码返回真 返回负的错误码
9. 用户空间和驱动程序数据之间的交互
long copy_to_user(void __user *to,const void *from,unsigned long n)
函数功能: 将内和空间的数据拷贝到用户空间,这里边需要注意的是,__user只是一个空的宏定义,为的就是标记用户空间
参数:
to :用户空间的地址
from: 内核空间的地址
n : 大小
成功返回0 , 失败返回未拷贝的数据大小
10. long copy_from_user(void *to ,const void __user from,unsigned long n)
参数 :
to : 内核空间的地址
from :用户空间的地址
n: 大小
成功返回0 ,失败返回未拷贝的数据的大小
11 .单个数据拷贝
宏: put_user(value , ptr)
功能:将value写到ptr指向的地址(用户)
宏: get_user(value,ptr)
功能: 将ptr指向的地址(用户)内容读到value
12.可以通过一些命令来控制的函数ioctl
这里面需要注意的,就是内核版本的不同,所使用的函数的名字是不同的
在2.6内核以及之前使用的是:
int (*ioctl )(struct inode *inode ,struct file* file,unsigned int cmd,unsigned long arg);
而在2.6版本之后使用的是:
long (*unlocked_ioctl)(struct file * file,unsigned int cmd,unsigned long arg);
参数:
cmd:传递的命令,通常就是一些宏定义之类的
arg :传递的参数(包括数值或地址)如果是地址值的话,这个地址是用户空间的,可以用put_user,or get_user来操作
13.另外,我们如果要对哪个GPIO口来进行操作的话,我们还要去查我们的用户手册,但是从手册上面我们得到的地址是实际的物理地址,而我们的操作系统使用的是虚拟的地址,那么这个时候,我们就需要将实际的物理地址做一个映射,映射为虚拟地址。
void *ioremap(unsigned long phy_addr,int size)
功能:将物理地址与虚拟地址进行映射
参数:
phy_addr: 物理地址
size : 映射的大小
14. 有些时候我们需要从一个很长的地址中取出那些我们需要的位,这个时候,就需要使用下面的这两个函数了
1》static inline u32 readl(const volatile void _iomem * addr)
{ return *(const valatile u32 __force *) addr; }
函数功能:从一个地址中读取四个字节
2》static inline void writel(u32 b, volatile void _iomem * addr)
{ (volatile u32 _force *)addr = b; }
函数功能:向一个地址中写四个字节
未完待续... ...
转载地址:http://ebjws.baihongyu.com/