linux設備模型十一(device resource management)

注:本文參考蝸窩大神的文章,採用4.18版本的內核代碼來分析

http://www.wowotech.net/device_model/device_resource_management.html

 

1. 前言

相信每一個寫過Linux driver的工程師,都在probe函數中遇到過下面的困惑:要順序申請多種資源(IRQ、Clock、memory、regions、ioremap、dma、等等),只要任意一種資源申請失敗,就要回滾釋放之前申請的所有資源。於是函數的最後,一定會出現很多的goto標籤(如下面的err_input_allocate_device、err_input_register_device、等等),並在申請資源出錯時,小心翼翼的goto到正確的標籤上,以便釋放已申請資源。


static int button_init(void)
{
    int error;
    int i;

    /* 1.分配一個input_dev結構體 */
    button_dev =  input_allocate_device();
    if(!button_dev) {   
        printk(KERN_ERR "input_button.c: Not enough memory\n");
        error = -ENOMEM;
        goto err_input_allocate_device;
    }   

    /* 2.設置結構體 */
    /* 2.1 能產生那類事件 */
    set_bit(EV_KEY, button_dev->evbit);     /* 按鍵類事件 */
    set_bit(EV_REP, button_dev->evbit);     /* 按鍵類事件 */

    /* 2.2 能產生上面事件的那些結果 */
    input_set_capability(button_dev, EV_KEY, KEY_L);
    input_set_capability(button_dev, EV_KEY, KEY_S);
    input_set_capability(button_dev, EV_KEY, KEY_ENTER);
    input_set_capability(button_dev, EV_KEY, KEY_LEFTSHIFT);

    /* 2.3 設置廠家,版本,總線類型等... */


    /* 3.註冊input_dev結構體 */
    error = input_register_device(button_dev);
    if(error) {
        printk(KERN_ERR"input_button.c: input_register_device fail\n");
        goto err_input_register_device;
    }

    /* 4.硬件相關的初始化 */
    /* 4.1 申請按鍵中斷 */
    for(i = 0; i < 4; i++)  {
        if(request_irq(pins_desc[i].irq, gpio_keys_irq_isr, IRQF_TRIGGER_RISING |
                    IRQF_TRIGGER_FALLING, pins_desc[i].name, &pins_desc[i])) {
            printk(KERN_ERR"input_button.c: Irq request fail\n");
            error = -EBUSY;
            goto err_request_irq;
        }
    }
 

    return 0;

err_request_irq:
    for( i -= 1; i >= 0; i--)
        free_irq(pins_desc[i].irq, &pins_desc[i]);
    input_unregister_device(button_dev);
err_input_register_device:
    input_free_device(button_dev);
err_input_allocate_device:

    return error;
}

正像上面代碼一樣,整個函數被大段的、重複的“if (condition) { err = xxx; goto xxx; }”充斥,浪費精力,容易出錯,不美觀。有困惑,就有改善的餘地,最終,Linux設備模型藉助device resource management(設備資源管理),幫我們解決了這個問題。就是:driver你只管申請就行了,不用考慮釋放,我設備模型幫你釋放。最終,我們的driver可以這樣寫:

 


static int button_init(void)
{
    int error;
    int i;

    /* 1.分配一個input_dev結構體 */
    button_dev =  devm_input_allocate_device(&pdev->dev);
    if(!button_dev) {   
        printk(KERN_ERR "input_button.c: Not enough memory\n");
        return = -ENOMEM;
    }   

    /* 2.設置結構體 */
    /* 2.1 能產生那類事件 */
    set_bit(EV_KEY, button_dev->evbit);     /* 按鍵類事件 */
    set_bit(EV_REP, button_dev->evbit);     /* 按鍵類事件 */

    /* 2.2 能產生上面事件的那些結果 */
    input_set_capability(button_dev, EV_KEY, KEY_L);
    input_set_capability(button_dev, EV_KEY, KEY_S);
    input_set_capability(button_dev, EV_KEY, KEY_ENTER);
    input_set_capability(button_dev, EV_KEY, KEY_LEFTSHIFT);

    /* 2.3 設置廠家,版本,總線類型等... */


    /* 3.註冊input_dev結構體 */
    error = input_register_device(button_dev);
    if(error) {
        printk(KERN_ERR"input_button.c: input_register_device fail\n");
        return error;
    }

    /* 4.硬件相關的初始化 */
    /* 4.1 申請按鍵中斷 */
    for(i = 0; i < 4; i++)  {
        if(devm_request_threaded_irq(&pdev->dev,pins_desc[i].irq, gpio_keys_irq_isr, IRQF_TRIGGER_RISING |
                    IRQF_TRIGGER_FALLING, pins_desc[i].name, &pins_desc[i])) {
            printk(KERN_ERR"input_button.c: Irq request fail\n");
            return = -EBUSY;
        }
    }
 

    return 0;
}

