Zadanie 3: sterownik urządzenia DiceDev¶
Data ogłoszenia: 25.04.2023
Termin oddania: 30.05.2023 (ostateczny 13.06.2023)
Materiały dodatkowe¶
Symulator urządzenia: https://github.com/AndrzejJackowskiMIMUW/qemu (branch
dicedev
)Do rozwiązania i testów potrzebny jest plik dicedev.h z powyższego repozytorium
z3-tst-2023.tar.gz
testy do zadania
Wprowadzenie¶
Zadanie polega na napisaniu sterownika do urządzenia DiceDev, będącego generatorem ciągów kości na potrzeby gier planszowych, RPG i innych. Gry od wielu lat nie ograniczają się jedynie do najbardziej typowych kości K6, dlatego sterownik umożliwia generowanie kości mających nawet 32 ścianki.
Istnieje wiele praktycznych ataków na generatory liczb losowych, dlatego często używa się zaawansowanych algorymtów oraz urządzeń, a DiceDev jest testowym urządzeniem udającym taki generator. DiceDev posiada również komendy pozwalające w wybranych momentach celowo wybierać lepsze lub gorsze kości aby np. podnieść lub obniżyć poziom trudności gry.
Urządzenie powinno być dostępne dla użytkownika w formie urządzenia znakowego.
Dla każdego urządzenia DiceDev obecnego w systemie należy utworzyć urządzenie znakowe
/dev/diceX
, gdzie X to numer kolejny urządzenia DiceDev, zaczynając od 0.
Interfejs urządzenia znakowego¶
Urządzenie /dev/dice*
powinno obsługiwać następujące operacje:
open
: alokuje nowy kontekst urządzenia, który będzie służył do wysyłania poleceń.close
: w oczywisty sposób.ioctl(DICEDEV_IOCTL_CREATE_SET)
: tworzy nowy bufor, który będzie widoczny dla urządzenia. Jako parametr tego wywołania przekazywany jest rozmiar bufora oraz pole opisujące dozwolone rodzaje kości. Wynikiem tego wywołania jest nowy deskryptor pliku odnoszący się do utworzonego bufora. Utworzony bufor powinien być wypełniony zerami. Każdy bufor ma przypisany seed użyty przy generowaniu zestawu kości, inicjalnie ustawiony na 42.Tak utworzony bufor będzie można później zmapować do przestrzeni programu użytkownika. Można go również wykorzystać przy wysyłaniu poleceń do urządzenia. Jeśli żądany rozmiar jest większy od maksymalnego rozmiaru wspieranego przez urządzenie (4MiB), bądź przysłany jest nieobsługiwany typ bufora, należy zwrócić błąd
EINVAL
.ioctl(DICEDEV_IOCTL_RUN)
: uruchamia bądź kolejkuje uruchomienie zadania na urządzeniu. Parametrami są:deskryptor pliku wskazujący na bufor zawierający polecenia
wskaźnik na początek bloku poleceń (wewnątrz powyższego bufora)
długośc bloku poleceń (w bajtach)
deskryptory plików dodatkowego bufora
Jeśli operacja się powiedzie, należy zwrócić 0. Jeśli któryś deskryptor pliku nie jest buforem utworzonym dla tego urządzenia należy zwrócić bład
EINVAL
. Jeśli podany wskaźnik bądź rozmiar nie są wielokrotnością 4, należy zwrócić błądEINVAL
. Jeśli na kontekście wystąpił wcześniej błąd wykonania, można zwrócić błądEIO
.ioctl(DICEDEV_IOCTL_WAIT)
: czeka na zakończenie wykonywania przesłanych wcześniej zadań. Parametr określa, na ile zadań do tyłu chcemy czekać — jeśli wartością parametru jest 13, należy czekać na zakończenie wszystkich zadań wysłanych na danym kontekście, z wyjątkiem ostatnich 13tu zadań. Aby poczekać na zakończenie wszystkich zadań wysłanych na danym kontekście, użytkownik wywołuje tą funkcję z parametrem 0. Operacja powinna zwrócić 0 w przypadku sukcesu, bądźEIO
jeśli na tym kontekście nastąpił błąd wykonania.ioctl(DICEDEV_IOCTL_ENABLE_SEED_INCREMENT)
: włącza lub wyłącza tryb urządzenia odpowiedzialny za inkrementowanie seedów buforów przy generacji nowych zbiorów kości. Domyślnie tryb ten jest wyłączony.
Na buforach, poza użyciem w ioctl(DICEDEV_IOCTL_RUN)
, można wykonywać następujące operacje:
mmap
: zmapowanie do programu użytkownika (tylko z flagąMAP_SHARED
).ioctl(DICEDEV_BUFFER_IOCTL_SEED)
: pozwala zmienić seed danego bufora. Jeśli urządzenie jest w trybie
SEED_INCREMENT
należy zacząć inkrementację od nowa.
write
pozwalające pisać komendy związane z danym buforem przy pomocy io_uring.read
pozwalający odczytywać wyniki przy pomcy io_uring
Należy pamiętać, że użytkownik może wysłać niepoprawne polecenia. W przypadku, gdy wykonanie zadania
spowoduje błąd (urządzenie zgłosi przerwanie *_ERROR
), należy oznaczyć kontekst, który wysłał to
zadanie jako “spalony” i zwracać błąd EIO
na wszystkich wywołaniach DICEDEV_IOCTL_RUN
na danym kontekście od tego momentu, po czym zresetować urządzenie i kontynuować przetwarzanie zadań wysłanych przez inne konteksty.
Sterownik powinien rejestrować swoje urządzenia w sysfs, aby udev
automatycznie utworzył pliki urzadzeń o odpowiednich nazwach w /dev
.
Numery major i minor dla tych urządzeń są dowolne (majory powinny być
alokowane dynamicznie).
Sterownik może przyjąć ograniczenie do 256 urządzeń w systemie.
Za zadanie można uzyskać do 10 punktów. Na ocenę zadania składają się dwie części:
wynik testów (od 0 do 10 punktów)
ocena kodu rozwiązania (od 0 do -10 punktów)
Forma rozwiązania¶
Sterownik powinien zostać zrealizowany jako moduł jądra Linux w wersji
6.2.1. Moduł zawierający sterownik powinien nazywać się dicedev.ko
.
Jako rozwiązanie należy dostarczyć paczkę zawierającą:
źródła modułu
pliki Makefile i Kbuild pozwalające na zbudowanie modułu
krótki opis rozwiązania
Paczka powinna nazywać się ab123456.tar.gz
(gdzie ab123456
jest loginem
na students) i po rozpakowaniu tworzyć katalog ab123456
ze źródłami.
Rozwiązania prosimy nadsyłać na adres a.jackowski@mimuw.edu.pl
z kopią
do p.zuk@mimuw.edu.pl
.
Prosimy o umieszczenie [ZSO]
w tytule wiadomości.
QEMU¶
Do użycia urządzenia DiceDev wymagana jest zmodyfikowana wersja qemu, dostępna w wersji źródłowej.
Aby skompilować zmodyfikowaną wersję qemu, należy:
Sklonować repozytorium https://github.com/AndrzejJackowskiMIMUW/qemu
git checkout dicedev
Upewnić się, że są zainstalowane zależności:
ncurses
,libsdl
,curl
, a w niektórych dystrybucjach takżencurses-dev
,libsdl-dev
,curl-dev
(nazwy pakietów mogą się nieco różnić w zależności od dystrybucji)Uruchomić
./configure
z opcjami wedle uznania (patrz./configure --help
). Zalecamy flagi:--target-list=x86_64-softmmu --enable-virtfs --enable-gtk
cd build
Wykonać
make
(lubninja
, jeśli mamy zainstalowane)Zainstalować wykonując
make install
, lub uruchomić bezpośrednio (binarka tobuild/qemu-system-x86_64
).
Aby zmodyfikowane qemu emulowało urządzenie DiceDev, należy przekazać mu
opcję -device dicedev
. Przekazanie tej opcji kilka razy spowoduje emulację
kilku instancji urządzenia.
Aby dodać na żywo (do działającego qemu) urządzenie DiceDev, należy:
przejść do trybu monitora w qemu (Ctrl+Alt+2 w oknie qemu)
wpisać
device_add dicedev
przejść z powrotem do zwykłego ekranu przez Ctrl-Alt-1
wpisać
echo 1 > /sys/bus/pci/rescan
, aby linux zauważył
Aby udać usunięcie urządzenia:
echo 1 > /sys/bus/pci/devices/0000:<idurządzenia>/remove
Wskazówki¶
Do tworzenia plików dla buforów polecamy użyć funkcji anon_inode_getfile
lub anon_inode_getfd
.
Aby dostać strukturę file
z deskryptora pliku, możemy użyć fdget
i fdput
.
Aby sprawdzić, czy przekazana nam struktura
jest odpowiedniego typu, wystarczy porównać jej wskaźnik na strukturę
file_operations
z naszą.
Użycie FENCE_WAIT
¶
Polecenie FENCE_WAIT może być wykorzystane do efektywnego monitorwania które zadania zostały już wykonane. Można użyć go na wiele sposobów m.in. aby zaimplementować oczekiwanie na zakończone polecenia, a w przypadku błędnych poleceń stwierdzić które zadanie odpowiada za zepsucie kontekstu.
Implementacja mmap¶
Aby obsłużyć wywołanie mmap
na buforach, należy:
Napisać nasz callback
mmap
do strukturyfile_operations
, który ustawi polevm_ops
w podanymvma
na naszą strukturę z callbackami.W naszej strukturze
vm_operations_struct
wypełnić callbackfault
W callbacku
fault
:zweryfikować, że
pgoff
mieści się w rozmiarze bufora (jeśli nie, zwrócićVM_FAULT_SIGBUS
)wziąć adres wirtualny (w jądrze) odpowiedniej strony bufora i przekształcić go przez
virt_to_page
nastruct page *
zwiększyć licznik referencji do tej strony (
get_page
)wstawić wskaźnik na tą strukturę do otrzymanej struktury
vm_fault
(polepage
)zwrócić 0