通过数据的传输方式,可以大致的分为三类驱动:
字符设备驱动:字节流
快设备驱动 :数据块 硬盘块叫block 512B,flash的块叫page 4K
网络设备驱动:数据包
首先学习的是字符设备驱动,
字符设备的大体调用流程:
上层应用程序通过open,read,write等函数对/dev/xxx文件进行操作
---------/dev/xxx--------------------- 其实这个文件是通过设备号与底层驱动进行关联的
ls -l /dev/xxxx
结果为 c 10 0 表示这个设备节点关联的是字符设备,并且设备的主设备好是10,次设备号是0
调用
-------sys_call----------------------
调用
----------vfs---------------------------
通过设备号在vfs中找到对应的处理函数
设备号:
设备号是cdev_t类型的一个标号,前12位表示的是主设备号,后20位表示的是次设备号,但是现在主次设备最多支持255个。
major = MAJOR(cdev_t id); 获取主设备号
minor = MINOR(cdev_t id); 获取次设备号
cdev_t id = MKDEV(int major,int minor);根据主次设备号计算dev_id
以上是在我们知道主次设备好的情况下可以进行:
dev_t base_id;
base_id = MKDEV(major, 0);
register_chrdev_region(base_id, DEV_NUM, "char_test");将设备号进行注册,放止重复使用
如果不清楚主次设备号,可以通过系统来分配:
alloc_chrdev_region(&base_id, 0, DEV_NUM, "char_test");让内核来回填base_id
major = MAJOR(base_id);来获取主设备号
这样设备的编号就已经申请好了,然后就可以在应用层进行mknode:
mknode /dev/led_01 c 10 0
这里的主次设备号要和驱动中注册的相同,应为驱动和应用层通过这id进行匹配对应的读写函数。
**在设备模型里,可以通过netlink来发送请求给udev让应用层的udev来动态创建节点。
字符驱动核心数据结构:
struct file_operations mem_fops{
.owner = THIS_MODULE, //结构体中有这个成员的一定要赋值成THIS_MODULE,放止在模块运行时被卸载
.read = my_read, //自己些的读函数,应用层读写结点文件,调用的就是对饮节点的read函数
.write = my_write, //与上面的类似
.open = my_open, //与上面的类似
.release = my_relese, //与上面的类似
.unlocked_ioctl = mem_ioctl, //与上面的类似
};
这个结构体在注册设备的时候,cdev会有成员指向这个结构体,当用户调用open函数的时候,
内核会为这次open创建一个文件描述符,并且指向一个file结构体,这个file结构体里面也有成员指向file_operations,
这样的话,打开一次之后,再对这个描述符操作的时候就可以直接调用函数了,不用在过vfs了
总结file_operations两处使用 :
1:file_operations在注册的时候会和cdev进行帮定,方便应用层通过dev_id找到执行函数,
在建立file结构体的时候会将file结中的f_ops执行file_operations
2:当open文件后,系统产生file结构体中的成员也会指向file_operations,如果要执行read操作的话,可以直接执行
---------------------------------------------------------
struct file filp{
.f_ops = &mem_f_ops,
.private_data = 私有结构体,将来可以之间通过file进行访问
};
私有结构体里面有cdev,而inode里也有cdev这个成员,可以在open的时候,
通过inode里的cdev找到私有结构体存放在.private_data成员里。
---------------------------------------------------------
struct inode node{
.i_rdev = base_id, //这里存放设备号
.i_cdev = &cdev, //这个cdev在注册的时候是指向私有结构体里的cdev的,所以可以通过cdev找到私有数据
};
然后就是字符驱动的注册了:
首先定义一个私有结构体,存放一些私有的信息:
struct cdev {
struct kobject kobj;
struct module *owner; //THIS_MODULE
const struct file_operations *ops; //初始化的时候会把ops指向file_operations
struct list_head list;
//会把这个结构体放在一个链表上,通过dev_id进行识别,并且给然对应的file中的f_op指向这里的ops,也就是我们自己些的file_operations
dev_t dev; //设备ID
unsigned int count;
};
struct mem_priv {
char *start, *end;
int buf_len;
int wp; /* write position */
dev_t dev_id; //存放设备ID
struct cdev mem_cdev; //存放cdev
};
static struct mem_priv *devs[MEM_NUM];
struct mem_priv *dev;
dev = (struct mem_priv *)kzalloc(sizeof(*dev),
GFP_KERNEL);//分配私有结构体空间,这个结构中有cdev,也就是贯穿始终的cdev
dev->dev_id = MKDEV(major, i+10);//设置devid
cdev_init(&dev->mem_cdev, &mem_fops); //将mem_cdev->ops 指向自己的file_operations
dev->mem_cdev.owner = THIS_MODULE;
cdev_add(&dev->mem_cdev, dev->dev_id, 1); //将设备id赋值给mem_cdev->dev_id,并且添加到链表上
*******到此为止,字符驱动驱动的注册搞定了**********
//完成open函数,
int mem_open(struct inode *inode, struct file *filp)
{
struct mem_priv *tmp = container_of(inode->i_cdev, struct mem_priv, mem_cdev);
//因为inode->i_cdev指向的就是私有结构体的mem_cdev,所以通过container_of找到对应的私有结构体
filp->private_data = tmp; //将这个私有结构存放到file的private_data中,以后在read,write的时候就可以直接使用了
}
ssize_t mem_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct mem_priv *dev = filp->private_data; //获取到私有数据
……………………
}
在用户态和内核态之间数据拷贝使用的是:
copy_to_user() copy_from_user(),如果参数表记为__user则说明是用户态的内存
内核态分配空间使用kzalloc(size,gfp_t )
把硬件寄存器映射成虚拟地址:ioremap(),iounmap()
GFP_KERNEL可能休眠,分配物理连续内存GFP_ATOMIC不会休眠可能会失败,使用紧急内存
思路整理如下:
*****************************************************************************
1:在私有结构体中存放 struct cdev结构体是一个技巧,用于之后找到私有结构体
2:私有结构体里的cdev会和file_operactions和dev_id结合在一起,并且把这个dev放在一个链表上
形成如下结构:
private_struct1 private_struct2
head------- cdev -----链表---> cdev
因为cdev中有id所以很轻松可以找到对应节点的cdev
3:当要open一个设备节点,返回一个fd的时候,会有两个结构体被创建,一个是file,一个是inode
file结构体中的f_op会指向cdev中的ops,这样对这个fd进行读写操作的时候就可以直接使用了,不用再通过VFS了
在执行open函数的时候,函数会传入inode和file两个结构体,inode结构体里有cdev结构,通过这个结构可以找到私有结构体
然后将filp->private_data指向私有解体,这样file结构中就有了操作行为fops和数据privatedata,为之后的read和write等操作作准备
最后就是ioctl,
.unlocked_ioctl = mem_ioctl, //应用层调用ioctl,对应于驱动的.unlocked_ioctl
ioctl号:
2 | 14 | 8 | 8 |
| | | |->ioctl号,1、2、3、4
| | |->类型,#define MEM_TYPE 'Z'
| |->参数大小(一般传递结构体指针)
|->对于内核的写入或者写出方向,内核中说的读写都是从用户态看的
#define MEM_TYPE 'Z'
#define MEM_RESET _IO(MEM_TYPE, 1) //ioctl号1没有参数
#define MEM_RESIZE _IOW(MEM_TYPE, 2, int) //ioctl号2带int类型的参数,从用户态到内核态写
#define MEM_RESIZE _IOR(MEM_TYPE, 3, int) //ioctl号3带参数int类型的参数,从用户态读取内核态的数据
long mem_ioctl(struct file *filp, unsigned int req, unsigned long arg)
{
req为ioctl请求号
_IOC_TYPE(req) //从请求号中获取请求类型
_IOC_NR(req) //获取请求号,进行switch case操作
}
用户程序:
#define MEM_TYPE 'Z'
#define MEM_RESET _IO(MEM_TYPE, 1)
#define MEM_RESIZE _IOW(MEM_TYPE, 2, int)
fd = open("/dev/abc0", O_RDWR);
ret = ioctl(fd, MEM_RESIZE, 0x10000);这样使用就好了
*******对寄存器的操作*******
#define GPIO_BASE 0x7F008800
#define GPIO_SIZE 0x1000
#define GPMCON 0x20
#define GPMDAT 0x24
static void __iomem *vir_base; //__iomem表示访问内存
vir_base = ioremap(GPIO_BASE, GPIO_SIZE); //将物理地址映射称虚拟地址
if (!vir_base) {
printk("Cannot ioremap\n");
return -EIO;
}
/* 8位寄存器 */
char value;
value = readb(vir_base + offset);
writeb(value, (vir_base+offset));
/* 16位寄存器 */
short value;
value = readw(vir_base+offset);
writew(value, (vir_base+offset));
/* 32位寄存器 */
int value;
value = readl(vir_base+offset);
writel(value, (vir_base+offset));
/* 64位寄存器 */
u64 value;
value = readq(vir_base+offset);
writeq(value, (vir_base+offset));
//如果不再访问寄存器,应该取消映射
iounmap(vir_base);
LED的例子.rar