原创 字符设备驱动——申请、创建、应用.

2021-10-20 16:56 1247 7 7 分类: MCU/ 嵌入式
、申请设备号

// 1、注册获取设备号// 2、初始化设备// 3、操作设备 file_operations – open release read write ioctl…// 4、两个宏定义 module_init module_exit // 5、注册设备号 register_chrdev_region// 6、cdev_init 初始化字符设备// 7、cdev_add 添加字符设备到系统

1)向系统申请主设备号

int register_chrdev(unsigned int major, const char * name, const struct file_operations * fops)
//参数://1、major:主设备号// 设备号(32bit–dev_t)==主设备号(高12bit) + 次设备号(低20bit)// 主设备号:表示一类设备—(如:camera)// 次设备号: 表示一类设备中某一个—(如:前置camera/后置camera)// 0 -->动态分配 ; 250 --> 给定整数,静态指定//2、name: 描述设备信息,可自定义// 在目录/proc/devices列举出了所有的已经注册的设备//3、fops: 文件操作对象// 提供open, read,write//返回值:成功-0,失败-负数


2)释放设备号
void unregister_chrdev(unsigned int major, const char * name)

3)例:主设备号的申请
chr_drv.c
加载驱动前:



加载驱动后:



2、创建设备节点
1)手动创建
··  缺点/dev/目录中文件都是在内存中,断电后/dev/文件就会消失

mknod /dev/设备名 类型 主设备号 次设备号
(主设备号要和驱动中申请的主设备号保持一致)
比如:
mknod /dev/chr0 c 250 0
eg:
[root@farsight drv_module]# ls /dev/chr0 -l
crw-r--r-- 1 0 0 250, 0 Jan 1 00:33 /dev/chr0

2)自动创建
  通过udev/mdev机制

struct class *class_create(owner, name)//创建一个类
//参数://1、owner:THIS_MODULE//2、name :字符串名字,自定义//返回:// 返回一个class指针

  创建一个设备文件:

//创建一个设备文件struct device *device_create(struct class * class, struct device * parent, dev_t devt, void * drvdata, const char * fmt,...)
//参数://1、class结构体,class_create调用之后的返回值//2、表示父亲,一般直接填NULL//3、设备号类型 dev_t//4、私有数据,一般直接填NULL//5/6、表示可变参数,字符串,表示设备节点名字
设备号类型:dev_t devt
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //获取主设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) //获取次设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //生成设备号

  销毁设备文件:

void device_destroy(devcls, MKDEV(dev_major, 0));//参数://1、class结构体,class_create调用之后到返回值//2、设备号类型 dev_t
void class_destroy(devcls);//参数:class结构体,class_create调用之后到返回值

3)示例:
chr_drv.c


3、实现文件IO接口--fops
1)驱动中实现文件io操作接口:struct file_operations

1 struct file_operations { 2 struct module *owner; 3 loff_t (*llseek) (struct file *, loff_t, int); 4 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 5 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 6 ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); 7 ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); 8 int (*iterate) (struct file *, struct dir_context *); 9 unsigned int (*poll) (struct file *, struct poll_table_struct *);10 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);11 long (*compat_ioctl) (struct file *, unsigned int, unsigned long);12 int (*mmap) (struct file *, struct vm_area_struct *);13 int (*open) (struct inode *, struct file *);14 ....16 long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len);17 int (*show_fdinfo)(struct seq_file *m, struct file *f);18 }; //函数指针的集合,其实就是接口,我们写驱动到时候需要去实现19 20 const struct file_operations my_fops = {21 .open = chr_drv_open,22 .read = chr_drv_read,23 .write = chr_drv_write,24 .release = chr_drv_close,25 };

示例:
chr_drv1.c
实现了底层的fops成员函数,再实现应用程序的调用

2)应用程序调用文件IO控制驱动 :open、read...
chr_drv1.c
chr_test.c
Makefile
 测试结果;



4、应用程序控制驱动
应用程序要控制驱动,就涉及用户空间与内核空间的数据交互,如何实现?通过以下函数:
1)copy_to_user

1 //将数据从内核空间拷贝到用户空间,一般是在驱动中chr_drv_read()用2 int copy_to_user(void __user * to, const void * from, unsigned long n)3 //参数:4 //1:应用驱动中的一个buffer5 //2:内核空间到一个buffer6 //3:个数7 //返回值:大于0,表示出错,剩下多少个没有拷贝成功等于0,表示正确

2)copy_from_user
1 //将数据从用户空间拷贝到内核空间,一般是在驱动中chr_drv_write()用2 int copy_from_user(void * to, const void __user * from, unsigned long n)3 //参数:4 //1:内核驱动中的一个buffer5 //2:应用空间到一个buffer6 //3:个数
示例:
chr_drv1.c
chr_test.c
测试:



5、驱动程序控制外设
  之前我们了解了应用程序如何与内核空间进行数据交互,那么内核驱动与外设间的控制是怎么样的?
  写过裸机程序的都知道,可以通过修改外设对应的控制寄存器来控制外设,即向寄存器的地址写入数据,这个地址就是物理地址,且物理地址是已知的,有硬件设计决定。
  在内核中,同样也是操作地址控制外设,但是内核中的地址,是经过MMU映射后的虚拟地址,而且CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内,然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源。Linux在io.h头文件中声明了函数ioremap(),用来将I/O内存资源的物理地址映射到核心虚地址空间中

ioremap的函数如下:
1 //映射虚拟地址2 void *ioremap(cookie, size)3 //参数:4 //1、cookie:物理地址5 //2、size:长度(连续映射一定长度的地址空间)6 //返回值:虚拟地址
  解除映射:
1 //去映射--解除映射2 void iounmap(void __iomem *addr)3 //参数:映射后的虚拟地址
  实例:通过驱动控制LED灯
LED —— GPX2_7 —— GPX2CON —— 0x11000C40
GPX2DAT—— 0x11000C44

将0x11000c40映射为虚拟地址

chr_drv1.c
chr_test.c
测试:


执行app后,可以看到LED等以一秒的间隔亮灭

作者: 安信实验室, 来源:面包板社区

链接: https://mbb.eet-china.com/blog/uid-me-3909376.html

版权声明:本文为博主原创,未经本人允许,禁止转载!

文章评论0条评论)

登录后参与讨论
我要评论
0
7
关闭 站长推荐上一条 /2 下一条