Zadanie 3: sterownik urządzenia Final HardDoom™¶
Data ogłoszenia: 04.05.2021
Termin oddania: 08.06.2021 (ostateczny 22.06.2021)
Materiały dodatkowe¶
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)
z3_test.tar
testy do zadania
Wprowadzenie¶
Zadanie polega na napisaniu sterownika do urządzenia Final HardDoom™, będącego akceleratorem grafiki dedykowanym dla gry Final Doom. Urządzenie dostarczane jest w postaci 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łądEINVAL
.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łądEINVAL
. Jeśli liczba dodatkowych buforów jest większa niż 60, 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(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łądEINVAL
. 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
.
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: 1puż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:
Brak README (-0.1)
Brak obsługi czekania gdy brakuje miejsca na zgłaszane polecenia (-0.2)
Brak sprawdzania błędu przy alokacji pamięci (-0.5)
Aktywne oczekiwanie na zakończenie zadań (-0.5)
Niepoprawne użycie locków (podwójne wywołanie mutex_unlock, etc.) (-0.2)
Nieprawidłowa obsługa wielu wątków (-0.5)
Brak obsługi suspend/resume (-0.3)
Brak adekwatnych zmian w konfiguracji urządzenia przy suspend / resume (-0.1)
Brak oczekiwania na zakończenie zadań przy suspend (-0.1)
Nieprawidłowa implementacja wykorzystująca automatyczne wczytywanie poleceń (-1.0)
Brak pełnego wyłączania urządzenia przy błędzie podczas inicjalizacji (-0.1)
Wyciek pamięci (-0.2)
Potencjalne spanie (kmalloc z GFP_KERNEL etc.) podczas trzymania spinlocka (-0.5)
Niepoprawne bindowanie buforów (-0.0; ujęte w pkt. za testy)
Niepełna konfiguracja urządzenia PCI (-0.0; ujęte w pkt. za testy)
Niepoprawne ustawianie
PRESENT
w PTE (-0.0; ujęte w pkt. za testy)Brak zwracania
EIO
we wszystkich wymaganych przypadkach (-0.5)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.
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:
Sklonować repozytorium https://github.com/mwkmwkmwk/qemu
git checkout fharddoom
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
). Oficjalna binarka była kompilowana z:--target-list=x86_64-softmmu
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 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:<idurządzenia>/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
lubfreedoom2.wad
z projektu Freedoom (https://freedoom.github.io/) – klon gry Doom dostępny na wolnej licencji.doom.wad
,doom2.wad
,tnt.wad
, lubplutonia.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 <dane_gry.wad>
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łaniuFDOOMDEV_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:
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