字符设备驱动——申请、创建、应用步骤

今日头条

1100人已加入

描述

  1、申请设备号

  // 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等以一秒的间隔亮灭

        ymf

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分