register_chrdev_region/alloc_chrdev_region和cdev注册字符设备驱动
Posted 铅笔
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了register_chrdev_region/alloc_chrdev_region和cdev注册字符设备驱动相关的知识,希望对你有一定的参考价值。
内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。
(1)register_chrdev 比较老的内核注册的形式 早期的驱动
(2)register_chrdev_region/alloc_chrdev_region + cdev 新的驱动形式
区别:register_chrdev()函数是老版本里面的设备号注册函数,可以实现静态和动态注册两种方法,主要是通过给定的主设备号是否为0来进行区别,为0的时候为动态注册。register_chrdev_region以及alloc_chrdev_region就是将上述函数的静态和动态注册设备号进行了拆分的强化。
register_chrdev_region(dev_t first,unsigned int count,char *name)
First :要分配的设备编号范围的初始值, 这组连续设备号的起始设备号, 相当于register_chrdev()中主设备号
Count:连续编号范围. 是这组设备号的大小(也是次设备号的个数)
Name:编号相关联的设备名称. (/proc/devices); 本组设备的驱动名称
alloc_chrdev_region函数,来让内核自动给我们分配设备号
(1)register_chrdev_region是在事先知道要使用的主、次设备号时使用的;要先查看cat /proc/devices去查看没有使用的。
(2)更简便、更智能的方法是让内核给我们自动分配一个主设备号,使用alloc_chrdev_region就可以自动分配了。
(3)自动分配的设备号,我们必须去知道他的主次设备号,否则后面没法去mknod创建他对应的设备文件。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
1:这个函数的第一个参数,是输出型参数,获得一个分配到的设备号。可以用MAJOR宏和MINOR宏,将主设备号和次设备号,提取打印出来,看是自动分配的是多少,方便我们在mknod创建设备文件时用到主设备号和次设备号。 mknod /dev/xxx c 主设备号 次设备号
2:第二个参数:次设备号的基准,从第几个次设备号开始分配。
3:第三个参数:次设备号的个数。
4:第四个参数:驱动的名字。
5:返回值:小于0,则错误,自动分配设备号错误。否则分配得到的设备号就被第一个参数带出来。
cdev介绍
cdev是一个结构体,里面的成员来共同帮助我们注册驱动到内核中,表达字符设备的,将这个struct cdev结构体进行填充,主要填充的内容就是
struct cdev { struct kobject kobj; struct module *owner;//填充时,值要为 THIS_MODULE,表示模块 const struct file_operations *ops;//这个file_operations结构体,注册驱动的关键,要填充成这个结构体变量 struct list_head list; dev_t dev;//设备号,主设备号+次设备号 unsigned int count;//次设备号个数 };
file_operations这个结构体变量,让cdev中的ops成员的值为file_operations结构体变量的值。这个结构体会被cdev_add函数想内核注册
cdev结构体,可以用很多函数来操作他。
如:
cdev_alloc:让内核为这个结构体分配内存的
cdev_init:将struct cdev类型的结构体变量和file_operations结构体进行绑定的
cdev_add:向内核里面添加一个驱动,注册驱动
cdev_del:从内核中注销掉一个驱动。注销驱动
设备号
(1)dev_t类型(包括了主设备号和次设备号 不同的内核中定义不一样有的是16位次设备号和16位主设备号构成 有的是20为次设备号12位主设备号 )
(2)MKDEV、MAJOR、MINOR三个宏
MKDEV: 是用来将主设备号和次设备号,转换成一个主次设备号的。(设备号)
MAJOR: 从设备号里面提取出来主设备号的。
MINOR宏:从设备号中提取出来次设备号的。
register_chrdev_region的使用对比register_chrdev:
1 // 模块安装函数 2 static int __init chrdev_init(void) 3 { 4 int retval; 5 6 printk(KERN_INFO "chrdev_init helloworld init\\n"); 7 8 /* 9 // 在module_init宏调用的函数中去注册字符设备驱动 10 // major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号 11 // 内核如果成功分配就会返回分配的主设备好;如果分配失败会返回负数 12 mymajor = register_chrdev(0, MYNAME, &test_fops); 13 if (mymajor < 0) 14 { 15 printk(KERN_ERR "register_chrdev fail\\n"); 16 return -EINVAL; 17 } 18 printk(KERN_INFO "register_chrdev success... mymajor = %d.\\n", mymajor); 19 */ 20 21 // 使用新的cdev接口来注册字符设备驱动 22 // 新的接口注册字符设备驱动需要2步 23 24 // 第1步:注册/分配主次设备号 25 mydev = MKDEV(MYMAJOR, 0); 26 retval = register_chrdev_region(mydev, MYCNT, MYNAME);//
//动态时如下直接改 同时将2526行去掉 其他都一样
//int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) 27 if (retval) { 28 printk(KERN_ERR "Unable to register minors for %s\\n", MYNAME); 29 return -EINVAL; 30 } 31 printk(KERN_INFO "register_chrdev_region success\\n"); 32 // 第2步:注册字符设备驱动 33 cdev_init(&test_cdev, &test_fops); 34 retval = cdev_add(&test_cdev, mydev, MYCNT); 35 if (retval) { 36 printk(KERN_ERR "Unable to cdev_add\\n"); 37 return -EINVAL; 38 } 39 printk(KERN_INFO "cdev_add success\\n"); 40 41 42 // 使用动态映射的方式来操作寄存器 43 if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON")) 44 return -EINVAL; 45 if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON")) 46 return -EINVAL; 47 48 pGPJ0CON = ioremap(GPJ0CON_PA, 4); 49 pGPJ0DAT = ioremap(GPJ0DAT_PA, 4); 50 51 *pGPJ0CON = 0x11111111; 52 *pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮 53 54 55 return 0; 56 } 57 58 // 模块下载函数 59 static void __exit chrdev_exit(void) 60 { 61 printk(KERN_INFO "chrdev_exit helloworld exit\\n"); 62 63 *pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); 64 65 // 解除映射 66 iounmap(pGPJ0CON); 67 iounmap(pGPJ0DAT); 68 release_mem_region(GPJ0CON_PA, 4); 69 release_mem_region(GPJ0DAT_PA, 4); 70 71 /* 72 // 在module_exit宏调用的函数中去注销字符设备驱动 73 unregister_chrdev(mymajor, MYNAME); 74 */ 75 76 // 使用新的接口来注销字符设备驱动 77 // 注销分2步: 78 // 第一步真正注销字符设备驱动用cdev_del 79 cdev_del(&test_cdev); 80 // 第二步去注销申请的主次设备号 81 unregister_chrdev_region(mydev, MYCNT); 82 }
完整代码:
1 #include <linux/module.h> // module_init module_exit 2 #include <linux/init.h> // __init __exit 3 #include <linux/fs.h> 4 #include <asm/uaccess.h> 5 #include <mach/regs-gpio.h> 6 #include <mach/gpio-bank.h> // arch/arm/mach-s5pv210/include/mach/gpio-bank.h 7 #include <linux/string.h> 8 #include <linux/io.h> 9 #include <linux/ioport.h> 10 #include <linux/cdev.h> 11 12 13 14 #define MYMAJOR 200 15 #define MYCNT 1 16 #define MYNAME "testchar" 17 18 #define GPJ0CON S5PV210_GPJ0CON 19 #define GPJ0DAT S5PV210_GPJ0DAT 20 21 #define rGPJ0CON *((volatile unsigned int *)GPJ0CON) 22 #define rGPJ0DAT *((volatile unsigned int *)GPJ0DAT) 23 24 #define GPJ0CON_PA 0xe0200240 25 #define GPJ0DAT_PA 0xe0200244 26 27 unsigned int *pGPJ0CON; 28 unsigned int *pGPJ0DAT; 29 30 31 int mymajor; 32 static dev_t mydev; 33 static struct cdev test_cdev; 34 35 char kbuf[100]; // 内核空间的buf 36 37 38 static int test_chrdev_open(struct inode *inode, struct file *file) 39 { 40 // 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分 41 // 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。 42 printk(KERN_INFO "test_chrdev_open\\n"); 43 44 rGPJ0CON = 0x11111111; 45 rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮 46 47 return 0; 48 } 49 50 static int test_chrdev_release(struct inode *inode, struct file *file) 51 { 52 printk(KERN_INFO "test_chrdev_release\\n"); 53 54 rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); 55 56 return 0; 57 } 58 59 ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos) 60 { 61 int ret = -1; 62 63 printk(KERN_INFO "test_chrdev_read\\n"); 64 65 ret = copy_to_user(ubuf, kbuf, count); 66 if (ret) 67 { 68 printk(KERN_ERR "copy_to_user fail\\n"); 69 return -EINVAL; 70 } 71 printk(KERN_INFO "copy_to_user success..\\n"); 72 73 74 return 0; 75 } 76 77 // 写函数的本质就是将应用层传递过来的数据先复制到内核中,然后将之以正确的方式写入硬件完成操作。 78 static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf, 79 size_t count, loff_t *ppos) 80 { 81 int ret = -1; 82 83 printk(KERN_INFO "test_chrdev_write\\n"); 84 85 // 使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间中的一个buf中 86 //memcpy(kbuf, ubuf); // 不行,因为2个不在一个地址空间中 87 memset(kbuf, 0, sizeof(kbuf)); 88 ret = copy_from_user(kbuf, ubuf, count); 89 if (ret) 90 { 91 printk(KERN_ERR "copy_from_user fail\\n"); 92 return -EINVAL; 93 } 94 printk(KERN_INFO "copy_from_user success..\\n"); 95 96 if (kbuf[0] == \'1\') 97 { 98 rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); 99 } 100 else if (kbuf[0] == \'0\') 101 { 102 rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); 103 } 104 105 106 /* 107 // 真正的驱动中,数据从应用层复制到驱动中后,我们就要根据这个数据 108 // 去写硬件完成硬件的操作。所以这下面就应该是操作硬件的代码 109 if (!strcmp(kbuf, "on")) 110 { 111 rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); 112 } 113 else if (!strcmp(kbuf, "off")) 114 { 115 rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); 116 } 117 */ 118 119 120 121 return 0; 122 } 123 124 125 // 自定义一个file_operations结构体变量,并且去填充 126 static const struct file_operations test_fops = { 127 .owner = THIS_MODULE, // 惯例,直接写即可 128 129 .open = test_chrdev_open, // 将来应用open打开这个设备时实际调用的 130 .release = test_chrdev_release, // 就是这个.open对应的函数 131 .write = test_chrdev_write, 132 .read = test_chrdev_read, 133 }; 134 135 136 // 模块安装函数 137 static int __init chrdev_init(void) 138 { 139 int retval; 140 141 printk(KERN_INFO "chrdev_init helloworld init\\n"); 142 143 /* 144 // 在module_init宏调用的函数中去注册字符设备驱动 145 // major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号 146 // 内核如果成功分配就会返回分配的主设备好;如果分配失败会返回负数 147 mymajor = register_chrdev(0, MYNAME, &test_fops); 148 if (mymajor < 0) 149 { 150 printk(KERN_ERR "register_chrdev fail\\n"); 151 return -EINVAL; 152 } 153 printk(KERN_INFO "register_chrdev success... mymajor = %d.\\n", mymajor); 154 */ 155 156 // 使用新的cdev接口来注册字符设备驱动 157 // 新的接口注册字符设备驱动需要2步 158 159 // 第1步:注册/分配主次设备号 160 mydev = MKDEV(MYMAJOR, 0); 161 retval = register_chrdev_region(mydev, MYCNT, MYNAME); 162 if (retval) { 163 printk(KERN_ERR "Unable to register minors for %s\\n", MYNAME); 164 return -EINVAL; 165 } 166 printk(KERN_INFO "register_chrdev_region success\\n"); 167 // 第2步:注册字符设备驱动 168 cdev_init(&test_cdev, &test_fops); 169 retval = cdev_add(&test_cdev, mydev, MYCNT); 170 if (retval) { 171 printk(KERN_ERR "Unable to cdev_add\\n"); 172 return -EINVAL; 173 } 174 printk(KERN_INFO "cdev_add success\\n"); 175 176 177 // 使用动态映射的方式来操作寄存器 178 if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON")) 179 return -EINVAL; 180 if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON")) 181 return -EINVAL; 182 183 pGPJ0CON = ioremap(GPJ0CON_PA, 4); 184 pGPJ0DAT = ioremap(GPJ0DAT_PA, 4); 185 186 *pGPJ0CON = 0x11111111; 187 *pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 亮 188 189 190 return 0; 191 } 192 193 // 模块下载函数 194 static void __exit chrdev_exit(void) 195 { 196 printk(KERN_INFO "chrdev_exit helloworld exit\\n"); 197 198 *pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); 199 200 // 解除映射 201 iounmap(pGPJ0CON); 202 iounmap(pGPJ0DAT); 203 release_mem_region(GPJ0CON_PA, 4); 204 release_mem_region(GPJ0DAT_PA, 4); 205 206 /* 207 // 在module_exit宏调用的函数中去注销字符设备驱动 208 unregister_chrdev(mymajor, MYNAME); 209 */ 210 211 // 使用新的接口来注销字符设备驱动 212 // 注销分2步: 213 // 第一步真正注销字符设备驱动用cdev_del 214 cdev_del(&test_cdev); 215 // 第二步去注销申请的主次设备号 216 unregister_chrdev_region(mydev, MYCNT); 217 } 218 219 220 module_init(chrdev_init); 221 module_exit(chrdev_exit); 222 223 // MODULE_xxx这种宏作用是用来添加模块描述信息 224 MODULE_LICENSE("GPL"); // 描述模块的许可证 225 MODULE_AUTHOR("aston"); // 描述模块的作者 226 MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息 227 MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <string.h> 6 7 8 #define FILE "/dev/test" // 刚才mknod创建的设备文件名 9 10 char buf[100]; 11 12 int main(void) 13 { 14 int fd = -1; 15 int i = 0; 16 17 fd = open(FILE, O_RDWR); 18 if (fd < 0) 19 { 20 printf("open %s error.\\n", FILE); 21 return -1; 22 } 23 printf("open %s success..\\n", FILE); 24 25 /* 26 // 读写文件 27 write(fd, "on", 2); 28 sleep(2); 29 write(fd, "off", 3); 30 sleep(2); 31 write(fd, "on", 2); 32 sleep(2); 33 */ 34 /* 35 write(fd, "1", 1); 36 sleep(2); 37 write(fd, "0", 1); 38 sleep(2); 39 write(fd, "1", 1); 40 sleep(2); 41 */ 42 while (1) 43 { 44 memset(buf, 0 , sizeof(buf)); 45 printf("请输入 on | off \\n"); 46 scanf("%s", buf); 47 if (!strcmp(buf, "on")) 48 { 49 write(fd, "1", 1); 50 } 51 else if (!strcmp(buf, "off")) 52 { 53 write(fd, "0", 1); 54 } 55 else if (!strcmp(buf, "flash")) 56 { 57 for (i=0; i<3; i++)