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

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

(0)

相关推荐