怎麼做到呢?注意上面“devm_”開頭的接口,答案就在那裏。不要再使用那些常規的資源申請接口,用devm_xxx的接口代替。爲了保持兼容,這些新接口和舊接口的參數保持一致,只是名字前加了“devm_”,並多加一個struct device指針。

 

2. devm_xxx

下面列舉一些常用的資源申請接口,它們由各個framework(如clock、regulator、gpio、等等)基於device resource management實現。使用時,直接忽略“devm_”的前綴,後面剩下的部分,driver工程師都很熟悉。只需記住一點,driver可以只申請,不釋放,設備模型會幫忙釋放。不過如果爲了嚴謹,在driver remove時,可以主動釋放\。

void __iomem *devm_ioremap(struct device *dev, resource_size_t offset,
			   resource_size_t size);


struct clk *devm_clk_get(struct device *dev, const char *id);


extern int __must_check
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);

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

static inline struct pinctrl * __must_check devm_pinctrl_get_select;

void __iomem *devm_ioremap_resource(struct device *dev, struct resource *res);


struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id);

 

void devm_ioremap_release(struct device *dev, void *res);


static void devm_clk_release(struct device *dev, void *res);


extern void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id);



void devm_iounmap(struct device *dev, void __iomem *addr);



static void devm_pwm_release(struct device *dev, void *res);

 

3. 什麼是“設備資源”

