在alloc_dr中初始化
static __always_inline struct devres * alloc_dr(dr_release_t release,
size_t size, gfp_t gfp)
{
size_t tot_size = sizeof(struct devres) + size;
struct devres *dr;
dr = kmalloc_track_caller(tot_size, gfp);
if (unlikely(!dr))
return NULL;
memset(dr, 0, offsetof(struct devres, data));
INIT_LIST_HEAD(&dr->node.entry);
dr->node.release = release;
return dr;
}
看第一句就可以了,在资源size之前,加一个struct devres
的size,就是total分配的空间。
除去struct devres的,就是资源的(由data指针访问)。
之后是初始化struct devres变量的node,可以看到,devres_alloc
指定的release
方法,便于在适当的时机执行。
devres_add/devres_remove
void devres_add(struct device *dev, void *res)
{
struct devres *dr = container_of(res, struct devres, data);
unsigned long flags;
spin_lock_irqsave(&dev->devres_lock, flags);
// 将资源添加到设备的资源链表头(devres_head)中。
add_dr(dev, &dr->node);
spin_unlock_irqrestore(&dev->devres_lock, flags);
}
从资源指针中,取出完整的struct devres指针,调用add_dr接口。
使用add_dr挂入devers链表中
将资源添加到设备的资源链表头(devres_head)中。
add_dr也很简单,把struct devres
指针挂到设备的devres_head
中即可
static void add_dr(struct device *dev, struct devres_node *node)
{
devres_log(dev, node, "ADD");
BUG_ON(!list_empty(&node->entry));
list_add_tail(&node->entry, &dev->devres_head);
}
devres_destroy
/**
* devres_destroy - Find a device resource and destroy it
* @dev: Device to find resource from
* @release: Look for resources associated with this release function
* @match: Match function (optional)
* @match_data: Data for the match function
*
* Find the latest devres of @dev associated with @release and for
* which @match returns 1. If @match is NULL, it's considered to
* match all. If found, the resource is removed atomically and freed.
*
* Note that the release function for the resource will not be called,
* only the devres-allocated data will be freed. The caller becomes
* responsible for freeing any other data.
*
* RETURNS:
* 0 if devres is found and freed, -ENOENT if not found.
*/
int devres_destroy(struct device *dev, dr_release_t release,
dr_match_t match, void *match_data)
{
void *res;
res = devres_remove(dev, release, match, match_data);
if (unlikely(!res))
return -ENOENT;
devres_free(res);
return 0;
}
EXPORT_SYMBOL_GPL(devres_destroy);
从设备中找出对应的资源,并摧毁。
以IRQ模块为例看看如何使用资源管理
先看一个使用device resource management的例子(IRQ模块):
/* include/linux/interrupt.h */
static inline int __must_check
devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
{
return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags,
devname, dev_id);
}
/* kernel/irq/devres.c */
int devm_request_threaded_irq(struct device *dev, unsigned int irq,
irq_handler_t handler, irq_handler_t thread_fn,
unsigned long irqflags, const char *devname,
void *dev_id)
{
struct irq_devres *dr;
int rc;
// 申请设备资源
dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres),
GFP_KERNEL);
if (!dr)
return -ENOMEM;
// 使用设备资源做自己的事情
rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
dev_id);
// 如果失败,可以通过devres_free接口释放资源占用的空间
if (rc) {
devres_free(dr);
return rc;
}
dr->irq = irq;
dr->dev_id = dev_id;
// 注册所使用的设备资源
devres_add(dev, dr);
return 0;
}
EXPORT_SYMBOL(devm_request_threaded_irq);
void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id)
{
struct irq_devres match_data = { irq, dev_id };
WARN_ON(devres_destroy(dev, devm_irq_release, devm_irq_match,
&match_data));
free_irq(irq, dev_id);
}
EXPORT_SYMBOL(devm_free_irq);
前面我们提过,上层的IRQ framework,会提供两个和request_irq/free_irq基本兼容的接口,这两个接口的实现非常简单,就是在原有的实现之上,封装一层devres的操作。
irq_devres原型
用于保存和resource有关的信息(对中断来说,就是IRQ num)
/*
* Device resource management aware IRQ request/free implementation.
*/
struct irq_devres {
unsigned int irq;
void *dev_id;
};
devm_irq_release
用于release resource的回调函数(这里的release,和memory无关,例如free IRQ)
static void devm_irq_release(struct device *dev, void *res)
{
struct irq_devres *this = res;
free_irq(this->irq, this->dev_id);
}
因为回调函数是由devres模块调用的,由它的参数可知,struct irq_devres变量就是实际的“资源”,但对devres而言,它并不知道该资源的实际形态,因而是void类型指针。也只有这样,devres模块才可以统一的处理所有类型的资源。
申请设备资源
dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres),
GFP_KERNEL);
if (!dr)
return -ENOMEM;
以回调函数、resource的size为参数,调用devres_alloc接口,为resource分配空间。
使用设备资源做自己的事情
// 使用设备资源做自己的事情
rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
dev_id);
// 如果失败,可以通过devres_free接口释放资源占用的空间
if (rc) {
devres_free(dr);
return rc;
}
调用原来的中断注册接口(这里是request_threaded_irq),注册中断。该步骤和device resource management无关。
如果失败了,可以通过devres_free
接口释放资源占用的空间。
注册所使用的设备资源
注册成功后,以设备指针(dev)和资源指针(dr)为参数,调用devres_add
,将资源添加到设备的资源链表头
devres_add(dev, dr);
到这里,设备资源管理框架就可以:用来在不需要使用的时候摧毁资源了。
用完以后摧毁资源
在irq
系统中,我们会调用devm_free_irq
来释放中断。
void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id)
{
struct irq_devres match_data = { irq, dev_id };
WARN_ON(devres_destroy(dev, devm_irq_release, devm_irq_match,
&match_data));
free_irq(irq, dev_id);
}
而其中就会调用devres_destroy
接口,将devres
从devres_head
中移除,并释放资源。
向设备模型提供的接口
向设备模型提供的接口:devres_release_all
这里是重点,用于自动释放资源。
devres_release_all
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);
}
以设备指针为参数,直接调用release_nodes
:
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;
// 将设备所有的`devres`从设备的`devres_head`中移除
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");
// 调用所有资源的release回调函数(例如上面`devm_irq_release`),
// 回调函数会回收具体的资源(如`free_irq`)。
dr->node.release(dev, dr->data);
// 最后,调用free,释放devres以及资源所占的空间
kfree(dr);
}
return cnt;
}
调用时机
先回忆一下设备模型中probe的流程,devres_release_all接口被调用的时机有两个:
really_probe
失败- 设备与驱动分离时:
deriver dettach
时(就是driver remove
时)
really_probe失败
probe调用过程为(就不详细的贴代码了):__driver_attach/__device_attach
-->driver_probe_device
—>really_probe
。
really_probe调用driver或者bus的probe接口,如果失败(返回值非零,可参考本文开头的例子),则会调用devres_release_all
。
static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = 0;
atomic_inc(&probe_count);
dev->driver = drv;
/* If using pinctrl, bind pins now before probing */
ret = pinctrl_bind_pins(dev);
if (ret)
goto probe_failed;
if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
driver_bound(dev);
ret = 1;
pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
goto done;
probe_failed:
devres_release_all(dev);
// ...
return ret;
}
设备与驱动分离时
另外一个时机是在,deriver dettach
时(就是driver remove
时):driver_detach/bus_remove_device
-->__device_release_driver-
->devres_release_all
我们看下leds-gpio.c中如何使用的:
int devm_of_led_classdev_register(struct device *parent, struct device_node *np, struct led_classdev *led_cdev) { struct led_classdev **dr; int rc; dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL); if (!dr) return -ENOMEM; rc = of_led_classdev_register(parent, np, led_cdev); if (rc) { devres_free(dr); return rc; } *dr = led_cdev; devres_add(parent, dr); return 0; }
这里就是通过devres_alloc分配了一个LED的设备资源空间,然后指向了led_cdev,最后通过devres_add挂到设备资源管理列表中。等到设备detach的时候就会释放掉 led_cdev所占的资源。