přejít na obsah přejít na navigaci

Linux E X P R E S, Vývoj jádra XVII. - sběrnice USB

Vývoj jádra XVII. - sběrnice USB

Drtivá většina dnešního příslušenství počítačů se připojuje přes sběrnici USB. Proto mají velký význam i ovladače takových zařízení. Tento článek ukazuje, jak se uvnitř linuxového jádra s USB pracuje a jak si vytvořit nový ovladač pro nějaké zařízení.


Všechny fragmenty kódu si můžete stáhnout v jednom souboru.

Princip USB

Sběrnice USB měla již při svém vzniku za cíl nahradit dosavadní způsoby připojování vnějších periférií. Nyní tak můžeme přes USB připojit širokou škálu různých zařízení: klávesnice, myši, úložná zařízení, síťová rozhraní, tiskárny, zvukové karty, radiové i televizní tunery, mobilní telefony a mnoho dalšího. Dokonce existují i konvertory umožňující připojit i zařízení určená pro jiná rozhraní.

USB má některé oproti jiným způsobům připojení zásadní odlišnosti – jednak poskytuje také možnost napájení po sběrnici, dále téměř neomezené „navěšování“ zařízení na jedinou zásuvku (přes rozbočovače) a konečně také zásadně jinak koncipovaný způsob přenosu dat.

V Linuxu se o většinu režie spojené s komunikací přes USB nemusíme starat. Na nejnižší úrovni operuje ovladač USB řadiče (obvykle nemáme potřebu si tvořit vlastní), nad ním je univerzální USB subsystém jádra, pak následuje ovladač USB zařízení (to nás nyní zajímá) a nad ním opět univerzální vrstva dalších subsystémů. Ovladače USB zařízení se do určité míry podobají ovladačům pro zařízení na PCI – například deklarují, která zařízení jsou schopny obsluhovat.

Kromě situace (jíž se týká tento článek), kdy přes USB komunikujeme s nějakým zařízením, může nastat i situace opačná. Tedy že je linuxové jádro použito v takovém periferním zařízení (např. ADSL modemu) a potřebujeme proto obsluhovat druhý konec komunikace. I toto lze v Linuxu snadno realizovat, a to prostřednictvím subsystému USB Gadgets.

Přenosy přes USB

Přes USB lze data přenášet čtyřmi různými způsoby. Liší se podle vlastností a určení.

CONTROL

Řídicí přenosy. Slouží ke konfiguraci, ovládání, zjišťování informací o zařízení. Pro tento typ přenosu řadič vždy vyhradí přenosové pásmo.

INTERRUPT

Periodické přenosy malých objemů dat. Hodí se pro klávesnice, myši, tablety a další podobná použití. Také pro tyto přenosy se vyhrazuje přenosové pásmo.

BULK

Přenosy velkých objemů dat. Používá se pro úložná a síťová zařízení či pro jiné účely vyžadující přenášení velkých množství dat. Pásmo se nevyhrazuje, data se mohou přenášet po částech, doba trvání přenosu není zaručena.

ISOCHRONOUS

Izochronní přenosy (pevně daný objem za časovou jednotku). Typické použití je u multimediálních zařízení (zvukové karty, televizní tunery apod.). Pásmo se nevyhrazuje; pokud není k dostatečné pásmo k dispozici, data se zahazují.

Jaký druh přenosu se pro ten který účel používá, záleží na zařízení. Většinou ale platí popsané rozdělení.

Koncové body, rozhraní, konfigurace

Komunikační kanály pro přenos dat z a do zařízení se pro ovladač jeví jako tzv. koncové body (endpoints). Jde v podstatě o jednosměrné roury, do kterých se posílají data nebo odtud přicházejí. V jádře je koncový bod reprezentován datovou strukturou s informacemi o druhu přenosu, max. velikosti paketu, periodě atd.

Vyšší entitou je rozhraní. Sdružuje určitý počet koncových bodů a představuje vlastně „funkci zařízení“ (v podobném smyslu jako u PCI). Fyzické zařízení může obsahovat více rozhraní (logických zařízení). Ale aby to bylo ještě zábavnější, toto zařízení může mít více konfigurací a každá může obsahovat jinou sadu rozhraní (aktivní je samozřejmě vždy jen jedna konfigurace). Samotná rozhraní mohou mít jedno nebo více nastavení, z toho jedno aktivní.

Bloky požadavků (URB)

Pro jednotlivé přenosy přes USB se používají speciální datové struktury – bloky požadavků USB (USB Request Block, URB). Je to obdobné jako třeba u síťových ovladačů. Blok se vytvoří, nastaví se mu odpovídající parametry a pak se předá USB subsystému (ten se přes ovladač USB řadiče postará o provedení přenosu). Až se přenesou data, USB subsystém o tom vyrozumí ovladač. Přenos je asynchronní, požadavky se tedy řadí do fronty a během zpracování může ovladač dělat něco jiného.

Zpracovávaný požadavek lze stornovat. Může být stornován i automaticky, například v případě, že se USB subsystém od řadiče dozví, že bylo příslušné zařízení odebráno. I s touto eventualitou musí ovladač USB zařízení počítat. V některých jednodušších případech se URB nepoužívají (u přenosů typu BULK).