一個設備能工作,需要依賴很多的外部條件,如供電、時鐘等等,這些外部條件稱作設備資源(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的眼中,“資源”的定義更爲廣義,比如PWM、RTC、Reset,都可以抽象爲資源,供driver使用。

在較早的kernel中,系統還不是特別複雜,且各個framework還沒有成型,因此大多的資源都由driver自行維護。但隨着系統複雜度的增加,driver之間共用資源的情況越來越多,同時電源管理的需求也越來越迫切。於是kernel就將各個resource的管理權收回,基於“device resource management”的框架,由各個framework統一管理,包括分配和回收。

 

4. device resource management的軟件框架

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之上,使用它們提供的機制和接口,開發起來就非常方便了。

 

5. 代碼分析

5.1 數據結構

先從struct device開始吧!該結構中有一個名稱爲“devres_head”的鏈表頭,用於保存該設備申請的所有資源,如下:


/**
 * struct device - The basic device structure
 * @parent:	The device's "parent" device, the device to which it is attached.
 * 		In most cases, a parent device is some sort of bus or host
 * 		controller. If parent is NULL, the device, is a top-level device,
 * 		which is not usually what you want.
 * @p:		Holds the private data of the driver core portions of the device.
 * 		See the comment of the struct device_private for detail.
 * @kobj:	A top-level, abstract class from which other classes are derived.
 * @init_name:	Initial name of the device.
 * @type:	The type of device.
 * 		This identifies the device type and carries type-specific
 * 		information.
 * @mutex:	Mutex to synchronize calls to its driver.
 * @bus:	Type of bus device is on.
 * @driver:	Which driver has allocated this
 * @platform_data: Platform data specific to the device.
 * 		Example: For devices on custom boards, as typical of embedded
 * 		and SOC based hardware, Linux often uses platform_data to point
 * 		to board-specific structures describing devices and how they
 * 		are wired.  That can include what ports are available, chip
 * 		variants, which GPIO pins act in what additional roles, and so
 * 		on.  This shrinks the "Board Support Packages" (BSPs) and
 * 		minimizes board-specific #ifdefs in drivers.
 * @driver_data: Private pointer for driver specific info.
 * @links:	Links to suppliers and consumers of this device.
 * @power:	For device power management.
 *		See Documentation/driver-api/pm/devices.rst for details.
 * @pm_domain:	Provide callbacks that are executed during system suspend,
 * 		hibernation, system resume and during runtime PM transitions
 * 		along with subsystem-level and driver-level callbacks.
 * @pins:	For device pin management.
 *		See Documentation/driver-api/pinctl.rst for details.
 * @msi_list:	Hosts MSI descriptors
 * @msi_domain: The generic MSI domain this device is using.
 * @numa_node:	NUMA node this device is close to.
 * @dma_ops:    DMA mapping operations for this device.
 * @dma_mask:	Dma mask (if dma'ble device).
 * @coherent_dma_mask: Like dma_mask, but for alloc_coherent mapping as not all
 * 		hardware supports 64-bit addresses for consistent allocations
 * 		such descriptors.
 * @dma_pfn_offset: offset of DMA memory range relatively of RAM
 * @dma_parms:	A low level driver may set these to teach IOMMU code about
 * 		segment limitations.
 * @dma_pools:	Dma pools (if dma'ble device).
 * @dma_mem:	Internal for coherent mem override.
 * @cma_area:	Contiguous memory area for dma allocations
 * @archdata:	For arch-specific additions.
 * @of_node:	Associated device tree node.
 * @fwnode:	Associated device node supplied by platform firmware.
 * @devt:	For creating the sysfs "dev".
 * @id:		device instance
 * @devres_lock: Spinlock to protect the resource of the device.
 * @devres_head: The resources list of the device.
 * @knode_class: The node used to add the device to the class list.
 * @class:	The class of the device.
 * @groups:	Optional attribute groups.
 * @release:	Callback to free the device after all references have
 * 		gone away. This should be set by the allocator of the
 * 		device (i.e. the bus driver that discovered the device).
 * @iommu_group: IOMMU group the device belongs to.
 * @iommu_fwspec: IOMMU-specific properties supplied by firmware.
 *
 * @offline_disabled: If set, the device is permanently online.
 * @offline:	Set after successful invocation of bus type's .offline().
 * @of_node_reused: Set if the device-tree node is shared with an ancestor
 *              device.
 * @dma_32bit_limit: bridge limited to 32bit DMA even if the device itself
 *		indicates support for a higher limit in the dma_mask field.
 *
 * At the lowest level, every device in a Linux system is represented by an
 * instance of struct device. The device structure contains the information
 * that the device model core needs to model the system. Most subsystems,
 * however, track additional information about the devices they host. As a
 * result, it is rare for devices to be represented by bare device structures;
 * instead, that structure, like kobject structures, is usually embedded within
 * a higher-level representation of the device.
 */
struct device {
	struct device		*parent;

	struct device_private	*p;

	struct kobject kobj;
	const char		*init_name; /* initial name of the device */
	const struct device_type *type;

	struct mutex		mutex;	/* mutex to synchronize calls to
					 * its driver.
					 */

	struct bus_type	*bus;		/* type of bus device is on */
	struct device_driver *driver;	/* which driver has allocated this
					   device */
	void		*platform_data;	/* Platform specific data, device
					   core doesn't touch it */
	void		*driver_data;	/* Driver data, set and get with
					   dev_set/get_drvdata */
	struct dev_links_info	links;
	struct dev_pm_info	power;
	struct dev_pm_domain	*pm_domain;

#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
	struct irq_domain	*msi_domain;
#endif
#ifdef CONFIG_PINCTRL
	struct dev_pin_info	*pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
	struct list_head	msi_list;
#endif

#ifdef CONFIG_NUMA
	int		numa_node;	/* NUMA node this device is close to */
#endif
	const struct dma_map_ops *dma_ops;
	u64		*dma_mask;	/* dma mask (if dma'able device) */
	u64		coherent_dma_mask;/* Like dma_mask, but for
					     alloc_coherent mappings as
					     not all hardware supports
					     64 bit addresses for consistent
					     allocations such descriptors. */
	unsigned long	dma_pfn_offset;

	struct device_dma_parameters *dma_parms;

	struct list_head	dma_pools;	/* dma pools (if dma'ble) */

	struct dma_coherent_mem	*dma_mem; /* internal for coherent mem
					     override */
#ifdef CONFIG_DMA_CMA
	struct cma *cma_area;		/* contiguous memory area for dma
					   allocations */
#endif
	/* arch specific additions */
	struct dev_archdata	archdata;

	struct device_node	*of_node; /* associated device tree node */
	struct fwnode_handle	*fwnode; /* firmware device node */

	dev_t			devt;	/* dev_t, creates the sysfs "dev" */
	u32			id;	/* device instance */

	spinlock_t		devres_lock;
	struct list_head	devres_head;

	struct klist_node	knode_class;
	struct class		*class;
	const struct attribute_group **groups;	/* optional groups */

	void	(*release)(struct device *dev);
	struct iommu_group	*iommu_group;
	struct iommu_fwspec	*iommu_fwspec;

	bool			offline_disabled:1;
	bool			offline:1;
	bool			of_node_reused:1;
	bool			dma_32bit_limit:1;
};

 這個是一個比較複雜的結構體,之前我們分析過。這裏主要關注兩個東西。


/**
 * struct device - The basic device structure

 * @devres_lock: Spinlock to protect the resource of the device.
 * @devres_head: The resources list of the device.

 * At the lowest level, every device in a Linux system is represented by an
 * instance of struct device. The device structure contains the information
 * that the device model core needs to model the system. Most subsystems,
 * however, track additional information about the devices they host. As a
 * result, it is rare for devices to be represented by bare device structures;
 * instead, that structure, like kobject structures, is usually embedded within
 * a higher-level representation of the device.
 */
struct device {

    ......
	spinlock_t		devres_lock;
	struct list_head	devres_head;
    ......

};

那資源的數據結構呢?在“drivers/base/devres.c”中,名稱爲struct devres,如下:

struct devres {
	struct devres_node		node;
	/* -- 3 pointers */
	unsigned long long		data[];	/* guarantee ull alignment */
};

咋一看非常簡單,一個struct devres_node的變量node,一個零長度數組data,但其中有無窮奧妙,讓我們繼續分析。

node用於將devres組織起來,方便插入到device結構的devres_head鏈表中,因此一定也有一個list_head(見下面的entry)。另外,資源的存在形式到底是什麼,device resource management並不知情,因此需要上層模塊提供一個release的回調函數,用於release資源,如下:

struct devres_node {
	struct list_head		entry;
	dr_release_t			release;
#ifdef CONFIG_DEBUG_DEVRES
	const char			*name;
	size_t				size;
#endif
};

拋開用於debug的變量不說,也很簡單,一個entry list_head,一個release回調函數。看不出怎麼抽象資源啊!別急,奧妙都在data這個零長度數組上面呢。

注1:不知道您是否注意到,devres有關的數據結構,是在devres.c中定義的(是C文件哦!)。換句話說,是對其它模塊透明的。這真是優雅的設計(儘量屏蔽細節)!

5.2 一個無關話題:零長度數組

零長度數組的英文原名爲Arrays of Length Zero,是GNU C的規範,主要用途是用來作爲結構體的最後一個成員,然後用它來訪問此結構體對象之後的一段內存(通常是動態分配的內存)。什麼意思呢?

以struct devres爲例,node變量的長度爲3個指針的長度,而struct devres的長度也是3個指針的長度。而data只是一個標記,當有人分配了大於3個指針長度的空間並把它轉換爲struct devres類型的變量後,我們就可以通過data來訪問多出來的memory。也就是說,有了零長度數組data,struct 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.3 向上層framework提供的接口:devres_alloc/devres_free、devres_add/devres_remove

先看一個使用device resource management的例子(IRQ模塊):

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





/**
 *	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),    // 見下面 3 分析
			  GFP_KERNEL);
	if (!dr)
		return -ENOMEM;

	if (!devname)
		devname = dev_name(dev);

	rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,    //見下面4分析
				  dev_id);
	if (rc) {
		devres_free(dr);
		return rc;
	}

	dr->irq = irq;
	dr->dev_id = dev_id;
	devres_add(dev, dr);                //見下面 5分析

	return 0;
}
EXPORT_SYMBOL(devm_request_threaded_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);
}

 

前面我們提過,上層的IRQ framework,會提供兩個和request_irq/free_irq基本兼容的接口,這兩個接口的實現非常簡單,就是在原有的實現之上,封裝一層devres的操作,如要包括:

1)一個自定義的數據結構(struct irq_devres),用於保存和resource有關的信息(對中斷來說,就是IRQ num),如下:

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

2)一個用於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模塊纔可以統一的處理所有類型的資源。

3)以回調函數、resource的size爲參數,調用devres_alloc接口,爲resource分配空間。該接口的定義如下:

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



/**
 * devres_alloc - Allocate device resource data
 * @release: Release function devres will be associated with
 * @size: Allocation size
 * @gfp: Allocation flags
 * @nid: NUMA node
 *
 * 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)
{
	struct devres *dr;

	dr = alloc_dr(release, size, gfp | __GFP_ZERO, nid);
	if (unlikely(!dr))
		return NULL;
	return dr->data;
}

調用alloc_dr,分配一個struct devres類型的變量,並返回其中的data指針(5.2小節講過了,data變量實際上是資源的代表)。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;

	/* We must catch any near-SIZE_MAX cases that could overflow. */
	if (unlikely(check_add_overflow(sizeof(struct devres), 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;
}

可以看到在資源size之前,加一個struct devres的size,就是total分配的空間。除去struct devres的,就是資源的(由data指針訪問)。之後是初始化struct devres變量的node。

4)調用原來的中斷註冊接口(這裏是request_threaded_irq),註冊中斷。該步驟和device resource management無關。

5)註冊成功後,以設備指針(dev)和資源指針(dr)爲參數,調用devres_add,將資源添加到設備的資源鏈表頭(devres_head)中,該接口定義如下:


/**
 * 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);
	spin_unlock_irqrestore(&dev->devres_lock, flags);
}

從資源指針中,取出完整的struct devres指針,調用add_dr接口。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);
}

6)如果失敗,可以通過devres_free接口釋放資源佔用的空間。


/**
 *	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);        //申請中斷失敗,通過這裏釋放掉通過devres_alloc申請的資源
		return rc;
	}

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

	return 0;
}




/**
 * 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_free_irq接口中,會調用devres_destroy接口,將devres從devres_head中移除,並釋放資源。。

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



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


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

上面這寫函數就很簡單了,根據傳進來的資源的釋放函數,在這個設備前面我們所說的entry鏈表中,找到對應的資源,先從entry鏈表中掉,再調用devres_free,釋放掉資源。

 

static struct devres *find_dr(struct device *dev, dr_release_t release,
			      dr_match_t match, void *match_data)
{
	struct devres_node *node;

	list_for_each_entry_reverse(node, &dev->devres_head, entry) {
		struct devres *dr = container_of(node, struct devres, node);

		if (node->release != release)
			continue;
		if (match && !match(dev, dr->data, match_data))
			continue;
		return dr;
	}

	return NULL;
}

資源的查找也很簡單,就是在這個設備所在資源的鏈表中根據release函數的地址和macth值一一查找,相等的就是要找的那個資源的devres 。

 

5.4 向設備模型提供的接口:devres_release_all

這裏是重點,用於自動釋放資源。

先回憶一下設備模型中probe的流程(可參考“https://blog.csdn.net/qq_16777851/article/details/81437352”),devres_release_all接口被調用的時機有兩個:

1)probe失敗時,調用過程爲(就不詳細的貼代碼了):

__driver_attach/__device_attach-->driver_probe_device—>really_probe,really_probe調用driver或者bus的probe接口,如果失敗(返回值非零,可參考本文開頭的例子),則會調用devres_release_all。

int device_add(struct device *dev)
{

    ......

	kobject_uevent(&dev->kobj, KOBJ_ADD);
	bus_probe_device(dev);                              // -------------------------
	if (parent)
		klist_add_tail(&dev->p->knode_parent,
			       &parent->p->klist_children);

    ......	

done:
	put_device(dev);
	return error;

    ......
    
name_error:
	kfree(dev->p);
	dev->p = NULL;
	goto done;
}




void bus_probe_device(struct device *dev)
{
	struct bus_type *bus = dev->bus;
	struct subsys_interface *sif;

	if (!bus)
		return;

	if (bus->p->drivers_autoprobe)
		device_initial_probe(dev);        //-----------------------------

	mutex_lock(&bus->p->mutex);
	list_for_each_entry(sif, &bus->p->interfaces, node)
		if (sif->add_dev)
			sif->add_dev(dev, sif);
	mutex_unlock(&bus->p->mutex);
}


void device_initial_probe(struct device *dev)
{
	__device_attach(dev, true);
}




static int __device_attach(struct device *dev, bool allow_async)
{
	int ret = 0;

	device_lock(dev);
	if (dev->driver) {
		if (device_is_bound(dev)) {
			ret = 1;
			goto out_unlock;
		}
		ret = device_bind_driver(dev);
		if (ret == 0)
			ret = 1;
		else {
			dev->driver = NULL;
			ret = 0;
		}
	} else {
		struct device_attach_data data = {
			.dev = dev,
			.check_async = allow_async,
			.want_async = false,
		};

		if (dev->parent)
			pm_runtime_get_sync(dev->parent);

		ret = bus_for_each_drv(dev->bus, NULL, &data,
					__device_attach_driver);               //--------------------------
		if (!ret && allow_async && data.have_async) {
			/*
			 * If we could not find appropriate driver
			 * synchronously and we are allowed to do
			 * async probes and there are drivers that
			 * want to probe asynchronously, we'll
			 * try them.
			 */
			dev_dbg(dev, "scheduling asynchronous probe\n");
			get_device(dev);
			async_schedule(__device_attach_async_helper, dev);
		} else {
			pm_request_idle(dev);
		}

		if (dev->parent)
			pm_runtime_put(dev->parent);
	}
