天天看點

linux clock()_Linux裝置模型(9)_device resource management

linux clock()_Linux裝置模型(9)_device resource management
轉載自蝸窩科技

Linux裝置模型(9)_device resource management​www.wowotech.net

linux clock()_Linux裝置模型(9)_device resource management

作者:wowo釋出于:2014-4-28 10:24 分類:統一裝置模型

題圖轉自:

Linux裝置模型(9)_device resource management​www.wowotech.net

linux clock()_Linux裝置模型(9)_device resource management
轉載者注:

本文在第一次閱讀時,建議初接觸核心代碼的小白最好不要太死摳代碼,先明白大緻的功能,頭腦中形成一個簡單的認識,在不斷學習中逐漸深入。

文章内的代碼為4.14版本的核心,與原作者使用的核心,略有差别。

本文主體是原作者文章,小編進行進一步的排版、内容增添、内容注釋,後期會根據自己的感悟用更多流程圖、思維導圖之類的框圖表達自己的了解,敬請期待!!!

1. 前言

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

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

1
           

相信每一個寫過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
           

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

2. devm_xxx

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

1
           

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的軟體架構

linux clock()_Linux裝置模型(9)_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
           

那資源的資料結構呢?在“drivers/base/devres.c”中,名稱為

struct devres

,如下:

1
           

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

node用于将devres組織起來,友善插入到device結構的devres_head連結清單中,是以一定也有一個list_head(見下面的entry)。另外,資源的存在形式到底是什麼,device resource management并不知情,是以需要上層子產品提供一個release的回調函數,用于release資源,如下:

1
           

抛開用于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
           

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

1)一個自定義的資料結構(struct irq_devres),用于儲存和resource有關的資訊(對中斷來說,就是IRQ num),如下:

1
           

2)一個用于release resource的回調函數(這裡的release,和memory無關,例如free IRQ),如下:

1
           
因為回調函數是由devres子產品調用的,由它的參數可知,struct irq_devres變量就是實際的“資源”,但對devres而言,它并不知道該資源的實際形态,因而是void類型指針。也隻有這樣,devres子產品才可以統一的處理所有類型的資源。

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

1
           

調用alloc_dr,配置設定一個struct devres類型的變量,并傳回其中的data指針(5.2小節講過了,data變量實際上是資源的代表)。alloc_dr的定義如下:

1
           

看第一句就可以了,在資源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
           

從資源指針中,取出完整的struct devres指針,調用add_dr接口。add_dr也很簡單,把struct devres指針挂到裝置的devres_head中即可:

1
           

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
           

以裝置指針為參數,直接調用release_nodes:

1
           

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

linux clock()_Linux裝置模型(9)_device resource management

歡迎大家關注我的微信公衆号——小白倉庫 原創經驗資料分享:包含但不僅限于FPGA、ARM、RISC-V、Linux、LabVIEW等軟硬體開發,另外分享生活中的趣事以及感悟。目的是建立一個平台記錄學習過的知識,并分享出來自認為有用的與感興趣的道友互相交流進步。