Ovladač je o dokončení nebo stornování přenosu vyrozuměn zavoláním obslužné funkce, která se v URB nastaví. Obslužná funkce musí být napsána podle pravidel pro obsluhu přerušení, protože může být takto zavolána – tedy co nejrychleji ven, žádné blokování atd. (v případě potřeby se musí pro „závadné“ operace použít odložené zpracování).

Registrace ovladače

Stejně jako všechny ovladače zařízení, i ty pro USB se musí odpovídajícím způsobem zaregistrovat. Nejdřív je ale potřeba připravit informace o tom, která zařízení ovladač podporuje. Slouží k tomu makra USB_DEVICE, USB_DEVICE_VER, USB_DEVICE_INFO a USB_INTERFACE_INFO. Pomocí maker se sestaví pole struktur usb_device_id a toto pole se zveřejní známým makrem MODULE_DEVICE_TABLE. Tím je zajištěno, že může program depmod informace o podporovaných zařízení přečíst a vytvořit z nich soubor modules.usbmap pro výběr správného modulu k načtení.

Pro vlastní registrace připravíme strukturu usb_driver, která kromě výše popsané tabulky zařízení a dalších informací obsahuje také ukazatele na funkce ovladače (budou popsány později). V inicializační funkci modulu se pak pro danou strukturu zavolá usb_register(), čímž se ovladač stane plně použitelným. Při uvolňování se pak musí ovladač odregistrovat funkcí usb_deregister().

Příklad ovladače

Protože vyprávět nasucho nemá příliš smysl, zde je ukázka jednoho velmi jednoduchého ovladače. Obsluhuje primitivní USB teploměr, ze kterého se čte aktuální teplota. V příkladu je pro úsporu místa uvedena jen ta část, která souvisí s USB komunikací – uživatelským programům lze naměřenou hodnotu poskytovat buď přes klasický znakový soubor zařízení, nebo třeba přes atribut objektu jádra (tj. přes sysfs).

#define MYVENDOR 0x4444
#define MYDEVICE 0x0001
static struct usb_device_id s_dev_table[]  = {
{ USB_DEVICE(MYVENDOR, MYDEVICE) },
{}
};
MODULE_DEVICE_TABLE(usb, s_dev_table);
struct mydev {
struct usb_device* dev;
struct usb_interface* uif;
u8* buf;
size_t blen;
u8 ep;
struct kref kref;
};
static void mydev_dtor(struct kref* ref)
{
struct mydev* md = container_of(ref, struct mydev, kref);
usb_put_dev(md->dev);
kfree(md->buf);
kfree(md);
}
static struct usb_class_driver s_mycldr = {
.name = "thermo%d",
.minor_base = 224
};

V příkladu je několik věcí, na které je dobré upozornit. První je datová struktura zařízení (mydev). Obsahuje data pro USB zařízení a rozhraní (zde je jen jediné), dále buffer pro příchozí data, jeho délku, adresu koncového bodu a také počitadlo referencí. Struktura slouží k obecnému použití v ovladači, například ji lze nastavit ve struktuře souborových operací. Ke zrušení datové struktury se používá „destruktor“ (mydev_dtor) volaný automaticky v okamžiku, kdy počitadlo referencí klesne na nulu.

Dále se vytvoří struktura třídy, kde se nastavuje vzor pro název (pro použití v sysfs), počáteční minor číslo (je potřeba jen v případě, že se používá statické přidělování – záleží na konfiguraci jádra) a byla by tam též struktura souborových operací.

Operace probe a disconnect se provádějí v kontextu jaderného vlákna používaného USB subsystémem. Nejde tedy o časově kritické operace, malé zdržení není pohromou. Ale je potřeba si uvědomit, že totéž vlákno musí obsluhovat i jiná USB zařízení a zbytečné prodlévání má tedy negativní dopady na funkčnost systému.

static int mymodule_probe(struct usb_interface* uif,
const struct usb_device_id* id)
{
struct usb_host_interface* uhi = NULL;
struct usb_endpoint_descriptor* epd = NULL;
int res = 0;
struct mydev* md = kzalloc(sizeof(struct mydev), GFP_KERNEL);
if (md == NULL) {
res = -ENOMEM;
goto alloc_failed;
}
kref_init(&(md->kref));
md->dev = usb_get_dev(interface_to_usbdev(uif));
md->uif = uif;
uhi = uif->cur_altsetting;
if (uhi->desc.bNumEndpoints == 0) {
res = -EINVAL;
goto error;
}
epd = &(uhi->endpoint[0].desc);
md->ep = epd-vbEndpointAddress;
md->blen = le16_to_cpu(epd->wMaxPacketSize);
md->buf = kzalloc(md->blen, GFP_KERNEL);
if (md->buf == NULL) {
res = -ENOMEM;
goto error;
}
usb_set_intfdata(uif, md);
res = usb_register_dev(uif, &s_mycldr);
if (res != 0)
goto error;
return 0;
error:
usb_set_intfdata(uif, NULL);
kref_put(&(md->kref), mydev_dtor);
alloc_failed:  
return res;
}
static void mymodule_disconnect(struct usb_interface* uif)
{
struct mydev* md = NULL;
lock_kernel();
md = usb_get_intfdata(uif);
usb_set_intfdata(uif, NULL);
usb_deregister_dev(uif, &s_mycldr);
unlock_kernel();
kref_put(&(md->kref), mydev_dtor);
}

