说明
本文将详细介绍devm的机制,包括怎么向设备添加各种资源,以及在设备卸载时,驱动是怎么自动释放相关资源的?
数据结构
drivers/base/devres.c:
struct devres_node {
struct list_head entry; //用于链表操作
dr_release_t release; //用于保存释放函数的函数指针
#ifdef CONFIG_DEBUG_DEVRES
const char *name;
size_t size;
#endif
};
struct devres {
struct devres_node node; //见上面
/* -- 3 pointers */
/* 0长度数组,C99是下面的定义,不需要data[0] */
unsigned long long data[]; /* guarantee ull alignment */
};
struct device {
...
/* 这个是跟设备资源管理相关的,设备资源都会添加到这个链表上 */
struct list_head devres_head;
}
核心机制
添加资源
在看代码之前一个非常重要的问题:这种机制是怎么样将各种的资源挂载这个链表上,进行统一管理的?当我们把这个问题搞明白了,那么设备资源管理devm相关的知识基本上也就明白了。
以devm_clk为例:
drivers/clk/clk-devres.c:
/* clk的释放函数
* 两个参数分别为对应的设备dev和资源res,这两个参数对所有的资源都是一样的,可以从链表释放函数中看出来,因为调用了统一的函数指针来释放资源。
*/
static void devm_clk_release(struct device *dev, void *res)
{
/* clk自己的释放操作函数 */
clk_put(*(struct clk **)res);
}
struct clk *devm_clk_get(struct device *dev, const char *id)
{
struct clk **ptr, *clk;
/* 1.调用devm中核心的分配函数,并注册devm_clk_release */
ptr = devres_alloc(devm_clk_release, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return ERR_PTR(-ENOMEM);
/* 2.clk自己的相关操作 */
clk = clk_get(dev, id);
if (!IS_ERR(clk)) {
*ptr = clk;
/* 3.devm中第二个核心操作,添加到设备资源管理中,这里的函数实际上有点小技巧 */
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return clk;
}
EXPORT_SYMBOL(devm_clk_get);
先来看下devres_alloc_node这个函数:
/**
* devres_alloc - Allocate device resource data
* @release: Release function devres will be associated with
* @size: Allocation size
* @gfp: Allocation flags
* @nid: NUMA node
*
* Allocate devres of @size bytes. The allocated area is zeroed, then
* associated with @release. The returned pointer can be passed to
* other devres_*() functions.
*
* RETURNS:
* Pointer to allocated devres on success, NULL on failure.
*/
void * devres_alloc_node(dr_release_t release, size_t size, gfp_t gfp, int nid)
{
struct devres *dr;
dr = alloc_dr(release, size, gfp | __GFP_ZERO, nid);
if (unlikely(!dr))
return NULL;
/* 注意这里的返回值,代表的是各种资源自己的内容 */
return dr->data;
}
EXPORT_SYMBOL_GPL(devres_alloc_node);
再深入看下:
static __always_inline struct devres * alloc_dr(dr_release_t release,
size_t size, gfp_t gfp, int nid)
{
/* 计算分配内存总长度,包括devres结构体的长度 */
size_t tot_size = sizeof(struct devres) + size;
struct devres *dr;
dr = kmalloc_node_track_caller(tot_size, gfp, nid);
if (unlikely(!dr))
return NULL;
memset(dr, 0, offsetof(struct devres, data));
/* 初始化链表 */
INIT_LIST_HEAD(&dr->node.entry);
/* 关联释放函数 */
dr->node.release = release;
return dr;
}
到这里的时候只是分配了对象内存、并将release填充到了dr的node.release中。但是还没有和设备关联起来?
在devm_clk_get()中的第二步,是和资源相关的操作,不用太关心。第三步就能回答我们上面的疑问。
/**
* devres_add - Register device resource
* @dev: Device to add resource to
* @res: Resource to register
*
* Register devres @res to @dev. @res should have been allocated
* using devres_alloc(). On driver detach, the associated release
* function will be invoked and devres will be freed automatically.
*/
void devres_add(struct device *dev, void *res)
{
/* 注意下这里,通过资源res来得到devm的通用的核心结构体 */
struct devres *dr = container_of(res, struct devres, data);
unsigned long flags;
spin_lock_irqsave(&dev->devres_lock, flags);
/* 资源管理的第二个核心操作 */
add_dr(dev, &dr->node);
spin_unlock_irqrestore(&dev->devres_lock, flags);
}
EXPORT_SYMBOL_GPL(devres_add);
在注释中有说明,小技巧也是在这里,利用container_of()函数来将不同的资源数据得到devm通用的核心结构体数据。此外还涉及到一个零字节数组,devm就是利用它来实现对不同的资源进行统一的管理,即提供一套通用的devm的机制。
static void add_dr(struct device *dev, struct devres_node *node)
{
devres_log(dev, node, "ADD");
BUG_ON(!list_empty(&node->entry));
/* 将node添加到dev的资源链表上 */
list_add_tail(&node->entry, &dev->devres_head);
}
上面的这个函数很简单,就是将资源添加到dev的资源管理链表上。
到此,设备不同资源添加到设备上过程就清晰了,devm最重要的作用是在设备卸载时能自动释放所有的资源,添加资源和释放资源是一个整体,而释放资源又是怎么操作的呢?
释放资源
在设备注销阶段会调用到device_release_driver()和driver_detach()函数(具体是怎么调用到的,请读者自己去分析下),而这两个函数会调用下面的函数:
drivers/base/dd.c
/* 释放dev资源的核心函数 */
void device_release_driver_internal(struct device *dev,
struct device_driver *drv,
struct device *parent)
{
...
if (!drv || drv == dev->driver)
__device_release_driver(dev, parent);
...
}
依次调用:
/*
* __device_release_driver() must be called with @dev lock held.
* When called for a USB interface, @dev->parent lock must be held as well.
*/
static void __device_release_driver(struct device *dev, struct device *parent)
{
struct device_driver *drv;
drv = dev->driver;
if (drv) {
...
/* 释放所有的资源 */
devres_release_all(dev);
...
}
}
接着往下看:
/**
* devres_release_all - Release all managed resources
* @dev: Device to release resources for
*
* Release all resources associated with @dev. This function is
* called on driver detach.
*/
int devres_release_all(struct device *dev)
{
unsigned long flags;
/* Looks like an uninitialized device structure */
if (WARN_ON(dev->devres_head.next == NULL))
return -ENODEV;
spin_lock_irqsave(&dev->devres_lock, flags);
/* 释放节点 */
return release_nodes(dev, dev->devres_head.next, &dev->devres_head,
flags);
}
最终会调用到下面的函数:
static int release_nodes(struct device *dev, struct list_head *first,
struct list_head *end, unsigned long flags)
__releases(&dev->devres_lock)
{
LIST_HEAD(todo);
int cnt;
struct devres *dr, *tmp;
cnt = remove_nodes(dev, first, end, &todo);
spin_unlock_irqrestore(&dev->devres_lock, flags);
/* Release. Note that both devres and devres_group are
* handled as devres in the following loop. This is safe.
*/
list_for_each_entry_safe_reverse(dr, tmp, &todo, node.entry) {
devres_log(dev, &dr->node, "REL");
/* 调用注册的释放函数去释放资源 */
dr->node.release(dev, dr->data);
kfree(dr);
}
return cnt;
}
到此,释放设备资源的过程也清晰了。
总结
这里只是以clk为例来分析,读者朋友可以自己去看下其它的函数,它们的原理基本上是一样的,比如添加资源:第一步调用devres_alloc()来分配对象,第二步是资源自己的相关操作,第三步调用devres_add()函数将资源添加到设备的资源链表上。释放资源时,遍历设备资源管理链表,然后调用资源注册的释放函数。总的来说,这种机制设计的还是挺好的,哈哈。。。
参考
需要知道的其它知识:零长度数组
参考:零长度数组