out_unlock:
	device_unlock(dev);
	return ret;
}




static int __device_attach_driver(struct device_driver *drv, void *_data)
{
    ................

	return driver_probe_device(drv, dev);          //-----------------------
}



int driver_probe_device(struct device_driver *drv, struct device *dev)
{
	int ret = 0;

    ........

	pm_runtime_barrier(dev);
	ret = really_probe(dev, drv);        //-------------------------
	pm_request_idle(dev);

    ........
	
	return ret;
}




static int really_probe(struct device *dev, struct device_driver *drv)
{
	

	
	if (dev->bus->probe) {
		ret = dev->bus->probe(dev);
		if (ret)
			goto probe_failed;
	} else if (drv->probe) {
		ret = drv->probe(dev);
		if (ret)
			goto probe_failed;
	}



	pinctrl_init_done(dev);

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

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

probe_failed:
	dma_deconfigure(dev);
dma_failed:
	if (dev->bus)
		blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
					     BUS_NOTIFY_DRIVER_NOT_BOUND, dev);
pinctrl_bind_failed:
	device_links_no_driver(dev);
	devres_release_all(dev);           //-------------------------------------
	driver_sysfs_remove(dev);
	dev->driver = NULL;
	dev_set_drvdata(dev, NULL);
	if (dev->pm_domain && dev->pm_domain->dismiss)
		dev->pm_domain->dismiss(dev);
	pm_runtime_reinit(dev);
	dev_pm_set_driver_flags(dev, 0);

	switch (ret) {
	case -EPROBE_DEFER:
		/* Driver requested deferred probing */
		dev_dbg(dev, "Driver %s requests probe deferral\n", drv->name);
		driver_deferred_probe_add_trigger(dev, local_trigger_count);
		break;
	case -ENODEV:
	case -ENXIO:
		pr_debug("%s: probe of %s rejects match %d\n",
			 drv->name, dev_name(dev), ret);
		break;
	default:
		/* driver matched but the probe failed */
		printk(KERN_WARNING
		       "%s: probe of %s failed with error %d\n",
		       drv->name, dev_name(dev), ret);
	}
	/*
	 * Ignore errors returned by ->probe so that the next driver can try
	 * its luck.
	 */
	ret = 0;
