Device Tree คืออะไร
คนที่เคยเล่น Raspbian OS image คงเคยเห็นไฟล์ .dtb ที่อยู่บน sd card และต้องใช้คู่กับลีนุกซ์เคอร์เนลใน ขั้นตอนการบูตระบบ แต่อาจจะยังไม่รู้ว่ามันคืออะไร
ไฟล์ .dtb นี้คือ Device Tree Blob (DTB) หรือ Flattened Device Tree (FDT) ใช้สำหรับการระบุรายละเอียดของฮาร์ดแวร์ของบอร์ดใหักับลินุกซ์เคอร์เนลในช่วงการบูต บน wikipedia อธิบายแบบไม่ยาวมากไว้ว่า
“The device tree is a data structure for describing hardware, which originated from Open Firmware. The data structure can hold any kind of data as internally it is a tree of named nodes and properties. Nodes contain properties and child nodes, while properties are name–value pairs.”
แปลเป็นไทยคงประมาณว่า “เป็นไฟล์ไบนารี่ที่เก็บรายละเอียดโครงสร้างฮาดร์แวร์ของบอร์ดไว้ โดยนำรูปแบบมาจาก Open Firmware โครงสร้างข้อมูลที่บรรจุอยู่ภายในไฟล์สามารถเป็นชนิดใดก็ได้ในรูปแบบแผนภูมิต้นไม้ของโหนด (node) และคุณสมบัติ (properties) แต่ละโหนดจะเก็บโหนดลูก (child nodes) และคุณสมบัติ (properties) เอาไว้ ส่วนคุณสมบัติ (properties) จะเก็บข้อมูลแบบคู่แพร์ของชื่อและค่าเอาไว้”
ตัวอย่างของ device tree source file (.dts)
ซ้อสโค้ดด้านล่างแสดงตัวอย่าง(บางส่วน) ของ device tree ซ้อสโค้ด ของบอร์ดราสเบอรี่พาย โมเดล B
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
/dts-v1/; #include "bcm2708.dtsi" / { compatible = "brcm,bcm2708"; model = "Raspberry Pi Model B"; }; &gpio { sdhost_pins: sdhost_pins { brcm,pins = <48 49 50 51 52 53>; brcm,function = <4>; /* alt0 */ }; spi0_pins: spi0_pins { brcm,pins = <9 10 11>; brcm,function = <4>; /* alt0 */ }; spi0_cs_pins: spi0_cs_pins { brcm,pins = <8 7>; brcm,function = <1>; /* output */ }; i2c0_pins: i2c0 { brcm,pins = <0 1>; brcm,function = <4>; }; i2c1_pins: i2c1 { brcm,pins = <2 3>; brcm,function = <4>; }; i2s_pins: i2s { brcm,pins = <28 29 30 31>; brcm,function = <6>; /* alt2 */ }; }; &sdhost { pinctrl-names = "default"; pinctrl-0 = <&sdhost_pins>; bus-width = <4>; status = "okay"; }; &fb { status = "okay"; }; &uart0 { status = "okay"; }; |
ความจำเป็นที่ต้องใช้ device tree
หนึ่งในปัญหาหลักของการบูตของ ARM platform ก็คือความหลากหลายในการสร้างบอร์ดต่างๆออกมาสู่ท้องตลาด ทำให้ต้องมีโค้ดส่วนการตั้งค่าเริ่มต้นเฉพาะ(board initialization) เนื่องจากแม้จะเลือกซีพียูชิป เบอร์เดียวกัน แต่เมื่อไปออกแบบเป็นบอร์ด สามารถมีความแตกต่างกันได้ตั้งแต่ระดับ architecture , core , โมดูลต่างๆบนตัวชิป (Soc) จนไปถึง อุปกรณ์ต่อพ่วงบนบอร์ด (board peripheral)
ยกตัวอย่างให้เห็นชัดเจนขึ้น เช่นความยืดหยุ่นในการเลือกใช้ขาของชิป ซึ่งอาจทำงานได้มากกว่าหนึ่งหน้าที่ต่อหนึ่งขาสัญญาณ (pin mux configuration) หากมีผู้ผลิตสองบริษัทนำชิปตัวเดียวกันไปออกแบบเป็นบอร์ดของตัวเอง ก็อาจจะเลือกใช้การใช้งานของขาไม่เหมือนกัน ผลของเรื่องนี้ก็คือ บอร์ดที่ใช้ชิปเดียวกันจากผู้ผลิตบอร์ดทั้งสองก็จะต้องมีโค้ดส่วนการตั้งค่าเริ่มต้นเฉพาะของตัวเองขึ้นมา (one board file per board) เรื่องฟังดูแล้วก็อาจจะไม่เป็นปัญหาสำหรับนักพัฒนาเท่าไหร่นัก แต่สำหรับผู้ดูแลซอสโค้ดของลีนุกซ์มันจะเป็นปัญหาในระยะยาว เพราะนับวันก็จะมีบอร์ดใหม่ๆ เพิ่มขึ้นมาเรื่อยๆ
บางคนอาจจะสงสัยว่าถ้าอย่างนั้นทำไมไม่ทำให้เคอร์เนลตรวจสอบฮาร์ดแวร์ที่มีอยู่ก่อนแล้วจึงค่อยทำการเรียกดีไวซ์ไดร์เวอร์ที่ถูกต้องมาทำงาน ฟังดูง่ายดี แต่ปัญหาของเรื่องนี้ก็คืออุปกรณ์ส่วนใหญ่ที่อยู่บนบอร์ดไม่สามารถทำการตรวจสอบได้โดยซีพียู ลองคิดดูว่าขาของชิปที่เป็นได้ทั้ง i2c , gpio หรือ mdio แล้วซีพียูจะรู้ได้อย่างไรว่าผู้ผลิตบอร์ดเอาไปต่อกับอะไร นี่ยังไม่รวมโมดูลต่างๆ(SoC) ของชิปว่าเคอร์เนลต้องเปิดหรือปิดการใช้งานส่วนไหนขึ้นมาบ้าง
ด้วยเหตุผลนี้ส่วนการบรรยายลักษณะของฮาร์ดแวร์ (hardware description) จึงจำเป็นต้องอยู่ในรูปแบบสแตติก
การนำ device tree มาใช้จึงเป็นการแยกส่วนของข้อมูลของฮาร์ดแวร์ที่เป็นสแตติก ออกมาจากส่วนที่เป็นโค้ดซึ่งจะทำให้โค้ดส่วนการตั้งค่าเริ่มต้น มีความเป็นไดนามิกมากขึ้น ทำให้ไฟล์เคอร์เนลไบนารี่ไฟล์เดียวสามารถบูตบอร์ดที่ใช้ชิปตัวเดียวกันแต่มีอุปกรณ์รอบข้างต่างกันได้
และเนื่องจาก Device Tree Blob นั้นไม่ขึ้นอยู่กับระบบปฏิบัติการ ทำให้สามารถนำไปใช้กับบูตโหลดเดอร์หรือระบบปฏิบัติการอื่นที่ไม่ใช่ลีนุกซ์ได้อีกด้วย
การนำไปใช้บน ราสเบอรี่พาย (Device tree on Raspberry pi)
ราสเบอรี่พาย เป็นตัวอย่างที่ดีในการนำ device tree ไปใช้ เนื่องจากความสำเร็จในการทำตลาดของบอร์ดรุ่นแรก ทำให้มีบอร์ดรุ่นอื่นๆ ตามมาในซีรี่ย์ ซึ่งถึงแม้จะเป็นบอร์ดในซีรี่ย์เดียวกัน แต่ก็ยังมีความแตกต่างในอุปกรณ์บางส่วน
การนำ device tree มาใช้ในราสเบอรี่พาย ทำให้ sd card boot images สามารถบูตบอร์ดได้มากกว่า 1 โมเดล ซึ่งช่วยตัดปัญหาอื่นๆ เช่น การออกรุ่นใหม่ๆของไฟล์อิมเมจ ง่ายขึ้น , ป้องกันการนำไฟล์ไปใช้ผิดรุ่น , ลดขนาดพื้นที่จัดเก็บ
ข้อเสียของการใช้ device tree
ต้องการความสามารถใหม่ของบูตโหลดเดอร์ เพื่อที่จะทำการโหลด device tree blob และส่งต่อไปให้กับเคอร์เนลทาง ATAGS (คหสต: ตรงส่วนนี้สำหรับเราซึ่งเป็น developer ปลายแถวก็คงไม่ได้เป็นปัญหาอะไรมาก นักเพราะทางลีนุกซ์ kernel main line ต้องทำให้เสร็จเรียบร้อยก่อนที่จะมาถึงมือเรา)
ต้องการความเสถียรของ ABI (Application binary interface) เพราะ (.dtb) ถูกกำหนดให้มีความเข้ากันได้กับรุ่นเก่าด้วย ฟอร์แมตของไฟล์ไบนารี่ที่ใช้จึงต้องมีความแน่นอน ซึ่งในสายการพัฒนาของลินุกซ์นั้นเรื่องนี้ถือว่าเกิดขึ้นยากเพราะมีความเปลี่ยนแปลงตลอดเวลา
ต้องการบูตสคริปที่ทำงานกับ (.dtb) ได้ ซึ่งการจะเปลื่ยนโค้ดส่วนการตั้งค่าบอร์ดจากเดิมที่เป็นสแตติกซึ่งง่ายให้มาเป็นแบบไดนามิกโดยแยกส่วนที่เป็น configurable ออกมาภายนอกนั้น เป็นเรื่องไม่ง่ายเลย
ใช้เวลาในการบูตนานขึ้น เพราะโค้ดส่วนการตั้งค่าบอร์ดต้องมีขั้นตอนการอ่านค่าของ (.dtb) แล้วจึงค่อยทำงาน
ทำให้เคอร์เนลไบนารี่ไฟล์ มีขนาดใหญ่ขึ้น เพราะว่าจะมีโค้ดส่วนที่ไม่ได้ใช้งานรวมอยู่ด้วย สำหรับในการที่ต้องรองรับบอร์ดหลายรุ่น
แหล่งข้อมูลในการศึกษาเกี่ยวกับ device tree เพิ่มเติม
เอกสารเกี่ยวกับสเปคของ device tree https://www.devicetree.org/
เอกสารเกี่ยวกับรายละเอียดเรื่อง device tree ของบอร์ด ราสเบอรรี่ พาย https://www.raspberrypi.org/documentation/configuration/device-tree.md