注:本文參考蝸窩大神的文章,採用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位於“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