Linux設備模型(9)_device resource management ---devm申請空間【轉】


轉自:http://www.wowotech.net/linux_kenrel/device_resource_management.html

 

1. 前言

蝸蝸建議,每一個Linux驅動工程師,都能瞄一眼本文。

之所以用“瞄”,因此它很簡單,幾乎不需要花費心思就能理解。之所有這建議,是因為它非常實用,可以解答一些困惑,可以使我們的代碼變得簡單、簡潔。先看一個例子:

   1: /* drivers/media/platform/soc_camera/mx1_camera.c, line 695 */
   2: static int __init mx1_camera_probe(struct platform_device *pdev)
   3: {
   4:     ...
   5:  
   6:     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
   7:     irq = platform_get_irq(pdev, 0);
   8:     if (!res || (int)irq <= 0) {
   9:         err = -ENODEV;
  10:         goto exit;
  11:     }
  12:  
  13:     clk = clk_get(&pdev->dev, "csi_clk");
  14:     if (IS_ERR(clk)) {
  15:         err = PTR_ERR(clk);
  16:         goto exit;
  17:     }
  18:  
  19:     pcdev = kzalloc(sizeof(*pcdev), GFP_KERNEL);
  20:     if (!pcdev) {
  21:         dev_err(&pdev->dev, "Could not allocate pcdev\n");
  22:         err = -ENOMEM;
  23:         goto exit_put_clk;
  24:     }
  25:  
  26:     ...
  27:  
  28:     /*
  29:      * Request the regions.
  30:      */
  31:     if (!request_mem_region(res->start, resource_size(res), DRIVER_NAME)) {
  32:         err = -EBUSY;
  33:         goto exit_kfree;
  34:     }
  35:  
  36:     base = ioremap(res->start, resource_size(res));
  37:     if (!base) {
  38:         err = -ENOMEM;
  39:         goto exit_release;
  40:     }
  41:     ...
  42:  
  43:     /* request dma */
  44:     pcdev->dma_chan = imx_dma_request_by_prio(DRIVER_NAME, DMA_PRIO_HIGH);
  45:     if (pcdev->dma_chan < 0) {
  46:         dev_err(&pdev->dev, "Can't request DMA for MX1 CSI\n");
  47:         err = -EBUSY;
  48:         goto exit_iounmap;
  49:     }
  50:     ...
  51:  
  52:     /* request irq */
  53:     err = claim_fiq(&fh);
  54:     if (err) {
  55:         dev_err(&pdev->dev, "Camera interrupt register failed\n");
  56:         goto exit_free_dma;
  57:     }
  58:  
  59:     ...
  60:     err = soc_camera_host_register(&pcdev->soc_host);
  61:     if (err)
  62:         goto exit_free_irq;
  63:  
  64:     dev_info(&pdev->dev, "MX1 Camera driver loaded\n");
  65:  
  66:     return 0;
  67:  
  68: exit_free_irq:
  69:     disable_fiq(irq);
  70:     mxc_set_irq_fiq(irq, 0);
  71:     release_fiq(&fh);
  72: exit_free_dma:
  73:     imx_dma_free(pcdev->dma_chan);
  74: exit_iounmap:
  75:     iounmap(base);
  76: exit_release:
  77:     release_mem_region(res->start, resource_size(res));
  78: exit_kfree:
  79:     kfree(pcdev);
  80: exit_put_clk:
  81:     clk_put(clk);
  82: exit:
  83:     return err;
  84: }
相信每一個寫過Linux driver的工程師,都在probe函數中遇到過上面的困惑:要順序申請多種資源(IRQ、Clock、memory、regions、ioremap、dma、等等),只要任意一種資源申請失敗,就要回滾釋放之前申請的所有資源。於是函數的最后,一定會出現很多的goto標簽(如上面的exit_free_irq、exit_free_dma、等等),並在申請資源出錯時,小心翼翼的goto到正確的標簽上,以便釋放已申請資源。

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

   1: static int __init mx1_camera_probe(struct platform_device *pdev)
   2: {
   3:     ...
   4:  
   5:     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
   6:     irq = platform_get_irq(pdev, 0);
   7:     if (!res || (int)irq <= 0) {
   8:         return -ENODEV;
   9:     }
  10:  
  11:     clk = devm_clk_get(&pdev->dev, "csi_clk");
  12:     if (IS_ERR(clk)) {
  13:         return PTR_ERR(clk);
  14:     }
  15:  
  16:     pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL);
  17:     if (!pcdev) {
  18:         dev_err(&pdev->dev, "Could not allocate pcdev\n");
  19:         return -ENOMEM;
  20:     }
  21:  
  22:     ...
  23:  
  24:     /*
  25:      * Request the regions.
  26:      */
  27:     if (!devm_request_mem_region(&pdev->dev, res->start, resource_size(res), DRIVER_NAME)) {
  28:         return -EBUSY;
  29:     }
  30:  
  31:     base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
  32:     if (!base) {
  33:         return -ENOMEM;
  34:     }
  35:     ...
  36:  
  37:     /* request dma */
  38:     pcdev->dma_chan = imx_dma_request_by_prio(DRIVER_NAME, DMA_PRIO_HIGH);
  39:     if (pcdev->dma_chan < 0) {
  40:         dev_err(&pdev->dev, "Can't request DMA for MX1 CSI\n");
  41:         return -EBUSY;
  42:     }
  43:     ...
  44:  
  45:     /* request irq */
  46:     err = claim_fiq(&fh);
  47:     if (err) {
  48:         dev_err(&pdev->dev, "Camera interrupt register failed\n");
  49:         return err;
  50:     }
  51:  
  52:     ...
  53:     err = soc_camera_host_register(&pcdev->soc_host);
  54:     if (err)
  55:         return err;
  56:  
  57:     dev_info(&pdev->dev, "MX1 Camera driver loaded\n");
  58:  
  59:     return 0;
  60: }
怎么做到呢?注意上面“devm_”開頭的接口,答案就在那里。不要再使用那些常規的資源申請接口,用devm_xxx的接口代替。為了保持兼容,這些新接口和舊接口的參數保持一致,只是名字前加了“devm_”,並多加一個struct device指針。

2. devm_xxx

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

   1: extern void *devm_kzalloc(struct device *dev, size_t size, gfp_t gfp);
   2:  
   3: void __iomem *devm_ioremap_resource(struct device *dev, 
   4:   struct resource *res);
   5: void __iomem *devm_ioremap(struct device *dev, resource_size_t offset,
   6:   unsigned long size);
   7:  
   8: struct clk *devm_clk_get(struct device *dev, const char *id);
   9:  
  10: int devm_gpio_request(struct device *dev, unsigned gpio,
  11:   const char *label);
  12:  
  13: static inline struct pinctrl * devm_pinctrl_get_select(
  14:   struct device *dev, const char *name)
  15:  
  16: static inline struct pwm_device *devm_pwm_get(struct device *dev,
  17:   const char *consumer);
  18:  
  19: struct regulator *devm_regulator_get(struct device *dev, const char *id);
  20:  
  21: static inline int devm_request_irq(struct device *dev, unsigned int irq, 
  22:   irq_handler_t handler, unsigned long irqflags, 
  23:   const char *devname, void *dev_id);
  24:  
  25: struct reset_control *devm_reset_control_get(struct device *dev, 
  26:   const char *id);
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 managementdevice 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”的鏈表頭,用於保存該設備申請的所有資源,如下:

   1: struct device {
   2:         ...
   3:         spinlock_t              devres_lock;
   4:         struct list_head        devres_head;
   5:         ...
   6: }
   7:  
那資源的數據結構呢?在“drivers/base/devres.c”中,名稱為struct devres,如下:

   1: struct devres {
   2:         struct devres_node              node;
   3:         /* -- 3 pointers */
   4:         unsigned long long              data[]; /* guarantee ull alignment */
   5: };
咋一看非常簡單,一個struct devres_node的變量node,一個零長度數組data,但其中有無窮奧妙,讓我們繼續分析。

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

   1: struct devres_node {
   2:         struct list_head                entry;
   3:         dr_release_t                    release;
   4: #ifdef CONFIG_DEBUG_DEVRES
   5:         const char                      *name;
   6:         size_t                          size;
   7: #endif
   8: };
拋開用於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模塊):

   1: /* include/linux/interrupt.h */
   2: static inline int __must_check
   3: devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
   4:                  unsigned long irqflags, const char *devname, void *dev_id)
   5: {
   6:         return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags,
   7:                                          devname, dev_id);
   8: }
   9:  
  10:  
  11: /* kernel/irq/devres.c */
  12: int devm_request_threaded_irq(struct device *dev, unsigned int irq,
  13:                               irq_handler_t handler, irq_handler_t thread_fn,
  14:                               unsigned long irqflags, const char *devname,
  15:                               void *dev_id)
  16: {
  17:         struct irq_devres *dr;
  18:         int rc;
  19:  
  20:         dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres),
  21:                           GFP_KERNEL);
  22:         if (!dr)
  23:                 return -ENOMEM;
  24:  
  25:         rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
  26:                                   dev_id);
  27:         if (rc) {
  28:                 devres_free(dr);
  29:                 return rc;
  30:         }
  31:  
  32:         dr->irq = irq;
  33:         dr->dev_id = dev_id;
  34:         devres_add(dev, dr);
  35:  
  36:         return 0;
  37: }
  38: EXPORT_SYMBOL(devm_request_threaded_irq);
  39:  
  40: void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id)
  41: {
  42:         struct irq_devres match_data = { irq, dev_id };
  43:  
  44:         WARN_ON(devres_destroy(dev, devm_irq_release, devm_irq_match,
  45:                                &match_data));
  46:         free_irq(irq, dev_id);
  47: }
  48: EXPORT_SYMBOL(devm_free_irq);
