Jak w Linuksie działa Device Tree?
Szkolenie „Linux w systemach embedded” W dniach 25…29 listopada 2013 r. organizuję szkolenie dla wszystkich zainteresowanych rozwijaniem projektów opartych o Embedded Linux. Kurs daje solidną wiedzę i bazę do pogłębiania jej. Uczestnicy będą w stanie zaplanować, rozpocząć i rozwijać własny projekt. Praktyczne ćwiczenia są prowadzone na zestawie BeagleBone Black. Uczestnicy stopniowo implementują kolejne elementy systemu Linux i aplikacji. Po szkoleniu wszyscy uczestnicy otrzymują komputery na własność. Pozwala to samodzielnie rozwijać wiedzę a nawet budować prototypy własnych projektów. Marcin Bis |
Co to właściwie jest Device Tree?
Plik tekstowy.
Zawiera strukturę, która opisuje sprzęt.
Nie ogranicza się do Linuksa. Jest to dość stary standard.
Jest skomplikowany (przynajmniej dla ludzi przyzwyczajonych do starego sposobu – kodu C).
W Linuksie, dla ARM, znajduje się w arch/arm/boot/dts. Są tam:
- .dts – pliki dla konkretnych urządzeń,
- .dtsi – „biblioteki” dla konkretnych SoC-ów.
Kompilator (scripts/dtc) tworzy z nich wersję binarną (Blob) – .dtb. Ten właśnie plik musi być załadowany przez Bootloader.
Co nam daje Device Tree?
Rys 6. Pierwsza zaleta – development jest prostszy
Firma nie musi już rozwijać 3 różnych wersji jądra dla 3 różnych wersji produktu (albo jednego obrazu z obsługą wszystkich wariantów sprzętu). Wystarczy jeden uniwersalny obraz (mniejszy) i oddzielne Device Tree dla każdego wariantu sprzętu. Ilość kodu specyficznego dla danej wersji urządzenia jest dużo mniejsza.
Rys 7. Druga zaleta – lepsza separacja naszych zmian
Device Tree może być modyfikowane w czasie pracy systemu. Pozwala to odseparować nasze zmiany od sterowników. W przykładzie: przechowujemy adres MAC w specjalnych rejestrach procesora (pamięć nieulotna, programowalne jede raz). Możemy zmodyfikować kod sterownika urządzenia Ethernet tak, aby ustawił adres z tych rejestrów. Zamiast tego stworzymy własny moduł, który wczyta adres i ustawi go w Device Tree. Takie rozwiązanie wydaje się bardziej skomplikowane, ale nie modyfikujemy kodu oryginalnego sterownika, więc łatwiej możemy aktualizować wersję jądra. Nowy sterownik może na przykład wspierać DMA.
Przykład 1 – port szeregowy
Skupmy się na Beagle Bone Black. Użyjemy jądra w wersji 3.12. W chwili pisania tego tekstu, najnowszą wersją było 3.12-rc7.
Przykład definicji portu szeregowego. Plik-biblioteka am33xx.dtsi.
uart0: serial@44e09000 { /* Pozwala sterownikom odnaleźć urządzenie */ compatible = "ti,omap3-uart"; /* Atrybut urządzenia - jego znaczenie znajdziemy w kodzie sterownika */ ti,hwmods = "uart1"; /* Częstotliwość zegara */ clock-frequency = <48000000>; /* Adres i rozmiar obszaru przestrzeni adresowej, w którym znajdują się rejestry */ reg = <0x44e09000 0x2000>; /* Numer przerwania */ interrupts = <72>; /* Urządzenie nie zostało włączone w tej konfiguracji */ status = "disabled"; };
Port szeregowy jest wyłączony w tej konfiguracji ponieważ jest to plik biblioteki. Musimy znaleźć konkretne urządzenie zbudowane na tym SoC-u. Definicja dla Beagle Bone Black znajduje się w pliku am335x-boneblack.dts, który z kolei załącza am335x-bone-common.dtsi. Definicję urządzenia znajdziemy w tym ostatnim pliku:
uart0: serial@44e09000 { /* Multipleksacja pin-ów dla tego urządzenia */ pinctrl-names = "default"; pinctrl-0 = <&uart0_pins>; /* W tej konfiguracji urządzenie wystąpuje */ status = "okay"; };
Plik Device Tree kompilowany dla Beagle Bone Black zawiera więc tylko informację o tym, jak ustawić multipleksację PIN-ów, oraz wskazuje, ze port szeregowy jest aktywny w tym zestawie. Wrócimy za chwilę do multipleksacji.
Port szeregowy jest prostym urządzeniem podłączonym bezpośrednio do procesora. Nie korzysta z żadnych magistral, które pozwoliłyby go automatycznie wykryć i skonfigurować (USB, PCI). Próba wykrywania portu szeregowego polegałaby na zapisywaniu jakichś wartości pod losowe adresy, co jest bardzo złym pomysłem (może nawet prowadzić do fizycznego uszkodzenia SoC-a). W Linuksie takie urządzenia nazywają się „platform devices”. Są one definiowane a nie wykrywane.
Jądro używa pojęcia magistrali (bus) do organizowania urządzeń i pasujących do nich sterowników. Na przykład, w działającym systemie, /sys/bus/usb/devices – zawiera listę urządzeń wykrytych przez kontroler USB, a /sys/bus/usb/drivers – listę załadowanych sterowników. Analogicznie w systemie wbudowany znajdziemy /sys/bus/platform/{devices|drivers}. Lista urządzeń odpowiada Device Tree.
Sterownik portu szeregowego dla tej architektury: drivers/tty/serial/omap-serial.c:
static const struct of_device_id omap_serial_of_match[] = { { .compatible = "ti,omap2-uart" }, { .compatible = "ti,omap3-uart" }, { .compatible = "ti,omap4-uart" }, {}, }; MODULE_DEVICE_TABLE(of, omap_serial_of_match);
Zawiera listę urządzeń, do których pasuje. Jest ona dołączana podczas rejestrowania sterownika w systemie.
static struct platform_driver serial_omap_driver = { .probe = serial_omap_probe, .remove = serial_omap_remove, .driver = { .name = DRIVER_NAME, .pm = &serial_omap_dev_pm_ops, .of_match_table = of_match_ptr(omap_serial_of_match), }, };
Pozwoli to automatycznie skojarzyć sterownik z kompatybilnym z nim urządzeniem. Następnie uruchamiana jest funkcja, wskaźnik do której przekazano w elemencie .probe:
static int serial_omap_probe(struct platform_device *pdev) { /* ... */ if (pdev->dev.of_node) omap_up_info = of_get_uart_port_info(&pdev->dev); /* ... */ \end{verbatim} Wywoływana jest oddzielna funkcja, zdefiniowana w tym samym pliku: \begin{verbatim} static struct omap_uart_port_info *of_get_uart_port_info(struct device *dev) { /* ... */ of_property_read_u32(dev->of_node, "clock-frequency", &omap_up_info->uartclk);
Odczytana w ten sposób częstotliwość zegara, jest następnie ustawiana.
W analogiczny sposób, sterownik odczytuje numer przerwania i adresy rejestrów:
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); /* ... */ irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
A także informacje o zegarach, numery kanałów DMA oraz dodatkowe atrybuty.
Skąd mam wiedzieć co umieścić w Device Tree?
Podstawowym źródłem informacji jest oczywiście kod danego sterownika. Bywa on jednak skomplikowany i nieprzejrzysty. Dla ułatwienia pracy architektom systemu, którzy przygotowują Device Tree dla sprzętu, w kodzie źródłowym jądra, zajdują się listy atrybutów poszczególnych sterowników: Documentation/devicetree/bindings/.
Na przykład dla omawianego tu portu szeregowego – Documentation/devicetree/bindings/serial/omap_serial.txt:
OMAP UART controller Required properties: – compatible : should be "ti,omap2-uart" for OMAP2 controllers – compatible : should be "ti,omap3-uart" for OMAP3 controllers – compatible : should be "ti,omap4-uart" for OMAP4 controllers – ti,hwmods : Must be "uart", n being the instance number (1-based) Optional properties: – clock-frequency : frequency of the clock input to the UART
Multipleksacja
Raz jeszcze dla BeagleBone Black. W am335x-bone-common.dtsi, mamy:
am33xx_pinmux: pinmux@44e10800 { /* ... */ uart0_pins: pinmux_uart0_pins { pinctrl-single,pins = < 0x170 (PIN_INPUT_PULLUP | MUX_MODE0) /* uart0_rxd.uart0_rxd */ 0x174 (PIN_OUTPUT_PULLDOWN | MUX_MODE0) /* uart0_txd.uart0_txd */ >; }; };
Wartości typu MUX_MODE0 zdefiniowane są z kolei w odpowiednim pliku nagłówkowym (am33xx.dtsi):
#include #include
Znaczenie poszczególnych pól, znajdziemy raz jeszcze w dokumentacji.
Przykład 2 – podłączamy termometr I2C
Dołączamy termometr I2C do Beagle Bone Black. Niech będzie to układ kompatybilny z LM75. Linux zawiera odpowiedni sterownik. Magistrala I2C0 (pierwsza) jest na stałe podłączona do układu kontrolującego zasilanie. Nie możemy jej użyć. Skorzystamy z I2C2.
Należy zmodyfikować Device Tree dla naszego urządenia:
/* To jest nasza definicja magistrali i2c - podstawowe ustawienia znajdują się już w pliku dtsi. */ i2c2: i2c@4819c000 { /* Multipleksacja musi zostać odpowiednio ustawiona */ pinctrl-names = "default"; pinctrl-0 = <&i2c2_pins>; /* Urządzenie jest włączone */ status = "okay"; /* Atrybut rozumiany przez sterownik - I2C działa na 400kHz */ clock-frequency = <400000>; /* Dodajemy termometr - LM75 pod adresem 0x4f*/ lm75@4f { compatible = "lm75"; reg = <0x4f>; }; };
Aby ustawić multipleksację, należy sięgnąć do dokumentacji zestawu. Z dokumentu BeagleBone Black System Reference Manual, dowiemy się że magistrala I2C2 wyprowadzona jest na jednym ze złącz.
Rys 8. Fragment dokumentacji multipleksacji pin-ów wyprowadzonych na złączu P9 BeagleBone Black
i2c2_pins: pinmux_i2c2_pins { pinctrl-single,pins = < 0x178 (PIN_INPUT_PULLUP | MUX_MODE3) /* uart1_ctsn.i2c2_sda */ 0x17c (PIN_INPUT_PULLUP | MUX_MODE3) /* uart1_rtsn.i2c2_scl */ >; };
Pozostaje tylko skompilować odpowiedni sterownik (make ARCH=arm menuconfig):
Device Drivers ---> <*> Hardware Monitoring support ---> <*> National Semiconductor LM75 and compatibles
Po kompilacji jądra i Device Tree …
make ARCH=arm CROSS_COMPILE=... zImage dtbs
… i uruchomieniu nowego systemu, można korzystać z funkcji dostarczanych przez sterownik.
Aktualna temperatura:
cat /sys/class/hwmon/hwmon0/device/temp1_input
Podsumowanie
Device Tree jest sposobem opisu urządzeń w Linuksie dla ARM. Pozwala w elegancki i standardowy sposób zdefiniować urządzenia, które nie mogą być automatycznie wykryte (czyli większość stosowanych w systemach wbudowanych). Jest stosowany we wszystkich nowych urządzeniach obsługiwanych przez Linux.
Szkolenie
25…29 listopada 2013r. organizuję szkolenie dla wszystkich zainteresowanych rozwijaniem projektów opartych o Embedded Linux. Kurs daje solidną wiedzę i bazę do pogłębiania jej. Uczestnicy będą w stanie zaplanować, rozpocząć i rozwijać własny projekt. Praktyczne ćwiczenia są prowadzone na zestawie BeagleBone Black. Uczestnicy stopniowo implementują kolejne elementy systemu Linux i aplikacji.
Po szkoleniu uczestnicy zatrzymują urządzenia. Pozwala to samodzielnie rozwijać wiedzę a nawet budować prototypy własnych projektów.
Zapraszam – Marcin Bis
Marcin Bis od 2007 roku zajmuje się zastosowaniami Linuksa w systemach embedded, przede wszystkim w rozwiązaniach przemysłowych działających w czasie rzeczywistym (real time applications). Konsekwentnie zdobywa doświadczenie praktyczne, uczestnicząc w projektach z dziedziny automatyki przemysłowej, domowej, multimediów, urządzeń sieciowych i wielu innych, zarówno w Polsce i za granicą. Swoją wiedzą i doświadczeniami dzieli się na konferencjach, prowadzi także szkolenia pod marką bis-linux.com. Zajmuje się zagadnieniami związanymi z projektowaniem urządzeń, portowaniem i uruchamianiem Linuksa, sterownikami urządzeń a także doborem komponentów OpenSource i wreszcie aplikacjami i skryptami składającymi się na gotowy system. Jest ponadto autorem pierwszej w języku polskim książki o zastosowaniach Linuksa w systemach embedded, która ukazała się nakładem Wydawnictwa BTC w roku 2011 („Linux w systemach embedded”). |