Wstęp
W tej lekcji zapoznamy się ze sposobami komunikacji ze sprzętem. W Linuksie, jak w każdym innym systemie operacyjnym, za interakcje ze sprzętem odpowiadają sterowniki. Nie będziemy się zajmować tym jak implementować sterowniki, ale tym jak ich używać.
Rodzaje urządzeń w Linuksie
W Linuksie wyróżniamy trzy typu urządzeń:
- Znakowe(character devices)- są to urządzenia, które przesyłają ciąg bajtów. Jednym z przykładowych urządzeń znakowych jest UART, który dosyć intensywnie używamy w naszych lekcjach. Ich reprezentacje są widoczne w katalogu /dev w postaci plików
- Blokowe- są to urządzenia pamięci masowej np. dyski, pendrive’y czy używane przez nas karty SD. Ich reprezentacje są widoczne w katalogu /dev w postaci plików
- Sieciowe- urządzenie przesyłające i odbierające dane po sieci. Często można się spotkać ze stwierdzeniem odnośnie systemów UNIXowych, że wszystko to plik. Nie do końca jest to prawda, urządzenia sieciowe nie są reprezentowane jako pliki, ale jako interfejsy i są one widoczne przy użyciu komend takich jak ifconfig lub ip
Urządzenia znakowe
Z urządzeniami znakowymi możemy się komunikować za pomocą funkcji biblioteki standardowej read() oraz write() co w konsoli przekłada się na polecenia cat(do czytania pliku) i echo(do zapisu do pliku). Aby się przekonać się, że tak jest możesz wykonać prosty eksperyment. Podłącz swoją płytkę do komputera, ale nie uruchamiaj screena. Otwórz dwa okna terminala. W pierwszym wydaj komendę:
sudo cat /dev/ttyUSB0
W drugim zaloguj się najpierw na konto roota, a następnie wydaj komendy:
sudo su # zaloguj się na konto roota na komputerze
echo root > /dev/ttyUSB0 # zaloguj się do systemu na płytce
echo uname -a > /dev/ttyUSB0 # sprawdź wersje systemu
W pierwszym oknie po zalogowaniu się do systemu powinieneś zobaczyć, że pojawił się prompt czyli znak #, a po wydaniu drugiej komendy powinieneś zobaczyć wersje systemu, w przypadku RPi4 powinno to być coś podobnego do:
Linux buildroot 5.10.60-rpi4-aarch64-v1.0+ #4 SMP PREEMPT Sat Sep 18 21:08:04 CEST 2021 aarch64 GNU/Linux
Urządzenia te są oznaczone literą „c” w wyjściu komendy ls:
ls -l
crw------- 1 root root 4, 64 Jan 1 00:01 ttyS0
Urządzenia blokowe
Z tymi urządzeniami rzadko komunikujemy się w sposób bezpośredni choć użytkownicy BBB oraz RPi4 mieli szansę na taką interakcję podczas przygotowania swojej karty uSD. Do bezpośrednich operacji na urządzeniach blokowych należy formatowanie, tworzenie nowych partycji, ustawianie nowego systemu plików oraz montowanie. Gdy urządzenie blokowe jest podmontowane i wykonujemy operacje na plikach nasza interakcja z urządzeniem nie jest bezpośrednia.
Urządzenia te są oznaczone literą „b” w wyjściu komendy ls:
ls -l
brw------- 1 root root 179, 0 Jan 1 00:00 mmcblk0
Urządzenia sieciowe
Interakcja z tymi urządzeniami zwykle odbywa się za pomocą programów takich jak ifconfig czy ip i z reguły nie są to częste interakcje ponieważ gdy raz skonfigurujemy sieć nie potrzebujemy jej konfigurować przy każdym uruchomieniu. Później gdy np. piszemy program komunikujący się po sieci nie wskazujemy interfejsu, który ma zostać użyty, zakładamy, że system ogarnie cały routing i tym podobne rzeczy, my wskazujemy z kim chcemy się komunikować. Nie mniej istnieje możliwość wymuszenia użycia danego interfejsu sieciowego przez program, trzeba do tego wykorzystać funkcje ioctl i parametr SIOCGIFHWADDR.
A co z urządzeniami, które nie pasują do żadnej z tej kategorii?
Jeśli bawiłeś się choć trochę jakimiś elektronicznymi elementami jak np. najprostsze diody i przyciski czy wszelkiego rodzaju czujniki czy sterowniki, które komunikują się przez GPIO, I2C, SPI czy 1Wire możesz się zastanawiać co z takimi urządzeniami. Dane w tych urządzeniach są dostępne w rejestrach, które chcemy odczytywać w sposób swobodny więc nie są to ewidentnie urządzenia znakowe, które przesyłają ciąg bajtów. Ewidentnie nie są to też urządzenia blokowe ani sieciowe więc jak tego używać w Linuksie?
Otóż z reguły takie urządzenia są reprezentowane poprzez urządzenia znakowe i mamy następnie dwa sposoby komunikacji z nimi:
- Za pomocą funkcji ioctl(), jest to obecnie odradzane podejście
- Za pomocą sysfs czyli plików w katalogu /sys. Aktualnie jest to preferowane podejście, może ono jednak wymagać implementacji własnego sterownika jeśli chcemy pracować z jakimś mało popularnym urządzeniem
ioctl() coraz bardziej przechodzi do przeszłości dlatego też nie będziemy się nim zajmować w tej lekcji. Komunikacja z urządzeniami zostanie przedstawiona za pomocą diod LED.
Czym jest sysfs?
sysfs to wirtualny system plików. Wirtualny w tym przypadku oznacza, że są to pliki tworzone podczas pracy systemu. Pliki obecne w sysfs nie są częścią rootfsa.
sysfs zawiera reprezentacje obiektów kernela(ang. kernel objects), atrybutów tych obiektów oraz powiązań między nimi. Obiekty będą reprezentowane przez katalogi, a atrybuty przez pliki. Obiektem kernela może być np. urządzenie, a atrybutem zawartość danego rejestru tego urządzenia.
Zerkając w katalog /sys zobaczysz kilka katalogów, w praktyce nas najbardziej będą interesować trzy katalogi:
- bus/- katalog zawierający reprezentacje dostępnych magistrali i urządzeń podłączonych do nich
- class/- katalog agregujący urządzenia w klasy bez względu na ich interfejs komunikacyjny, przykładowo moglibyśmy mieć klasę termometr do której należałyby dwa różne termometry z czego jeden używał by do komunikacji interfejsu 1Wire, a drugi SPI
- devices/- katalog zawierający informacje o podłączonych urządzeniach
Podłączenie
Niestety użytkownicy QEMU nie będą mogli przejść części praktycznej ze względu na brak możliwości fizycznego podłączenia czegokolwiek.
BeagleBone Black
Podłącz diodę LED do BBB poprzez pin oznaczony jako GPIO_48:
Pamiętaj o użyciu rezystora podczas podłączenia diody LED.
Raspberry Pi 4
Podłącz diodę LED do RPi4 poprzez pin oznaczony jako GPIO23:
Pamiętaj o użyciu rezystora podczas podłączenia diody LED.
GPIO w kernelu
Aby móc obsługiwać diody musimy uruchomić wsparcie dla GPIO w kernelu. Domyślnie w przypdaku BBB oraz RPi4 wsparcie dla GPIO powinno być uruchomione, ale dla pewności sprawdźmy to, przy okazji dowiemy się jak konfigurować kernel Linuksa z poziomu Buildroota. Przejdź do katalogu Buildroot i wydaj komendę:
make linux-menuconfig
Sprawdź czy są zaznaczone opcje CONFIG_GPIOLIB oraz CONFIG_GPIO_SYSFS. Jeśli nie są to zaznacz je jako wkompilowane w kernel. Dla przypomnienia możesz użyć klawisza „/” do włączenia trybu wyszukiwania. Przebuduj swój kernel:
make
A następnie skopiuj obraz kernela oraz plik device-tree na kartę pamięci z katalogu output/images.
Gdy już masz tak przygotowany kernel uruchom swoją płytkę i wykonaj następujące komendy:
# BBB i RPi4
cd /sys/class/gpio
# BBB
echo 48 > export
# RPi4
echo 23 > export
W ten sposób właśnie aktywowałeś dany pin GPIO w kernelu. Przejdź do nowopowstałego katalogu:
# BBB
cd gpio48
# RPi4
cd gpio23
Wewnątrz tego katalogu jest kilka plików(atrybutów). Na ten moment nas interesują pliki direction czyli czy dany pin jest wejściem lub wyjściem oraz value czyli stan pinu. Domyślnie piny są ustawione jako wejście(in), my pracujemy z diodami więc chcemy aby piny były wyjściami, zatem wykonaj następującą komendę:
echo out > direction
A teraz możemy włączać i wyłączać diodę:
echo 1 > value
echo 0 > value
Brawo! Właśnie ogarnąłeś jak komunikować się ze sterownikami w Linuksie. W przypadku innych urządzeń będzie to wyglądać podobnie choć wiadomo, obsługa zawsze się będzie nieznacznie różnić ze względu na charakterystykę danego urządzenia czy klasy urządzeń.
Jako pracę domową możesz się zapoznać z obsługą diod LED, które są dostępne na twojej płytce. W przypadku BBB chodzi mi o te niebieskie diody znajdujące się przy gnieździe ethernetowym, a w przypadku RPi4 o czerwoną i zieloną diodę przy gnieździe USB-C. Ich reprezentacja znajduje się w katalogu /sys/class/leds.
Uwaga dla użytkowników BBB, wydaje się, że nowsze wersje kernela(5.x) mają jakiś problem z obsługą GPIO. Buildroot używa kernela 4.19 dla BBB i w tej wersji wygląda, że wszystko działa prawidłowo. Możliwe, że wkradł się jakiś błąd do kodu pomiędzy tymi wersjami.