Linux字符设备驱动模型小记

基于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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值