Linux设备模型之device resource management

相信每一个写过Linux driver的工程师,都在probe函数中遇到过上面的困惑:要顺序申请多种资源(IRQClockmemoryregionsioremapdma、etc.),只要任意一种资源申请失败,就要回滚释放之前申请的所有资源。于是函数的最后,一定会出现很多的gotoreturn标签,并在申请资源出错时,小心翼翼的goto到正确的标签上,以便释放已申请资源。

Linux设备模型借助device resource management(设备资源管理),帮我们解决了整个函数被大段的、重复的“if (condition) { err = xxx; goto xxx; }”充斥的问题。就是:driver只管申请就行了,不用考虑释放,设备模型帮住释放。最终,我们的driver可以这样写:

static int ls1x_rtc_probe(struct platform_device *pdev)
{
	struct rtc_device *rtcdev;
	unsigned long v;

	v = readl(SYS_COUNTER_CNTRL);
	if (!(v & RTC_CNTR_OK)) {
		dev_err(&pdev->dev, "rtc counters not working\n");
		return -ENODEV;
	}

	/* set to 1 HZ if needed */
	if (readl(SYS_TOYTRIM) != 32767) {
		v = 0x100000;
		while ((readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_TTS) && --v)
			usleep_range(1000, 3000);

		if (!v) {
			dev_err(&pdev->dev, "time out\n");
			return -ETIMEDOUT;
		}
		writel(32767, SYS_TOYTRIM);
	}
	/* this loop coundn't be endless */
	while (readl(SYS_COUNTER_CNTRL) & SYS_CNTRL_TTS)
		usleep_range(1000, 3000);

	rtcdev = devm_rtc_allocate_device(&pdev->dev);
	if (IS_ERR(rtcdev))
		return PTR_ERR(rtcdev);

	platform_set_drvdata(pdev, rtcdev);
	rtcdev->ops = &ls1x_rtc_ops;
	rtcdev->range_min = RTC_TIMESTAMP_BEGIN_1900;
	rtcdev->range_max = RTC_TIMESTAMP_END_2099;

	return devm_rtc_register_device(rtcdev);
}

怎么做到呢?注意上面devm_开头的接口,答案就在那里。不要再使用那些常规的资源申请接口,用devm_xxx的接口代替。为了保持兼容,这些新接口和旧接口的参数保持一致,只是名字前加了devm_,并多加一个struct device指针。

1. devm_xxx

下面列举一些常用的资源申请接口,它们由各个framework(如clockregulatorgpio、等等)基于device resource management实现。使用时,直接忽略devm_的前缀,后面剩下的部分。只需记住一点,driver可以只申请,不释放,设备模型会帮忙释放。不过如果为了严谨,在driver remove时,可以主动释放(也有相应的接口,这里没有列出)。

 extern void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp);
  
 void __iomem *devm_ioremap_resource(struct device *dev, 
   struct resource *res);
 void __iomem *devm_ioremap(struct device *dev, resource_size_t offset,
   unsigned long size);
  
 struct clk *devm_clk_get(struct device *dev, const char *id);
  
 int devm_gpio_request(struct device *dev, unsigned gpio,
   const char *label);
  
 static inline struct pinctrl * devm_pinctrl_get_select(
   struct device *dev, const char *name)
  
 static inline struct pwm_device *devm_pwm_get(struct device *dev,
   const char *consumer);
  
 struct regulator *devm_regulator_get(struct device *dev, const char *id);
  
 static inline int devm_request_irq(struct device *dev, unsigned int irq, 
   irq_handler_t handler, unsigned long irqflags, 
   const char *devname, void *dev_id);
  
 struct reset_control *devm_reset_control_get(struct device *dev, 
   const char *id);

2. 设备资源

一个设备能工作,需要依赖很多的外部条件,如供电、时钟等等,这些外部条件称作设备资源(device resouce)。对于现代计算机的体系结构,可能的资源包括:

a)power,供电。
b)clock,时钟。
c)memory,内存,在kernel中一般使用kzalloc分配。
d)GPIO,用户和CPU交换简单控制、状态等信息。
e)IRQ,触发中断。
f)DMA,无CPU参与情况下进行数据传输。
g)虚拟地址空间,一般使用ioremap、request_region等分配。
h)等等

