基于file_operations结构的字符设备驱动模型
file_operations是内核提供用来操作文件的操作集合,我们示例中只使用到了下面四个函数,因此示例中的hello_drv只实现了这四个函数。
truct file_operations {
...
struct module *owner;
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
...
}
实现了file_operations数据结构后,然后通过module_init/xxx_initcall等方式让kernel在启动阶段自动调用驱动函数。
常见的注册设备驱动的方式有以下两种:
1.使用alloc_chrdev_region+cdev注册设备驱动
1.) 调用alloc_chrdev_region获取主设备号。
intalloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
让内核分配给我们一个尚未使用的主设备号,不是由我们自己指定的,四个参数的意义如下:
dev :alloc_chrdev_region函数向内核申请下来的设备号
baseminor :次设备号的起始
count: 申请次设备号的个数
name :执行 cat /proc/devices显示的名称
示例:
alloc_chrdev_region(&devno, 0, 1, NIM_DEVICE_NAME);
2.) 调用cdev_init或者cdev_alloc,将cdev和file_operations关联起来,需要先实现file_operations的各个成员 -- 操作函数。
struct cdev*cdev_alloc(void);
示例:
m_hello_cdev = cdev_alloc();
if (m_hello_cdev == NULL) {
printk("hello cdev alloc fail\n");
return 0;
}
m_hello_cdev->ops = &hello_fops;
m_hello->owner = hello_fops.owner;
kobject_set_name(&m_hello_cdev->kobj, "%s", "hello");
voidcdev_init(struct cdev *, const struct file_operations *);
示例:
cdev_init(&mxl241_nim_dev.cdev, &mxl241_nim_fops);
3.) 调用cdev_add函数,将cdev和主设备号关联起来。
intcdev_add(struct cdev *, dev_t, unsigned);
示例:
ret=cdev_add(&mxl241_nim_dev.cdev, devno, 1);
将上述步骤封装在函数中,通过module_init/xxx_initcall让kernel在启动阶段自动调用该驱动函数。
2. 使用register_chrdev注册设备驱动。
1.) 调用register_chrdev获取主设备号。
intregister_chrdev(unsigned int major, const char *name,const structfile_operations *fops)
参数说明:
major,表示当前设备的主设备号,范围是1~254。可以自己指定,也可以设置为0让内核自动分配。犹如学号。
name,表示当前设备驱动的名字,犹如名字。
fops,是file_operations结构体指针。
返回值:
major如果设置为0,则返回自动分配的主设备号;
如果设置为指定的主设备号,成功则返回值为0,失败返回负数。
示例,获取主设备号,并指定设备名称。
major =register_chrdev(0, "hello", &hello_drv);
2.) 调用class_create与device_create来创建设备节点。
内核中定义了structclass结构体,顾名思义,一个structclass结构体类型变量对应一个类,内核同时提供了class_create(…)函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点。
class_create与device_create匹配使用。
示例,将设备注册到“/dev/hello”。
hello_class = class_create(THIS_MODULE, "hello_class");
device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */
上述步骤封装在函数中,通过module_init/xxx_initcall让kernel在启动阶段自动调用该驱动函数。
基于platform_driver结构的字符设备驱动模型
platform是一条虚拟的总线。设备用platform_device表示,驱动用platform_driver进行注册,Linux platform driver机制和传统的device driver机制(通过driver_register进行注册)相比,一个明显的优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动中使用这些资源时通过platform device提供的标准结构进行申请并使用。这样提高了驱动和资源的独立性,并且具有较好的可移植性和安全性(这些标准接口是安全的)。
pltform机制本身使用并不复杂,由两部分组成:platform_device和platform_driver。通过platform机制开发底层驱动的大致流程为:定义platform_deive->注册platform_device->定义platform_driver->注册platform_driver。
platform_device通常情况下,kernel会从dts中匹配,并展开自动生成。dts文件最终在linux内核中会转化成platform_device:
dts -> dtb -> device_node -> platform_device
platform_driver的基本形态如下
static struct platform_driver plt_hello_driver = {
.driver =
{
.name = "plt_hello",
.owner = THIS_MODULE,
.of_match_table = plt_hello_of_match,
},
.probe = plt_hello_probe,
.remove = plt_hello_remove,
};
PS:MODULE_DEVICE_TABLE(类型, ID表);
设备树ID表
类型:of
该宏定义用来指定dts中的设备表,示例如下:
static const struct of_device_id plt_hello_of_match[] = {
{
.compatible = "tech, plt_hello",
},
{},
};
MODULE_DEVICE_TABLE(of, plt_hello_of_match);
在.probe函数中实现驱动的方式与前述file_operations结构比较相似,也是通过cdev_alloc/cdev_init与cdev_add创建设备,然后再通过class_create/device_create创建总线及设备。
1. 使用module_platform_driver注册设备驱动
例子,从dts获取主设备号:
ret = of_get_major_minor(pdev->dev.of_node, &m_hello_dev_t, 0, 1, "hello");
if (ret < 0) {
pr_err("unable to get major and minor for char devive\n");
return ret;
}
创建hello设备并关联file_operation(hello_fops)。
m_hello_cdev = cdev_alloc();
if (m_hello_cdev == NULL) {
HELLO_PRF(" hello cdev alloc fail\n");
return 0;
}
m_hello_cdev->ops = &hello_fops;
m_hello_cdev->owner = hello_fops.owner;
kobject_set_name(&m_hello_cdev->kobj, "%s", "hello");
if (cdev_add(m_hello_cdev, m_hello_dev_t, 1) < 0) {
HELLO_PRF(" hello cdev add fail\n");
goto FAIL;
}
创建class/device
m_hello_class = class_create(THIS_MODULE, "hello");
if (m_hello_class == NULL) {
HELLO_PRF(" hello create class fail\n");
goto FAIL;
}
m_hello_device = device_create(m_hello_class, NULL, m_hello_dev_t, NULL, "hello0");
if (m_hello_device == NULL) {
HELLO_PRF(" hello create device fail\n");
goto FAIL;
}
2. 使用module_init/xxx_initcall来注册设备驱动
也可以通过上面的接口自己控制设备驱动的调用顺序。只需要在设备驱动中
通过platform_driver_register接口来注册platform_driver接口即可。
示例:
static struct platform_driver hello_driver = {
.probe = hello_probe,
.remove = hello_remove,
.suspend = hello_suspend,
.resume = hello_resume,
.driver =
{
.name = "plt-hello",
.owner = THIS_MODULE,
.of_match_table = plt_hello_dt_ids,
},
};
static int __init hello_init(void)
{
int ret = 0;
ret = platform_driver_register(&hello_driver);
return ret;
}
module_init(hello_init);