###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);
}
  1. 使用irq_devres结构体来标识一个IRQ资源, irq_devres.irq 为IRQ号, irq_devres.dev_id为请求irq时的dev_id
  2. 在调用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链表中
  3. 调用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流程中:

  1. probe失败返回时, 在really_probe中会调用 devres_release_all
  2. driver remove时, 在__device_release_driver中调用 devres_release_all

devres_release_all调用release_nodes来释放device的所有资源, 其中, 先从 device.devres_head上移除该链表, 然后遍历该链表, 对每一个devres_node调用其release回调来释放对应的资源, 然后释放该devres的memory