而在Linux kernel的眼中,“资源”的定义更为广义,比如PWMRTCReset,都可以抽象为资源,供driver使用。

在较早的kernel中,系统还不是特别复杂,且各个framework还没有成型,因此大多的资源都由driver自行维护。但随着系统复杂度的增加,driver之间共用资源的情况越来越多,同时电源管理的需求也越来越迫切。于是kernel就将各个resource的管理权收回,基于device resource management的框架,由各个framework统一管理,包括分配和回收。

3. device resource management的软件框架

在这里插入图片描述
device resource management位于drivers/base/devres.c中,它的实现非常简单,为什么呢?因为资源的种类有很多,表现形式也多种多样,而devres不可能一一知情,也就不能进行具体的分配和回收。因此,devres能做的(也是它的唯一功能),就是:

提供一种机制,将系统中某个设备的所有资源,以链表的形式,组织起来,以便在driver detach的时候,自动释放。

而更为具体的事情,如怎么抽象某一种设备,则由上层的framework负责。这些framework包括:regulator framework(管理power资源),clock framework(管理clock资源),interrupt framework(管理中断资源)、gpio framework(管理gpio资源),pwm framework(管理PWM),等等。

其它的driver,位于这些framework之上,使用它们提供的机制和接口,开发起来就非常方便了。

4. 代码分析

4.1 devres_head

先从struct device开始吧!该结构中有一个名称为devres_head的链表头,用于保存该设备申请的所有资源。

struct device {
    // ...
    spinlock_t    devres_lock;
    struct list_head   devres_head;
    //...
}

不知道您是否注意到,devres有关的数据结构,是在devres.c中定义的。
换句话说,是对其它模块透明的。这真是优雅的设计(尽量屏蔽细节)!
当然了,有关的接口还是公开的

// drivers/base/devres.c
struct devres {
	struct devres_node		node;
	/*
	 * Some archs want to perform DMA into kmalloc caches
	 * and need a guaranteed alignment larger than
	 * the alignment of a 64-bit integer.
	 * Thus we use ARCH_KMALLOC_MINALIGN here and get exactly the same
	 * buffer alignment as if it was allocated by plain kmalloc().
	 */
	u8 __aligned(ARCH_KMALLOC_MINALIGN) data[];
};

咋一看非常简单,一个struct devres_node的变量node,一个零长度数组data,但其中有无穷奥妙,让我们继续分析。

  • node用于将devres组织起来,方便插入到device结构的devres_head链表中,因此一定也有一个list_head(见下面的entry)。
  • data是一个零长数组,用于存放所申请的不定长内存;因为整个memory空间是连续的,因此可以通过释放devres指针,释放所有的空间,包括data所指的那片不定长度的、具体资源所用的空间。

另外,资源的存在形式到底是什么,device resource management并不知情,因此需要上层模块提供一个release的回调函数,用于release资源,如下:

struct devres_node {
	struct list_head		entry;
	dr_release_t			release;
	const char			*name;
	size_t				size;
};

  • entry:刚刚说了,devres使用node用于将devres组织起来,方便插入到device结构的devres_head链表中
  • release:资源的存在形式到底是什么,device resource management并不知情,因此需要上层模块提供一个release的回调函数,用于release资源。

也很简单,一个entry list_head,一个release回调函数。看不出怎么抽象资源啊!别急,奥妙都在data这个零长度数组上面呢。

注1:不知道您是否注意到,devres有关的数据结构,是在devres.c中定义的。换句话说,是对其它模块透明的。这真是优雅的设计(尽量屏蔽细节)!

4.2 一个无关话题:零长度数组

零长度数组的英文原名为Arrays of Length Zero,是GNU C的规范,主要用途是用来作为结构体的最后一个成员,然后用它来访问此结构体对象之后的一段内存(通常是动态分配的内存)。什么意思呢?

struct devres为例,node变量的长度为3个指针的长度,而struct devres的长度也是3个指针的长度。而data只是一个标记,当有人分配了大于3个指针长度的空间并把它转换为struct devres类型的变量后,我们就可以通过data来访问多出来的memory。也就是说,有了零长度数组datastruct devres结构的长度可以不定,完全依赖于你分配的空间的大小。有什么用呢?

