一、前言
作為一個多年耕耘在linux 2.6.23內核的開發者,各個不同項目中各種不同周邊外設驅動的開發以及各種瑣碎的、扯皮的俗務占據了大部分的時間。當有機會下載3.14的內核并準備學習的時候,突然發現linux kernel對于我似乎變得非常的陌生了,各種新的機制,各種framework、各種新的概念讓我感到閱讀內核代碼變得舉步維艱。 還好,剖析內核的熱情還在,剩下的就交給時間的。首先進入視線的是Device Tree機制,這是和porting內核非常相關的機制,如果想讓將我們的硬件平臺遷移到高版本的內核上,Device Tree是一個必須要掃清的障礙。
我想從下面三個方面來了解Device Tree:
1、為何要引入Device Tree,這個機制是用來解決什么問題的?(這是本文的主題)
2、Device Tree的基礎概念(請參考DT基礎概念)
3、ARM linux中和Device Tree相關的代碼分析(請參考DT代碼分析)
閱讀linux內核代碼就像欣賞冰山,有看得到的美景(各種內核機制及其代碼),也有埋在水面之下看不到的基礎(機制背后的源由和目的)。沉醉于各種內核機制的代碼固然有無限樂趣,但更重要的是注入更多的思考,思考其背后的機理,真正理解軟件抽象。這樣才能舉一反三,并應用在具體的工作和生活中。
本文主要從下面幾個方面闡述為何ARM linux會引入Device Tree:
1、沒有Device Tree的ARM linux是如何運轉的?
2、混亂的ARM architecture代碼和存在的問題
3、新內核的解決之道
二、沒有Device Tree的ARM linux是如何運轉的?
我曾經porting內核到兩個ARM-based的平臺上。一個是小的芯片公司的應用處理器,公司自己購買了CPU core,該CPU core使用ARM兼容的指令集(但不是ARM)加上各種公司自行設計的多媒體外設整合成公司的產品進行銷售。而我的任務就是porting 2.4.18內核到該平臺上。在黑白屏幕的手機時代,那顆AP(application process)支持了彩屏、camera、JPEG硬件加速、2D/3D加速、MMC/SD卡、各種音頻加速(內置DSP)等等特性,功能強大到無法直視。另外一次移植經歷是讓2.6.23內核跑在一個大公司的冷門BP(baseband processor)上。具體porting的方法是很簡單的:
1、自己撰寫一個bootloader并傳遞適當的參數給kernel。除了傳統的command line以及tag list之類的,最重要的是申請一個machine type,當拿到屬于自己項目的machine type ID的時候,當時心情雀躍,似乎自己已經是開源社區的一份子了(其實當時是有意愿,或者說有目標是想將大家的代碼并入到linux kernel main line的)。
2、在內核的arch/arm目錄下建立mach-xxx目錄,這個目錄下,放入該SOC的相關代碼,例如中斷controller的代碼,時間相關的代碼,內存映射,睡眠相關的代碼等等。此外,最重要的是建立一個board specific文件,定義一個machine的宏:
MACHINE_START(project name, "xxx公司的xxx硬件平臺")?
??? .phys_io??? = 0x40000000,?
??? .boot_params??? = 0xa0000100,???
??? .io_pg_offst??? = (io_p2v(0x40000000) >> 18) & 0xfffc,?
??? .map_io??????? = xxx_map_io,?
??? .init_irq??? = xxx_init_irq,?
??? .timer??????? = &xxx_timer,?
??? .init_machine??? = xxx_init,?
MACHINE_END
在xxx_init函數中,一般會加入很多的platform device。因此,伴隨這個board specific文件中是大量的靜態table,描述了各種硬件設備信息。
3、調通了system level的driver(timer,中斷處理,clock等)以及串口terminal之后,linux kernel基本是可以起來了,后續各種driver不斷的添加,直到系統軟件支持所有的硬件。
綜上所述,在linux kernel中支持一個SOC平臺其實是非常簡單的,讓linux kernel在一個特定的平臺上“跑”起來也是非常簡單的,問題的重點是如何優雅的”跑”。
三、混亂的ARM architecture代碼和存在的問題
每次正式的linux kernel release之后都會有兩周的merge window,在這個窗口期間,kernel各個部分的維護者都會提交各自的patch,將自己測試穩定的代碼請求并入kernel main line。每到這個時候,Linus就會比較繁忙,他需要從各個內核維護者的分支上取得最新代碼并merge到自己的kernel source tree中。Tony Lindgren,內核OMAP development tree的維護者,發送了一個郵件給Linus,請求提交OMAP平臺代碼修改,并給出了一些細節描述:
1、簡單介紹本次改動
2、關于如何解決merge conficts。有些git mergetool就可以處理,不能處理的,給出了詳細介紹和解決方案
一切都很平常,也給出了足夠的信息,然而,正是這個pull request引發了一場針對ARM linux的內核代碼的爭論。我相信Linus一定是對ARM相關的代碼早就不爽了,ARM的merge工作量較大倒在其次,主要是他認為ARM很多的代碼都是垃圾,代碼里面有若干愚蠢的table,而多個人在維護這個table,從而導致了沖突。因此,在處理完OMAP的pull request之后(Linus并非針對OMAP平臺,只是Tony Lindgren撞在槍口上了),他發出了怒吼:
Gaah. Guys, this whole ARM thing is a f*cking pain in the ass.
負責ARM linux開發的Russell King臉上掛不住,進行了反駁:事情沒有那么嚴重,這次的merge conficts就是OMAP和IMX/MXC之間一點協調的問題,不能抹殺整個ARM linux團隊的努力。其他的各個ARM平臺維護者也加入討論:ARM平臺如何復雜,如何龐大,對于arm linux code我們已經有一些思考,正在進行中……一時間,討論的氣氛有些尖銳,但總體是坦誠和友好的。
對于一件事情,不同層次的人有不同層次的思考。這次爭論涉及的人包括:
1、內核維護者(CPU體系結構無關的代碼)
2、維護ARM系統結構代碼的人
3、維護ARM sub architecture的人(來自各個ARM SOC vendor)
維護ARM sub architecture的人并沒有強烈的使命感,作為公司的一員,他們最大的目標是以最快的速度支持自己公司的SOC,盡快的占領市場。這些人的軟件功力未必強,對linux kernel的理解未必深入(有些人可能很強,但是人在江湖身不由己)。在這樣的情況下,很多SOC specific的代碼都是通過copy and paste,然后稍加修改代碼就提交了。此外,各個ARM vendor的SOC family是一長串的CPU list,每個CPU多多少少有些不同,這時候#ifdef就充斥了各個源代碼中,讓ARM mach-和plat-目錄下的代碼有些不忍直視。
作為維護ARM體系結構的人,其能力不容置疑。以Russell King為首的team很好的維護了ARM體系結構的代碼。基本上,除了mach-和plat-目錄,其他的目錄中的代碼和目錄組織是很好的。作為ARM linux的維護者,維護一個不斷有新的SOC加入的CPU architecture code的確是一個挑戰。在Intel X86的架構一統天下的時候,任何想正面攻擊Intel的對手都敗下陣來。想要擊倒巨人(或者說想要和巨人并存)必須另辟蹊徑。ARM的策略有兩個,一個是focus在嵌入式應用上,也就意味著要求低功耗,同時也避免了和Intel的正面對抗。另外一個就是博采眾家之長,采用license IP的方式,讓更多的廠商加入ARM建立的生態系統。毫無疑問,ARM公司是成功的,但是這種模式也給ARM linux的維護者帶來了噩夢。越來越多的芯片廠商加入ARM陣營,越來越多的ARM platform相關的代碼被加入到內核,不同廠商的周邊HW block設計又各不相同……
內核維護者是真正對操作系統內核軟件有深入理解的人,他們往往能站在更高的層次上去觀察問題,發現問題。Linus注意到每次merge window中,ARM的代碼變化大約占整個ARCH目錄的60%,他認為這是一個很明顯的符號,意味著ARM linux的代碼可能存在問題。其實,60%這個比率的確很夸張,因為unicore32是在2.6.39 merge window中第一次全新提交,它的代碼是全新的,但是其代碼變化大約占整個ARCH目錄的9.6%(需要提及的是unicore32是一個中國芯)。有些維護ARM linux的人認為這是CPU市場占用率的體現,不是問題,直到內核維護者貼出實際的代碼并指出問題所在。內核維護者當然想linux kernel支持更多的硬件平臺,但是他們更愿意為linux kernel制定更長遠的規劃。例如:對于各種繁雜的ARM平臺,用一個kernel image來支持。
經過爭論,確定的問題如下:
1、ARM linux缺少platform(各個ARM sub architecture,或者說各個SOC)之間的協調,導致arm linux的代碼有重復。值得一提的是在本次爭論之前,ARM維護者已經進行了不少相關的工作(例如PM和clock tree)來抽象相同的功能模塊。
2、ARM linux中大量的board specific的源代碼應該踢出kernel,否則這些垃圾代碼和table會影響linux kernel的長期目標。
3、各個sub architecture的維護者直接提交給Linux并入主線的機制缺乏層次。
四、新內核的解決之道
針對ARM linux的現狀,最需要解決的是人員問題,也就是如何整合ARM sub architecture(各個ARM Vendor)的資源。因此,內核社區成立了一個ARM sub architecture的team,該team主要負責協調各個ARM廠商的代碼(not ARM core part),Russell King繼續負責ARM core part的代碼。此外,建立一個ARM platform consolidation tree。ARM sub architecture team負責review各個sub architecture維護者提交的代碼,并在ARM platform consolidation tree上維護。在下一個merge window到來的時候,將patch發送給Linus。
針對重復的代碼問題,如果不同的SOC使用了相同的IP block(例如I2C controller),那么這個driver的code要從各個arch/arm/mach-xxx中獨立出來,變成一個通用的模塊供各個SOC specific的模塊使用。移動到哪個目錄呢?對于I2C或者USB OTG而言,這些HW block的驅動當然應該移動到kernel/drivers目錄。因為,對于這些外設,可能是in-chip,也可能是off-chip的,但是對于軟件而言,它們是沒有差別的(或者說好的軟件抽象應該掩蓋底層硬件的不同)。對于那些system level的code呢?例如clock control、interrupt control。其實這些也不是ARM-specific,應該屬于linux kernel的核心代碼,應該放到linux/kernel目錄下,屬于core-Linux-kernel frameworks。當然對于ARM平臺,也需要保存一些和framework交互的code,這些code叫做ARM SoC core architecture code。OK,總結一下:
1、ARM的核心代碼仍然保存在arch/arm目錄下
2、ARM SoC core architecture code保存在arch/arm目錄下
3、ARM SOC的周邊外設模塊的驅動保存在drivers目錄下
4、ARM SOC的特定代碼在arch/arm/mach-xxx目錄下
5、ARM SOC board specific的代碼被移除,由Device Tree機制來負責傳遞硬件拓撲和硬件資源信息。
OK,終于來到了Device Tree了。本質上,Device Tree改變了原來用hardcode方式將HW 配置信息嵌入到內核代碼的方法,改用bootloader傳遞一個DB的形式。對于基于ARM CPU的嵌入式系統,我們習慣于針對每一個platform進行內核的編譯。但是隨著ARM在消費類電子上的廣泛應用(甚至桌面系統、服務器系統),我們期望ARM能夠象X86那樣用一個kernel image來支持多個platform。在這種情況下,如果我們認為kernel是一個black box,那么其輸入參數應該包括:
1、識別platform的信息
2、runtime的配置參數
3、設備的拓撲結構以及特性
對于嵌入式系統,在系統啟動階段,bootloader會加載內核并將控制權轉交給內核,此外,還需要把上述的三個參數信息傳遞給kernel,以便kernel可以有較大的靈活性。在linux kernel中,Device Tree的設計目標就是如此。
?
評論
查看更多