SAM9260 a grafický TFT displej - 8. ovladač framebufferového zařízení
V předchozím a předposledním dílu našeho seriálu o TFT displeji jsme si řekli něco málo o framebufferovém zařízení. Z prostorových důvodů (dlouhé články nikdo nečte) jsem implementaci samotnou přesunul až do tohoto dílu. Takže ti, co vydrželi a četli seriál až do konce, budou odměněni. Zdrojovým kódem ovladače framebufferového zařízení pro TFT displej.
Implementace
Na produktové stránce modulu SAM9260 si stáhněte zdrojové kódy ovladače framebufferového zařízení INT070ATFT-TS pro TFT displej. Jen připomínám, že tento ovladač předpokládá připojení k modulu uCSimply SAM9260 způsobem popsaným v předchozích dílech seriálu. Dále chci zdůraznit, že tento zdrojový kód je spíše příkladem, inspirac9 apod., ale rozhodně není připravený pro produkční použití.
Zdrojový kód ovladače předpokládá kompilaci mimo strom jádra. To znamená, že ovladač nelze zakompilovat do jádra jako jeho součást. Výsledkem kompilace bude prostě jen modul jádra, který lze za běhu do jádra zavést pomocí příkazu insmod nebo modprobe.
Nemá cenu rozebírat kompletní zdrojový kód mého ovladače. I s ohledem na to, že je jsem jej snažil bohatě komentovat. V následujících odstavcích proto jen vypíchnu to nejzajímavější.
Poznámka: Zjistil jsem, že než pátrat na internetu a snažit se odhalit princip fungování framebufferového zařízení, je často efektivnější prostě vzít zdrojáky nějakého ovladače, takové co působí dojmem, že autor věděl co dělá, a z nich odvodit princip obsluhy toho či jiného zařízení/HW.
První věcí, která není na první pohled jasná, je mechanismus, jakým jsou data z framebufferu přenášena do zobrazovacího zařízení. Nejběžnější způsob je takový, že zobrazovací zařízení, v případě TFT/LCD displeje řadič displeje, má framebuffer přímo na sobě a tento framebuffer je namapovatelný do adresového prostoru procesoru/mikrokontroléru jako další vnější paměťové zařízení. Podmínkou samozřejmě je, že řadič displeje má vyvedenou adresovou a datovou sběrnici. V tomto případě se z pohledu vývojáře ovladače nemusíme o přenos dat starat. Cokoliv, co je zapsáno z uživatelské aplikace do framebufferu, je okamžitě přístupné i řadiči displeje, který už sám s danou obnovovací frekvencí zajistí vykreslení na TFT panelu (samotném zobrazovacím elementu).
V mém případě to tak jednoduché nebylo. TFT displej INT070ATFT-TS má v sobě integrovaný řadič SSD1963, který bohužel není přímo přístupný (z řadiče není vyvedena adresová sběrnice), takže jej není možné namapovat do adresového prostoru mikrokontroléru SAM9260. Musel jsem na to jít jinak. Při inicializaci ovladače si alokuji blok v systémové paměti a ten používám jako framebuffer, který si může kdokoliv z uživatelského prostoru namapovat pomocí operace mmap(). S tím ovšem vyvstala otázka, jak detekovat, že někdo do tohoto framebufferu zapsal nová data. Použil jsem řešení, kdy mám framebuffery v systémové RAMce hned dva. Jeden je k dispozici aplikacím, druhý je referenční, přístupný pouze pro ovladač. V pravidelném intervalu pak srovnávám pixel po pixelu obsah těchto dvou framebufferů a všechny změny odesílám do framebufferu v TFT displeji, resp. jeho řadiči SSD1963. Řadič displeje se pak už sám postará o vykreslení dat ze svého interního framebufferu. Toto řešení samozřejmě není ideální, protože jednak zbytečně zatěžuje CPU mikrokontroléru (není možné využít DMA přenos) a jednak se značně snižuje snímková frekvence (fps) na asi 2 - 4 snímky za vteřinu. Na přehrávání videa to tedy rozhodně není, pro jednoduché GUI, kdy se zpravidla mění jen část plochy je to dostačující.
Kód porovnávající obsah framebufferů v systémové RAMce pro 16bitovou barevnou hloubku a schéma RGB565 je ve funkci refresh_screen_16(). Tato funkce je volána z funkce task_refresh_screen(), což je uživatelská funkce, kterou v definované periodě (30 ms) volá časovač refresh_timer, přiřazený mému ovladači. Prakticky je to tak, že na konci inicializace ovladače požádám jádro o přiřazení časovače, ten inicializuji a spustím jej. Od toho momentu je možné na TFT displeji zobrazovat data.
Složitosti spojené s alokováním framebufferů v systémové paměti za mě řeší funkce rvmalloc() a rvfree(). Ono totiž alokování systémové paměti v prostoru jádra není jen tak. Jádro totiž, bez ohledu na to jestli pracuje s fyzickou nebo virtuální pamětí, rozděluje tuto paměť na stránky - bloky velké obvykle 4kB. Proto je vhodné si požadovanou velikost alokované zaokrouhlit směrem nahoru tak, aby byla dělitelná velikostí stránky. A protože ”půjčujeme” jeden z framebufferů ”ven” do uživatelského prostoru, je vhodné nastavit i příznak, že tato paměť nesmí být odswapována na pevný disk, byť jej na modulu SAM9260 nemáme. To víte, jistota je jistota :)
S půjčováním framebufferu, vytvořeného v systémové paměti, zlým aplikacím do uživatelského prostoru souvisí další ”jobovka” - nemohl jsem pro obsluhu systémového volání mmap() využít výchozí funkci fb_mmap() ze souboru fbmem.c, ale musel jsem si napsat vlastní funkci int070atftfb_mmap(). Důvod? Po pravdě ten, že framebuffer alokuji pomocí funkce vmalloc(), která alokuje virtuální paměť, nikoliv fyzickou jak to děla funkce kmalloc(). Jenže do uživatelského prostoru je možné mapovat jen fyzickou paměť, takže mi nezbývalo nic jiného, než vzít co mi funkce vmalloc() vrátila a stránku po stránce převádět framebuffer z virtuálního adresového prostoru do fyzického adresového prostoru a následně každou převedenou stránku namapovat do uživatelského prostoru. Uznávám, je to komplikované až běda. Proč to? Protože funkce kmalloc() byla a stále je určena pro alokování malého množství paměti, řádově desítky bytů až kilobytů. V moderních jádrech už ovšem zvládá až 4MB. Problém ale je, že funkce kmalloc() alokuje fyzickou paměť vždy jen spojitě. Takže jakmile například poslední potřebná stránka patří jinému ovladači/procesu, tak funkce selže, místo aby stránku přeskočila a alokovala další volnou. Funkce vmalloc() se takto nechová, selže jen při nedostatku paměti. Zase je pomalejší a vyžaduje opičky okolo.
Kód pro obsluhu řadiče SSD1963 - inicializace, rutiny pro zápis příkazu a dat - jsem s mírnými úpravami převzal z testovacího ovladače, který jsme spolu dali dohromady v předchozích dílech. Do většiny původních funkcí přibyl parametr pio_base, který byl předtím součástí globální struktury popisující ovladač. Funkce clear_tft() byla napsána kompletně znova, tak aby respektovala způsob přenosu dat k zobrazení ze systémového framebufferu.
Pro zajímavost uvedu ještě výpis kódu, kde inicializuji proměnou typu struct fb_ops:
static struct fb_ops int070atftfb_fbops = { .owner = THIS_MODULE, .fb_read = fb_sys_read, .fb_write = fb_sys_write, .fb_mmap = &int070atftfb_mmap, .fb_check_var = NULL, .fb_set_par = NULL, .fb_setcolreg = &int070atftfb_setcolreg, .fb_blank = NULL, .fb_fillrect = sys_fillrect, .fb_copyarea = sys_copyarea, .fb_imageblit = sys_imageblit };
Obecně platí, že pokud není v struktuře fb_ops nastaven funkční ukazatel různý od NULL, je použita funkce ze souboru fbmem.c.
Poznámka: Statická definice strukturní proměnné int070atftfb_fbops zajišťuje, že explicitně nevyjmenovaní členové struktury jsou nastaveni na hodnotu 0, resp. NULL.
Z výpisu je patrné, že nevyužívám výchozí implementace pro funkce fb_read(), fb_write() nízkoúrovňového rozhraní ovladače, ale používám implementaci ze souboru fb_sys_ops.c, která je vhodná v situaci, kdy je framebuffer alokován v systémové operační paměti, což je přesně můj případ. Implementaci funkce fb_mmap() jsem si napsal kompletně sám - je ve funkci int070atftfb_mmap(). Podobně i pro funkci fb_setcolreg() mám vlastní implementaci v podobě funkce int070atftfb_setcolreg().
Můj ovladač neumožňuje změnu parametrů framebufferového zařízení za běhu, proto pro funkce fb_check_var() a fb_set_par() nemám vlastní implementaci. Stejně tak nemám implementaci pro funkci fb_ blank(), která řeší power managment. Normálně bych jejich funkční ukazatele prostě nechal na výchozí hodnotě - NULL (zajištěno statickou deklarací proměnné int070atftfb_fbops) a tím zajistil, že bude použita funkce ze souboru fbmem.c s výchozí implementací. Ovšem tyto funkce jsou natolik důležité, že jsem chtěl explicitně vyjádřit, že změna parametrů je v mém případě pasé.
Funkce fb_fillrect(), fb_copyarea() a fb_imageblit() slouží pro akceleraci základních grafických úkonů. Řadič SSD1963 v mém TFT displeji ovšem žádnou hardwarovou akceleraci nemá, takže musím využít softwarové akcelerace vhodné pro framebuffer v systémové paměti. To už za mě naštěstí někdo udělal - viz funkce sys_fillrect(), sys_copyarea() a sys_imageblit(). Kdybych neměl framebuffer v systémové paměti, použil bych funkce s prefixem cfb_.
Poslední věc, která stojí za zmínku, je klasicky inicializace a ukončení práce ovladače. V mém testovacím ovladači jsem použil obvyklou dvojici - funkce init() a exit(). Ovšem při procházení zdrojáků jiných ovladačů pro framebufferové zařízení jsem si uvědomil, že všichni bez vyjímky používají funkci probe() a remove(). Ve funkcích init() a exit() pak už nedělají nic jiného, než že jen registrují a deregistrují tzv. platform driver. Proč to? Holt je to zkrátka relativně nový koncept, který vychází z předpokladu, že i framebufferové zařízení je obvykle do systému připojeno přes nějakou sběrnici - AGP, PCI express atd. podobně jako hotplug zařízení přes sběrnici USB. A protože ne všechna framebufferová zařízení jsou skutečně připojena přes fyzickou sběrnici, byla vytvořena virtuální sběrnice tzv. platform bus.
Jak už to na sběrnici bývá, stejných zařízeních může být na jednu sběrnici připojeno hned několik. A bylo by samozřejmě nelogické, mít pro každé zařízení v jádře zavedený extra ovladač, když se zařízení dané třídy liší jen potřebnými systémovými zdroji (I/O paměť, IRQ, ...). Proto se logika ovladače umisťuje do ovladače - platform driver, zatímco se požadavky na systémové prostředky (popis zařízení) umísťují do struktury platform device. Shoda ovladače a popisu zařízení se řeší přes jméno zařízení. Jakmile se registruje platform driver i platform device a oba se úspěšně spárují na základě shodného jména zařízení, dojde k volání funkce probe(), do které je předán ukazatel na strukturu platform device. Odsud si platform driver vytáhne informace o potřebných systémových prostředcích, zkusí je alokovat, ověří, že zařízení reaguje a provede další potřebné úkony. Struktura platform device a její registrace do jádra se obvykle umisťuje do kódu pro inicializaci dané platformy, např. v mém případě do souboru arch/arm/mach-at91/board-ucsimply_sam9260.c. Registrace platform driveru pak do funkce init(). Já toto pravidlo porušil a registraci platform device jsem vložil také do funkce init() pomocí obezličky - funkce platform_device_register_simple() - vhodné pro staré a s tímto konceptem nekompatibilní ovladače. Důvod je prostý - TFT displej je k modulu SAM9260 připojen na běžné I/O piny, možností jak jej připojit je tak mnoho. Žádný z nich tedy není specifický pro danou platformu, ale je specifický pro daný ovladač.
No a úplně poslední věc je úprava konfigurace jádra tak, aby jádro obsahovalo kód potřebný pro běh ovladače framebufferového zařízení. Patch, který do výchozí konfigurace jádra pro modul SAM9260 přidá podporu pro framebufferové zařízení, je ke stažení na našich stránkách. Tento patch přidává také ovladač PS/2 myši a AT klávesnice, což se nám bude hodit pro embedded alternativy subsystému X Windows.
Zajímavé je, že pro podporu framebufferového zařízení nestačilo zatrhnout volbu Support for frame buffer devices, ale bylo nutné vybrat i podvolbu Virtual Frame Buffer support. Jinak nebyly zkompilovány soubory s funkcemi pro práci s framebufferem v systémové paměti. Také se musím přiznat, že jsem neodolal, a zatrhl i volbu pro podporu framebufferové konzole a obligátního loga s Tuxem. K čemu je to dobré? Jakmile je náš ovladač framebufferového zařízení zaveden, tak funguje jako konzolové zařízení a je na něm zobrazován prompt Linuxu (příkazový řádek). No a kdybychom tento ovladač zavedli hned někdy brzo na začátku bootování jádra (třeba v ramdisku), viděli bychom bootovací hlášky a logo Tuxe. Úplně optimální by bylo ovladač zakompilovat do jádra.
Kompilace a test
Modul jsem kompiloval mimo strom jádra vůči jádru 2.6.38, které jsem opatřil patchem at91 a patchem ucsimply-sam9260 pro modul SAM9260. Samozřejmě jsem pak přidal patch add-fb-support pro podporu framebufferového zařízení. Všechny jsou ke stažení na produktových stránkách modulu SAM9260. Kdo neví jak aplikovat patche, nechť si přečte návod.
Jádro samotné je po aplikaci patchů potřeba nakonfigurovat pro modul SAM9260 a buď zkompilovat komplet nebo jen do stavu vhodného pro kompilaci modulů jádra. Kompilaci jsem prováděl cross-kompilátorem CodeBench GNU/Linux 2010.09-50 od Mentor Graphics (dříve CodeSourcery). Níže uvádím sadu příkazů, které připraví jádro opatřené patchi pro kompilaci modulu:
$ export ARCH=arm $ export CROSS_COMPILE=arm-none-linux-gnueabi- $ make distclean $ make ucsimply_sam9260_defconfig $ make modules_prepare
Samotný modul ovladače se kompiluje klasicky příkazem make v adresáři se zdrojovým kódem modulu. Jen je potřeba si pohlídat v Makefile cestu ke stromu jádra 2.6.38 v rámci vašeho Linuxového počítače. Výsledkem kompilace je soubor int070atft-fb.ko. Tento soubor je potřeba nějak přenést do modulu SAM9260 nabootovaného do Linuxu. Já využil souborový systém NFS, kdy jsem si nasdílel adresář v mém počítači, tam jsem nakopíroval modul ovladače int070atft-fb.ko a pomocí NFS klienta si jej zpřístupnil v souborovém systému modulu SAM9260, pak už jen stačilo příkazem insmod int070atft-fb.ko zavést modul do jádra a bylo to. Rozsvítilo se podsvícení a displej se smazal, resp. přepsal černou barvou. Vyjmutí modulu ovladače zajistí příkaz rmmod int070atft-fb.ko.
Protože jsem tvor od přírody líný, nechtělo se mi pokaždé ručně připojovat modul SAM9260 přes NFS k mému počítači a pak modul ovladače zavádět. Chtěl jsem využít automatické zavádění modulu ovladače v rámci inicializačního skriptu /etc/init.d/rcS, které využívá příkaz modprobe. Proto jsem musel:
- Modul ovladače zkopírovat do předem vytvořeného adresáře /lib/modules/2.6.38.8/kernel/drivers/user/.
- Aktualizovat databázi závislostí /lib/modules/2.6.38.8/modules.dep pomocí příkazu depmod -a.
- Zkusit zadat příkaz modprobe int070atft-fb.ko. Musel projít bez chyb a zavést modul ovladače.
- Pak už jen stačilo zapsat modul ovladače int070atft-fb.ko do seznamu automaticky zaváděných modulů pomocí příkazu echo -e "int070atft-fb.ko\n" >>/etc/modules.
- Resetovat modul SAM9260 příkazem reboot. Těsně na konci inicializace by se měly na TFT displeji objevit poslední bootovací hlášky a pak přihlašovací prompt (viz obrázek). Měl jsem připojenou USB klávesnici, takže jsem se dokázal dokonce i přihlásit do Linuxu a psát příkazy - paráda ne?
Ale abychom neskončili tak černobíle, zkusil jsem si napsat jednoduchou testovací aplikaci fbtest, kde přímo využívám framebuffer a kreslím do něj jednoduché obrazce typu čára či čtverec. Její zdrojový kód je ke stažení na produktových stránkách modulu SAM9260. Odměnou za její spuštění vám budou dva blikající čtverečky uprostřed plochy a dokonce i dva statické čtverečky vlevo nahoře - to víte, nějak jsem ten ovladač pro framebufferové zařízení odladit musel :)