以本文的应用场景为例,多出来的、可通过data访问的空间,正是具体的device resource所占的空间。资源的类型不同,占用的空间的多少也不同,但devres模块的主要功能又是释放资源所占的资源。这是就是零长度数组的功能之一,因为整个memory空间是连续的,因此可以通过释devres指针,释放所有的空间,包括data所指的那片不定长度的、具体资源所用的空间。

零长度数组(data[0]),在不同的C版本中,有不同的实现方案,包括1长度数组(data[1])和不定长度数组(data[],本文所描述就是这一种),具体可参考GCC的规范:
https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html

5. 向上层framework提供的接口

其实有两对:

  • devres_alloc/devres_free(这个在内核中没有找到)
  • devres_add/devres_remove
commit 171a70afbde9a3e7499d7d3efde8ca49f7e5f00d
Author: Yicong Yang <yangyicong@hisilicon.com>
Date:   Thu Apr 8 19:38:15 2021 +0800

    iio: trigger: simplify __devm_iio_trigger_register
    
    Use devm_add_action_or_reset() instead of devres_alloc() and
    devres_add(), which works the same. This will simplify the
    code. There is no functional changes.
    
    Signed-off-by: Yicong Yang <yangyicong@hisilicon.com>
    Reviewed-by: Lars-Peter Clausen <lars@metafoo.de>
    Reviewed-by: Nuno Sa <nuno.sa@analog.com>
    Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
    Link: https://lore.kernel.org/r/1617881896-3164-7-git-send-email-yangyicong@hisilicon.com
    Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>

commit cf5724e91515e8b016019b148c99bdf7c58f3ab7
Author: Yicong Yang <yangyicong@hisilicon.com>
Date:   Thu Apr 8 19:38:14 2021 +0800

    iio: core: simplify some devm functions
    
    Use devm_add_action_or_reset() instead of devres_alloc() and
    devres_add(), which works the same. This will simplify the
    code. There is no functional changes.
    
    Signed-off-by: Yicong Yang <yangyicong@hisilicon.com>
    Reviewed-by: Lars-Peter Clausen <lars@metafoo.de>
    Reviewed-by: Nuno Sa <nuno.sa@analog.com>
    Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
    Link: https://lore.kernel.org/r/1617881896-3164-6-git-send-email-yangyicong@hisilicon.com
    Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>

5.1 devres_alloc/devres_free

static inline int devm_add_action_or_reset(struct device *dev,
					   void (*action)(void *), void *data)
{
	int ret;

	ret = devm_add_action(dev, action, data);
	if (ret)
		action(data);

	return ret;
}
/**
 * devm_add_action() - add a custom action to list of managed resources
 * @dev: Device that owns the action
 * @action: Function that should be called
 * @data: Pointer to data passed to @action implementation
 *
 * This adds a custom action to the list of managed resources so that
 * it gets executed as part of standard resource unwinding.
 */
int devm_add_action(struct device *dev, void (*action)(void *), void *data)
{
	struct action_devres *devres;

	devres = devres_alloc(devm_action_release,
			      sizeof(struct action_devres), GFP_KERNEL);
	if (!devres)
		return -ENOMEM;

	devres->data = data;
	devres->action = action;

	devres_add(dev, devres);
	return 0;
}
EXPORT_SYMBOL_GPL(devm_add_action);
#define devres_alloc(release, size, gfp) \
	__devres_alloc_node(release, size, gfp, NUMA_NO_NODE, #release)
/**
 * __devres_alloc_node - Allocate device resource data
 * @release: Release function devres will be associated with
 * @size: Allocation size
 * @gfp: Allocation flags
 * @nid: NUMA node
 * @name: Name of the resource
 *
 * Allocate devres of @size bytes.  The allocated area is zeroed, then
 * associated with @release.  The returned pointer can be passed to
 * other devres_*() functions.
 *
 * RETURNS:
 * Pointer to allocated devres on success, NULL on failure.
 */
void *__devres_alloc_node(dr_release_t release, size_t size, gfp_t gfp, int nid,
			  const char *name)
{
	struct devres *dr;

	dr = alloc_dr(release, size, gfp | __GFP_ZERO, nid);
	if (unlikely(!dr))
		return NULL;
	set_node_dbginfo(&dr->node, name, size);
	return dr->data;
}
EXPORT_SYMBOL_GPL(__devres_alloc_node);
/**
 * devres_free - Free device resource data
 * @res: Pointer to devres data to free
 *
 * Free devres created with devres_alloc().
 */
void devres_free(void *res)
{
	if (res) {
		struct devres *dr = container_of(res, struct devres, data);

		BUG_ON(!list_empty(&dr->node.entry));
		kfree(dr);
	}
}
EXPORT_SYMBOL_GPL(devres_free);

devm_add_action_or_reset调用devm_add_action继而调用devres_alloc,调用alloc_dr,分配一个struct devres类型的变量,并返回其中的data指针(data变量实际上是资源的代表)。

5.1.1 在alloc_dr中初始化
static __always_inline struct devres * alloc_dr(dr_release_t release,
						size_t size, gfp_t gfp, int nid)
{
	size_t tot_size;
	struct devres *dr;

	if (!check_dr_size(size, &tot_size))
		return NULL;

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

5.2 devres_add/devres_remove

/**
 * devres_add - Register device resource
 * @dev: Device to add resource to
 * @res: Resource to register
 *
 * Register devres @res to @dev.  @res should have been allocated
 * using devres_alloc().  On driver detach, the associated release
 * function will be invoked and devres will be freed automatically.
 */
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);// 将资源添加到设备的资源链表头(devres_head)中。
	spin_unlock_irqrestore(&dev->devres_lock, flags);
}
EXPORT_SYMBOL_GPL(devres_add);


/**
 * devres_remove - Find a device resource and remove 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
 * returned.
 *
 * RETURNS:
 * Pointer to removed devres on success, NULL if not found.
 */
void * devres_remove(struct device *dev, dr_release_t release,
		     dr_match_t match, void *match_data)
{
	struct devres *dr;
	unsigned long flags;

	spin_lock_irqsave(&dev->devres_lock, flags);
	dr = find_dr(dev, release, match, match_data);
	if (dr) {
		list_del_init(&dr->node.entry);
		devres_log(dev, &dr->node, "REM");
	}
	spin_unlock_irqrestore(&dev->devres_lock, flags);

	if (dr)
		return dr->data;
	return NULL;
}
EXPORT_SYMBOL_GPL(devres_remove);

从资源指针中,取出完整的struct devres指针,调用add_dr接口。

5.2.1 使用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);
}
5.2.2 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);

6. 以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 */

/**
 *	devm_request_threaded_irq - allocate an interrupt line for a managed device
 *	@dev: device to request interrupt for
 *	@irq: Interrupt line to allocate
 *	@handler: Function to be called when the IRQ occurs
 *	@thread_fn: function to be called in a threaded interrupt context. NULL
 *		    for devices which handle everything in @handler
 *	@irqflags: Interrupt type flags
 *	@devname: An ascii name for the claiming device, dev_name(dev) if NULL
 *	@dev_id: A cookie passed back to the handler function
 *
 *	Except for the extra @dev argument, this function takes the
 *	same arguments and performs the same function as
 *	request_threaded_irq().  IRQs requested with this function will be
 *	automatically freed on driver detach.
 *
 *	If an IRQ allocated with this function needs to be freed
 *	separately, devm_free_irq() must be used.
 */
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);
	 // 如果失败,可以通过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);
/* kernel/irq/devres.c */
/**
 *	devm_free_irq - free an interrupt
 *	@dev: device to free interrupt for
 *	@irq: Interrupt line to free
 *	@dev_id: Device identity to free
 *
 *	Except for the extra @dev argument, this function takes the
 *	same arguments and performs the same function as free_irq().
 *	This function instead of free_irq() should be used to manually
 *	free IRQs allocated with devm_request_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的操作。

6.1 irq_devres原型

用于保存和resource有关的信息(对中断来说,就是IRQ num)

/*
 * Device resource management aware IRQ request/free implementation.
 */
struct irq_devres {
	unsigned int irq;
	void *dev_id;
};

6.2 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模块才可以统一的处理所有类型的资源。

