Linux E X P R E S

Facebook

Vývoj jádra XI. - objekty v praxi

Minule jsem sliboval praktické ukázky. Nyní na ně přichází řada, většina dílu bude zaměřena právě na to, jak použít to, o čem jste se mohli dočíst v předchozích číslech časopisu. Objekty linuxového jádra ukáží svoji sílu.


Načítání firmwaru

Přece jen si nemohu odpustit ještě malý kousek teorie – dříve na něj nebylo místo. Bude se týkat toho, jak načíst z uživatelského prostoru firmware, tedy programovou výbavu nějakého zařízení.

I jen trochu složitější zařízení již nebývají kompletně realizována jako elektrický obvod. Buď se používá programovatelné hradlové pole, nebo – a to čím dál častěji – se jedná o specializovaný počítač s jednoduchým procesorem, malou operační pamětí a samozřejmě programem, který ho řídí. A tento program někdy nebývá přítomen přímo v zařízení (např. v paměti ROM), ale jeho načtení do zařízení musí zajistit operační systém.

Někoho by mohlo napadnout, že by firmware mohl být přímo součástí ovladače. To je ale v praxi nepoužitelné – jednak z licenčních důvodů, ale hlavně z technických. Kód firmwaru se může měnit (bez vztahu ke změně ovladače) a bylo by nepraktické kvůli tomu měnit ovladač. Kromě toho by se muselo řešit jeho uvolnění z paměti počítače po načtení do zařízení. Proto je lepší mít firmware samostatně a načítat ho prostředky, které k tomu jádro poskytuje.

Mechanismus je založen na událostech přenášených do uživatelského prostoru. Funguje to tak, že se vytvoří binární atribut (sysfs soubor) pro přenesení dat a odešle se událost požadující načtení firmwaru. Na uživatelské straně na to nějaký program (např. udev) zareaguje a nakopíruje do atributu potřebná data. Na závěr nastaví stavový atribut, zda se zápis podařil. Jádro na základě stavového atributu data buď převezme a předá tomu, kdo si je objednal, nebo je zahodí. Pro načtení je časový limit (například 10 sekund). V závěru článku ukážu, jak to vypadá v praxi.

Jdeme na to

Vrátíme se k modulu mymodule z příkladu u čtvrté kapitoly seriálu. Tehdy jsme vytvořili virtuální zařízení a nastavili mu funkce pro souborové operace. Přesně z tohoto bodu nyní vyjdeme. Následující kód vytvoří třídu zařízení, zaregistruje datovou struktury zařízení a vytvoří zařízení třídy:

s_class = class_create(THIS_MODULE, "myclass");
if (IS_ERR(s_class)) {
  res = PTR_ERR(s_class);
  goto class_failed;
}

res = device_register(&s_device);
if (res < 0) {
  goto device_failed;
}

s_cl_dev = class_device_create(s_class, NULL, s_dev,
    &s_device, "mycldev");
if (IS_ERR(s_cl_dev)) {
  res = PTR_ERR(s_cl_dev);
  goto cldev_failed;
}

Od jádra 2.6.18 lze při vytváření objektu zařízení (device) jít i jinou cestou. Přibyla totiž funkce device_create() (a její protějšek device_destroy()), která umožňuje vytvořit strukturu zařízení obdobně, jako se vytváří třeba třída. Funkce vyžaduje poněkud jiné parametry, než jsme přiřazovali do struktury před registrací (např. se nenastavuje úklidová funkce). Pro moduly určené pouze pro nová jádra je jednodušší používat tento postup.

Návěští pro chybové stavy jsou definována tak, jako to bylo v původním příkladu – prostě se musí vše uvést do původního stavu. Příklad by nebyl úplný, kdybychom již předtím nedefinovali potřebné proměnné a jednu funkci:

static void mymodule_dev_release(struct device* dev) {}

static struct device s_device =
{
  .bus_id = "mydev",
  .release = mymodule_dev_release
};

static struct class* s_class = NULL;
static struct class_device* s_cl_dev = NULL;

Registrace třídy je celkem triviální věc. Povšimněte si ale, jak se detekují chyby. U funkcí, které vracejí ukazatel, existují vyhrazené chybové hodnoty. Makro IS_ERR() umožňuje zjistit, zda došlo k chybě, a makrem PTR_ERR() získáme příslušný chybový kód. Pro vytváření speciálních hodnot ukazatelů slouží makro ERR_PTR().

Dále stojí za povšimnutí, že struktura device má ukazatel na funkci mymodule_dev_release, kterou jsme museli (byť prázdnou) vytvořit. Toto přiřazení je povinné a když se vynechá, jádro se bude při registraci hodně zlobit.

Posledním krokem je vytvoření zařízení třídy. K tomu snad není co dodat. Kromě zajímavosti, že při rušení nebudeme potřebovat ukazatel na toto zařízení, nýbrž číslo zařízení a ukazatel na třídu.

Podívejme se nyní, co to udělalo v sysfs. V adresáři /sys/class přibyl adresář myclass, představující třídu. V něm je pak adresář mycldev, což je zařízení třídy. V něm je pak soubor dev (výchozí atribut; obsahuje major a minor číslo – viz minulý díl), dále pak soubor uevent (slouží k explicitnímu vyžádání události předávané do uživatelského prostoru) a také symbolický odkaz na objekt zařízení. Ten se nachází na cestě /sys/devices/mydev, obsahuje zpětný odkaz na zařízení třídy, opět atribut uevent a konečně objekt/adresář power, který souvisí s napájením fyzického zařízení a jeho provozními stavy (na podrobnosti bohužel teď není prostor).