前面我們提過,上層的IRQ framework,會提供兩個和request_irq/free_irq基本兼容的接口,這兩個接口的實現非常簡單,就是在原有的實現之上,封裝一層devres的操作,如要包括:

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

   1: /*
   2:  * Device resource management aware IRQ request/free implementation.
   3:  */
   4: struct irq_devres {
   5:         unsigned int irq;
   6:         void *dev_id;
   7: };
2)一個用於release resource的回調函數(這里的release,和memory無關,例如free IRQ),如下:

   1: static void devm_irq_release(struct device *dev, void *res)
   2: {
   3:         struct irq_devres *this = res;
   4:  
   5:         free_irq(this->irq, this->dev_id);
   6: }
因為回調函數是由devres模塊調用的,由它的參數可知,struct irq_devres變量就是實際的“資源”,但對devres而言,它並不知道該資源的實際形態,因而是void類型指針。也只有這樣,devres模塊才可以統一的處理所有類型的資源。

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

   1: void * devres_alloc(dr_release_t release, size_t size, gfp_t gfp)
   2: {
   3:         struct devres *dr;
   4:  
   5:         dr = alloc_dr(release, size, gfp);
   6:         if (unlikely(!dr))
   7:                 return NULL;
   8:         return dr->data;
   9: }
調用alloc_dr,分配一個struct devres類型的變量,並返回其中的data指針(5.2小節講過了,data變量實際上是資源的代表)。alloc_dr的定義如下:

   1: static __always_inline struct devres * alloc_dr(dr_release_t release,
   2:                                                 size_t size, gfp_t gfp)
   3: {
   4:         size_t tot_size = sizeof(struct devres) + size;
   5:         struct devres *dr;
   6:  
   7:         dr = kmalloc_track_caller(tot_size, gfp);
   8:         if (unlikely(!dr))
   9:                 return NULL;
  10:  
  11:         memset(dr, 0, tot_size);
  12:         INIT_LIST_HEAD(&dr->node.entry);
  13:         dr->node.release = release;
  14:         return dr;
  15: }
看第一句就可以了,在資源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)中,該接口定義如下:

   1: void devres_add(struct device *dev, void *res)
   2: {
   3:         struct devres *dr = container_of(res, struct devres, data);
   4:         unsigned long flags;
   5:  
   6:         spin_lock_irqsave(&dev->devres_lock, flags);
   7:         add_dr(dev, &dr->node);
   8:         spin_unlock_irqrestore(&dev->devres_lock, flags);
   9: }
從資源指針中,取出完整的struct devres指針,調用add_dr接口。add_dr也很簡單,把struct devres指針掛到設備的devres_head中即可:

   1: static void add_dr(struct device *dev, struct devres_node *node)
   2: {
   3:         devres_log(dev, node, "ADD");
   4:         BUG_ON(!list_empty(&node->entry));
   5:         list_add_tail(&node->entry, &dev->devres_head);
   6: }
6)如果失敗,可以通過devres_free接口釋放資源占用的空間,devm_free_irq接口中,會調用devres_destroy接口,將devres從devres_head中移除,並釋放資源。這里就不詳細描述了。

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

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

先回憶一下設備模型中probe的流程(可參考“Linux設備模型(5)_device和device driver”),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

devres_release_all的實現如下:

   1: int devres_release_all(struct device *dev)
   2: {
   3:         unsigned long flags;
   4:  
   5:         /* Looks like an uninitialized device structure */
   6:         if (WARN_ON(dev->devres_head.next == NULL))
   7:                 return -ENODEV;
   8:         spin_lock_irqsave(&dev->devres_lock, flags);
   9:         return release_nodes(dev, dev->devres_head.next, &dev->devres_head,
  10:                              flags);
  11: }
以設備指針為參數,直接調用release_nodes:

   1: static int release_nodes(struct device *dev, struct list_head *first,
   2:                          struct list_head *end, unsigned long flags)
   3:         __releases(&dev->devres_lock)
   4: {
   5:         LIST_HEAD(todo);
   6:         int cnt;
   7:         struct devres *dr, *tmp;
   8:  
   9:         cnt = remove_nodes(dev, first, end, &todo);
  10:  
  11:         spin_unlock_irqrestore(&dev->devres_lock, flags);
  12:  
  13:         /* Release.  Note that both devres and devres_group are
  14:          * handled as devres in the following loop.  This is safe.
  15:          */
  16:         list_for_each_entry_safe_reverse(dr, tmp, &todo, node.entry) {
  17:                 devres_log(dev, &dr->node, "REL");
  18:                 dr->node.release(dev, dr->data);
  19:                 kfree(dr);
  20:         }
  21:  
  22:         return cnt;
  23: }
release_nodes會先調用remove_nodes,將設備所有的struct devres指針從設備的devres_head中移除。然后,調用所有資源的release回調函數(如5.3小節描述的devm_irq_release),回調函數會回收具體的資源(如free_irq)。最后,調用free,釋放devres以及資源所占的空間。

 

原創文章,轉發請注明出處。蝸窩科技,www.wowotech.net。

 


免責聲明!

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



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