6.3 申请设备资源

/**
 *	devm_request_threaded_irq - allocate an interrupt line for a managed device
 *	@dev: device to request interrupt for
 *	@irq: Interrupt line to allocate
 *	@handler: Function to be called when the IRQ occurs
 *	@thread_fn: function to be called in a threaded interrupt context. NULL
 *		    for devices which handle everything in @handler
 *	@irqflags: Interrupt type flags
 *	@devname: An ascii name for the claiming device, dev_name(dev) if NULL
 *	@dev_id: A cookie passed back to the handler function
 *
 *	Except for the extra @dev argument, this function takes the
 *	same arguments and performs the same function as
 *	request_threaded_irq().  IRQs requested with this function will be
 *	automatically freed on driver detach.
 *
 *	If an IRQ allocated with this function needs to be freed
 *	separately, devm_free_irq() must be used.
 */
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;
}
EXPORT_SYMBOL(devm_request_threaded_irq);

以回调函数、resourcesize为参数,调用devres_alloc接口,为resource分配空间。

	rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
				  dev_id);
	if (rc) {
		devres_free(dr);
		return rc;
	}

6.4 使用设备资源做自己的事情

/**
 *	devm_request_threaded_irq - allocate an interrupt line for a managed device
 *	@dev: device to request interrupt for
 *	@irq: Interrupt line to allocate
 *	@handler: Function to be called when the IRQ occurs
 *	@thread_fn: function to be called in a threaded interrupt context. NULL
 *		    for devices which handle everything in @handler
 *	@irqflags: Interrupt type flags
 *	@devname: An ascii name for the claiming device, dev_name(dev) if NULL
 *	@dev_id: A cookie passed back to the handler function
 *
 *	Except for the extra @dev argument, this function takes the
 *	same arguments and performs the same function as
 *	request_threaded_irq().  IRQs requested with this function will be
 *	automatically freed on driver detach.
 *
 *	If an IRQ allocated with this function needs to be freed
 *	separately, devm_free_irq() must be used.
 */
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);
	// 如果失败,可以通过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);

调用原来的中断注册接口(这里是request_threaded_irq),注册中断。该步骤和device resource management无关。

如果失败了,可以通过devres_free接口释放资源占用的空间。

6.5 注册所使用的设备资源

注册成功后,以设备指针(dev)和资源指针(dr)为参数,调用devres_add,将资源添加到设备的资源链表头

	dr->irq = irq;
	dr->dev_id = dev_id;
	devres_add(dev, dr);

到这里,设备资源管理框架就可以:用来在不需要使用的时候摧毁资源了。

6.6 用完以后摧毁资源

irq系统中,我们会调用devm_free_irq来释放中断。

/**
 *	devm_free_irq - free an interrupt
 *	@dev: device to free interrupt for
 *	@irq: Interrupt line to free
 *	@dev_id: Device identity to free
 *
 *	Except for the extra @dev argument, this function takes the
 *	same arguments and performs the same function as free_irq().
 *	This function instead of free_irq() should be used to manually
 *	free IRQs allocated with devm_request_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);

而其中就会调用devres_destroy接口,将devresdevres_head中移除,并释放资源。

7. 向设备模型提供的接口

向设备模型提供的接口:devres_release_all

这里是重点,用于自动释放资源。

7.1 devres_release_all

/**
 * devres_release_all - Release all managed resources
 * @dev: Device to release resources for
 *
 * Release all resources associated with @dev.  This function is
 * called on driver detach.
 */
int devres_release_all(struct device *dev)
{
	unsigned long flags;
	LIST_HEAD(todo);
	int cnt;

	/* Looks like an uninitialized device structure */
	if (WARN_ON(dev->devres_head.next == NULL))
		return -ENODEV;

	/* Nothing to release if list is empty */
	if (list_empty(&dev->devres_head))
		return 0;

	spin_lock_irqsave(&dev->devres_lock, flags);
	cnt = remove_nodes(dev, dev->devres_head.next, &dev->devres_head, &todo);
	spin_unlock_irqrestore(&dev->devres_lock, flags);

	release_nodes(dev, &todo);
	return cnt;
}

以设备指针为参数,直接调用release_nodes