Funkci mymodule_probe() volá USB subsystém v okamžiku, kdy detekuje zařízení (tedy buď při jeho připojení do USB zásuvky nebo při inicializaci modulu). Smyslem funkce je připravit ovladač pro práci s novým zařízením. Subsystém jí předá (ve struktuře rozhraní) informace o zařízení, tedy zejména o komunikačních vlastnostech (koncové body atd.). Zde se alokuje potřebná paměť a provedou různá přiřazení hodnot a proběhne-li vše dobře, zařízení se nakonec zaregistruje v subsystému.

Opakem je funkce mymodule_disconnect(). Je volána ze subsystému při odebrání zařízení nebo při uvolnění modulu. Odregistrovává zařízení a uvolňuje použitou paměť. Určitou zajímavostí je, že je potřeba zamknout celé jádro, tedy použít Big Kernel Lock (je to jeden z mála případů, kde se takový postup ještě vyskytuje).

static void mymodule_on_data(struct urb* req)
{
atomic_set((atomic_t*) (req->context), req->status == 0
? *((s32*) (req->transfer_buffer)) : 0);
usb_buffer_free(req->dev, sizeof(s32), req->transfer_buffer,
req->transfer_dma);
}
static int mymodule_get_data(struct mydev* md, atomic_t* data)
{
int res = 0;
int len = sizeof(s32);
struct urb* req = NULL;
struct usb_device* dev = md->dev;
u8* buf = usb_buffer_alloc(dev, len, GFP_KERNEL,
&(req->transfer_dma));
if (buf == NULL)
return -ENOMEM;
req = usb_alloc_urb(0, GFP_KERNEL);
if (req == NULL) {
res = -ENOMEM;
goto alloc_failed;
}
usb_fill_bulk_urb(req, dev, usb_rcvbulkpipe(dev, md->ep),
buf, len, mymodule_on_data, data);
req->transfer_flags |= URB_NO_TRANSFER_DMA_MAP
| URB_SHORT_NOT_OK;
res = usb_submit_urb(req, GFP_KERNEL);
if (res == 0)
return 0;
usb_free_urb(req);
alloc_failed:
usb_buffer_free(dev, len, buf, req->transfer_dma);
return res;
}

Funkce mymodule_get_data() slouží k odeslání požadavku na přečtení dat ze zařízení. Nedělá nic jiného, než že vytvoří URB a odešle ho do USB subsystému. Samozřejmě také alokuje paměť. Můžete si povšimnout toho, jaké příznaky (transfer_flags) se nastavují – jednak je to URB_NO_TRANSFER_DMA_MAP (říká, že se má pro přenos použít DMA buffer), a dále URB_SHORT_NOT_OK (pakety o kratší než plné délce se považují za chybné). Podobných příznaků existuje ještě celá řada. Jak je z kódu vidět, používá se přenos typu BULK.

Po přenesení dat se volá funkce mymodule_on_data(), kde se pouze překopíruje jedna hodnota (zařízení nic dalšího neposílá), při chybě se nastaví nula. Uvolní se paměť použitá pro buffer.

static struct usb_driver s_driver = {
.name = "mymodule",
.id_table = s_dev_table,
.probe = mymodule_probe,
.disconnect = mymodule_disconnect
};
static int __init mymodule_init(void)
{
int res = 0;
res = usb_register(&s_driver);
return res;
}
static void __exit mymodule_cleanup(void)
{
usb_deregister(&s_driver);
}

Zbývající úsek kódu už obsahuje pouze datovou strukturu ovladače a inicializační a úklidovou funkci. V datové struktuře se nastavuje název modulu, tabulka zařízení a funkce probe a disconnect. Je to podobné jako u jiných druhů ovladačů.

Nahoru

Odkazy

Přidat téma diskuse

Nejsou podporovány žádné značky, komentáře jsou jen čistě textové. Více o diskuzích a pravidlech najdete v nápovědě.
Diskuzi můžete sledovat pomocí RSS kanálu rss



 
 

Top články z OpenOffice.cz

Lukáš Jelínek

Lukáš Jelínek

Dlouholetý člen autorského týmu LinuxEXPRESu a OpenOffice.cz. Vystudoval FEL ČVUT v oboru Výpočetní technika. Žije v Kutné Hoře, podniká v oblasti IT a zároveň pracuje v týmu projektu Turris. Ve volném čase rád fotografuje, natáčí a stříhá video, občas se věnuje powerkitingu a na prahu čtyřicítky začal hrát tenis.


  • Distribuce: Debian, Kubuntu, Linux Mint
  • Grafické prostředí: KDE

| proč linux | blog