linux中以devm开头的一些函数(设备资源管理)

时间:2022-11-02 08:10:57

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