static void release_nodes(struct device *dev, struct list_head *todo)
{
	struct devres *dr, *tmp;

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

7.2 调用时机

先回忆一下设备模型中probe的流程,devres_release_all接口被调用的时机有两个:

  • really_probe失败
  • 设备与驱动分离时:deriver dettach时(就是driver remove时)
7.2.1 really_probe失败

probe调用过程为(就不详细的贴代码了):

__driver_attach/__device_attach-->driver_probe_device—>really_probe

really_probe调用driver或者busprobe接口,如果失败(返回值非零,可参考本文开头的例子),则会调用devres_release_all

static int really_probe(struct device *dev, struct device_driver *drv)
{
	bool test_remove = IS_ENABLED(CONFIG_DEBUG_TEST_DRIVER_REMOVE) &&
			   !drv->suppress_bind_attrs;
	int ret;

	if (defer_all_probes) {
		/*
		 * Value of defer_all_probes can be set only by
		 * device_block_probing() which, in turn, will call
		 * wait_for_device_probe() right after that to avoid any races.
		 */
		dev_dbg(dev, "Driver %s force probe deferral\n", drv->name);
		return -EPROBE_DEFER;
	}

	ret = device_links_check_suppliers(dev);
	if (ret)
		return ret;

	pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
		 drv->bus->name, __func__, drv->name, dev_name(dev));
	if (!list_empty(&dev->devres_head)) {
		dev_crit(dev, "Resources present before probing\n");
		ret = -EBUSY;
		goto done;
	}

re_probe:
	dev->driver = drv;

	/* If using pinctrl, bind pins now before probing */
	ret = pinctrl_bind_pins(dev);
	if (ret)
		goto pinctrl_bind_failed;

	if (dev->bus->dma_configure) {
		ret = dev->bus->dma_configure(dev);
		if (ret)
			goto pinctrl_bind_failed;
	}

	ret = driver_sysfs_add(dev);
	if (ret) {
		pr_err("%s: driver_sysfs_add(%s) failed\n",
		       __func__, dev_name(dev));
		goto sysfs_failed;
	}

	if (dev->pm_domain && dev->pm_domain->activate) {
		ret = dev->pm_domain->activate(dev);
		if (ret)
			goto probe_failed;
	}

	ret = call_driver_probe(dev, drv);
	if (ret) {
		/*
		 * Return probe errors as positive values so that the callers
		 * can distinguish them from other errors.
		 */
		ret = -ret;
		goto probe_failed;
	}

	ret = device_add_groups(dev, drv->dev_groups);
	if (ret) {
		dev_err(dev, "device_add_groups() failed\n");
		goto dev_groups_failed;
	}

	if (dev_has_sync_state(dev)) {
		ret = device_create_file(dev, &dev_attr_state_synced);
		if (ret) {
			dev_err(dev, "state_synced sysfs add failed\n");
			goto dev_sysfs_state_synced_failed;
		}
	}

	if (test_remove) {
		test_remove = false;

		device_remove(dev);
		driver_sysfs_remove(dev);
		device_unbind_cleanup(dev);

		goto re_probe;
	}

	pinctrl_init_done(dev);

	if (dev->pm_domain && dev->pm_domain->sync)
		dev->pm_domain->sync(dev);

	driver_bound(dev);
	pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
		 drv->bus->name, __func__, dev_name(dev), drv->name);
	goto done;

dev_sysfs_state_synced_failed:
dev_groups_failed:
	device_remove(dev);
probe_failed:
	driver_sysfs_remove(dev);
sysfs_failed:
	if (dev->bus)
		blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
					     BUS_NOTIFY_DRIVER_NOT_BOUND, dev);
	if (dev->bus && dev->bus->dma_cleanup)
		dev->bus->dma_cleanup(dev);
pinctrl_bind_failed:
	device_links_no_driver(dev);
	device_unbind_cleanup(dev);
done:
	return ret;
}
7.2.2 设备与驱动分离时

另外一个时机是在,deriver dettach时(就是driver remove时):

driver_detach/bus_remove_device-->__device_release_driver-->devres_release_all

refer to
Linux 内核:设备驱动模型(6)设备资源管理
Linux设备模型(9)_device resource management

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值