linux4.6.3

devm简介

在驱动代码中我们经常会见到一些以devm开头的函数,这一类的函数都是和设备资源管理(Managed Device Resource)相关的,驱动中提供这些函数主要是为了方便对于申请的资源进行释放,比如:irq、regulator、gpio等等。在驱动进行初始化的时候如果失败,那么通常会goto到某个地方释放资源,这样的标签多了之后会让代码看起来不简洁,devm就是为来处理这种情况。

devm相关的代码一般在各个目录的一个叫devres.c的文件中,devm核心的代码是在drivers/base/devres.c中,irq相关的代码在kernel/irq/devres.c中,regulator相关的代码在drivers/regulator/devres.c中,另外各个驱动也有相关代码,如spi的devm代码在spi.c文件中。

devm架构是如何运作的

上面说了devm主要是用来方便释放设备资源的,那么什么时候释放呢?驱动是怎么释放的呢?设备资源又如何使用呢?带着这些问题下面来看代码。

总体上来说,devm架构就两个流程注册&调用:
1. 首先向dev注册release函数
2. 驱动注册失败时调用release函数

下面介绍的代码都在drivers/base/devres.c中,devm架构中代表资源的机构体是struct devres和struct devres_node

首先向dev注册release函数

在注册之前得先申请一个struct devres,申请函数为
static inline void *devres_alloc(dr_release_t release, size_t size, gfp_t gfp),参数:
(1)release为release函数
(2)size为大小
(3)gfp为申请内存的标志

申请完struct devres就可以进行注册,devres_add就是注册函数,他把devres加入到device的相关链表中

void devres_add(struct device *dev, void *res)
{
    struct devres *dr = container_of(res, struct devres, data);---申请一个devres
    unsigned long flags;

    spin_lock_irqsave(&dev->devres_lock, flags);
    add_dr(dev, &dr->node);---------list_add_tail(&node->entry, &dev->devres_head);
    spin_unlock_irqrestore(&dev->devres_lock, flags);
}

我们来看一下device结构体(摘取), 链表字段在devres_node结构体中,从下面的结构体及上面的介绍可以看到是怎么做的,就是把devres_node->entry加入到dev->devres_head链表中

struct device {
    。
    。
    pinlock_t       devres_lock;
    struct list_head    devres_head;
    。
    。
};
struct devres_node {
    struct list_head        entry;--------此字段就是要加入到device的链表中
    dr_release_t            release;------release函数字段,最终要调取的release函数
#ifdef CONFIG_DEBUG_DEVRES
    const char          *name;
    size_t              size;
#endif
};

struct devres {
    struct devres_node      node;
    /* -- 3 pointers */
    unsigned long long      data[]; /* guarantee ull alignment */
};

device会有很多资源,所以提供了一个链表devres_head,各个资源都被加入到这个链表中,而对于资源来说也有构体来代表,就是struct devres

驱动注册失败时调用release函数

驱动注册时的函数(如下代码),注册失败后进入函数bus_remove_driver中,接着沿着下面的调用关系bus_remove_driver->driver_detach->__device_release_driver->devres_release_all->release_nodes->(dr->node.release(dev, dr->data));就进入了上面说的devres ->node->release函数

int driver_register(struct device_driver *drv)
{
    int ret;
    struct device_driver *other;

    BUG_ON(!drv->bus->p);

    if ((drv->bus->probe && drv->probe) ||
        (drv->bus->remove && drv->remove) ||
        (drv->bus->shutdown && drv->shutdown))
        printk(KERN_WARNING "Driver '%s' needs updating - please use "
            "bus_type methods\n", drv->name);

    other = driver_find(drv->name, drv->bus);
    if (other) {
        printk(KERN_ERR "Error: Driver '%s' is already registered, "
            "aborting...\n", drv->name);
        return -EBUSY;
    }

    ret = bus_add_driver(drv);
    if (ret)
        return ret;
    ret = driver_add_groups(drv, drv->groups);
    if (ret) {
        bus_remove_driver(drv);--------------注册失败进入此函数
        return ret;
    }
    kobject_uevent(&drv->p->kobj, KOBJ_ADD);

    return ret;

这里有一点说明:上面进入release函数给的参数为dr->node.release(dev, dr->data),dev好理解,那么dr->data又是什么呢?我们来看devres结构体,data是个零长度数组,表示可以访问结构体之后的一段内存,见参考。每个具体资源的data是不一样的具体看下面介绍。

举例说明具体资源如何实现

spi中使用的devm机制

先把devres挂到dev链中

int devm_spi_register_master(struct device *dev, struct spi_master *master)
{
    struct spi_master **ptr;
    int ret;

    ptr = devres_alloc(devm_spi_unregister, sizeof(*ptr), GFP_KERNEL);//设置release函数devm_spi_unregister
    if (!ptr)
        return -ENOMEM;

    ret = spi_register_master(master);
    if (!ret) {
        *ptr = master;
        devres_add(dev, ptr);-----------//向dev注册release
    } else {
        devres_free(ptr);
    }

    return ret;
}

devres_alloc先分配一个devres,然后返回的指针ptr是data指针,然后把master指针传给data,这样spi的资源就是spi_master
注册失败的时候调用devm_spi_unregister

static void devm_spi_unregister(struct device *dev, void *res)
{
    spi_unregister_master(*(struct spi_master **)res);
}

iomem使用的devm机制

先把devres挂到dev链中(代码在lib/devres.c中)

void __iomem *devm_ioport_map(struct device *dev, unsigned long port,
                   unsigned int nr)
{
    void __iomem **ptr, *addr;

    ptr = devres_alloc(devm_ioport_map_release, sizeof(*ptr), GFP_KERNEL);//设置release函数devm_ioport_map_release
    if (!ptr)
        return NULL;

    addr = ioport_map(port, nr);
    if (addr) {
        *ptr = addr;
        devres_add(dev, ptr);//向dev注册devres
    } else
        devres_free(ptr);

    return addr;
}

同样iomem的资源就是__iomem
注册失败的时候调用devm_ioport_map_release

static void devm_ioport_map_release(struct device *dev, void *res)
{
    ioport_unmap(*(void __iomem **)res);
}

Ref.

Linux设备模型(9)_device resource management

零长度数组-GNU

GitHub 加速计划 / li / linux-dash
6
1
下载
A beautiful web dashboard for Linux
最近提交(Master分支:3 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