=============================================== Zadanie 3: sterownik urządzenia CasinoDev =============================================== Data ogłoszenia: 26.04.2022 Termin oddania: 31.05.2022 (ostateczny 14.06.2022) .. contents:: .. toctree:: :hidden: casinodev Materiały dodatkowe =================== - :ref:`z3-casinodev` - Symulator urządzenia: https://github.com/AndrzejJackowskiMIMUW/qemu (branch ``casinodev``) - Do rozwiązania i testów potrzebny jest plik casinodev.h z powyższego repozytorium - :download:`z3_test.tar.gz` testy do zadania Wprowadzenie ============ Zadanie polega na napisaniu sterownika do :ref:`urządzenia CasinoDev`, będącego generatorem ciągów kart na potrzeby gier losowych np. w automatach. Istnieje wiele praktycznych ataków na generatory liczb losowych, dlatego często używa się zaawansowanych algorymtów oraz urządzeń, a CasinoDev jest testowym urządzeniem udającym taki generator. CasinoDev posiada również komendy pozwalające w wybranych momentach celowo wybierać lepsze lub gorsze karty aby np. podnieść lub obniżyć poziom trudności gry (na szczęście regulacje prawne w wielu krajach uniemożliwiają wykorzystanie tego typu komend w komercyjnych urządzeniach). Urządzenie powinno być dostępne dla użytkownika w formie urządzenia znakowego. Dla każdego urządzenia CasinoDev obecnego w systemie należy utworzyć urządzenie znakowe ``/dev/casinoX``, gdzie X to numer kolejny urządzenia CasinoDev, zaczynając od 0. Interfejs urządzenia znakowego ============================== Urządzenie ``/dev/casino*`` 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(CASINODEV_IOCTL_CREATE_DECKS)``: tworzy nowy bufor, który będzie widoczny dla urządzenia. Jako parametr tego wywołania przekazywany jest rozmiar bufora oraz jego typ (bufor na wszystkie karty lub większe od dziewiątek). 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 talii kart, 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(CASINODEV_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łąd ``EINVAL``. Jeśli na kontekście wystąpił wcześniej błąd wykonania, można zwrócić błąd ``EIO``. - ``ioctl(CASINODEV_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(CASINODEV_IOCTL_ENABLE_SEED_INCREMENT)``: włącza lub wyłącza tryb urządzenia odpowiedzialny za inkrementowanie seedów buforów przy generacji nowych tali kart. Domyślnie tryb ten jest wyłączony. Na buforach, poza użyciem w ``ioctl(CASINODEV_IOCTL_RUN)``, można wykonywać następujące operacje: - ``mmap``: zmapowanie do programu użytkownika (tylko z flagą ``MAP_SHARED``). - ``ioctl(CASINODEV_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. 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 ``CASINODEV_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 5.16.5. Moduł zawierający sterownik powinien nazywać się ``casinodev.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. .. _z3-qemu: QEMU ==== Do użycia urządzenia CasinoDev wymagana jest zmodyfikowana wersja qemu, dostępna w wersji źródłowej. Aby skompilować zmodyfikowaną wersję qemu, należy: 1. Sklonować repozytorium https://github.com/AndrzejJackowskiMIMUW/qemu 2. ``git checkout casinodev`` 3. Upewnić się, że są zainstalowane zależności: ``ncurses``, ``libsdl``, ``curl``, a w niektórych dystrybucjach także ``ncurses-dev``, ``libsdl-dev``, ``curl-dev`` (nazwy pakietów mogą się nieco różnić w zależności od dystrybucji) 4. Uruchomić ``./configure`` z opcjami wedle uznania (patrz ``./configure --help``). Zalecamy flagi:: --target-list=x86_64-softmmu --enable-virtfs --enable-gtk 5. ``cd build`` 6. Wykonać ``make`` (lub ``ninja``, jeśli mamy zainstalowane) 7. Zainstalować wykonując ``make install``, lub uruchomić bezpośrednio (binarka to ``build/qemu-system-x86_64``). Aby zmodyfikowane qemu emulowało urządzenie CasinoDev, należy przekazać mu opcję ``-device casinodev``. Przekazanie tej opcji kilka razy spowoduje emulację kilku instancji urządzenia. Aby dodać na żywo (do działającego qemu) urządzenie CasinoDev, należy: - przejść do trybu monitora w qemu (Ctrl+Alt+2 w oknie qemu) - wpisać ``device_add casinodev`` - 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:/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: 1. Napisać nasz callback ``mmap`` do struktury ``file_operations``, który ustawi pole ``vm_ops`` w podanym ``vma`` na naszą strukturę z callbackami. 2. W naszej strukturze ``vm_operations_struct`` wypełnić callback ``fault`` 3. 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`` na ``struct page *`` - zwiększyć licznik referencji do tej strony (``get_page``) - wstawić wskaźnik na tą strukturę do otrzymanej struktury ``vm_fault`` (pole ``page``) - zwrócić 0