Lekcja 08- sysfs

Wstęp

W tej lekcji zapoznamy się z implementacją kolejnej metody komunikacji z urządzeniem. W lekcji 03 zapoznaliśmy się z urządzeniami znakowymi i tworzyliśmy plik urządzenia w katalogu /dev, który reprezentował to urządzenie. Teraz utworzymy atrybut dla tego urządzenia. Mówiąc obrazowo, za atrybut urządzenia możemy uznać plik dostępny gdzieś w katalogu /sys za pomocą którego możemy wpływać na działanie urządzenia lub odczytać jakiś jego parametr np. atrybuty mogą reprezentować zawartość jakiegoś rejestru danego urządzenia.

Implementacja

W tej lekcji wykorzystamy naszą wiedzę o urządzeniach znakowych oraz(jakże by inaczej) o GPIO. Tym razem utworzymy sterownik za pomocą, którego będziemy mogli zmieniać stan diody poprzez zapis wartości 0 lub 1 do odpowiedniego pliku. Oczywiście będziemy mogli ten stan również odczytać. Diodę podłącz jak w poprzednich lekcjach. Implementacja wygląda następująco:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/gpio.h>

#define MOD_NAME "myled"
#define LED 48

static struct class *cls;
static struct device *dev;
static dev_t first;
static unsigned int count = 1;

static ssize_t led_state_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	int state = gpio_get_value(LED);
	return sprintf(buf, "%i", state);
}

static ssize_t led_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
	int state;
	if(sscanf(buf, "%i", &state) < 1) {
		return -EINVAL;
	}

	if(state < 0) {
		return -EINVAL;
	}

	gpio_set_value(LED, state);
	return count;
}

static DEVICE_ATTR(led_state, 0644, led_state_show, led_state_store);

static int __init my_init(void)
{
	if(alloc_chrdev_region(&first, 0, count, MOD_NAME) < 0) {
		pr_err("%s: Failed to allocate character device region\n", MOD_NAME);
		goto err1;
	}

	cls = class_create(THIS_MODULE, "myled");
	if(cls == NULL) {
		pr_err("%s: Cannot create class\n", MOD_NAME);
		goto err2;
	}

	dev = device_create(cls, NULL, first, "%s", "myled0");
	if(dev == NULL) {
		pr_err("%s: Cannot create device\n", MOD_NAME);
		goto err3;
	}

	if(device_create_file(dev, &dev_attr_led_state) != 0)	{
		pr_err("%s: cannot create sysfs entry!!!\n", MOD_NAME);
		goto err4;
	}

	if (!gpio_is_valid(LED)){
		pr_err("Invalid GPIO pin!!1\n");
		goto err5;
	}
	gpio_request(LED, "gpioLED");
	gpio_direction_output(LED, 1);
	gpio_export(LED, false);

	pr_info("%s: Loading succeeded, major=%i, minor=%i\n", MOD_NAME, MAJOR(first), MINOR(first));
	return 0;

err5:
	device_remove_file(dev, &dev_attr_led_state);
err4:
	device_destroy(cls, first);
err3:
	class_destroy(cls);
err2:
	unregister_chrdev_region(first, count);
err1:
	return -1;
}

static void __exit my_exit(void)
{
	gpio_set_value(LED, 0);
	gpio_unexport(LED);
	gpio_free(LED);
	device_remove_file(dev, &dev_attr_led_state);
	device_destroy(cls, first);
	class_destroy(cls);
	unregister_chrdev_region(first, count);
	pr_info("%s: Unloading succeeded\n", MOD_NAME);
}

module_init(my_init);
module_exit(my_exit);

MODULE_LICENSE("GPL");

Pierwsze co się pewnie wrzuciło Wam w oczy to dwie dodatkowe funkcje- funkcja led_state_show oraz led_state_store. Funkcje show i store służą odpowiednio do odczytu atrybutu oraz do zapisu atrybutu. Oczywiście nie trzeba implementować obu funkcji gdyż np. możemy implementować obsługę urządzenia, które nie obsługuje modyfikacji danej cechy, w takim wypadku zaimplementowalibyśmy tylko funkcję show.

Patrząc na te funkcje pewnie też zauważyłeś, że nie używamy funkcji copy_to_user i copy_from_user do pracy z przekazywanymi buforami, funkcje te nie wymagają użycia tych funkcji. Szukałem odpowiedzi na to pytanie i nie znalazłem go, jedyne co znalazłem to wpis na stockoverflow(zobacz ten wątek), że bufor jest bezpośrednim odniesieniem do bufora z user-space’a i jedyne co trzeba zrobić to sprawdzić czy dane, które się w nim znajdują są poprawne(w przypadku funkcji store oczywiście). Jedyne co nam pozostaje to zaakceptować ten fakt i nie dyskutować z tym…

