LinkedIn YouTube Facebook
Szukaj

Newsletter

Proszę czekać.

Dziękujemy za zgłoszenie!

Wstecz
Artykuły

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

Szczegóły, program, ceny.

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 

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

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 

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.

Szczegóły, program, ceny.

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”).

Autor: Marcin Bis