设备号
Linux的设备管理是和文件系统紧密结合的,各种设备都以文件的形式存放在/dev目录下,称为设备文件。应用程序可以打开、关闭和读写这些设备文件,完成对设备的操作,就像操作普通的数据文件一样。为了管理这些设备,系统为设备编了号,每个设备号又分为主设备号和次设备号。主设备号用来区分不同种类的设备,而次设备号用来区分同一类型的多个设备。对于常用设备,Linux有约定俗成的编号,如硬盘的主设备号是3。
一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各设备。例如一个嵌入式系统,有两个LED指示灯,LED灯需要独立的打开或者关闭。那么,可以写一个LED灯的字符设备驱动程序,可以将其主设备号注册成5号设备,次设备号分别为1和2。这里,次设备号就分别表示两个LED灯。
函数定义:
1 2 3 4 5 6
| #include <sys/sysmacros.h> #include <linux/cdev.h> MKDEV(int major,int minor) #define MKDEV(major,minor) (((major) << MINORBITS) | (minor))。 unsigned int major(dev_t dev); unsigned int minor(dev_t dev);
|
在程序中用宏MAJOR(dev_t dev)可以解析出主设备号,用宏MINOR(dev_t dev)可以解析出次设备号
申请设备号
动态申请设备号,没有指定设备号用以下函数:
1
| int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
|
dev :alloc_chrdev_region函数向内核申请下来的设备号
baseminor :次设备号的起始
count: 申请次设备号的个数
name :执行 cat /proc/devices显示的名称
静态申请设备号,给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可:
1
| int register_chrdev_region(dev_t from, unsigned count, const char *name)
|
from:起始设备号
count:数量设备
name:设备名字
注 销 字 符 设 备 之 后 要 释 放 掉 设 备 号 , 不 管 是 通 过 alloc_chrdev_region 函 数 还 是register_chrdev_region 函数申请的设备号,统一使用如下释放函数:
1
| void unregister_chrdev_region(dev_t from, unsigned count)
|
新字符设备驱动下,设备号分配如下:
1 2 3 4 5 6 7 8 9 10 11 12
| int major; int minor; dev_t devid; if (major) { devid = MKDEV(major, 0); register_chrdev_region(devid, 1, "test"); } else { alloc_chrdev_region(&devid, 0, 1, "test"); major = MAJOR(devid); minor = MINOR(devid); }
|
字符设备结构
1 2 3 4 5 6 7 8
| struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; };
|
cdev两种定义初始化方式: Linux驱动|cdev_init、cdev_alloc区别_一口Linux的博客-CSDN博客
静态初始化:cdev_init函数
1
| void cdev_init(struct cdev *cdev, const struct file_operations *fops)
|
1 2 3 4
| struct cdev my_cdev; cdev_init(&my_cdev, &fops); my_cdev.owner = THIS_MODULE; my_cdev.ops = &fops;
|
动态初始化:cdev_alloc函数
1 2 3
| struct cdev *my_cdev = cdev_alloc(); my_cdev->ops = &fops; my_cdev->owner = THIS_MODULE;
|
将cdev添加到系统中去 cdev_add函数
1
| int cdev_add(struct cdev *p, dev_t dev, unsigned count)
|
1 2 3 4 5 6 7 8 9
| struct cdev testcdev;
static struct file_operations test_fops = { .owner = THIS_MODULE, }; testcdev.owner = THIS_MODULE; cdev_init(&testcdev, &test_fops); cdev_add(&testcdev, devid, 1);
|
cdev_del 函数
卸载驱动的时候一定要使用 cdev_del 函数从 Linux 内核中删除相应的字符设备。
1
| void cdev_del(struct cdev *p)
|
参数 p 就是要删除的字符设备。如果要删除字符设备,参考如下代码:
cdev_del 和 unregister_chrdev_region 这两个函数合起来的功能相当于 unregister_chrdev 函数。
自动创建设备节点
早期的Linux内核(版本2.4之前)并没有实现一个统一的设备模型,设备节点的创建一般是mknod命令手动创建或利用devfs文件系统创建。早期的Linux发行版一般会采用手动创建的方式预先把通常用到的节点都创建出来,而嵌入式系统则会采用devfs的方式。起初Linux2.6 内核还支持devfs,但从2.6.18开始,内核完全移除了devfs系统而采用的udev的方式动态的创建设备节点。因此,新的Linux发行版都采用udev的方式管理设备节点文件。
class可以自动创建设备节点,不需要udev,而udev自动创建节点需要用到class。
创建和删除类
自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添加自动创建设备节点相关代码。首先要创建一个 class 类, class 是个结构体,定义在文件include/linux/device.h 里面。 class_create 是类创建函数, class_create 是个宏定义。
1 2 3 4 5 6
| #define class_create(owner, name) \ ({ \ static struct lock_class_key __key; \ __class_create(owner, name, &__key); \ }) struct class *__class_create(struct module *owner, const char *name,struct lock_class_key *key)
|
根据上述代码,将宏 class_create 展开以后内容如下:
1
| struct class *class_create (struct module *owner, const char *name)
|
class_create 一共有两个参数,参数 owner 一般为 THIS_MODULE,参数 name 是类名字。
返回值是个指向结构体 class 的指针,也就是创建的类。
卸载驱动程序的时候需要删除掉类,类删除函数为 class_destroy,函数原型如下:
1
| void class_destroy(struct class *cls);
|
参数 cls 就是要删除的类。
创建设备
使用 device_create 函数在类下面创建设备, device_create 函数原型如下:
1 2 3 4 5
| struct device *device_create( struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
|
device_create 是个可变参数函数,参数 class 就是设备要创建哪个类下面;参数 parent 是父设备,一般为 NULL,也就是没有父设备;参数 devt 是设备号;参数 drvdata 是设备可能会使用的一些数据,一般为 NULL;参数 fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx这个设备文件。返回值就是创建好的设备。
同样的,卸载驱动的时候需要删除掉创建的设备,设备删除函数为 device_destroy,函数原型如下:
1
| void device_destroy(struct class *class, dev_t devt)
|
参数 class 是要删除的设备所处的类,参数 devt 是要删除的设备号。
设置文件私有数据
每个硬件设备都有一些属性,比如主设备号(dev_t),类(class)、设备(device)、开关状态(state)等等,在编写驱动的时候你可以将这些属性全部写成变量的形式 ,一般以设备的所有属性来作为一个结构体。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| struct test_dev{ dev_t devid; struct cdev cdev; struct class *class; struct device *device; int major; int minor; }; struct test_dev testdev;
static int test_open(struct inode *inode, struct file *filp) { filp->private_data = &testdev; return 0; }
|
在 open 函数里面设置好私有数据以后,在 write、 read、 close 等函数中直接读取 private_data即可得到设备结构体。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
| #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/errno.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/fs.h>
#define NEWCHRLED_CNT 1 #define NEWCHRLED_NAME "newchrled" #define LED_MAJOR 200 #define LED_NAME "led"
#define CCM_CCGR1_BASE (0X020C406C) #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4) #define SW_MUX_GPIO1_IO03_BASE (0X020E0068) #define GPIO1_DR_BASE (0X0209C000) #define GPIO1_GDIR_BASE (0X0209C004)
#define on 1 #define off 0 static void __iomem *CCM_CCGR1; static void __iomem *SW_PAD_GPIO1_IO03; static void __iomem *SW_MUX_GPIO1_IO03; static void __iomem *GPIO1_DR; static void __iomem *GPIO1_GDIR;
struct newchrled_dev{ dev_t devid; struct cdev cdev; struct class *class; struct device *device; int major; int minor; };
struct newchrled_dev newchrled;
void led_switch(u8 sta) { u32 num; if(sta == on) { num =readl(GPIO1_DR); num &= ~(1 << 3); writel( num, GPIO1_DR); printk("on\n"); } else if(sta == off) { num = readl(GPIO1_DR); num |= (1 << 3); writel( num, GPIO1_DR); printk("off\n"); } }
static int led_dev_open(struct inode *inode, struct file *filp) { filp->private_data = &newchrled;
return 0; }
static int led_dev_release(struct inode *inode, struct file *filp) { return 0; }
static ssize_t led_dev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { return 0; }
static ssize_t led_dev_write(struct file *filp,const char __user *buf, size_t cnt, loff_t *offt) { int val = 0; unsigned char databuf[1]; unsigned char state ;
val = copy_from_user(databuf, buf, cnt); if(val < 0) { printk("kernl write failed \r\n"); return -EFAULT; } state = databuf[0]; if(state == on) led_switch(on); else if(state == off) led_switch(off);
return 0; }
static struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_dev_open, .read = led_dev_read, .write = led_dev_write, .release = led_dev_release, }; static int __init led_init(void) { u32 val = 0; CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4); SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4); SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4); GPIO1_DR = ioremap(GPIO1_DR_BASE, 4); GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4); val = readl(CCM_CCGR1); val &= ~(3 << 26); val |= (3 << 26); writel(val, CCM_CCGR1); writel(5,SW_MUX_GPIO1_IO03); writel(0x10B0, SW_PAD_GPIO1_IO03); val = readl(GPIO1_GDIR); val &= ~(1 << 3); val |= (1 << 3); writel(val, GPIO1_GDIR); val = readl(GPIO1_DR); val |= (1 << 3); writel( val , GPIO1_DR);
if(newchrled.major) { newchrled.devid = MKDEV(newchrled.major, 0); register_chrdev_region(newchrled.devid, 1, "led"); } else{ alloc_chrdev_region(&newchrled.devid, 0, 1, "led"); newchrled.major = MAJOR(newchrled.devid); newchrled.minor = MINOR(newchrled.devid); } cdev_init(&newchrled.cdev, &led_fops); newchrled.cdev.owner = THIS_MODULE; cdev_add(&newchrled.cdev, newchrled.devid, 1); newchrled.class = class_create(THIS_MODULE , "led"); if(IS_ERR(newchrled.class)) { return PTR_ERR(newchrled.class); } newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, "led"); if(IS_ERR(newchrled.device)) { return PTR_ERR(newchrled.class); } return 0; } static void __exit led_exit(void) { iounmap(CCM_CCGR1); iounmap(GPIO1_DR); iounmap(GPIO1_GDIR); iounmap(SW_PAD_GPIO1_IO03); iounmap(SW_MUX_GPIO1_IO03); cdev_del(&newchrled.cdev); unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);
device_destroy(newchrled.class, newchrled.devid); class_destroy(newchrled.class); printk("led exit\r\n"); }
module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL");
|