Przejdźmy do samej implementacji, zaczniemy od funkcji show. Jak zostało wcześniej wspomniane zostanie ona wywołana podczas odczytu danych z danego atrybutu. U nas jest ona bardzo prosta, sprawdzamy stan piu GPIO za pomocą funkcji gpio_get_value i zapisujemy otrzymaną wartość do bufora za pomocą funkcji sprintf. Funkcja show zwraca ilość zapisanych bajtów czyli akurat tę samą wartość, którą zwraca sprintf.

Dalej przechodzimy do funkcji store, która to zostanie wywołana podczas modyfikacji danego atrybutu. W tym przypadku funkcja jest nieco bardziej złożona ponieważ musimy sprawdzić czy przekazane dane są poprawne. W tym przypadku próbujemy zapisać dane przekazane w buforze do zmiennej o nazwie state, która ma typ int, jeśli się ta operacja się nie powiedzie to zwracamy błąd. Następnie sprawdzamy czy jej wartość jest mniejsza niż 0, jeśli jest to zwracamy błąd. Gdy już jesteśmy pewni, że przekazane dane są poprawne ustawiamy stan pinu GPIO, do kótego podłączyliśmy diodę. Na końcu zwracamy ilość odczytanych danych czyli w naszym przypadku możemy zwrócić parametr count funkcji.

Poniżej tych dwóch funkcji mamy taką dziwną linijkę:

static DEVICE_ATTR(led_state, 0644, led_state_show, led_state_store);

DEVICE_ATTR jest makrem, które służy do prostego zdefiniowania struktury atrybuty dlatego też możemy przed nim umieścić słowo kluczowe static. Jak widać posiada ono 4 parametry. Pierwszy to nazwa atrybutu pod jaką będzie on widoczny w systemie plików, nie podajemy go w cudzysłowie. Kolejny parametr to uprawnienia dostępu, w tym przypadku użytkownik root posiada prawo do odczytu i zapisu, wszyscy pozostali będą mogli tylko odczytywać plik. Dwa ostatnie parametry makra to odpowiednio funkcja show oraz store. Struktura utworzona przez to makro będzie się nazywać dev_attr_led_state czyli w ogólności nazwa to dev_attr_pierwszyparametr

Funkcja init powinna być w większości zrozumiała. Najpierw tworzymy klasę myled i urządzenie znakowe myled0. Dodatkowo tworzymy atrybut tworzonemu urządzeniu za pomocą funkcji device_create_file, która przyjmuje dwa parametry- urządzenie dla którego tworzymy atrybut oraz strukturę opisującą ten atrybut. W dalszej części funkcji wykonuje wszystkie niezbędne ustawienia pinu GPIO do którego jest podłączona dioda LED.

Funkcja exit również powinna być zrozumiała, jedyną nowością w niej jest wywołanie funkcji device_remove_file, która to usuwa utworzony atrybut urządzenia.

To jest wszystko co potrzeba. Przygotuj Makefile:

obj-m += 08_sysfs.o
all:
	make -C /ścieżka/do/zbudowanego/kernela M=$(PWD) modules
clean:
	make -C /ścieżka/do/zbudowanego/kernela M=$(PWD) clean

I przebuduj moduł dla swojej płytki:

# BBB
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
# RPi4
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- KERNEL=kernel8

Testowanie sterownika

Prześlij zbudowany sterownik na swoją płytkę np. za pomocą scp, a następnie załaduj sterownik:

sudo insmod 08_sysfs.ko

Jeśli instalacja modułu się powiodła to przejdź do katalogu urządzenia i wyświetl jego zawartość:

cd /sys/class/myled/myled0
ls

Powinieneś zobaczyć plik led_state. Ponieważ do pliku led_state pełen dostęp na tylko użytkownik root to zaloguj się na tego użytkownika:

sudo su

Teraz możesz zmieniać i odczytywać stan diody za pomocą pliku led_state:

echo 1 > led_state
cat led_state
echo 0 > led_state
cat led_state

Jeśli dioda zapalała się i gasła oraz odczyt odpowiadał zapisowi to znaczy, że wszystko zostało dobrze zaimplementowane.

Ten wpis został opublikowany w kategorii Kurs pisania sterowników. Dodaj zakładkę do bezpośredniego odnośnika.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *