###1. devres
传统的linux driver, 在驱动的probe函数中会进行资源的申请, 一旦初始化的过程中出现error, 就需要释放之前申请的资源, 为此需要使用一堆if语句进行条件判断, 或者使用一堆标签配合goto语句来进行跳转,致使整个probe的代码结构变得异常混乱, 并且driver代码不像linxu的core代码, 经过大量的测试, 很多时候, driver的作者都没有考虑完全driver的出错过程的资源释放, 因此进场发生应probe失败导致系统崩溃
为了解决这一问题, linux引入了devres来管理device的资源分配
###2. 为何使用devres
早期的linux系统中, 大多的资源都由driver自行维护, 但是随着系统的复杂度的增加,各driver之间共用资源的情况也越来越多, kernel将resource的管理权回收, 同一管理和回收
devres的代码位于 driver/base/devres.c中, 它的实现非常简单, 并不负责具体回收和分配, 它实现的唯一功能是:提供一种机制, 将系统中某一个设备的所有资源, 以链表的形式组织起来, 以便在driver dettach的时候, 能够被自动释放
具体的实现, 由上层的framwork来实现, 比如 regulator, clock, IRQ, gpio这些framework再提供上层接口给driver开发使用
###3. devres的数据结构
struct device {
......
struct list_head devres_head;
......
}
struct devres {
struct devres_node node;
/* -- 3 pointers */
unsigned long long data[]; /* guarantee ull alignment */
};
struct devres_node {
struct list_head entry;
dr_release_t release;
#ifdef CONFIG_DEBUG_DEVRES
const char *name;
size_t size;
#endif
};
- device.devres_head指向devres.node组成的链表头
- devres.data是一个0长度数组, 在分配资源时, 紧跟在devres结构体后面分配空间, 这样就能够通过devres.data来访问后面资源的memory, 并且可以通过devres的空间来一起释放资源所占用的空间
- devres_node.release回调用于释放该devres
###4. devrest提供给上层的API
devres提供一组API给各个资源的framwork使用(并不是给device driver开发使用) :
- void *devres_alloc(dr_release_t release, size_t size, gfp_t gfp);
- void devres_free(void *res);
- void devres_add(struct device *dev, void *res);
- void *devres_remove(struct device *dev, dr_release_t release,dr_match_t match, void *match_data);
按照系统中的资源分类, devres最终提供给device driver开发所使用的API如下:
MEM
devm_kzalloc()
devm_kfree()
devm_kmemdup()
devm_get_free_pages()
devm_free_pages()
IO region
devm_request_region()
devm_request_mem_region()
devm_release_region()
devm_release_mem_region()
IRQ
devm_request_irq()
devm_free_irq()
DMA
dmam_alloc_coherent()
dmam_free_coherent()
dmam_alloc_noncoherent()
dmam_free_noncoherent()
dmam_declare_coherent_memory()
dmam_pool_create()
dmam_pool_destroy()
PCI
pcim_enable_device() /* after success, all PCI ops become managed */
pcim_pin_device() /* keep PCI device enabled after release */
pcim_iomap()
pcim_iounmap()
pcim_iomap_table() /* array of mapped addresses indexed by BAR */
pcim_iomap_regions() /* do request_region() and iomap() on multiple BARs */
IOMAP
devm_ioport_map()
devm_ioport_unmap()
devm_ioremap()
devm_ioremap_nocache()
devm_iounmap()
devm_ioremap_resource() /* checks resource, requests memory region, ioremaps */
devm_request_and_ioremap() /* obsoleted by devm_ioremap_resource() */
REGULATOR
devm_regulator_get()
devm_regulator_put()
devm_regulator_bulk_get()
CLOCK
devm_clk_get()
devm_clk_put()
PINCTRL
devm_pinctrl_get()
devm_pinctrl_put()
PWM
devm_pwm_get()
devm_pwm_put()
PHY
devm_usb_get_phy()
devm_usb_put_phy()
###5. IRQ实现devres接口
以IRQ的devres接口的实现来说明devres的实现原理
IRQ提供了和 request_irq, free_irq 相兼容的接口:
- devm_request_irq
- devm_free_irq
其实现代码如下:
struct irq_devres {
unsigned int irq;
void *dev_id;
};
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);
}
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);
if (rc) {
devres_free(dr);
return rc;
}
dr->irq = irq;
dr->dev_id = dev_id;
devres_add(dev, dr);
return 0;
}
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);
}
static void devm_irq_release(struct device *dev, void *res)
{
struct irq_devres *this = res;
free_irq(this->irq, this->dev_id);
}
- 使用irq_devres结构体来标识一个IRQ资源, irq_devres.irq 为IRQ号, irq_devres.dev_id为请求irq时的dev_id
- 在调用devm_request_irq时, 分配一块连续内存以存放devres结构体和irq_devres结构体,并指定devres的release函数为devm_irq_release, 设置irq_devres.irq和irq_devres.dev_id成员, 然后调用request_threaded_irq请求irq, 最后调用devres_add将该devres_node添加到device.devres_head链表中
- 调用devm_free_irq时, 会调用devres_remove将该devres_node从device.devres_head链表中移除,并销毁devres结构体(irq_devres), 最后调用free_irq来释放该irq
仅从上面的步骤来看, 和使用原生的 request_irq, free_irq接口相比较, 并无任何优势, 但是若driver中只调用devm_request_irq而不调用devm_free_irq,仍能够保证资源被主动释放(调用devm_irq_release回调), 这个时候, 优势就体现出来了
###6. devres主动释放资源
devres提供了一个api “devres_release_all” 来释放一个device的所有的资源, 在设备驱动的probe和remove流程中:
- probe失败返回时, 在really_probe中会调用 devres_release_all
- driver remove时, 在__device_release_driver中调用 devres_release_all
devres_release_all调用release_nodes来释放device的所有资源, 其中, 先从 device.devres_head上移除该链表, 然后遍历该链表, 对每一个devres_node调用其release回调来释放对应的资源, 然后释放该devres的memory