Jak napsat ovladač pro zařízení USB pro Linux

20. 7. 2008 17:11 (aktualizováno) Kamil Pošvic

Jedná se o překlad článku Greg Kroah-Hartman – How to Write a Linux USB Device Driver z roku 2001, ale mnoho z něj je stále aktuální a dá se využít.

USB subsystém v Linuxu vyrostl z podpory 2 typů zařízení v jádru 2.2.7 (myš a klávesnice), do více než 20 různých typů zařízení v jádře 2.4. V současné době Linux podporuje téměř všechna zařízení USB (stadnardn typy zařízení jako klávesnice, myši, modemy, tiskárny a reproduktory) a stále rostoucí počet zařízení specifických dle výrobce (jako jsou kontervory USB → serial, digitální kamery, síťové zařízení a MP3 přehrávače). Pro kompletní seznam různých zařízení, která jsou v současné době podporovány se podívejte do Odkazů.

Zbývající typy USB zařízení které nejsou podporovány Linuxem jsou téměř všechny specifické dle výrobce. Každý výrobce si zavádí své protokoly pro komunikaci se svým zařízením, takže je většinou potřeba vytvořit vlastní ovladač. Někteří výrobci jsou s USB protokoly otevření a pomáhají s vytvářením ovladačů pro Linux, zatímco ostatní je nepublikují a vývojáři jsou nuceni k reverznímu inženýrství. Podívejte se do odkazů pro vhodné nástroje pro reverzní inženýrství.

Protože každý protokol potřebuje nový ovladač, tak jsem vytvořil obecnou kostru ovladače USB, která je vytvořena podle souboru pci-skeleton.c ze zdroje jádra na kterém je založeno mnoho síťových ovladačů na PCI. Tato kostra je dostupná na drivers/usb/usb-skeleton.c ve zdroji jádra. V tomto článku projdu základy kostry ovladače, vysvětlím různé části a co je potřeba si upravit pro vaše zařízení.

Jestliže chcete napsat USB ovladač pro Linux, tak se prosím nejprve seznamte se specifikací USB protokolu. Můžete ho najít, spolu se spoustou další užitečné dokumentace, na domovské stránce USB (viz Odkazy). Skvělý úvod do USB subsystému Linuxu můžete najít na USB Working Devices List (viz Odkazy). Vysvětluje to strukturu USB subsystému a pomůže vám to pochopit strukturu USB urb, která je základ pro ovladač USB.

Jako první věc je třeba aby se ovladač do systému zaregistroval, předal informace o tom pro jaké zařízení je a jaké funkce zavolat, když je zařízení připojeno a odpojeno. Všechny tyto informace jsou předány USB subsystému ve struktuře usb_driver. Kostra deklaruje usb_driver jako:

static struct usb_driver skel_driver = { name: "skeleton", probe: skel_probe, disconnect: skel_disconnect, fops: &skel_fops, minor: USB_SKEL_MINOR_BASE, id_table: skel_table, };

Proměnná name je řetězec který popisuje ovladač. Je použit do informačních zpráv zapsaných do systémového logu. Funkce probe a disconnect jsou zavolány když zařízení popsané v id_table  je připojeno nebo odpojeno.

Proměnné fops a minor jsou volitelné. Většina USB ovladačů je propojena do dalších subsystémů jádra jako SCSI, síť nebo subsystém TTY. Tyto typy ovladačů se propojí s dalšími subsystémy jádra a jakákoliv interakce s uživatelským prostředím se poskytuje přes toto rozhraní. Ale pro ovladače, které nemají odpovídající subsystém v jádře, jako přehrávače MP3 nebo scannery, je potřeba vytvořit metodu komunikace s uživatelským prostředím. Subsystém USB poskytuje způsob jak zaregistrovat číslo zařízení a sadu funkcí file_operations, které poskytují komunikaci s uživatelským prostředím. Kostra ovladače potřebuje tento druh rozhraní, takže poskytuje číslo zařízení a ukazuje na jeho file_operations funkce.

USB ovladač se zaregistruje voláním usb_register, většinou v inicializační funkci ovladače, viz. Listing 1.

Listing 1 – Registrace USB ovladače

Když je systém odstraněn ze systému, je potřeba ho odregistrovat z USB subsystému. To vykoná funkce  usb_unregister:

static void __exit usb_skel_exit(void) { /* deregister this driver with the USB subsystem */ usb_deregister(&skel_driver); } module_exit(usb_skel_exit);

Aby fungoval systém linux-hotplug pro nahrání ovladače automaticky po připojení zařízení je potřeba vytvořit MODULE_DEVICE_TABLE. Následující kód řekne hotplug skriptu, že tento modul podporuje toto zařízení s číslem výrobce a číslem zařízení:

/* table of devices that work with this driver */ static struct usb_device_id skel_table [] = { { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) }, { } /* Terminating entry */ }; MODULE_DEVICE_TABLE (usb, skel_table);

Jsou i další makra která mohou být použita pro popis usb_device_id pro ovladače, které podporují celou třídu ovladačů USB. Podívejte se do usb.h pro další informace.

Když je připojeno zařízení na patici USB, které odpovídá ID zařízení které váš ovladač zaregistroval do jádra USB, je zavolána funkce probe. Struktura usb_device, číslo a ID rozhraní jsou předána funkci:

static void * skel_probe(struct usb_device *dev, unsigned int ifnum, const struct usb_device_id *id)

Ovladač nyní potřebuje ověřit že toto zařízení je opravdu to, které může použít. Jestliže ne nebo vyvstala jakákoliv chyba během inicializace, je vrácena hodnota NULL z funkce probe. Jinak je vrácen ukazatel na strukturu dat obsahující status ovladače pro toto zařízení. Tento ukazatel je uložen ve struktuře usb_device a všechna volání ovladače jsou přes tento ukazatel.

V kostře ovladače určíme který end_point je označen bulk-in a bulk-out. Vytvoříme buffer, který bude obsahovat data, která budou odeslána a přijata ze zařízení a USB urb pro zapsání dat do zařízení pro inicializaci. Také zaregistrujeme zařízení do devfs subsystému, což umožní uživatelům devfs přístup k našemu zařízení. Tato registrace vypadá přibližně takto:

/* initialize the devfs node for this device and register it */ sprintf(name, "skel%d", skel->minor); skel->devfs = devfs_register (usb_devfs_handle, name, DEVFS_FL_DEFAULT, USB_MAJOR, USB_SKEL_MINOR_BASE + skel->minor, S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, &skel_fops, NULL);

Jestliže selže funkce devfs_register, tak se o to nestaráme. Subsystém devfs to nahlásí uživateli.

A naopak, když je zařízení odstraněno z patice, je zavolána funkce disconnect s ukazatelem na zařízení. Ovladač potřebuje vyčistit všechny data která mohla být získána a ukončit všechny setrvávající urbsy které jsou v USB systému. Ovladač se také odregistruje z devfs subsystému pomocí volání:

/* remove our devfs node */ devfs_unregister(skel->devfs);

Teď, když je zařízení připojeno do systému a ovladač je spojen se zařízením, všechny funkce popsané v file_operations  které byly předány do USB subsystému mohou být volány dalšími programy při komunikaci se zařízením. První volaná funkce bude open, jak se program pokouší otevřít zařízení pro vstup/výstup. V rámci kostry ovladače jsem vložili počítadlo použití ovladače. Je to module s názvem MODULE_INC_USE_COUNT. Zavoláním tohoto makra, jestliže je ovladač kompilován jako modul, nemůže být ovladač odvolán, dokud odpovídající makro MODULE_DEC_USE_COUNT není zavoláno. Také jsme vložili naše interní počítadlo použití a možnost uložení ukazatele na naší interní strukturu do souboru. Je to pro případ budoucího volání file_operations umožní ovladači určit které zařízení uživatel adresuje. Všechno tohle vytvoříme pomocí následujícího kódu:

/* increment our usage count for the module */ MOD_INC_USE_COUNT; ++skel->open_count; /* save our object in the file's private structure */ file->private_data = skel;

Po té co byla zavolána funkce open, jsou volány funkce read a write k získání a odeslání dat do zařízení. Ve funkci skel_write obdržíme ukazatel na data, která chce uživatel odeslat do zařízení a velikost dat. Funkce určuje, kolik dat lze odeslat do zařízení podle velikosti zapisovacího urb, který je vytvořen (velikost závisí na velikosti bulk-out endpointu daného zařízení).
Pak okopíruje data z uživatelského prostředí do prostředí jádra, nastaví ukazatel urb na data a odešle urb do USB subsystému (viz Listing 2).

Listing 2 – Funkce skel_write

Když je urb pro zápis naplněn odpovídajícími informacemi za použití funkce FILL_BULK_URB, nastavíme ukazatel na urb abychom zavolali naší vlastní funkci skel_write_bulk_callback. Tato funkce je zavolána když urb je dokončen pomocí USB subsystému. Funkce callback je volána během přerušení, takže musíme dávat pozor aby jsme toho nevolali mnoho zároveň. Naše implementace skel_write_bulk_callback převážně zjišťuje, zda urb byl vytvořen správně nebo ne.

Funkce pro čtení funguje trošku odlišně než pro zápis. Nepoužíváme zde urb pro přenos dat ze zařízení do ovladače. Místo toho volám funkci usb_bulk_msg, která lze použít pro odesílání a příjem dat ze zařízení bez vytváření urbů a funkcí kontrolující kompletnost urb. Zavoláme funkci usb_bulk_msg, předáme jí buffer do kterého budou zapsána všechna data vrácena ze zařízení a hodnotu timeout. Jestliže timeout vyprší aniž by jsme obdrželi nějaká data, funkce selže a vrátí chybové hlášení (viz Listing 3).

Listing 3 – funkce usb_bulk_msg

Funkce usb_bulk_msg může být velmi užitečná pro jednoduché čtení nebo zápis do zařízení; jakkoliv, jestliže potřebujete číst nebo zapisovat konstantně do zařízení, je doporučeno nastavit si vlastní urb a odeslat ho do subsystému USB.

Když se vlastní program pokusí získat handle pro komunikaci se zařízením, je zavolána funkce v rámci ovladače. V této funkci musíme odstranit počítadlo použití zavoláním MOD_DEC_USE_COUNT (k uzavření naší předešlého volání MOD_INC_USE_COUNT. Také musíme zjistit, jestli není nějaký jiný program který v současnosti komunikuje se zařízením (zařízení může komunikovat naráz s více programy). Jestliže to je alespoň stejný uživatel, tak ukončíme všechny nedokončené komunikace. Toto všechno vykoná následující kód:

/* decrement our usage count for the device */ --skel->open_count; if (skel->open_count write_urb); skel->open_count = 0; } /* decrement our usage count for the module */ MOD_DEC_USE_COUNT;

Jeden z nejvíce obtížných problémů aby mohl ovladač USB dobře pracovat je fakt, že zařízení USB může být kdykoliv odstraněno ze systému a to i právě když na něj odesíláme data. Je potřeba aby byl schopen zastavit jakékoliv čtení nebo zápis a poslal upozornění programu, že zařízení již není dostupné (viz Listing 4).

Listing 4 – Funkce skel_disconnect

Jestliže má program právě otevřené spojení se zařízením, tak jen vymažeme záznam usb_device v našem záznamu, jako kdyby bylo právě odpojeno. Pro každé čtení, zápis, kontrolu a další funkce které předpokládají že je zařízení připojeno, zkontroluje ovladač jestli je záznam usb_device aktivní. Jestli ne, tak vrátí že zařízení bylo odpojeno a je vrácena chyba -ENODEV. Když je zavolána funkce pro odpojení, tak zjišťuje jestli není usb_device aktivní. Jestliže ne, tak spustí funkci skel_disconnect jako kdyby zařízení bylo normálně odpojeno (viz Listing 5).

Listing 5 – Úklid

Tato kostra ovladače nemá příklady na interrupt nebo isochronní odesílání dat do a ze zařízení. Interrupt data se posílají téměř stejně jako bulk, až na pár drobných vyjímek. Isochronní data pracují jinak se spojitým tokem dat odesílaným do a ze zařízení. Audio a video kamery jsou velmi dobrý příklad ovladačů které přistupují k zařízení isochronně a budou se hodit, když to budete potřebovat.

Napsat ovladač pro USB zařízení pro Linux není obtížný úkol, jak tato kostra ukazuje. Tento ovladač, kombinovaný s dalšími stávajícími USB ovladači, poskytuje dostatek příkladů aby pomohli začínajícímu autorovi napsat funkční ovladač za minimální čas. Archiv mailing listu linux-usb-devel obsahuje také spoustu užitečných informací.

Odkazy

Greg Kroah-Hartman je jeden z vývojářů Linuxového jádra USB. Jeho free software je používán více lidmi než za kolik projektů byl kdy zaplacen.

Sdílet