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);
}