=============================================== Zadanie 3: sterownik urządzenia Final HardDoom™ =============================================== Data ogłoszenia: 04.05.2021 Termin oddania: 08.06.2021 (ostateczny 22.06.2021) .. contents:: .. toctree:: :hidden: fharddoom Materiały dodatkowe =================== - :ref:`z3-fharddoom` - Symulator urządzenia: https://github.com/mwkmwkmwk/qemu (branch ``fharddoom``) - Program testowy: https://github.com/mwkmwkmwk/prboom-plus (branch ``fdoomdev``) - Pliki nagłówkowe do skopiowania do rozwiązania: - https://github.com/mwkmwkmwk/fharddoom/blob/main/fharddoom.h (definicje rejestrów sprzętowych) - https://github.com/mwkmwkmwk/fharddoom/blob/main/fdoomdev.h (definicje interfejsu urządzenia znakowego) - https://github.com/mwkmwkmwk/fharddoom/blob/main/fdoomfw.h (firmware potrzebny do uruchomienia urządzenia) - :download:`z3_test.tar` testy do zadania Wprowadzenie ============ Zadanie polega na napisaniu sterownika do :ref:`urządzenia Final HardDoom™ `, będącego akceleratorem grafiki dedykowanym dla gry Final Doom. Urządzenie dostarczane jest w postaci :ref:`zmodyfikowanej wersji qemu `. Urządzenie powinno być dostępne dla użytkownika w formie urządzenia znakowego. Dla każdego urządzenia Final HardDoom™ obecnego w systemie należy utworzyć urządzenie znakowe ``/dev/fdoomX``, gdzie X to numer kolejny urządzenia Final HardDoom™, zaczynając od 0. Interfejs urządzenia znakowego ============================== Urządzenie ``/dev/fdoom*`` służy do tworzenie zasobów Final HardDoom™ oraz wysyłania poleceń do urządzenia Final HardDoom™. Powinno ono 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(FDOOMDEV_IOCTL_CREATE_BUFFER)``: tworzy nowy bufor, który będzie widoczny dla urządzenia. Jako parametr tego wywołania przekazywany jest rozmiar bufora oraz przeskok bufora (który może być zerem, jeśli bufor nie jest płaszczyzną). Wynikiem tego wywołania jest nowy deskryptor pliku odnoszący się do utworzonego bufora. Utworzony bufor powinien być wypełniony zerami. Tak utworzony bufor będzie można później zmapować do przestrzeni programu użytkownika oraz wykorzystać przy wysyłaniu poleceń do urządzenia. Jeśli żądany rozmiar lub przeskok jest większy od maksymalnego rozmiaru wspieranego przez urządzenie (4MiB), bądź przeskok nie jest wielokrotnością 64, należy zwrócić błąd ``EINVAL``. - ``ioctl(FDOOMDEV_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) - liczba dodatkowych buforów, które są używane w danym bloku poleceń (nie więcej niż 60) - deskryptory plików dodatkowych buforów (powinny one być widoczne pod kolejnymi numerami slotów, zaczynając od 0) Jeśli operacja się powiedzie, należy zwrócić 0. Jeśli któryś deskryptor pliku nie jest buforem utworzonym dla tego urządzenia Final HardDoom™, 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 liczba dodatkowych buforów jest większa niż 60, 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(FDOOMDEV_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. Na buforach, poza użyciem w ``ioctl(FDOOMDEV_IOCTL_RUN)``, można wykonywać następujące operacje: - ``mmap``: zmapowanie do programu użytkownika (tylko z flagą ``MAP_SHARED``). - ``ioctl(FDOOMDEV_IOCTL_BUFFER_RESIZE)``: zwiększa bufor do podanego rozmiaru. Jeśli żądany rozmiar jest większy od maksymalnego rozmiaru wspieranego przez urządzenie (4MiB), należy zwrócić błąd ``EINVAL``. Jeśli żądany rozmiar jest mniejszy od obecnego bufora, można zignorować polecenie i zwrócić 0. 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`` bądź ``PAGE_FAULT_*``), należy oznaczyć kontekst, który wysłał to zadanie jako "spalony" i zwracać błąd ``EIO`` na wszystkich wywołaniach ``FDOOMDEV_IOCTL_RUN`` i ``FDOOMDEV_IOCTL_WAIT`` 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). Plik nagłówkowy z odpowiednimi definicjami można znaleźć tutaj: https://github.com/mwkmwkmwk/fharddoom/blob/main/fdoomdev.h Sterownik może przyjąć ograniczenie do 256 urządzeń w systemie. Założenia interakcji ze sprzętem ================================ Można założyć, że przed załadowaniem sterownika, urządzenie ma stan jak po resecie sprzętowym. Urządzenie należy też w takim stanie zostawić przy odładowaniu sterownika. Pełnowartościowe rozwiązanie powinno działać asynchronicznie i używać automatycznego wczytywania poleceń — operacje ``FDOOMDEV_IOCTL_RUN`` powinny dopisywać zadanie do bufora ``CMD_MAIN`` i wracać natychmiast do przestrzeni użytkownika bez oczekiwania na zakończenie zadania (ale jeśli bufor ``CMD_MAIN`` jest już pełny, dopuszczalne jest oczekiwanie na zwolnienie miejsca w nim). Oczekiwanie na zakończenie poleceń powinno być wykonane dopiero przy wywołaniu ``FDOOMDEV_IOCTL_WAIT``. .. Implementując sterownik można zignorować funkcje suspend itp. - zakładamy, .. że komputer nie będzie usypiany (jest to bardzo zła praktyka, ale niestety .. nie mamy możliwości sensownego przetestowania suspendu w qemu ze względu na .. bugi w virtio). Zasady oceniania ================ Za zadanie można uzyskać do 10 punktów. Na ocenę zadania składają się trzy części: - pełne wykorzystanie urządzenia (od 0 do 2 punktów): - operacja asynchroniczna — ``FDOOMDEV_IOCTL_RUN`` na urządzeniu nie czeka na zakończenie wysłanego zadania ani zadań wysłanych wcześniej (z rozsądnymi ograniczeniami), ``FDOOMDEV_IOCTL_WAIT`` czeka tylko na tyle zadań ile musi: 1p - użycie automatycznego wczytywania poleceń (``CMD_MAIN_GET/PUT``) zamiast ręcznego (``CMD_MANUAL_FEED``): 1p - wynik testów (od 0 do 8 punktów) - ocena kodu rozwiązania (od 0 do -10 punktów) Punktacja --------- Najczęstsze błędy - spis oznaczeń w USOSwebie: 1. Brak README (-0.1) 2. Brak obsługi czekania gdy brakuje miejsca na zgłaszane polecenia (-0.2) 3. Brak sprawdzania błędu przy alokacji pamięci (-0.5) 4. Aktywne oczekiwanie na zakończenie zadań (-0.5) 5. Niepoprawne użycie locków (podwójne wywołanie mutex_unlock, etc.) (-0.2) 6. Nieprawidłowa obsługa wielu wątków (-0.5) 7. Brak obsługi suspend/resume (-0.3) 8. Brak adekwatnych zmian w konfiguracji urządzenia przy suspend / resume (-0.1) 9. Brak oczekiwania na zakończenie zadań przy suspend (-0.1) 10. Nieprawidłowa implementacja wykorzystująca automatyczne wczytywanie poleceń (-1.0) 11. Brak pełnego wyłączania urządzenia przy błędzie podczas inicjalizacji (-0.1) 12. Wyciek pamięci (-0.2) 13. Potencjalne spanie (kmalloc z GFP_KERNEL etc.) podczas trzymania spinlocka (-0.5) 14. Niepoprawne bindowanie buforów (-0.0; ujęte w pkt. za testy) 15. Niepełna konfiguracja urządzenia PCI (-0.0; ujęte w pkt. za testy) 16. Niepoprawne ustawianie ``PRESENT`` w PTE (-0.0; ujęte w pkt. za testy) 17. Brak zwracania ``EIO`` we wszystkich wymaganych przypadkach (-0.5) 18. Bufor odwołuje się do urządzenia poprzez kontekst (który może istnieć krócej) (-0.5) Forma rozwiązania ================= Sterownik powinien zostać zrealizowany jako moduł jądra Linux w wersji 5.11.2. Moduł zawierający sterownik powinien nazywać się ``fharddoom.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 ``p.zuk@mimuw.edu.pl`` z kopią do ``mwk@mimuw.edu.pl``. Prosimy o umieszczenie ``[ZSO]`` w tytule wiadomości. .. _z3-qemu: QEMU ==== Do użycia urządzenia Final HardDoom™ 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/mwkmwkmwk/qemu 2. ``git checkout fharddoom`` 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``). Oficjalna binarka była kompilowana z:: --target-list=x86_64-softmmu 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 Final HardDoom™, należy przekazać mu opcję ``-device fharddoom``. Przekazanie tej opcji kilka razy spowoduje emulację kilku instancji urządzenia. Aby dodać na żywo (do działającego qemu) urządzenie Final HardDoom™, należy: - przejść do trybu monitora w qemu (Ctrl+Alt+2 w oknie qemu) - wpisać ``device_add fharddoom`` - 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 Program testowy =============== Do testowania sterownika przygotowaliśmy zmodyfikowaną wersję ``prboom-plus``, który jest uwspółcześnioną wersją silnika gry Final Doom. Aby go uruchomić, należy: - pobrać źródła z repozytorium https://github.com/mwkmwkmwk/prboom-plus - wybrać branch ``fdoomdev`` - skompilować źródła i zainstalować program (bez instalacji, program nie będzie w stanie znaleźć swojego pliku z danymi, ``prboom-plus.wad``): - ``./bootstrap`` - ``./configure --prefix=$HOME`` - ``make`` - ``make install`` - pobrać plik z danymi gry. Można użyć dowolnego z następujących plików: - ``freedoom1.wad`` lub ``freedoom2.wad`` z projektu Freedoom (https://freedoom.github.io/) -- klon gry Doom dostępny na wolnej licencji. - ``doom.wad``, ``doom2.wad``, ``tnt.wad``, lub ``plutonia.wad`` z pełnej wersji oryginalnej gry, jeśli zakupiliśmy taką. - ``doom1.wad`` z wersji shareware oryginalnej gry. - załadować nasz sterownik i upewnić się, że mamy dostęp do ``/dev/fdoom0`` - uruchomić X11, a w nim grę: ``$HOME/bin/prboom-plus -iwad `` - w menu Options -> General -> Video mode wybrać opcję "doomdev" (domyślne ustawienie "8bit" wybiera renderowanie programowe w trybie bardzo podobnym do naszego urządzenia i można go użyć do porównania wyników). Aby w grze działał dźwięk, należy przekazać do qemu opcję ``-soundhw hda`` i przy kompilacji jądra włączyć stosowny sterownik (Device Drivers -> Sound card support -> HD-Audio). 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ą. Jeżeli popsujemy konfigurację ``prboom-plus`` tak, że przestanie się uruchamiać w stopniu wystarczającym do zmiany ustawień, możemy znaleźć jego plik konfiguracyjny w ``$HOME/.prboom-plus/prboom-plus.cfg``. Usunięcie go spowoduje przywrócenie ustawień domyślnych. Użycie ``FENCE_WAIT`` --------------------- W przypadku użycia bufora ``CMD_MAIN``, prawdopodobnie nie chcemy otrzymywać przerwań po każdym zakończeniu zadania — dobrym pomysłem jest więc użycie przerwania ``FENCE_WAIT`` by czekać tylko na wybane zadania. Ponieważ możemy chcieć jednocześnie czekać na zakończenie kilku operacji, sterownik musi mieć listę aktywnych oczekiwań i ustawić ``FENCE_WAIT`` na najwcześniejszą z nich. W szczególności, prawdopodobnie będzie konieczne oczekiwanie w następujących przypadkach: - wykonanie ``FDOOMDEV_IOCTL_WAIT`` - brak wolnego miejsca w buforze ``CMD_MAIN`` przy wywołaniu ``FDOOMDEV_IOCTL_RUN`` (trzeba czekać na zakończenie najstarszego wysłanego zadania) - wywołanie ``suspend`` (trzeba czekać na zakończenie najnowszego wysłanego zadania, czyli na opróżnienie bufora) 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