done:
	atomic_dec(&probe_count);
	wake_up(&probe_waitqueue);
	return ret;
}


 

2)deriver dettach時(就是driver remove時)

driver_detach/bus_remove_device-->__device_release_driver-->devres_release_all

void driver_unregister(struct device_driver *drv)
{
	if (!drv || !drv->p) {
		WARN(1, "Unexpected driver unregister!\n");
		return;
	}
	driver_remove_groups(drv, drv->groups);
	bus_remove_driver(drv);            //---------------------------
}


void bus_remove_driver(struct device_driver *drv)
{
	if (!drv->bus)
		return;

	if (!drv->suppress_bind_attrs)
		remove_bind_files(drv);
	driver_remove_groups(drv, drv->bus->drv_groups);
	driver_remove_file(drv, &driver_attr_uevent);
	klist_remove(&drv->p->knode_bus);
	pr_debug("bus: '%s': remove driver %s\n", drv->bus->name, drv->name);
	driver_detach(drv);           //---------------------------------------
	module_remove_driver(drv);
	kobject_put(&drv->p->kobj);
	bus_put(drv->bus);
}

void driver_detach(struct device_driver *drv)
{
	struct device_private *dev_prv;
	struct device *dev;

	for (;;) {
		spin_lock(&drv->p->klist_devices.k_lock);
		if (list_empty(&drv->p->klist_devices.k_list)) {
			spin_unlock(&drv->p->klist_devices.k_lock);
			break;
		}
		dev_prv = list_entry(drv->p->klist_devices.k_list.prev,
				     struct device_private,
				     knode_driver.n_node);
		dev = dev_prv->device;
		get_device(dev);
		spin_unlock(&drv->p->klist_devices.k_lock);
		device_release_driver_internal(dev, drv, dev->parent);        //---------------
		put_device(dev);
	}
}





