ARM devicetree的來源
在過去的arm linux中,存在大量的冗余代碼。這些設備代碼與特定公司的單板啟動或運行細節緊密耦合,無法被重用或移植。同時,內核缺乏引導標準,導致代碼不斷膨脹。最終,由于tony lindgren向linus發送了一封郵件,請求提交omap平臺代碼的修改,并附上了修改內容以及如何解決合并沖突的方法,使得linus怒不可遏地抱怨道:“該死。伙計們,這整個arm的事情真是個討厭的麻煩。”(linus對arm的代碼肯定已經忍耐了很久了)。
經過討論后,對ARM平臺相關代碼做出了一些規范:
- ARM的核心代碼仍然存放在arch/arm目錄下;
- ARM SoC核心架構代碼存放在arch/arm目錄下;
- ARM SoC周邊外設模塊的驅動存放在drivers目錄下;
- ARM SoC特定的代碼存放在arch/arm/mach-xxx目錄下;
- ARM SoC板級特定的代碼被移除,由Device Tree機制來傳遞硬件拓撲和硬件資源信息。
從本質上講,Device Tree改變了以前將硬件設備配置信息硬編碼到內核代碼中的方式,改為使用引導加載程序傳遞一個描述性的數據結構。
DTS知識介紹
Arm系統啟動,硬件設備可以通過DTS(devicetree)或ACPI引導初始化,這里只講DTS方式,ACPI是由BIOS配置。
如上圖,一般來說,arm內核通過dts引導啟動,需要內核Image、dtb和Filesystem,其中dtb是由dts通過dtc工具生成,里面包括初始化設備的硬件信息。內核Image啟動過程中會解析dtb中內容,并根據信息初始化設備平臺。這里提一句,dts由雖然由用戶配置,但是配置必須與硬件信息相匹配,否則會出現初始化失敗或設備部分功能不正常的問題。
DTS描述
Device Tree由一系列被命名的結點(node)和屬性(Property)組成,而結點本身可包含子結點。所謂屬性,其實就是成對出現的name和value。在Device Tree中,可描述的信息包括(原先這些信息大多被hard code到kernel中),CPU的數量和類別、內存基地址和大、timer時鐘、外設連接、中斷配置、串口等。內核在啟動過程中會解析每個node的硬件配置信息,根據這些信息初始化設備。
舉例,如下是arm gicv3中斷控制器的節點配置信息(來源Documentation/devicetree/bindings/interrupt-controller/arm,gic-v3.txt):
gic:?interrupt-controller@2cf00000?{ ?compatible?=?"arm,gic-v3"; ?#interrupt-cells?=?; ?#address-cells?=?; ?#size-cells?=?; ?ranges; ?interrupt-controller; ?reg?=?,??//?GICD ?,?//?GICR ?,?//?GICC ?,?//?GICH ?;??//?GICV ?interrupts?=?; ? ?msi-controller; ?mbi-ranges?=?; ? ?gic-its@2c200000?{ ?compatible?=?"arm,gic-v3-its"; ?msi-controller; ?#msi-cells?=?; ?reg?=?; ?}; ?};
gic: interrupt-controller@2cf00000:表示node節點信息,interrupt-controller@2cf00000是node節點的名稱,一般命名規范是 設備名@基地址或設備名,其中冒號前面gic可以看過node的“小名”,后續關聯node節點可以直接”&gic”;
{}中內容:interrupt-controller的屬性,包括:
1)compatible = “arm,gic-v3”; compatible名稱,gicv3驅動代碼匹配”arm,gic-v3”后才會執行初始化probe代碼;
2)#interrupt-cells = ; 表示interrupt由3部分組成,對應interrupts = ,分別是中斷類型(1表示PPI),中斷號9和中斷觸發方式(4表示上升沿觸發);
3)#address-cells = ;
#size-cells = ; 表示地址信息用64位表示,比如reg = 表示GICD的基地址是0x02f000000(其中0x是高32bit,0x2f000000是低32bit),size是0x10000;
4)msi-controller; 表示msi,此字符串含義可以具體查看驅動代碼,驅動會get到這個字符串,然后做判斷,有會走A,沒有就跳過。
5)gic-its@2c200000 表示its節點的配置,gic-v3引入了its來接收SPI中斷;
如上,便是dts的基本配置,更加詳細的介紹可以參考linux內核源碼:Documentation/devicetree/,每個硬件設備配置格式和屬性都有描述。
重要點
一般來說,Linux菜鳥級別只要會看dts即可,linux驅動開發才需要掌握dts的每行含義,因為任一行出錯,都可能導致驅動某項功能失效。下面我列一些經常遇到的問題:
1.dts和dtb如何轉換
內核源碼下scripts/dtc/dtc,dtc工具只有內核編譯時才會編譯,所以如果希望得到,先編譯一遍內核即可。
轉換命令:
dts轉dtb:dtc –I dts –O dtb test.dts –o test.dtb
dtb轉dts: dtc –I dtb –O dts test.dtb –o test.dts
2.dts中node節點如何查看,每行又是什么意義
這個問題經常遇到,不知道設備節點配置的意義,其實每個配置都可以根據compatible在Documentation/devicetree/中查到說明,只是不搞內核不知道這個方法,比如dts中看到:
v2m_serial0:?uart@090000?{ ??compatible?=?"arm,pl011",?"arm,primecell"; ?reg?=?; ?interrupts?=?; ?clocks?=?,?; ?clock-names?=?"uartclk",?"apb_pclk"; ?};
到linux內核源碼運行:
cuibixuan@ubuntu:~/git/linux/Documentation/devicetree/bindings$?cd?Documentation/devicetree/bindings/ cuibixuan@ubuntu:~/git/linux/Documentation/devicetree/bindings$?grep?"arm,pl011"?-rn?* clock/hi3660-clock.txt:41:?compatible?=?"arm,pl011",?"arm,primecell"; clock/hi3670-clock.txt:37:?compatible?=?"arm,pl011",?"arm,primecell"; clock/lsi,axm5516-clks.txt:22:?compatible?=?"arm,pl011",?"arm,primecell"; clock/hix5hd2-clock.txt:25:?compatible?=?"arm,pl011",?"arm,primecell"; dma/ste-dma40.txt:130:?compatible?=?"arm,pl011",?"arm,primecell"; dma/snps-dma.txt:65:?compatible?=?"arm,pl011",?"arm,primecell"; pinctrl/axis,artpec6-pinctrl.txt:71:?compatible?=?"arm,pl011",?"arm,primecell"; pinctrl/axis,artpec6-pinctrl.txt:80:?compatible?=?"arm,pl011",?"arm,primecell"; pinctrl/ste,nomadik.txt:141:?compatible?=?"arm,pl011",?"arm,primecell"; serial/pl011.txt:4:-?compatible:?must?be?"arm,primecell",?"arm,pl011",?"zte,zx296702-uart" serial/pl011.txt:44:?compatible?=?"arm,pl011",?"arm,primecell"; vim?serial/pl011.txt
如下圖,有說明,有舉例,很清晰
如果還不懂,或者找不到,那么恭喜你,你要看驅動代碼了
cuibixuan@ubuntu:~/git/linux$?cd?drivers/tty/serial/ cuibixuan@ubuntu:~/git/linux/drivers/tty/serial$?grep?"arm,pl011"?-rn?* amba-pl011.c:2473:OF_EARLYCON_DECLARE(pl011,?"arm,pl011",?pl011_early_console_setup);
3.內核初始化設備驅動,根據compatible來決定是否初始化
compatible的字符串,是驅動匹配的關鍵,如果匹配不到,那么就不會初始化。這點設計非常棒,在編譯Image完畢后,用戶還可以根據dts選配啟動哪些硬件。
一直有人有疑問,既然內核都有config選項來決定了,為什么還要dts來再加一道門禁呢。你可以設想下,如果內核啟動配置了哪些config就初始化哪些功能,那么啟動要多么繁瑣呀,大部分都是你不知道的功能都在啟動,萬一失敗了,還得查看哪里問題,至少編譯一次內核。尤其是嵌入式設備,要精簡,更要達到“我只關心我配置的設備”的目的。
好了扯遠了,以上面串口驅動代碼舉例(提示:驅動代碼的開頭是probe函數,一般翻到代碼底部即可,上面都是功能的實現),
static?const?Struct?of_device_id?sbsa_uart_of_match[]?=?{ ?{?.compatible?=?"arm,sbsa-uart",?}, ?{}, }; MODULE_DEVICE_TABLE(of,?sbsa_uart_of_match); ? static?struct?platform_driver?arm_sbsa_uart_platform_driver?=?{ ?.probe?=?sbsa_uart_probe, ?.remove??=?sbsa_uart_remove, ?.driver?=?{ ?.name?=?"sbsa-uart", ?.of_match_table?=?of_match_ptr(sbsa_uart_of_match), ?.acpi_match_table?=?ACPI_PTR(sbsa_uart_acpi_match), ?.suppress_bind_attrs?=?IS_BUILTIN(CONFIG_SERIAL_AMBA_PL011), ?}, }; ? … ? static?int?__init?pl011_init(void) { ?printk(KERN_INFO?"Serial:?AMBA?PL011?UART?driver "); ? ?if?(platform_driver_register(&arm_sbsa_uart_platform_driver)) ?pr_warn("could?not?register?SBSA?UART?platform?driver "); ?return?amba_driver_register(&pl011_driver); }
內核通過platform_driver_register()來注冊設備,arm_sbsa_uart_platform_driver是初始化成struct platform_driver的結構體,結構體指定了設備的probe,remove等鉤子函數,.driver記錄設備的name,.compatible = “arm,sbsa-uart”(of_match_table來匹配),這里多提一句,.of_match_table是dts啟動匹配字符串”arm,sbsa-uart”,.acpi_match_table是ACPI啟動匹配PTR。
如上,如果dts中node節點有compatible帶”arm,sbsa-uart”,那么就會執行指定的鉤子函數.probe = sbsa_uart_probe,函數再進行node節點其他參數的解析(說是解析,就是get字符串或數值,在進行對應初始化或讀寫寄存器)。
4.reg=和interrupts=里面數值代表什么?
reg和interrupt數值都有具體的含義,上文提到:
#interrupt-cells = ; 表示interrupt由3部分組成,對應interrupts = ,分別是中斷類型(1表示PPI),中斷號9和中斷觸發方式(4表示高電平觸發);
這里再補充一下:
interrupts = ,分別是
中斷類型:0表示SPI,1表示PPI
中斷號9,其中PPI是[0-15],SPI范圍[32-1019]
中斷觸發方式:
1 = low-to-high edge triggered
2 = high-to-low edge triggered
4 = active high edge triggered
8 = active low edge triggered
恩,翻譯一下就時上升沿觸發、下降沿觸發、高電平觸發、低電平觸發。
#address-cells = ;
#size-cells = ; 表示地址信息用64位表示,比如reg = 表示GICD的基地址是0x02f000000(其中0x是高32bit,0x2f000000是第32bit),size是0x10000;
舉例, = 0x12f000000,= 0x100000001
5.dts支持include
對于可復用的描述節點,支持以include方式被多個dts包含,可放在dtsi文件,在dts中以
#include “uart.dtsi”包含使用。
dts文件:Foundation-platform.dts
默認變量
一般dts首個{}前面的信息表示全局默認變量,意思即node無特殊配置,則默認采用這里的配置
????????#address-cells?=?;?//地址長度64bit ????????#size-cells?=?;??//size長度32bit ????????model?=?"V2P-AARCH64";?//model名稱 ????????compatible?=?"arm,vexpress,v2p-aarch64",?"arm,vexpress"; ????????interrupt-parent?=?;?//中斷parent是gic
CPU配置
????????cpus?{ ????????????????#address-cells?=?;? ????????????????#size-cells?=?; //?配置cpu0-cpu3的信息,最終啟動4核 ????????????????cpu@0?{ ????????????????????????device_type?=?"cpu";??//設備類型:cpu ????????????????????????compatible?=?"arm,armv8";?//表示armv8的cpu ????????????????????????reg?=?;?//cpu信息 ????????????????????????enable-method?=?"spin-table";?//采用spintable方式拉起從核 ????????????????????????cpu-release-addr?=?;?//cpu初啟動的pc指針存放位置 ????????????????}; ????????????????cpu@1?{ ????????????????????????device_type?=?"cpu"; ????????????????????????compatible?=?"arm,armv8"; ????????????????????????reg?=?; ????????????????????????enable-method?=?"spin-table"; ????????????????????????cpu-release-addr?=?; ????????????????}; ????????????????cpu@2?{ ????????????????????????device_type?=?"cpu"; ????????????????????????compatible?=?"arm,armv8"; ????????????????????????reg?=?; ????????????????????????enable-method?=?"spin-table"; ????????????????????????cpu-release-addr?=?; ????????????????}; ????????????????cpu@3?{ ????????????????????????device_type?=?"cpu"; ????????????????????????compatible?=?"arm,armv8"; ????????????????????????reg?=?; ????????????????????????enable-method?=?"spin-table"; ????????????????????????cpu-release-addr?=?; ????????????????}; ????????};
內存配置
????????memory@80000000?{ ????????????????device_type?=?"memory"; //此節點配置內存,內存2塊: //起始地址:0x80000000,?長度0x80000000; //起始地址:0x880000000,?長度0x80000000; ????????????????reg?=?; ????????};
中斷控制器
????????gic:?interrupt-controller@2c001000?{ //?中斷控制器使用?cortex?a15,gic ????????????????compatible?=?"arm,cortex-a15-gic"; ????????????????#interrupt-cells?=?; ????????????????#address-cells?=?; ????????????????interrupt-controller; //?GICD,GICH等信息,具體參考Documentation/devicetree/bindings/interrupt-controller/arm,gic.txt //-?reg?:?Specifies?base?physical?address(s)?and?size?of?the?GIC?registers.?The ??first?region?is?the?GIC?distributor?register?base?and?size.?The?2nd?region?is ??the?GIC?cpu?interface?register?base?and?size. ????????????????reg?=?, ??????????????????????, ??????????????????????, ??????????????????????; //?虛擬化使用,-?interrupts?:?VGIC?maintenance?interrupt. //*?GIC?virtualization?extensions?(VGIC) ????????????????interrupts?=?; ????????};
PMU
????????pmu?{ //?使用armv8?pmuv3 ????????????????compatible?=?"arm,armv8-pmuv3"; //?pmu的中斷配置,這段配置完畢,即可以使用arm?cpu的pmu功能 ????????????????interrupts?=?; ????????};
Timer:
????????timer?{ //定時器配置 ????????????????compatible?=?"arm,armv8-timer"; ????????????????interrupts?=?, ?????????????????????????????, ?????????????????????????????, ?????????????????????????????; ????????????????clock-frequency?=?; ????????};
串口配置
????????????????iofpga@3,00000000?{ ????????????????????????compatible?=?"arm,amba-bus",?"simple-bus"; ????????????????????????#address-cells?=?; ????????????????????????#size-cells?=?; ????????????????????????ranges?=?; ????????????????????????sysreg@010000?{ ????????????????????????????????compatible?=?"arm,vexpress-sysreg"; ????????????????????????????????reg?=?; ????????????????????????}; ????????????????????????v2m_serial0:?uart@090000?{ ????????????????????????????????compatible?=?"arm,pl011",?"arm,primecell";?//串口驅動 ????????????????????????????????reg?=?;?//串口基地址和長度 ????????????????????????????????interrupts?=?;??//串口中斷號 ????????????????????????????????clocks?=?,?;?//串口波特率 ????????????????????????????????clock-names?=?"uartclk",?"apb_pclk"; ????????????????????????}; ????????????????????????v2m_serial1:?uart@0a0000?{ ????????????????????????????????compatible?=?"arm,pl011",?"arm,primecell"; ????????????????????????????????reg?=?; ????????????????????????????????interrupts?=?; ????????????????????????????????clocks?=?,?; ????????????????????????????????clock-names?=?"uartclk",?"apb_pclk"; ????????????????????????}; ????????????????????????v2m_serial2:?uart@0b0000?{ ????????????????????????????????compatible?=?"arm,pl011",?"arm,primecell"; ????????????????????????????????reg?=?; ????????????????????????????????interrupts?=?; ????????????????????????????????clocks?=?,?; ????????????????????????????????clock-names?=?"uartclk",?"apb_pclk"; ????????????????????????}; ????????????????????????v2m_serial3:?uart@0c0000?{ ????????????????????????????????compatible?=?"arm,pl011",?"arm,primecell"; ????????????????????????????????reg?=?; ????????????????????????????????interrupts?=?; ????????????????????????????????clocks?=?,?; ????????????????????????????????clock-names?=?"uartclk",?"apb_pclk"; ????????????????????????}; ????????????????????????virtio_block@0130000?{ ????????????????????????????????compatible?=?"virtio,mmio"; ????????????????????????????????reg?=?; ????????????????????????????????interrupts?=?; ????????????????????????}; ????????????????}; ????????}; ????????/*?chosen?*/ };
從device_node中獲取信息:
int?of_property_read_u8_array(const?struct?device_node?*np,?const?char?*propname,u8?*out_values,?size_t?sz); int?of_property_read_u16_array(const?struct?device_node?*np,?const?char?*propname,u16?*out_values,?size_t?sz); int?of_property_read_u32_array(const?struct?device_node?*np,?const?char?*propname,u32?*out_values,?size_t?sz);
從設備結點np中讀取屬性名為propname,類型為8、16、32、位整型數組的屬性值,并放入out_values,sz指明了要讀取的個數。
static?inline?int?of_property_read_u8(const?struct?device_node?*np,const?char?*propname,u8?*out_value)? static?inline?int?of_property_read_u16(const?struct?device_node?*np,const?char?*propname,u8?*out_value)? static?inline?int?of_property_read_u32(const?struct?device_node?*np,const?char?*propname,u8?*out_value)
從設備結點np中讀取屬性名為propname,類型為8、16、32位的屬性值,并放入out_values。實際上這里調用的就是sz為1的XXX_array函數。
int?of_property_read_u32_index(const?struct?device_node?*np,const?char*propname,u32?index,?u32?*out_value)
從設備結點np中讀取屬性名為propname的屬性值中第index個u32數值給out_value
int?of_property_read_u64(conststruct?device_node?*np,?const?char?*propname,u64?*out_value)
從設備結點np中讀取屬性名為propname,類型為64位的屬性值,并放入out_values
int?of_property_read_string(struct?device_node?*np,?const?char?*propname,const?char**out_string)
從設備結點np中讀取屬性名為propname的字符串型屬性值
int?of_property_read_string_index(struct?device_node?*np,?const?char?*propname,intindex,?const?char?**output)
從設備結點np中讀取屬性名為propname的字符串型屬性值數組中的第index個字符串
int?of_property_count_strings(struct?device_node?*np,?const?char?*propname)
從設備結點np中讀取屬性名為propname的字符串型屬性值的個數
unsigned?int?irq_of_parse_and_map(struct?device_node?*dev,?int?index)
從設備節點dev中讀取第index個irq號
int?of_irq_to_Resource(struct?device_node?*dev,?int?index,?struct?resource?*r)
從設備節點dev中讀取第index個irq號,并填充一個irq資源結構體
int?of_irq_count(struct?device_node?*dev)
獲取設備節點dev的irq個數
static?inline?bool?of_property_read_bool(const?struct?device_node?*np,const?char?*propname);
如果設備結點np含有propname屬性,則返回true,否則返回false。一般用于檢查空屬性是否存在。
struct?property*?of_find_property(const?struct?device_node?*np,const?char?*name,int?*lenp)
根據name參數,在指定的設備結點np中查找匹配的property,并返回這個property
const?void?*?of_get_property(const?struct?device_node?*np,?const?char?*name,int?*lenp)
根據name參數,在指定的設備結點np中查找匹配的property,并返回這個property的屬性值
struct?device_node*?of_get_parent(const?struct?device_node?*node)
獲得node節點的父節點的device node
int?of_device_is_compatible(const?struct?device_node?*device,const?char?*compat);
判斷設備結點device的compatible屬性是否包含compat指定的字符串
從of_allnodes中查找信息:
struct?device_node*?of_find_node_by_path(const?char?*path) 根據路徑參數,在全局鏈表of_allnodes中,查找匹配的device_node
struct?device_node*?of_find_node_by_name(struct?device_node?*from,const?char?*name) 則根據name在全局鏈表of_allnodes中查找匹配的device_node,若from=NULL表示從頭開始查找
struct?device_node*?of_find_node_by_type(struct?device_node?*from,const?char?*type)
根據設備類型在全局鏈表of_allnodes中查找匹配的device_node
struct?device_node?*?of_find_compatible_node(struct?device_node?*from,?const?char*type,?const?char,*compatible);
根據compatible的屬性值在全局鏈表of_allnodes中查找匹配的device_node,大多數情況下,from、type為NULL。
struct?device_node*?of_find_node_with_property(struct?device_node?*from,const?char?*prop_name)
根據節點屬性的name在全局鏈表of_allnodes中查找匹配的device_node
struct?device_node*?of_find_node_by_phandle(phandle?handle)
根據phandle在全局鏈表of_allnodes中查找匹配的device_node
雜:
void?__iomem*?of_iomap(struct?device_node?*node,?int?index);
通過設備結點直接進行設備內存區間的 ioremap(),index是內存段的索引。若設備結點的reg屬性有多段,可通過index標示要ioremap的是哪一段,只有1段的情況,index為0
unsigned?long?__init?of_get_flat_dt_root(void)
用來查找在dtb中的根節點,好像返回的都是0
int?of_alias_get_id(struct?device_node?*np,?const?char?*stem)
獲取節點np對應的aliasid號
struct?device_node*?of_node_get(struct?device_node?*node) void?of_node_put(struct?device_node?*node)
device node計數增加/減少
const?struct?of_device_id*?of_match_node(const?struct?of_device_id?*matches,const?struct?device_node*node)
將matches數組中of_device_id結構的name和type與device node的compatible和type匹配,返回匹配度最高的of_device_id結構
platform_device和resource相關:
int?of_address_to_resource(struct?device_node?*dev,?int?index,struct?resource?*r)
根據設備節點dev的reg屬性值,填充資源結構體r。Index參數指明了使用reg屬性中第幾個屬性值,一般設置為0,表示第一個。
struct?platform_device*?of_device_alloc(struct?device_node?*np,const?char?*bus_id,struct?device?*parent)
根據device node,bus_id以及父節點創建該設備的platform_device結構,同時會初始化它的resource成員。
int?of_platform_bus_probe(struct?device_node?*root,const?struct?of_device_id?*matches,struct?device?*parent)
遍歷of_allnodes中的節點掛接到of_platform_bus_type總線上,由于此時of_platform_bus_type總線上還沒有驅動,所以此時不進行匹配
int?of_platform_populate(struct?device_node?*root,const?struct?of_device_id?*matches,const?struct?of_dev_auxdata?*lookup,struct?device?*parent)
遍歷of_allnodes中的所有節點,生成并初始化所以節點的platform_device結構
struct?platform_device*?of_find_device_by_node(struct?device_node?*np)
根據device_node查找返回該設備對應的platform_device結構