一、设备号处理及设备注册
1、创建设备结构体
驱动设备号除了直接指定外,还可以让linux内核分配。首先创建设备结构体,结构体内包含的设备的属性:
struct newchrled_dev{
struct cdev cdev; //字符设备
int major; //主设备号
int minor; //次设备号
dev_t devid; //设备号
struct class *class; //类
struct device *device; //设备
};
struct newchrled_dev newchrled;
2、设备号处理方法
在设备入口函数xxx_init中添加设备号处理代码:
/* 获取设备号 */
if(newchrled.major){ //如果设置了主设备号
newchrled.devid = MKDEV(newchrled.major, 0); //定义设备号,次设备号为0
register_chrdev_region(newchrled.devid, 1, DEVICE_NAME); //向内核注册设备号
}
else{ //没有设置主设备号
alloc_chrdev_region(&newchrled.devid, 0, 1, DEVICE_NAME); //申请设备号
newchrled.major = MAJOR(newchrled.devid); //可要可不要
newchrled.minor = MINOR(newchrled.devid);
}
设备号处理分为两种情况,一是直接指定主设备号(次设备号一般设置为0),然后使用MKDEV这个宏获取dev_t类型的设备号,此类型的设备号包含了主设备号和次设备号信息。最后使用register_chrdev_region函数向内核注册设备号,告知系统此设备号已被占用。该函数第一个参数为设备号;第二个参数为申请的数量,一般为1;第三个参数为设备名。
二是没有指定主设备号,使用alloc_chrdev_region函数向内核申请一个设备号,该函数第一个参数为设备号指针,用来储存申请到的设备号;第二个参数为次设备号;第三个参数为申请的数量,一般为1;第四个参数为设备名。
3、注册设备
设备结构体中有一个cdev的架构体变量,表示一个字符设备,cdev结构体在include/linux/cdev.h文件中的定义如下:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops; //操作函数结构体
struct list_head list;
dev_t dev; //设备号
unsigned int count;
};
设备注册实质上就是初始化cdev,然后使用cdev进行注册。在设备号处理代码后添加设备注册代码:
/* 注册设备 */
newchrled.cdev.owner = THIS_MODULE; //初始化cdev中的owner
cdev_init(&newchrled.cdev, &led_fops); //初始化cdev结构体变量
cdev_add(&newchrled.cdev, newchrled.devid, 1); //向linux内核添加字符设备
设备注册使用了两个函数:
cdev_init(struct cdev *cdev, const struct file_operations *fops),参数一是要初始化的cdev的指针,参数二是操作函数结构体指针。
int cdev_add(struct cdev *p, dev_t dev, unsigned count),参数一是要注册的cdev的指针,参数二是设备号,参数三是要注册的设备数量。
4、自动创建设备节点
在终端使用应用程序前要使用mkond命令创建驱动设备节点,这一步可以在驱动代码内自动实现,在终端只要使用modprobe加载驱动模块后就会自动创建设备节点。
udev是一个用户程序,在linux下通过udev实现设备文件的创建与删除,比如使用modprobe后自动在/dev下创建设备节点文件,使用rmmod后删除设备节点文件。使用busybox构建根文件系统后,会创建一个udev的简化版mdev,嵌入式linux一般使用mdev。mdev同时也管理热拔插时间,在==/etc/init.d/rcS==中添加如下语句:
echo /sbin/mdev > /proc/sys/kernel/hotplug
自动创建设备节点一般在设备注册代码后:
/* 自动创建设备节点 */
newchrled.class = class_create(THIS_MODULE, DEVICE_NAME); //创建类
newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, DEVICE_NAME); //创建设备
1)首先要在设备结构体中创建类和设备结构体指针。类定义在include/linux/device.h里面。
2)然后使用class_create函数创建类,函数原型:struct class *class_create (struct module *owner, const char *name),参数一owner一般为THIS_MODULE,参数二为类的名字,返回值是指向结构体class的指针。
3)最后使用device_create函数创建设备。参数一为设备创建在哪个类下,参数二为父设备,无则NULL,参数三为设备号,参数四为设备可能使用的一些数据,无则NULL,参数五是设备名字,即创建设备节点文件的名字。
二、删除各种注册申请
在驱动出口函数xxx_exit中要删除设备号、设备注册信息、类和自动创建设备,在出口函数中添加代码:
/* 出口函数 */
static void __exit led_exit(void)
{
......
/* 删除设备 */
device_destroy(newchrled.class, newchrled.devid);
/* 删除类 */
class_destroy(newchrled.class);
/* 删除字符设备 */
cdev_del(&newchrled.cdev);
/* 注销设备号 */
unregister_chrdev_region(newchrled.devid, 1);
}
在驱动入口函数中先创建的在出口函数中后删除。入口函数中的创建顺序是:设备号——注册字符设备——创建类——创建设备。出口函数中的删除顺序则是:删除设备——删除类——删除字符设备——删除设备号。
三、设置私有数据
设备属性结构体可以作为私有数据来使用,操作函数的参数中有一个file类型的结构体指针filp,该结构体有一个private_data的成员变量,表示私有数据。可在open函数中设置私有数据:
/* open函数 */
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &newchrled;
return 0;
}
然后在write、read等其他操作函数中使用:
/* release函数 */
static int led_release(struct inode *inode, struct file *filp)
{
struct newchrled_dev *dev = filp->private_data;
printk("major: %d\r\n",dev->major);
return 0;
}