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 ");
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 ");
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 ");
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 ");
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 ");
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 ");
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 ");
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 ");
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 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以及資源所占的空間。
?
評論
查看更多