O tom, které objekty vlastně potřebujeme, rozhoduje architektura ovladačů. Máme-li jednoduché zařízení (třeba jen virtuální, bez hardwaru), stačí si vytvořit pouze objekt zařízení (ve vhodné třídě). Naopak u zařízení typu disků je to mnohem složitější – disků může být v systému více, mohou být různého druhu, připojené přes různé sběrnice atd. Pak je důležité určit, jaká funkcionalita je společná určitým skupinám zařízení na různých úrovních a podle toho ovladače rozdělit. Ještě se k tomu dostaneme později.

Zařízení existující třídy

Uvedený příklad má určité méně obvyklé vlastnosti. Běžně nepotřebujeme vytvářet třídu, navíc musíme vykonat hodně práce, která nepřináší mnoho užitečného. Místo vytváření nové třídy můžeme využít nějakou stávající – např. misc. Tím se zásadně změní celý proces inicializace modulu – a to směrem k jednoduchosti. Nejprve definujeme potřebnou strukturu:

static struct miscdevice s_device = {
  .minor = MISC_DYNAMIC_MINOR,
  .name = "mydev",
  .fops = &mymodule_fops
};

Tato struktura nahradí všechna dříve používaná data – samozřejmě kromě souborových operací. Všechno se nastaví zde, a ani toho moc není. Uvedený příklad zajistí dynamické přidělené minor číslo, číslo major budeme sdílet se všemi zařízeními třídy misc. Stačí si pouze zvolit název a je hotovo. Celá inicializační funkce pak vypadá takto:

static int __init mymodule_init(void)
{
  return misc_register(&s_device);
}

Nádherně jednoduché, že? A přitom silné, protože v adresáři třídy misc (tedy /sys/class/misc) vznikl adresář pro naše zařízení mydev. Sice má jen ony dva základní atributy, ale pro základní použití to stačí. Tedy podtrženo a sečteno: toto je nejjednodušší možný způsob, jak si vytvořit plnohodnotné virtuální zařízení ve všech případech, kdy nepotřebujeme využívat komplikovanější aparát.

Vlastní atributy

Oba uvedené příklady se vyznačovaly jednou společnou vlastností – a to použitím pouze výchozích atributů. Pokud ale potřebujeme z modulu číst nějaké informace nebo něco nastavovat, musíme si pro to atributy vytvořit. Lze je vytvářet na různých místech – například na úrovni zařízení třídy, což je způsob vhodný i pro jednoduché moduly podle předchozího příkladu. Ukážeme si nyní vytvoření jednoduchého atributu, který bude poskytovat systémový čas s přesností na mikrosekundy:

static ssize_t mymodule_show_time(struct class_device* cldev,
    char* buf)
{
  struct timeval tv;
  do_gettimeofday(&tv);
  return (ssize_t) sprintf(buf, "%lu.%06lu",
      (unsigned long) tv.tv_sec, (unsigned long) tv.tv_usec);
}

static CLASS_DEVICE_ATTR(time, S_IRUGO,
    mymodule_show_time, NULL);

K obslužné funkci snad není příliš do dodat – jednoduše se do poskytnutého bufferu zapíše textový řetězec (při přenosu opačným směrem by to bylo obdobné). Zajímavější je deklarace. Lze to udělat celé ručně, ale jednodušší je použít makro, které to provede samo. Ve složitějších případech, kde se musí atributy vytvářet dynamicky, to ale samozřejmě takto nejde. Atributu nastavíme vhodná práva, například zde bude mít právo pro čtení každý. Aby atribut fungoval, musí se zaregistrovat – to vypadá takto (inicializátor se samozřejmě musí adekvátně přizpůsobit):

res = class_device_create_file(s_device.class,
    &class_device_attr_time);
if (res < 0) {
  misc_deregister(&s_device);
  return res;
}

Všimněte si, jaký název atributu makro vygenerovalo. Když zavedete takto upravený modul, v adresáři /sys/class/misc/mydev bude soubor time. Ze souboru můžete přečíst aktuální systémový čas.

Firmware v praxi

Načítání firmwaru lze řešit různými způsoby. Nyní ukážu ten nejprimitivnější, vhodný pro jednoduché moduly. Opět bude vztažen k příkladu se zařízením třídy misc.

struct firmware* fw;
res = request_firmware(&fw, "myfw", s_device.dev);
if (res < 0)
  return res;
...
release_firmware(fw);

Tímto kódem si vyžádáme firmware s názvem „myfw“. Místo těch tří teček bude kus kódu, který firmware vloží do zařízení (struktura obsahuje velikost dat a ukazatel na alokovanou paměť). Po provedení této činnosti nesmíme zapomenout uvolnit použitou paměť.

Uvedený postup načítá firmware synchronně (blokuje do načtení nebo vypršení limitu). Existuje i asynchronní verze request_firmware_nowait(), která nastaví callback pro zavolání, až bude načítání dokončeno. Většinou si ale vystačíme s jednodušší synchronní verzí.

Diskuze (0) Nahoru