DRM(device resource management)介紹


一、DRM簡介

1. 在DRM出現之前,在probe函數中要順序申請多種資源(IRQ、Clock、memory、regions、ioremap、dma、等等),只要任意一種資源申請失敗,就要回滾釋放之前申請的所有資源。於是函數的最后,一定會出現很多的goto標簽。最終Linux設備模型借助device resource management(設備資源管理)解決了這個問題。通過”devm_xxx()“函數申請的資源驅動只管申請,不用考慮釋放,設備模型幫你釋放。

2. devm_xxx()函數
它們由各個framework(如clock、regulator、gpio、等等)基於device resource management實現。使用時,直接忽略“devm_”的前綴,后面剩下的部分就是DRM出現之前驅動工程師使用的。雖然通過"devm_xxx()"驅動可以只申請,不釋放,設備模型會幫忙釋放,但是為了嚴謹,在driver remove時,也可以主動釋。

3. 一個設備能工作,需要依賴的外部條件,如供電、時鍾等等,這些外部條件稱作設備資源。可能的資源包括:power、clock、memory(一般使用kzalloc分配)、GPIO、IRQ、DMA、虛擬地址空間(般使用ioremap、request_region等分配)。而在Linux kernel的眼中,“資源”的定義更為廣義,比如PWM、RTC、Reset,都可以抽象為供driver使用的資源。下面列舉出devm的一些常用函數:

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);
struct pinctrl * devm_pinctrl_get_select(struct device *dev, const char *name);
struct pwm_device *devm_pwm_get(struct device *dev,const char *consumer);
struct regulator *devm_regulator_get(struct device *dev, const char *id);
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);

4.device resource management實現位於“drivers/base/devres.c”中,提供一種機制,將系統中某個設備的所有資源,以鏈表的形式,組織起來,以便在driver detach
的時候,自動釋放。

5. devm實現框架簡圖

 

二、代碼分析

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

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

那資源的數據結構在“drivers/base/devres.c”中定義為struct devres結構,如下。每一份資源都通過一個devres結構表示,通過其node域中的鏈表節點掛接到struct device結構中的devres_head鏈表中。

struct devres {
    struct devres_node node;
    /* -- 3 pointers -- */
    /* guarantee ull alignment */
    unsigned long long data[]; /*data邊長數組就表示資源,資源是什么類型就分配多大的空間,然后將其轉換成對應的類型*/
};

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

設備資源框架是不知道該怎么釋放的,因此提供一個回調函數release來讓調用者決定。

注意:devres有關的數據結構,是在devres.c中定義的(不是在.h文件中定義的哦!)。換句話說就是對其它模塊透明的。盡量屏蔽細節,多么優雅的設計。

 

三、以irq為例的資源申請

1. irq的devm的申請和釋放函數

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

    /*分配data的大小是sizeof(struct irq_devres),對返回的data直接轉換成irq_devres結構*/
    dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres), GFP_KERNEL);
    if (!dr)
        return -ENOMEM;

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

    dr->irq = irq;
    dr->dev_id = dev_id;
    /*再將devres結構放到device結構中的鏈表上*/
    devres_add(dev, dr);

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

2. 由上可知,irq的資源由irq模塊的一個自定義的數據結構struct irq_devres來描述,用於保存和resource有關的信息(對中斷來說,就是IRQ num),如下:

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

irq的釋放資源的回調函數,如下:

static void devm_irq_release(struct device *dev, void *res)
{
    struct irq_devres *this = res;
    free_irq(this->irq, this->dev_id);
}

3. devres_alloc函數以release回調和資源大小為參數來分配內存

void * devres_alloc(dr_release_t release, size_t size, gfp_t gfp)
{
    struct devres *dr;
    dr = alloc_dr(release, size, gfp);
    if (unlikely(!dr))
        return NULL;
    return dr->data;
}

調用alloc_dr,分配一個struct devres類型的變量,並返回其中代表資源的data指針,alloc_dr的定義如下:

static __always_inline struct devres * alloc_dr(dr_release_t release, size_t size, gfp_t gfp)
{
    size_t tot_size = sizeof(struct devres) + size;
    struct devres *dr;

    dr = kmalloc_track_caller(tot_size, gfp);
    if (unlikely(!dr))
        return NULL;

    memset(dr, 0, tot_size);
    INIT_LIST_HEAD(&dr->node.entry);
    dr->node.release = release; /*release回調賦值過去*/
    return dr;
}

4. 將資源添加到device結構的鏈表上

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

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); /*注意這里是尾插法插入設備的資源鏈表的*/
}

 

三、資源的自動釋放

1. DRM向設備模型提供的資源釋放的接口為devres_release_all()函數,它是重點,用於自動釋放資源。根據設備模型中probe的流程devres_release_all()接口被調用的時機有兩個:

(1) probe失敗時,調用過程如下

__driver_attach/__device_attach --> driver_probe_device —> really_probe,really_probe調用driver或者bus的probe接口,如果失敗,則會調用devres_release_all()來釋放資源。

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

driver_detach/bus_remove_device --> __device_release_driver --> devres_release_all

2. devres_release_all()的實現如下:

int devres_release_all(struct device *dev)
{
    unsigned long flags;
    /* Looks like an uninitialized device structure */
    /* 如果在函數probe()失敗時驅動中自己已經釋放了,這里就是NULL,就不再次釋放了*/
    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);
}

sstatic int release_nodes(struct device *dev, struct list_head *first, struct list_head *end, unsigned long flags)
 __releases(&dev->devres_lock) /*此為內核代碼靜態分析工具Sparse的annotation。Sparse通過gcc的擴展屬性__attribute__
 以及自己定義的__context__來對代碼進行靜態檢查。*/
{
    LIST_HEAD(todo);
    int cnt;
    struct devres *dr, *tmp;

    cnt = remove_nodes(dev, first, end, &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);
        kfree(dr);
    }

    return cnt;
}

release_nodes會先調用remove_nodes,將設備所有的struct devres指針從設備的devres_head中移除。然后,調用所有資源的release回調函數(如irq的devm_irq_release),回調函數會回收具體的資源(如free_irq)。最后,調用free,釋放devres結構自身以及資源所占的空間。

 

參考:
Linux設備模型(9)_device resource management: http://www.wowotech.net/device_model/device_resource_management.html
Linux設備模型(5)_device和device driver: http://www.wowotech.net/linux_kenrel/device_and_driver.html
內核工具–Sparse 簡介:https://www.cnblogs.com/wang_yb/p/3575039.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM