一、普通字符设备驱动设计流程
------------------------定义一个普通字符设备---------------------------
1)定义一个普通字符设备
2)定义普通字符设备所对应的文件操作集
3)给普通字符设备申请一个设备号
4)初始化普通字符设备
5)将普通字符设备加入到内核中
--------------------------给定一个设备文件,给应用程序提供访问---------
6)创建一个class
7) 创建一个device,并且该device要属于class ------》指定一个设备文件名,比如:led_dev/gec6818led
------------------------申请物理内存,并得到物理地址对应的虚拟地址---------
8)申请物理内存区
9)完成io动态映射—>得到物理地址与虚拟地址之间关系
10)在驱动程序中,通过虚拟地址来访问硬件
二、分析
1、定义一个普通字符设备
一个驱动程序是可以由内核模块来体现出来,所以在驱动设计时,它的载体就是一个内核模块
头文件:
kernel\include\linux\cdev.h
1 struct cdev { 2 struct kobject kobj; 3 struct module *owner; 4 const struct file_operations *ops; 5 struct list_head list; 6 dev_t dev; 7 unsigned int count; 8 };
每一个字符设备都可以 看作是一个struct cdev 变量,比如:
struct cdev gec6818led_cdev;
对该结构体关键成员进行说明:
1)struct kobject kobj; —> 内核对象管理者,使用object,该成员由内核自己来实现
2)struct module *owner; ----> 内核模块对象,说明当前驱动程序是属于哪一个内核模块,固定写法:THIS_MODULE
3) const struct file_operations *ops;----->文件操作集,字符设备都一个对应的文件操作集接口
struct file_operations gec6818led_fops;
gec6818led_cdev.ops = &gec6818led_fops;---->给一个字符设备设置一个文件操作
4)struct list_head list; —>管理字符设备的链表,由内核自己实现
5)dev_t dev; ---->设备号---->本质上是一个无 符号整型数,由程序员完成
6)unsigned int count; ----->一个字符设备的次设备号的数目 ,程序员完成
2、定义普通字符设备所对应的文件操作集
kernel\include\linux\fs.h
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 (*readdir) (struct file *, void *, filldir_t); 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 int (*flush) (struct file *, fl_owner_t id); 15 int (*release) (struct inode *, struct file *); 16 int (*fsync) (struct file *, loff_t, loff_t, int datasync); 17 int (*aio_fsync) (struct kiocb *, int datasync); 18 int (*fasync) (int, struct file *, int); 19 int (*lock) (struct file *, int, struct file_lock *); 20 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); 21 unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned l ong); 22 int (*check_flags)(int); 23 int (*flock) (struct file *, int, struct file_lock *); 24 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); 25 ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); 26 int (*setlease)(struct file *, long, struct file_lock **); 27 long (*fallocate)(struct file *file, int mode, loff_t offset, 28 loff_t len); 29 };
比如:
//定义的接口,要根据应用程序中,接口对应
int gec6818led_open (struct inode *node, struct file *file) { return 0; } static struct file_operations gec6818led_fops = { .owner = THIS_MODULE, .open = gec6818led_open, };
3、给普通字符设备申请一个设备号
设备号: dev_t dev
设备号是由主设备和次设备组合而成
设备号 = 主设备<<20,再与次设备号进行位运算得到设备号
3.1 产一个设备号 —>用宏函数实现
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
1)由主设备号和次设备号得到设备号
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
dev_t gec6818led_dev = MKDEV(major,minor);
2)由设备号得到主设备号和次设备号
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) —>
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
static unsigned int major = MAJOR(gec6818led_dev);
static unsigned int minor = MINOR(gec6818led_dev);
3.2 将设备号注册到内核中
1)静态注册
1 /** 2 * register_chrdev_region() ‐ register a range of device numbers 3 * @from: the first in the desired range of device numbers; must include 4 * the major number. 5 * @count: the number of consecutive device numbers required 6 * @name: the name of the device or driver. 7 * 8 * Return value is zero on success, a negative error code on failure. 9 */ 10 int register_chrdev_region(dev_t from, unsigned count, const char *name)
功能:向内核中注册一个设备号,由程序员提供一个设备号,向内核进行申请,并不一定成功
参数说明:
参数一:dev_t from —>要向内核申请的设备号,做为一个传入参数
参数二: unsigned count —>连续编号的次设备号数目
参数三:const char *name —> 设备的名字
返回值:
成功:0
失败:负数
2)动态注册
1 /** 2 * alloc_chrdev_region() ‐ register a range of char device numbers 3 * @dev: output parameter for first assigned number 4 * @baseminor: first of the requested range of minor numbers 5 * @count: the number of minor numbers required 6 * @name: the name of the associated device or driver 7 * 8 * Allocates a range of char device numbers. The major number will be 9 * chosen dynamically, and returned (along with the first minor number) 10 * in @dev. Returns zero or a negative error code. 11 */ 12 int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, 13 const char *name)
功能:由内核动态分配一个设备号
参数说明:
参数一:dev_t *dev ---->内核动态分配的设备号,----输出参数
参数二:unsigned baseminor ---->第一个次设备号
参数三: unsigned count —>连续编号的次设备号数目
参数四:const char *name —> 设备的名字
返回值:
成功:0
失败:负数
备注:对于内核的资源中,当申请的设备号使用完时,要及时还回给系统,所以资源申请和释放在内核中要保持成对 使用
3)释放设备号
1 /** 2 * unregister_chrdev_region() ‐ return a range of device numbers 3 * @from: the first in the range of numbers to unregister 4 * @count: the number of device numbers to unregister 5 * 6 * This function will unregister a range of @count device numbers, 7 * starting with @from. The caller should normally be the one who 8 * allocated those numbers in the first place... 9 */ 10 void unregister_chrdev_region(dev_t from, unsigned count)
功能:将设备号返回给内核
参数说明:
参数一:dev_t from----要释放的设备号
参数二:unsigned count ---- 要释放次设备号的数目
对于程序设计而言,使用一个兼容的方式,可以提供静态或者动态注册
/* * Register a single major with a specified minor range. * * If major == 0 this functions will dynamically allocate a major and return * its number. * * If major > 0 this function will attempt to reserve the passed range of * minors and will return zero on success. * * Returns a -ve errno on failure. */
主设备号为0 ------》则说明为动态注册,否则,就是静态注册
4、 初始化普通字符设备
函数原型:
1 void cdev_init(struct cdev *, const struct file_operations *);
参数说明:
参数一:struct cdev * ----> 要初始化的字符设备
参数二: const struct file_operations * ----->要初始化字符设备的文件操作集
5、 将字符设备加入到内核中
5.1 增加字符设备到内核中
函数原型:
1 /** 2 * cdev_add() ‐ add a char device to the system 3 * @p: the cdev structure for the device 4 * @dev: the first device number for which this device is responsible 5 * @count: the number of consecutive minor numbers corresponding to this 6 * device 7 * 8 * cdev_add() adds the device represented by @p to the system, making it 9 * live immediately. A negative error code is returned on failure. 10 */ 11 int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数说明:
参数一:struct cdev *p ---->要增加的字符设备
参数二:dev_t dev ----->字符设备的设备号
参数三:unsigned count ----> 次设备号的数目
返回值:
成功:0
失败:负数
5.2 从内核中删除字符设备
函数原型
1 /** 2 * cdev_del() ‐ remove a cdev from the system 3 * @p: the cdev structure to be removed 4 * 5 * cdev_del() removes @p from the system, possibly freeing the structure 6 * itself. 7 */ 8 void cdev_del(struct cdev *p)
参数说明:
参数一:struct cdev *p ---->要删除的字符设备
6、 创建一个class
头文件:kernel\include\linux\device.h
#include <linux/device.h>
6.1 创建class
函数原型:
1 /* This is a #define to keep the compiler from merging different 2 * instances of the __key variable */ 3 #define class_create(owner, name) \ 4 ({ \ 5 static struct lock_class_key __key; \ 6 __class_create(owner, name, &__key); \ 7 }) 8 owner, name参数的类型要根据__class_create,除此之外,__class_create函数的返回值 9 也是class_create函数返回值 10 class_create函数原型如下: 11 /** 12 * class_create ‐ create a struct class structure 13 * @owner: pointer to the module that is to "own" this struct class 14 * @name: pointer to a string for the name of this class. 15 * @key: the lock_class_key for this class; used by mutex lock debugging 16 * 17 * This is used to create a struct class pointer that can then be used 18 * in calls to device_create(). 19 * 20 * Returns &struct class pointer on success, or ERR_PTR() on error. 21 * 22 * Note, the pointer created here is to be destroyed when finished by 23 * making a call to class_destroy(). 24 */ 25 struct class *__class_create(struct module *owner, const char *name, 26 struct lock_class_key *key)
综上所述,最张得到可使用的接口原型如下:
1 struct class * class_create(struct module *owner, const char *name)
功能:创建一个class
参数说明:
参数一:struct module *owner ----> 说明创建的class是属于哪一个模块---->固定写法:THIS_MODULE
参数二: const char *name ----> class名字 ---->在开发板系统中可以查看
返回值:
成功:struct class *
失败:NULL
6.2 销毁class
函数原型:
1 /** 2 * class_destroy ‐ destroys a struct class structure 3 * @cls: pointer to the struct class that is to be destroyed 4 * 5 * Note, the pointer to be destroyed must have been created with a call 6 * to class_create(). 7 */ 8 void class_destroy(struct class *cls)
参数说明:
struct class *cls ---->要销毁的class
7、创建device
----这一步的目的是为了得到一个设备文件的名字,在开发板上的/dev目录下可以查看
7.1 创建一个device
函数原型:
1 /** 2 * device_create ‐ creates a device and registers it with sysfs 3 * @class: pointer to the struct class that this device should be registered to 4 * @parent: pointer to the parent struct device of this new device, if any 5 * @devt: the dev_t for the char device to be added 6 * @drvdata: the data to be added to the device for callbacks 7 * @fmt: string for the device's name 8 * 9 * This function can be used by char device classes. A struct device 10 * will be created in sysfs, registered to the specified class. 11 * 12 * A "dev" file will be created, showing the dev_t for the device, if 13 * the dev_t is not 0,0. 14 * If a pointer to a parent struct device is passed in, the newly created 15 * struct device will be a child of that device in sysfs. 16 * The pointer to the struct device will be returned from the call. 17 * Any further sysfs files that might be required can be created using this 18 * pointer. 19 * 20 * Returns &struct device pointer on success, or ERR_PTR() on error. 21 * 22 * Note: the struct class passed to this function must have previously 23 * been created with a call to class_create(). 24 */ 25 struct device *device_create(struct class *class, struct device *parent, 26 dev_t devt, void *drvdata, const char *fmt, ...)
功能:创建一个device,并且注册到系统的文件系统中
参数说明:
参数一:struct class *class ---->说明device是属于哪 一个class的,class_create的函数返回值
参数二:struct device *parent —>当前创建设备的父设备,一般设置NULL
参数三:dev_t devt —> 设备的设备号,查看设备文件时,查看设备号
参数四:void *drvdata —> 创建device时,系统回调的数据,不需时,也使用NULL
参数五: const char *fmt ----> 设备文件的名字,可以在/dev目录下查看
返回值:
成功:struct device *
失败:NULL
7.2 销毁device
函数原型:
1 /** 2 * device_destroy ‐ removes a device that was created with device_create() 3 * @class: pointer to the struct class that this device was registered with 4 * @devt: the dev_t of the device that was previously registered 5 * 6 * This call unregisters and cleans up a device that was created with a 7 * call to device_create(). 8 */ 9 void device_destroy(struct class *class, dev_t devt)
参数说明:
参数一:struct class *class -----device的class
参数二: dev_t devt ---- 设备的设备号
8、 申请物理内存区
头文件:#include <linux/ioport.h>
8.1 申请物理内存区
宏原型如下:
1 #define request_mem_region(start,n,name) 2 __request_region(&iomem_resource, (start), (n), (name), 0) 3 4 start,n,name 三个参数的类型源于__request_region函数 5 /** 6 * __request_region ‐ create a new busy resource region 7 * @parent: parent resource descriptor 8 * @start: resource start address 9 * @n: resource region size 10 * @name: reserving caller's ID string 11 * @flags: IO resource flags 12 */ 13 struct resource * __request_region(struct resource *parent, 14 resource_size_t start, resource_size_t n, 15 const char *name, int flags)
综上所述,可得request_mem_region的原型:
1 struct resource *request_mem_region(resource_size_t start, resource_size_t n, 2 const char *name)
参数说明:
参数一:resource_size_t -----> 要申请的物理内存的首地址
#ifdef CONFIG_PHYS_ADDR_T_64BIT
typedef u64 phys_addr_t;
#else
typedef u32 phys_addr_t;
#endif
typedef phys_addr_t resource_size_t;
参数二:resource_size_t n —> 要申请物理内存区的大小
参数三: const char *name ---->申请成功后,该物理内存区的名字,在开发板的/proc/iomem查看
返回值:
成功:struct resource *
失败:NULL
8.2 释放物理内存区
宏的原型:
1 #define release_mem_region(start,n) 2 __release_region(&iomem_resource, (start), (n)) 3 /** 4 * __release_region ‐ release a previously reserved resource region 5 * @parent: parent resource descriptor 6 * @start: resource start address 7 * @n: resource region size 8 * 9 * The described resource region must match a currently busy region. 10 */ 11 void __release_region(struct resource *parent, resource_size_t start, 12 resource_size_t n)
综上所述,宏函数的原型如下:
1 void release_mem_region(resource_size_t start, 2 resource_size_t n)
参数说明:
参数一:resource_size_t -----> 要申请的物理内存的首地址
参数二:resource_size_t n —> 要申请物理内存区的大小
9、IO映射 ----- 目的是为了建立物理地址与虚拟地址的映射关系
头文件:#include <linux/io.h>
9.1 IO动态映射
1 #define ioremap(cookie,size) 2 __arm_ioremap((cookie), (size), MT_DEVICE) 3 cookie,size参数的类型源自于函数__arm_ioremap 4 void __iomem *__arm_ioremap(unsigned long phys_addr, size_t size, 5 unsigned int mtype)
综上所述,宏函数的原型如下:
1 void __iomem *ioremap(unsigned long phys_addr, size_t size)
参数说明:
参数一:unsigned long phys_addr ---->要进行映射的物理首地址(芯片手册)
参数二:size_t size ----> 要映射地址的大小
返回值:
成功:void __iomem * ----虚拟地址的首地址
失败:NULL
9.2 解映射
1 #define iounmap __arm_iounmap 2 void __arm_iounmap(volatile void __iomem *addr)
三、驱动代码
#include <linux/module.h> //内核模块接口头文件 #include <linux/printk.h> //printk函数头文件 #include <linux/cdev.h> // struct cdev #include <linux/fs.h> //struct file_operations #include <linux/device.h> //struct class #include <linux/err.h> //#define ENOMEM 12 #include <linux/io.h> //ioremap #include <linux/ioport.h> //request_mem_region #include <linux/uaccess.h> //copy_from_user static dev_t gec6818led_dev;//定义一个设备号 static unsigned int major = 0; //主设备号表示该设备是哪一类设备 static unsigned int minor = 0; //次设备号表示这类设备中哪一个设备 static struct class *gec6818led_class = NULL; static struct device *gec6818led_device = NULL; static struct resource *gec6818led_res = NULL; //虚拟地址: static void __iomem *gec6818led_va_base = NULL; //GPIOEOUT ----0xC001E000 static void __iomem *gec6818led_va_eout = NULL; //GPIOEOUTENB --- 0xC001E004 static void __iomem *gec6818led_va_eoutenb = NULL; //GPIOEALTFN0 ---- 0xC001E020 static void __iomem *gec6818led_va_ealtfn0 = NULL; int gec6818led_open (struct inode *node, struct file *file) { return 0; } /* 驱动程序与应用程序之间的数据传输: 问题一: 数据协议:系统调用(open write ) ----> struct file_operations --- write 问题二: 数据格式:控灯:一个灯 定义一个数组:char kbuf[1] --- kbuf[0] --->表示控制灯的命令:1 ---开灯 0 --- 关灯 多个灯 定义一个数组:char kbuf[2] ---- kbuf[0] --->表示控制灯的命令,kbuf[1] --- 表示灯的编号 驱动程序和应用程序之间的数据协议+数据格式要保持 一致 对于驱动程序使用的内核空间,而应用程序使用的是用户空间,两个空间之间不能直接相互访问,只能通过特定的接口: copy_to_user copy_from_user */ ssize_t gec6818led_write(struct file *file, const char __user *buf, size_t size, loff_t *offset) { printk(KERN_WARNING "gec6818led_write\n"); //[1]定义数据格式 char kbuf[1]; int ret; //int copy_from_user(void *to, const void __user *from, int n) ret = copy_from_user(kbuf,buf,size); //从用户空间把数据获取出来存放在kbuf中 //[2] 根据数据的命令来实现控制硬件 if(kbuf[0] == 1) //开灯 { *((volatile unsigned int*)gec6818led_va_eout) &= ~(1<<13); }else if(kbuf[0] == 0)//关灯 { *((volatile unsigned int*)gec6818led_va_eout) |= (1<<13); } return size; } //2.定义一个普通字符设备的文件操作集对象 static struct file_operations gec6818led_fops = { .owner = THIS_MODULE, .open = gec6818led_open, .write = gec6818led_write, }; //1.定义一个普通字符设备对象 static struct cdev gec6818led_cdev; static int __init gec6818led_init(void) { int ret; printk(KERN_WARNING "gec6818led_init\n"); //3.申请一个设备号,设备号就相当于一个人的身份证号码一样,在Linux它是唯一,设备号还要注册到linux内核中, //如果内核中没有,则说明设备号可用,否则,不行 if(major == 0)//动态注册 { //int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, //const char *name) ret = alloc_chrdev_region(&gec6818led_dev,minor,1,"gec6818led_number"); }else //静态注册 { //先由主设备号和次设备号生成一个设备号 gec6818led_dev = MKDEV(major,minor); //再将设备号注册到内核中 int register_chrdev_region(dev_t from, unsigned count, const char *name) ret = register_chrdev_region(gec6818led_dev,1,"gec6818led_number"); } if(ret != 0) { printk(KERN_WARNING "register_device_number error\n"); goto register_device_number_error; } //4.初始化普通字符设备 void cdev_init(struct cdev *, const struct file_operations *); cdev_init(&gec6818led_cdev,&gec6818led_fops); //在初始化函数中,对普通字符设备与文件操作集进行了关联 //5. 增加字符设备到内核中 int cdev_add(struct cdev *p, dev_t dev, unsigned count) ret = cdev_add(&gec6818led_cdev,gec6818led_dev,1); if(ret != 0) { printk(KERN_WARNING "cdev_add error\n"); goto cdev_add_error; } //6.创建一个class struct class * class_create(struct module *owner, const char *name) gec6818led_class = class_create(THIS_MODULE,"gec6818led_class"); if(gec6818led_class == NULL) { printk(KERN_WARNING "class_create error\n"); //如果函数返回的值不是整型,但是,总函数中的返回值为整型 ,所以,要重新给ret设置一个返回值 ret = -ENOMEM; // #define ENOMEM 12 /* Out of memory */ 这个宏常量的头文件引用进来 goto class_create_error; } //7.创建device struct device *device_create(struct class *class, struct device *parent, // dev_t devt, void *drvdata, const char *fmt, ...) gec6818led_device = device_create(gec6818led_class,NULL,gec6818led_dev,NULL,"gz1or2_led_drv"); if(gec6818led_device == NULL) { printk(KERN_WARNING "device_create error\n"); ret = -ENOMEM; goto device_create_error; } //8.申请物理内存区 //struct resource *request_mem_region(resource_size_t start, resource_size_t n, // const char *name) //该函数的参数的传递,要根据具体的硬件的地址来确定,以D7 --- GPIOE13 0XC001E000 //申请物理内存区的大小,最好跟GPIO口分组:A组的大小:0XC001A000 B:0xc001b000 ---> B-A --0x1000--4kb gec6818led_res = request_mem_region(0XC001E000,0x1000,"GPIOE_MEM"); if(gec6818led_res == NULL) { printk(KERN_WARNING "request_mem_region error\n"); ret = -EBUSY; //#define EBUSY 16 /* Device or resource busy */ goto request_mem_region_error; } //9.Io动态映射 void __iomem *ioremap(unsigned long phys_addr, size_t size) gec6818led_va_base = ioremap(0XC001E000,0x1000); if(gec6818led_va_base == NULL) { printk(KERN_WARNING "ioremap error\n"); ret = -EBUSY; //#define EBUSY 16 /* Device or resource busy */ goto ioremap_error; } gec6818led_va_eout = gec6818led_va_base + 0x00; gec6818led_va_eoutenb = gec6818led_va_base + 0x04; gec6818led_va_ealtfn0 = gec6818led_va_base + 0x20; //[1] 设置引脚的功能 *((volatile unsigned int*)gec6818led_va_ealtfn0) &= ~(3<<26); //[2]设置引脚的工作模式:输出 *((volatile unsigned int*)gec6818led_va_eoutenb) |= (1<<13); //[3] 默认设置LED灯为亮 *((volatile unsigned int*)gec6818led_va_eout) &= ~(1<<13); return 0; ioremap_error: release_mem_region(0XC001E000,0x1000); request_mem_region_error: device_destroy(gec6818led_class,gec6818led_dev); device_create_error: class_destroy(gec6818led_class); gec6818led_class = NULL; class_create_error: cdev_del(&gec6818led_cdev); cdev_add_error: unregister_chrdev_region(gec6818led_dev,1); register_device_number_error: return ret; } static void __exit gec6818led_exit(void) { printk(KERN_WARNING "gec6818led_exit\n"); //进行资源的释放 iounmap(gec6818led_va_base); gec6818led_va_base = NULL; release_mem_region(0XC001E000,0x1000); //void device_destroy(struct class *class, dev_t devt) device_destroy(gec6818led_class,gec6818led_dev); //void class_destroy(struct class *cls) class_destroy(gec6818led_class); gec6818led_class = NULL; //void cdev_del(struct cdev *p) cdev_del(&gec6818led_cdev); //void unregister_chrdev_region(dev_t from, unsigned count) unregister_chrdev_region(gec6818led_dev,1); } module_init(gec6818led_init); module_exit(gec6818led_exit); MODULE_AUTHOR("gec.zhang3");//驱动作者 MODULE_DESCRIPTION("GEC6818 Led driver");//驱动的描述 MODULE_LICENSE("GPL"); //module所遵守的协议许可,GPL---->公共通用许可证
四、应用代码
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(void) { char buf[4096]; //1.打开文件 int fd = open("/udata/rfid_test.c",O_RDWR); if(fd == -1) { perror("open"); return -1; } //2.读数据 read(fd,buf,sizeof(buf)); printf("data = %s\n",buf); //3.关闭文件 close(fd); return 0; }