devm_xxx的函数是在申请资源,释放资源的一种补充,对于devm_xxx打头的申请资源的函数,在设备detach的时候会自动释放申请的资源。本文来看看devm_xxxx机制的实现
首先在设备device结构体中,有一个devres_head链表,维护该设备所绑定的资源,在设备detach的时候,遍历这个链表,一个一个进行资源释放。
struct device {
...
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
...
};
资源的数据结构位于drivers/base/devres.c当中,如下
struct devres {
struct devres_node node;
/* -- 3 pointers */
unsigned long long data[]; /* guarantee ull alignment */
};
第二个参数是零长度数据,由于devres是供所有资源使用的,而每个资源使用的长度不是完全一致的,所以需要有一个不定长的数组,零长度数组的知识可参考https://blog.csdn.net/u012142460/article/details/100080849
第一个参数是devres的node节点,详细如下,一个entry,以及一个释放函数
struct devres_node {
struct list_head entry;
dr_release_t release;
#ifdef CONFIG_DEBUG_DEVRES
const char *name;
size_t size;
#endif
};
相比于普通情况下的资源申请和释放,dev_xxx函数中会使用到devres_alloc/devres_free、devres_add/devres_remove来实现这个机制,下面以申请irq为例来进行说明,首先每种资源都需要有一个资源结构体,中断的资源就是irq和dev_id
struct irq_devres {
unsigned int irq;
void *dev_id;
};
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;
if (!devname)
devname = dev_name(dev);
rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
dev_id);
if (rc) {
devres_free(dr);
return rc;
}
dr->irq = irq;
dr->dev_id = dev_id;
devres_add(dev, dr);
return 0;
}
在上面的函数中,
1、devres_alloc分配该资源结构体空间
2、申请相关资源
3、假如该设备的资源链表中
上面三部就是一个整体的流程,然后我们再把第一步和第三步在深入看一下
static __always_inline struct devres * alloc_dr(dr_release_t release,
size_t size, gfp_t gfp, int nid)
{
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;
}
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;
}
static inline void *devres_alloc(dr_release_t release, size_t size, gfp_t gfp)
{
return devres_alloc_node(release, size, gfp, NUMA_NO_NODE);
}
alloc_dr就是在申请devres结构体,根据传入的size大小来申请不同的大小,并将release释放函数赋值。
完成dr申请之后,将申请的dr放入到该设备结构体device的资源链表中,也就是第三步
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);
}
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);
add_dr(dev, &dr->node);
spin_unlock_irqrestore(&dev->devres_lock, flags);
}
而对于释放的过程,有两类,一种是probe失败,一种是device remove的时候,最终都会调用到devres_release_all
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;
}
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);
}
从上面的代码来看,在释放的时候,遍历dev中的资源链表,然后一个一个注册的release函数,将相应的资源释放掉