void device_release_driver_internal(struct device *dev,
				    struct device_driver *drv,
				    struct device *parent)
{
	if (parent && dev->bus->need_parent_lock)
		device_lock(parent);

	device_lock(dev);
	if (!drv || drv == dev->driver)
		__device_release_driver(dev, parent);       //---------------------

	device_unlock(dev);
	if (parent && dev->bus->need_parent_lock)
		device_unlock(parent);
}


static void __device_release_driver(struct device *dev, struct device *parent)
{
	struct device_driver *drv;

	drv = dev->driver;
	if (drv) {
		if (driver_allows_async_probing(drv))
			async_synchronize_full();

		while (device_links_busy(dev)) {
			device_unlock(dev);
			if (parent)
				device_unlock(parent);

			device_links_unbind_consumers(dev);
			if (parent)
				device_lock(parent);

			device_lock(dev);
			/*
			 * A concurrent invocation of the same function might
			 * have released the driver successfully while this one
			 * was waiting, so check for that.
			 */
			if (dev->driver != drv)
				return;
		}

		pm_runtime_get_sync(dev);
		pm_runtime_clean_up_links(dev);

		driver_sysfs_remove(dev);

		if (dev->bus)
			blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
						     BUS_NOTIFY_UNBIND_DRIVER,
						     dev);

		pm_runtime_put_sync(dev);

		if (dev->bus && dev->bus->remove)
			dev->bus->remove(dev);
		else if (drv->remove)
			drv->remove(dev);

		device_links_driver_cleanup(dev);
		dma_deconfigure(dev);

		devres_release_all(dev);        //---------------------------------------------
		dev->driver = NULL;
		dev_set_drvdata(dev, NULL);
		if (dev->pm_domain && dev->pm_domain->dismiss)
			dev->pm_domain->dismiss(dev);
		pm_runtime_reinit(dev);
		dev_pm_set_driver_flags(dev, 0);

		klist_remove(&dev->p->knode_driver);
		device_pm_check_callbacks(dev);
		if (dev->bus)
			blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
						     BUS_NOTIFY_UNBOUND_DRIVER,
						     dev);

		kobject_uevent(&dev->kobj, KOBJ_UNBIND);
	}
}

 

 

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;

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




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);        //把first鏈表的內容放到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);        //一次執行所有todo鏈表中的release函數
		kfree(dr);                              //之後再把devres 釋放掉
	}

	return cnt;
}

release_nodes會先調用remove_nodes,將設備所有的struct devres指針從設備的devres_head中移除。然後,調用所有資源的release回調函數(如5.3小節描述的devm_irq_release),回調函數會回收具體的資源(如free_irq)。最後,調用free,釋放devres以及資源所佔的空間。

 

http://www.wowotech.net/device_model/device_resource_management.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章