+%FIXME
+% vlna
+% \caption{} bez tečky na konci
+% \item[] a \item s tečkou na konci
+% odsazování zdrojáků
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\chapter{Úvod}
\section{Motivace, cíl}
\begin{figure}[h!]
\begin{center}
\includegraphics[width=100mm]{img/mmio.pdf}
- \caption{Paměťový a vstupně-výstupní prostor u architektury IA-32.}
+ \caption{Paměťový a vstupně-výstupní prostor u architektury IA-32}
\label{mmio}
\end{center}
\end{figure}
Komunikace mezi zařízeními připojenými na sběrnici a procesorem zajišťuje tzv. \textit{PCI most} (PCI bridge). Propojení více nezávislých sběrnic v jednom počítači jsou zajištěny také PCI mosty.
\subsection{Historie}
-V roce 1990 začala práce na specifikaci PCI v laboratořích firmy Intel. První specifikace definující jak komunikační protokol, tak vzhled konektoru a slotu, byla zveřejněna 30. dubna 1993 (jedná se o PCI 2.0). PCI sběrnice se poté začala objevovat v počítačích architektury PC XT a Power Macintosh.
+V roce 1990 začala práce na specifikaci PCI v laboratořích firmy Intel. První specifikace definující jak komunikační protokol, tak vzhled konektoru a slotu, byla zveřejněna 30. dubna 1993 (jedná se o PCI 2.0). PCI sběrnice se poté začala objevovat v počítačích architektury PC XT a PowerPC.
V pozdějších letech se původní standard dočkal vylepšení -- zvýšení šířky paralelní sběrnice z 32 bitů na 64 bitů a zrychlení z 33 MHz na 66 Mhz a výše. Tyto pokročilejší verze se však příliš neujaly.
\subsection{Konektory}
\end{figure}
-\subsection{Dynamická konfigurace a konfigurační adresní prostor}
+\subsection{Dynamická konfigurace a konfigurační adresní prostor}\label{pci_conf}
Mezi hlavní výhody PCI sběrnice (oproti její předchůdkyni -- sběrnicic ISA) patří dynamická konfigurace připojených zařízení: Ve většině případů probíhá komunikace mezi hostitelským systémem a připojenou (a nakonfigurovanou) PCI kartou zápisem/čtením do určité paměťové (nebo vstupně-výstupní) oblasti. U starší sběrnice ISA si každá karta pevně určila, kam se její část paměti namapuje -- v takovém případě mohl nastat problém, že více než jedna karta mapovala svoji paměť na stejnou adresu (nebo se jednolivá mapování překrývala). PCI sběrnice tomuto problému předchází takovým způsobem, že každá z karet nese informaci o tom, kolik jak velkých paměťových nebo I/O regionů potřebuje namapovat -- o samotné mapování se poté postará PCI most.
Informaci o tom, kolik (a jaké) paměti karta bude potřebovat má před nakonfigurováním uloženu v tzv. \textbf{Base Address Registrech} -- BAR0--BAR5\footnote{FIXME Ve skutečnosti funguje vyčtení požadované velikosti z registru karty PCI mostem takovým způsobem, že se PCI most snaží do každého BAR registru zapsat 0xFF a poté zapsanou hodnotu přečíst -- do BAR registru je možné zapsat pouze .... bla bla}. Poté co se PCI mostu podaří tuto hodnotu přečíst a požadovanou paměť alokovat, zapíše zpět do daného registru adresu, na které se alokovaná paměť nachází. Tu si poté pro potřeby komunikace vyčteovladač zařízení, který je součástí opračního systému.
\begin{figure}[h!]
\begin{center}
\includegraphics[width=80mm]{img/pci-config-space2.pdf}
- \caption{Obsah 256 bajtů konfiguračního prostoru PCI karty (zvýrazněny jsou nejdůležitější registry).}
+ \caption{Obsah 256 bajtů konfiguračního prostoru PCI karty (zvýrazněny jsou nejdůležitější registry)}
\label{sa1}
\end{center}
\end{figure}
\subsection{Komunikace s kartou}
Způsob komunikace s kartou MF614 se mírně liší od MF624.
-Po nahlédnutí do manuálu (dostupný ze stránek výrobce: \url{http://www2.humusoft.cz/www/datacq/manuals/mf614um.pdf}) je z tabulky 9 (zde tab. \ref{tab_mf614_bars}) patrné, že karta využívá více regionů, přičemž některé jsou mapovány do paměti, jiné do vstupně-výstupního adresního prostoru. Po prohlédnutí tabulky popisující rozložení registrů (zde tab. \ref{tab_mf614_regs}) je zřejmé, že pro přístup k digitálním vstupům/výstupům a analogovým vstupům/výstupům jsou použity vstupně-výstupní regiony BAR0, BAR2.
+Po nahlédnutí do manuálu (dostupný ze stránek výrobce: \url{http://www2.humusoft.cz/www/datacq/manuals/mf614um.pdf}) je z tabulky 9 (zde tab. \ref{tab_mf614_bars}) patrné, že karta využívá více regionů než MF624, přičemž některé jsou mapovány do paměti, jiné do vstupně-výstupního adresního prostoru. Po prohlédnutí tabulky popisující rozložení registrů (zde tab. \ref{tab_mf614_regs}) je zřejmé, že pro přístup k digitálním vstupům/výstupům a analogovým vstupům/výstupům jsou použity vstupně-výstupní regiony BAR0, BAR2.
\begin{table}[h!]
\begin{center}
\hline BADR2 + 0x10 & \textbf{STATUS} -- Status register & \\
\hline
\end{tabular}
- \caption{Paměťové a vstupně-výstupní regiony, které využívá karta MF614}
+ \caption{Registry karty MF614 názežící digitálním vstupům/výstupům a analogovým vstupům/výstupům}
\label{tab_mf614_regs}
\end{center}
\end{table}
-Jednotlivé registry v těchto regionech jsou 8bitové, proto je potřeba při čtení/zápisu používat pouze 8bitové funkce. Např. 16bitové hodnoty jsou rozděleny do dvou 8bitových registrů -- v takovém případě, obsahuje-li registr ve svém názvu písmena \textbf{LO}, jedná se o spodní bajt, zatímco \textbf{HI} značí horní bajt. Vásledná 16bitová hodnota se získá složením dvou 8bitových:
+Jednotlivé registry v těchto regionech jsou 8bitové, proto je potřeba při čtení/zápisu používat pouze 8bitové funkce. 16bitové hodnoty jsou rozděleny do dvou 8bitových registrů -- v takovém případě, obsahuje-li registr ve svém názvu písmena \textbf{LO}, jedná se o spodní bajt, zatímco \textbf{HI} značí horní bajt. Výsledná 16bitová hodnota se získá složením dvou 8bitových:
\begin{verbatim}
u8 regAHI, regALO;
u16 regA;
\begin{description}
\item[Žádná ochrana paměti]~\\Libovolný jaderný modul má přístup k veškeré paměti počítače. V případě, že se chybně pokusí zapsat do paměti, do které by zapisovat neměl, není zde žádný mechanismus, který by mu v tom zabránil nebo ho na to alespoň upozornil.
\item[Uvolňování paměti]~\\Stejně jako pro programy psané v uživatelském prostoru platí, že nepotřebná dynamicky alokovaná paměť by měla být dealokována. V případě neuvolňování paměti programem v uživatelském prostoru je zde stále operační systém, který po skončení programu veškerou paměť uvolní. Nic takového však v jádře operačního systému nefunguje -- po uvolnění modulu z jádra není nikdo, kdo by se postaral o naalokovanou paměť. FIXME
-\item[Přímý přístup k hardwaru]~\\Základní jaderný modul psaný například nezkušeným studentem má zcela stejné možnosti přístupu k hardware jako subsystémy jádra, které se starají o správnou funkci jednotlivých ovladačů. V lepším případě může špatný ovladač způsobit pád systému, v horším např. zničení dat na disku nebo dokonce zničení hardware\footnote{Například poškození síťových karet Intel e1000e: \url{http://www.abclinuxu.cz/clanky/jaderne-noviny/jaderne-noviny-22.-10.-2008\#pricina-chyby-poskozujici-e1000e}}.
+\item[Přímý přístup k hardwaru]~\\Základní jaderný modul psaný například nezkušeným studentem má zcela stejné možnosti přístupu k hardware jako subsystémy jádra, které se starají o správnou funkci jednotlivých ovladačů. V lepším případě může špatný ovladač způsobit pád systému, v horším např. zničení dat na disku nebo dokonce zničení hardware\footnote{Například velmi těžko opravitelné poškození firmware síťových karet Intel e1000e: \\ \url{http://www.abclinuxu.cz/clanky/jaderne-noviny/jaderne-noviny-22.-10.-2008\#pricina-chyby-poskozujici-e1000e}}.
\item[Globální proměnné]~\\Každý ovladač může být spuštěn ve více instancích, proto by v kódu neměly být globální proměnné. Proměnné, které je potřeba zpřístupnit z více míst ovladače se vloží do jedné struktury, která je poté přístupná skrze ukazatel na \textit{privátní data} ovladače. FIXME.
\end{description}
Obecně je doporučováno příkaz \texttt{goto} nepoužívat. Najdou se ale případy, kdy jeho použití usnadní práci a i přesto neznepřehlední kód. V jádře Linux se tento příkaz používá při postupné dealokace. FIXME
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-\section{Ovladače PCI zařízení}
+\section{Ovladače PCI zařízení}\label{pci_driv}
\ibox{Jako nejlepší reference jednotlivých funkcí slouží zdrojové kódy jádra. Pro prohlížení zdrojových kódů jádra mohu doporučit online \textit{The Linux Cross Reference} -- \\
-\url{http://lxr.linux.no/}.}
+\url{http://lxr.linux.no/linux/}.}
+
+Ovladače PCI zařízení jsou ve většině případů kompilovány jako jaderné moduly, dynmicky načítané za běhu jádra. Takový modul je možné buď načíst ručně, pomocí příkazu \texttt{insmod} (se zadanou absolutní cestou) nebo, nachází-li se v adresáři \texttt{/lib/modules/\$(uname -r)/} a je součástí seznamu \texttt{modules.dep}\footnote{Tento seznam je aktualizován pomocí příkazu \texttt{depmod}.} (v témže adresáři), je možné ho načíst pomocí příkazu \texttt{modprobe} (kde se jako parametr předá pouze název modulu bez koncovky \texttt{.ko}). Druhá varianta se týká všech ovladačů standardně zkompilovaných s jádrem.
+
+V případě, že se v systému objeví nové PCI zařízení, je jádrem informován subsystém v uživatelském prostoru, který má na starosti správu \textit{hotplug} zařízení (např. \textit{udev}), o tomto zařízení. Hotplug subsystém (v uživatelském prosotru) poté na základě informací od jádra, jako je Vendor ID a Device ID , rozhodne, který ovladač má být pro dané zařízení načten. Seznam, dle kterého se rozhodne, který ovladač bude načten je v souboru \texttt{/lib/modules/\$(uname -r)/modules.pcimap}.
+
+Každý ovladač by měl tedy obsahovat informaci o tom, pro které zařízení je určen. To je uvedeno ve struktuře \texttt{struct pci\_device\_id}.
+
+\subsection{Struktura \texttt{struct pci\_device\_id}}\label{pci_dev_id}
+Struktura \texttt{struct pci\_device\_id} slouží k identifikaci, pro která zařízení je ovladač určen. Mezi hlavní položky struktury patří \texttt{vendor}, \texttt{device}, \texttt{subvendor}, \texttt{subdevice} (typu \texttt{\_\_u32}) -- jejichž hodnota odpovídá stejnojmenným registrům v konfiguračním prostoru PCI zařízení. Jelikož může být ovladač napsán pro více zařízení, je tato struktura inicializována jako prvek pole, které je zakončeno prvkem prázdným. Různé způsoby inicializace mohou vypadat následovně:
+
+\begin{verbatim}
+ 1 | #define PCI_VENDOR_ID_HUMUSOFT 0x186c
+ 2 | #define PCI_DEVICE_ID_MF624 0x0624
+ 3 | #define PCI_DEVICE_ID_MF614 0x0614
+ 4 | #define PCI_SUBVENDOR_ID_HUMUSOFT 0x186c
+ 5 | #define PCI_SUBDEVICE_MF624 0x0624
+ 6 |
+ 7 | static struct pci_device_id mf624_pci_id[] = {
+ 8 | {
+ 9 | .vendor = PCI_VENDOR_ID_HUMUSOFT,
+10 | .device = PCI_DEVICE_ID_MF624,
+11 | .subvendor = PCI_SUBVENDOR_ID_HUMUSOFT,
+12 | .subdevice = PCI_SUBDEVICE_MF624,
+13 | },
+14 |
+15 | { PCI_VENDOR_ID_HUMUSOFT, PCI_DEVICE_ID_MF614,
+16 | PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
+17 |
+18 | { 0, } /* seznam je vždy zakončen prázdným prvkem */
+19 | };
+\end{verbatim}
+
+V případě, že je u zařízení rozhodující Vendor ID, ale na Subvendor ID nezáleží, je možné použít makro \texttt{PCI\_ANY\_ID} (to platí
+i pro Subdevice ID).
+
+\ibox{\texttt{MODULE\_DEVICE\_TABLE(type, struct pci\_device\_id* name);}}
-První věc, kterou ovladač PCI zařízení potřebuje udělat, aby se stal součástí systému, je registrace do seznamu všech PCI ovladačů v operačním systému. To se provede voláním funkce \texttt{pci\_register\_driver()}, které se jako parametr předá ukazatel na strukturu \texttt{struct pci\_driver}.
+Tato struktura se -- pro nástroje v uživatelském prostoru vytvářející seznamy ovladačů -- exportuje pomocí makra \texttt{MODULE\_DEVICE\_TABLE(pci, mf624\_pci\_id)}, kde první parametr určuje typ zařízení a druhý je ukazatel na seznam typu \texttt{struct pci\_device\_id}.
\subsection{Struktura \texttt{struct pci\_driver}}
-Tato struktura obsahuje základní informace o našem ovladači. Mezi hlavní položky patří:
+Pro to, aby mohl se mohl ovladač PCI zařízení stát součástí jaderného PCI subsystému, je potřeba ho do zaregistrovat. To se provede voláním funkce \texttt{pci\_register\_driver()}, které se jako parametr předá ukazatel na strukturu \texttt{struct pci\_driver}.
+
+Tato struktura obsahuje základní informace o ovladači -- základní proměnné a ukazatele na funkce. Mezi hlavní položky patří:
\begin{description}
\item[\texttt{const char name*}]~\\Název ovladače. Tento název by měl být unikátní mezi všemi ovladači PCI zařízení. Většinou je totožný s názvem modulu.
-\item[\texttt{const struct pci\_device\_id *id\_table}]~\\Pole struktur popisujících, pro která zařízení je ovladač vytvořen (jednotlivé položky struktury jsou popsány níže).
+\item[\texttt{const struct pci\_device\_id *id\_table}]~\\Pole struktur popisujících, pro která zařízení je ovladač vytvořen (viz kap. \ref{pci_dev_id}).
\item[\texttt{int (*probe) (struct pci\_dev *dev, const struct pci\_device\_id *id)}]~\\Ukazatel na funkci, která je volána PCI subsystémem, v případě, že je přítomno zařízení, pro které je tento ovladač vytvořen.
\item[\texttt{void (*remove) (struct pci\_dev *dev)}]~\\Ukazatel na funkci, která je volána poté, co je tento ovladač odstraňován ze seznamu ovladačů aktuálně používaných PCI subsystémem.
\end{description}
Příklad, jak může být struktura \texttt{pci\_driver} inicializována a následně zaregistrována:
\begin{verbatim}
-1 | static struct pci_driver mf624_pci_driver = {
-2 | .name = "mf624",
-3 | .id_table = mf624_pci_id,
-4 | .probe = mf624_pci_probe,
-5 | .remove = mf624_pci_remove,
-6 | };
-pci_register_driver(&mf624_pci_driver);
+ 1 | static struct pci_driver mf624_pci_driver = {
+ 2 | .name = "mf624",
+ 3 | .id_table = mf624_pci_id,
+ 4 | .probe = mf624_pci_probe,
+ 5 | .remove = mf624_pci_remove,
+ 6 | };
+ 7 | pci_register_driver(&mf624_pci_driver);
\end{verbatim}
-\subsection{Struktura \texttt{struct pci\_device\_id}}
-Jak již bylo zmíněno, struktura \texttt{struct pci\_device\_id} slouží k identifikaci, pro která zařízení je ovladač určen. Mezi hlavní položky struktury patří \texttt{vendor}, \texttt{device}, \texttt{subvendor}, \texttt{subdevice} (typu \texttt{\_\_u32}) -- jejichž hodnota odpovídá stejnojmenným registrům v konfiguračním prostoru PCI zařízení. Jelikož může být ovladač napsán pro více zařízení, je tato struktura inicializována jako prvek pole, které je zakončeno prvkem prázdným. Možná inicializace vypadá následovně:
-
-\begin{verbatim}
- 1 | #define PCI_VENDOR_ID_HUMUSOFT 0x186c
- 2 | #define PCI_DEVICE_ID_MF624 0x0624
- 3 | #define PCI_SUBVENDOR_ID_HUMUSOFT 0x186c
- 4 | #define PCI_SUBDEVICE_DEVICE 0x0624
- 5 |
- 6 | static struct pci_device_id mf624_pci_id[] = {
- 7 | {
- 8 | .vendor = PCI_VENDOR_ID_HUMUSOFT,
- 9 | .device = PCI_DEVICE_ID_MF624,
-10 | .subvendor = PCI_SUBVENDOR_ID_HUMUSOFT,
-11 | .subdevice = PCI_SUBDEVICE_DEVICE,
-12 | },
-13 | { 0, } /* seznam je vždy zakončen prázdným prvkem */
-14 | };
-\end{verbatim}
-
-\subsection{Funkce \texttt{probe()}}
+\subsection{Funkce \texttt{probe()}}\label{pci_init}
\ibox{\texttt{int probe(struct pci\_dev *dev, const struct pci\_device\_id *id);}}
-Funkce \texttt{probe()} náležící danému ovladači zařízení je volána poté, co subsystém PCI zařízení zjistí, že se v systému nachází zařízení, pro které je tento ovladač určen. Tato funkce má na starosti inicializaci zařízení.
+Funkce \texttt{probe()} náležící danému ovladači zařízení je volána poté, co jaderný subsystém PCI zařízení zjistí, že se v systému nachází zařízení, pro které je tento ovladač určen. Tato funkce má na starosti inicializaci zařízení.
-V prvním parametru PCI subsystém předává strukturu \texttt{struct pci\_dev}, která repre\-zentuje fyzické zařízení. V druhém parametru je předána struktura, na základě byl zvolen daný ovladač.
+Prvním parametrem funkce předává PCI subsystém ukazatel na strukturu \texttt{struct pci\_dev}, která repre\-zentuje fyzické zařízení. V druhém parametru je předán ukazatel na strukturu, na základě které byl zvolen daný ovladač (viz kap. \ref{pci_dev_id}).
\ibox{\texttt{pci\_enable\_device(struct pci\_dev *dev);}}
-V rámci inicializace je nejprve potřeba zavolat funkci \texttt{pci\_enable\_device()} -- ta se postará o inicializaci karty na úrovni hardware -- např.: přiřazení linky přerušení, zresetování registrů karty a její probuzení. Poté již můžeme začít přistupovat ke zdrojům zařízení.
+V rámci inicializace ovladače je nejprve potřeba zavolat funkci \texttt{pci\_enable\_device()} -- ta se postará o inicializaci karty na úrovni hardware -- např.: přiřazení linky přerušení, zresetování registrů karty a její probuzení. Poté je již možné začít přistupovat ke zdrojům zařízení.
\subsection{Přístup ke zdrojům karty}
-Jak bylo popsáno v kapitole \ref{pcich}, PCI zařízení může využívat až 6 paměťových nebo vstupně-výstupních regionů (označovaných jako \textit{zdroje} karty). Jejich alokace do paměťového nebo I/O prostoru počítače je zajištěna dynamicky PCI mostem. Pro přístup do nich si musí ovladač zařízení zjistit jejich adresu a vyžádat si u operačního systému výlučný přístup.
+Jak bylo popsáno v kapitole \ref{pci_conf}, PCI zařízení může využívat až 6 paměťových nebo vstupně-výstupních regionů (označovaných jako \textit{zdroje} karty). Jejich alokace do paměťového nebo I/O prostoru počítače je zajištěna dynamicky PCI mostem. Pro přístup do regionů si musí ovladač zařízení zjistit jejich adresu a vyžádat si u operačního systému \textit{výlučný přístup}.
\ibox{\texttt{int pci\_request\_regions(struct pci\_dev *pdev, const char *res\_name);}}
-Nejprve je potřeba operační systém požádat o výlučný přístup ke zdrojům zařízení. To se provede voláním funkce \texttt{pci\_request\_regions()}. V případě, že ovladači není přístup umožněn (jiný ovladač přistupuje ke stejné kartě nebo po jeho odstranění nedošlo k uvolnění zdrojů karty), ovladač by měl korektním způsobem ukončit svoji funkci a nesnažit se k zařízení přistupovat.
+Nejprve je potřeba operační systém požádat o výlučný přístup ke zdrojům zařízení. To se provede voláním funkce \texttt{pci\_request\_regions()}. Je-li návratová hodnota zavolané funkce negativní, není ovladači umožněn přístup (jiný ovladač přistupuje ke stejné kartě nebo po jeho odstranění nedošlo k uvolnění zdrojů karty). V takovém případě by ovladač měl korektním způsobem ukončit svoji funkci a nesnažit se k zařízení přistupovat.
\ibox{\texttt{unsigned long pci\_resource\_start(struct pci\_dev *dev, int bar);}}
-V případě, že volání \texttt{pci\_request\_regions()} proběhlo úspěšně, je již možné získat přístup přímo k jednotlivým regionům karty. Fyzickou adresu jednotlivých regionů zjistíme voláním funkce \texttt{pci\_resource\_start()}, kde se jako druhý parametr uvede číslo BAR registru určujícího region (tj. 0--5).
+V případě, že volání \texttt{pci\_request\_regions()} proběhlo úspěšně, je již možné získat přístup přímo k jednotlivým regionům karty. Fyzickou adresu jednotlivých regionů lze zjistit voláním funkce \texttt{pci\_resource\_start()}, kde se jako druhý parametr uvede číslo BAR registru určujícího region (tj. 0--5).
+%Volání této funkce má pouze informativní charakter
\ibox{\texttt{unsigned long pci\_resource\_len(struct pci\_dev *dev, int bar);}}
S ukazatelem, který vrátí funkce \texttt{pci\_request\_regions()} však není možné přímo pracovat -- je to toiž \textbf{fyzická adresa} daného regionu, ke které neumí procesor přímo přistupovat. Aby tato fyzická adresa byla přemapována na adresu \textbf{virtuální}, je potřeba zavolat funkci \texttt{pci\_ioremap\_bar()}.
-K ukazateli, který vrátí volání \texttt{pci\_ioremap\_bar()} je již možné pomocí speciálních funkcí přistupovat.
-
-\ibox{FIXME povídání o out-of-order execution a cache a proč musíme používat speciální funkce}
+K ukazateli, který vrátí volání \texttt{pci\_ioremap\_bar()} je již možné pomocí speciálních funkcí (popsány v kap. \ref{iofce}) přistupovat.
\subsection{Funkce \texttt{remove()}}
Funkce je volána, když PCI subsystém ze svého seznamu odstraňuje strukturu \texttt{struct pci\_dev} reprezentující dané zařízení, nebo v případě, že dochází k uvolnění modulu.
-Tato funkce by se měla postarat o úklid všech naalokovaných prostředků apod. Měla by obsahovat volání:
+Tato funkce by se měla postarat o úklid všech naalokovaných prostředků. Měla by obsahovat volání:
\begin{description}
\item[\texttt{iounmap()}]~\\Uvolnění virtuální paměti namapované voláním \texttt{pci\_ioremap\_bar()}.
\item[\texttt{pci\_release\_regions()}]~\\Uvolnění zdrojů karty, které byly zarezervovány voláním \texttt{pci\_request\_regions()}.
\end{description}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-\section{Přístup k paměti zařízení}
+\section{Přístup k paměti zařízení}\label{iofce}
Poté co se ovladači podařilo získat přístup ke zdrojům zařízení, je nutné využít speciálních volání pro zápis/čtení do/z těchto zdrojů.
\subsection{Vstupně-výstupní adresní prostor}
Třetí písmeno, stejně jako u funkcí pro čtení, značí o kolikabitový přístup se jedná.
-Stejná volání je možné používat i z uživatelského prostoru (potřebný hlavičkový soubor je \texttt{<sys/io.h>}).
+Funkce se stejným \textit{prototypem} jsou k dispozici i v uživatelském prostoru (potřebný hlavičkový soubor je \texttt{<sys/io.h>}).
+
\subsection{Paměťový adresní prostor}
I přesto, že se k přístupu k paměti zařízení mapované do paměťového adresního prostoru používá virtuální adresa, stejně jako k přístupu do operační paměti, není možné k paměti zařízení přistupovat přímo \textit{přes ukazatel}. Důvodem je to, že buď překladač (při kompilaci) nebo procesor (za běhu) zoptimalizují\footnote{Tyto optimalizace, v případě přístupu k operační paměti, urychlují vykonávání programu, aniž by negativně ovlivnily jeho funkci. V případě zápisu/čtení do/z registrů, u kterých mohou tyto operace vyvolávat tzv. \textit{side effects}, již může dojít k nesprávné funkci programu.
Příklad optimalizace: V programu se do jedné paměťové buňky ihned po sobě zapíší dvě různé hodnoty, poté se výsledná hodnota přečte -- optimalizace možná u klasického programu je taková, že se ve skutečnosti provede pouze druhý zápis, protože ten první nemá žádný efekt (hodnota je ihned přepsána druhým zápisem). V případě přístupu do registru zařízení může zápis například spouštět převod A/D převodníků -- po optimalizaci se však provede pouze jednou, nikoliv dvakrát.} sekvenci zápisů/čtení do/z paměti zařízení takovým způsobem, že se výsledek bude lišit od toho, jak to bylo v programu zamýšleno.
-Těmto optimalizacím lze nejsnáze zabránit použitím volání:
+Těmto optimalizacím lze nejsnáze zabránit použitím volání pro čtení:
\ibox{\texttt{unsigned int ioread8(void *addr);}}
\ibox{\texttt{unsigned int ioread32(void *addr);}}
-pro čtení a volání
+a pro zápis:
\ibox{\texttt{void iowrite8(u8 value, void *addr);}}
\ibox{\texttt{void iowrite32(u32 value, void *addr);}}
-pro zápis. Číslo na konci funkce označuje o kolikabitový přístup se jedná.
+Číslo na konci funkce označuje o kolikabitový přístup se jedná.
V případě, že se na paměť ve vstupně-výstupním adresním prostoru zavolá funkce
\ibox{\texttt{void *ioport\_map(unsigned long port, unsigned int count);}}
nebo v případě PCI zařízení funkce
-\ibox{\texttt{void \_\_iomem *pci\_iomap(struct pci\_dev *dev, int bar, unsigned long maxlen);}}
+\ibox{\texttt{void *pci\_iomap(struct pci\_dev *dev, int bar, unsigned long maxlen);}}
-je tato paměť \textit{přemapována} do paměťového adresního prostoru a je nutné pro přístup k ní používat výše popsaná volání.
+se paměť chová jakoby byla v paměťovém adresním prostoru a je nutné pro přístup k ní používat volání popsaná v této kapitole.
-Z uživatelského prostoru je potřeba používat volání FIXME.
+
+Pro přístup k paměti zařízení z uživatelského prostoru bohužel žádná sada funkcí není. Je potřeba nadefinovat funkce vlastní, které přistupují k paměti skrze ukazatel, který je ale označen jako \texttt{volatile}. To opět zabrání překladači v optimalizaci kódu manipulujícího s ukazatelem.
+
+Příklad jak takové funkce mohou vypadat:
+
+\begin{verbatim}
+ 1 | static inline void mf624_write32(uint32_t val, uint32_t *ptr)
+ 2 | {
+ 3 | *(volatile uint32_t*) ptr = val;
+ 4 | }
+ 5 | static inline int32_t mf624_read32(uint32_t *ptr)
+ 6 | {
+ 7 | return (volatile uint32_t) *ptr;
+ 8 | }
+\end{verbatim}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\section{UIO ovladač}
V případě, že je vytvářen ovladač pro linuxové jádro, mělo by být rozhodnuto, kterého subsystému se stane součástí -- např. zda jde o jednoduché znakové zařízení, síťovou kartu nebo zvukovou kartu. Tato volba uřčí, kterou sadu pomocných funkcí bude moci ovladač používat a jakým způsobem bude zpřístupněno zařízení do uživatelského prostoru.
-V případě, že jde o PCI zařízení, které nelze snadno zařadit do žádné kategorie (jedná-li se například o neobvyklou průmyslovou kartu), je možné použít tzv. UIO (\textit{Userspace I/O}) ovladač. Tento ovladač se skládá ze dvou částí: jaderného modulu a aplikace v uživatelském prostoru. Mezi jeho hlavní výhody patří to, že v jádře je obsažena pouze malá obecná část, která zpřístupňuje zdroje zařízení do uživatelského prostoru (její implementace je poměrně snadná).
+V případě, že jde o PCI zařízení, které nelze snadno zařadit do žádné kategorie (jedná-li se například o neobvyklou průmyslovou kartu), je možné vytvořit tzv. UIO (\textit{Userspace I/O}) ovladač. Tento ovladač se skládá ze dvou částí: jednoduchého jaderného modulu a aplikace v uživatelském prostoru.
-Druhou částí je aplikace v uživateském prosotru, která přistupuje k jednotlivým zdrojům karty a tvoří hlavní logiku ovladače. Většina vývoje tedy probíhá v uživatelském prostoru, čímž klesá riziko narušení stability jádra.
+Mezi jeho hlavní výhody patří to, že v jádře je obsažena pouze malá obecná část, která zpřístupňuje zdroje zařízení do uživatelského prostoru (její implementace je poměrně snadná). Druhou částí je aplikace v uživateském prostoru, která přistupuje k jednotlivým zdrojům karty a tvoří hlavní logiku ovladače. Většina vývoje tedy probíhá v uživatelském prostoru, čímž klesá riziko narušení stability jádra.
\subsection{Jaderný modul}
Jaderný modul UIO ovladače by měl obsahovat:
\item Funkce pro \textit{úklid} a uvolnění regionů karty
\end{itemize}
-Většina z těchto úkonů již byla popsána v kapitole \ref{pcich} a jsou zcela standardní pro jakýkoliv ovladač PCI zažízení, navíc je zde pouze \textit{registrace do UIO subsystému}.
+Většina z těchto úkonů již byla popsána v kapitole \ref{pci_driv} a jsou zcela standardní pro jakýkoliv ovladač PCI zažízení. Co nebylo dosud popsáno je pouze úkon \textit{registrace do UIO subsystému}.
\ibox{\texttt{int uio\_register\_device(struct device *parent, struct uio\_info *info);}}
-Ta se provede zavoláním funkce \texttt{uio\_register\_device()}, které se jako první parametr předá \textit{rodič} struktury \texttt{struct pci\_dev} struktury -- tj. ukazatel na její položku \texttt{dev}.\footnote{FIXME; WTF is this?} Druhý parametr předá ukazatel na strukturu \texttt{struct uio\_info}.
+Registrace UIO ovladače se provede zavoláním funkce \texttt{uio\_register\_device()}, které se jako první parametr předá \textit{rodič} FIXME struktury \texttt{struct pci\_dev} struktury -- tj. ukazatel na její položku \texttt{dev}. Druhý parametr předá ukazatel na strukturu \texttt{struct uio\_info}.
\subsubsection{Struktura \texttt{struct uio\_info}}
+
+Jedná se o strukturu vyplněnou informacemi o zařízení, která je předána při registraci UIO subsystému.
Mezi její hlavní položky patří:
\begin{description}
-\item[\texttt{const char *name}]~\\Název ovladače
-\item[\texttt{const char *version}]~\\Verze ovladače
-\item[\texttt{struct uio\_mem mem[MAX\_UIO\_MAPS]}]~\\Pole struktur obsahujících informace o regionech PCI zařízení mapovaných do paměťového prostoru
-\item[\texttt{struct uio\_port port[MAX\_UIO\_PORT\_REGIONS]}]~\\Pole struktur obsahujících informace o regionech PCI zařízení mapovaných do vstupně-výstupního prostoru.
+\item[\texttt{const char *name}]~\\Název ovladače. Většinou se shoduje s názvem modulu.
+\item[\texttt{const char *version}]~\\Verze ovladače v textové podobě.
+\item[\texttt{struct uio\_mem mem[MAX\_UIO\_MAPS]}]~\\Pole struktur obsahujících informace o regionech PCI zařízení mapovaných do paměťového prostoru (bude podrobně vysvětleno dále).
+\item[\texttt{struct uio\_port port[MAX\_UIO\_PORT\_REGIONS]}]~\\Pole struktur obsahujících informace o regionech PCI zařízení mapovaných do vstupně-výstupního prostoru (bude podrobně vysvětleno dále).
%\item[\texttt{}]
\end{description}
-\subsubsection{Struktura \texttt{struct uio\_mem} a \texttt{struct uio\_port}}
+\subsubsection{Struktura \texttt{struct uio\_mem} a \texttt{struct uio\_port}}\label{uio_mem_port}
Tyto struktury obsahují imformace o regionech zařízení. Které (a kolik) z těchto dvou struktur budou inicializovány záleží na tom, zda karta mapuje regiony do paměťového nebo vstupně-výstupního prostoru.
Struktura \texttt{struct uio\_mem} obsahuje položky:
\item[\texttt{unsigned long addr}]~\\Fyzická adresa regionu získaná voláním \texttt{pci\_resource\_start()}.
\item[\texttt{unsigned long size}]~\\Délka regionu. Nejsnáze získaná voláním \texttt{pci\_resource\_len()}.
\item[\texttt{int memtype}]~\\Typ paměti. Pro fyzickou paměť na zařízení se použije \texttt{UIO\_MEM\_PHYS}.
-\item[\texttt{void \_\_iomem *internal\_addr}]~\\Virtuální adresa získaná voláním \texttt{pci\_ioremap\_bar()}
+\item[\texttt{void \_\_iomem *internal\_addr}]~\\Virtuální adresa získaná voláním \texttt{pci\_ioremap\_bar()}.
\end{description}
Struktura \texttt{struct uio\_port} obsahuje položky:
\item[\texttt{const char *name}]~\\Textový popis daného regionu (viditelný z uživatelského prostoru).
\item[\texttt{unsigned long start}]~\\Fyzická adresa regionu získaná voláním \texttt{pci\_resource\_start()}.
\item[\texttt{unsigned long size}]~\\Délka regionu. Nejsnáze získaná voláním \texttt{pci\_resource\_len()}.
-\item[\texttt{int porttype}]~\\Typ portu. Pro porty na architektuře IA-32 se použije \texttt{UIO\_PORT\_X86}
+\item[\texttt{int porttype}]~\\Typ portu. Pro porty na architektuře IA-32 se použije \texttt{UIO\_PORT\_X86}.
\end{description}
~\\
-Příklad, jak taková jednoduchá inicializace struktury \texttt{struct uio\_info} včetně registrace může vypadat (bez ošetření chybných stavů):
+Příklad, jak taková jednoduchá inicializace struktury \texttt{struct uio\_info} včetně registrace může vypadat (bez ošetření chybových stavů):
\begin{verbatim}
1 | /* struct pci_dev *dev */
2 | struct uio_info *info;
\ibox{\texttt{void pci\_set\_drvdata(struct pci\_dev *pdev, void *data)}}
-Na posledním řádku je, dosud nepopsané, volání \texttt{pci\_set\_drvdata()}. To (v tomto případě) zajistí, že struktura \texttt{struct uio\_info} se stane součástí struktury reprezentující zařízení (\texttt{struct pci\_dev}) -- což umožní přístup ke struktuče \texttt{struct uio\_info} z funkcí jako je například funkce \texttt{remove()}, která jako parametr získá ukazatel na strukturu \texttt{struct pci\_dev}
+Na posledním řádku je, dosud nepopsané, volání \texttt{pci\_set\_drvdata()}. To (v tomto případě) zajistí, že struktura \texttt{struct uio\_info} se stane součástí struktury reprezentující zařízení (\texttt{struct pci\_dev}) -- což umožní pozdější přístup ke struktuře \texttt{struct uio\_info} z funkcí jako je například \texttt{remove()}, která jako parametr získá ukazatel na strukturu \texttt{struct pci\_dev}.
+
+\ibox{\texttt{static inline void *pci\_get\_drvdata(struct pci\_dev *pdev)}}
+
+Funkce \texttt{pci\_get\_drvdata()} slouží k \textit{získání} dat uložených do struktury \texttt{struct pci\_dev} pomocí volání \texttt{pci\_set\_drvdata()}.
+
+Příklad použití:
+\begin{verbatim}
+ 1 | static void mf624_pci_remove(struct pci_dev *dev)
+ 2 | {
+ 3 | struct uio_info *info = pci_get_drvdata(dev);
+ 4 | /* ... */
+ 5 | }
+\end{verbatim}
+
\subsection{Program v uživatelském prostoru}
Poté, co je jaderná část UIO ovladače úspěšně zkompilována a zavedena do systému, ve kterém se nachází požadované zařízení, je rozhraní mezi tímto modulem a uživatelským prostorem tvořeno:
\begin{itemize}
-\item souborem \texttt{/dev/uio0\footnote{Pro názornost v textu uvádím konkrétní zařízení \texttt{uio0}. V případě, že systém obsahuje více aktivních UIO ovladačů, jsou postupně číslovány od 0 výše.}}.
+\item souborem \texttt{/dev/uio0\footnote{Pro názornost je v textu uvedeno konkrétní zařízení \texttt{uio0}. V případě, že systém obsahuje více aktivních UIO ovladačů, jsou postupně číslovány od 0 výše.}}.
\item složkou \texttt{/sys/class/uio/uio0}, která obsahuje informace o regionech, které jsou zpřístupněny skrze UIO modul v jádře.
\end{itemize}
\subsubsection{Obsah složky \texttt{/sys/class/uio/uio0}}
-Tato složka obsahuje soubory převážně pouze pro čtení. Obsahuje podložku \texttt{maps}, ve které se nachází pro každý region PCI zařízení mapovaný do paměti (zpřístupněný jaderným ovladačem) složka obsahující soubory popisující tyto regiony (Soubor \texttt{addr} obsahuje fyzickou adresu regionu; \texttt{name} slovní pojmenování; \texttt{size} velikost regionu).
+Tato složka obsahuje soubory převážně pouze pro čtení. Obsahuje podsložku \texttt{maps}, ve které se nachází pro každý region PCI zařízení mapovaný do paměti (zpřístupněný jaderným ovladačem) složka obsahující soubory popisující tyto regiony (Soubor \texttt{addr} obsahuje fyzickou adresu regionu; \texttt{name} slovní pojmenování; \texttt{size} velikost regionu).
+
+V případě, že jsou zpřístupněny regiony rařízení, které jsou mapovány do vstupně-výstupního adresního prostoru, nacházejí se jednotlivé podsložky a soubory popisující regiony ve složce \texttt{portio}.
\subsubsection{Soubor \texttt{/dev/uio0}}
-Tento soubor je využíván k samotnému přístupu k regionům karty. K souboru se přistupuje pomocí volání \texttt{mmap()}.
+Tento soubor tvoří rozhraní mezi jaderným subsystémem UIO a uživatelským prostorem. Skrze něj je přistupováno k regionům karty. K souboru se přistupuje pomocí volání \texttt{mmap()}.
\ibox{\texttt{void *mmap(void *addr, size\_t length, int prot, int flags, int fd, off\_t offset);}}
-Tato funkce slouží k \textit{namapování} souboru nebo zařízení do operační paměti. V případě, že funkci zavoláme na soubor, proběhne-li vše správně, návratová hodnota bude obsahovat ukazatel do paměti, kam je možné přistu\-povat k obsahu souboru pomocí ukazatelové aritmetiky -- stejně, jako by to byla paměť.
+Tato funkce slouží k \textit{namapování} souboru nebo zařízení do operační paměti. V případě, že je funkce zavolána na soubor, proběhne-li vše správně, návratová hodnota bude obsahovat ukazatel do paměti, kam je možné přistu\-povat k obsahu souboru pomocí ukazatelové aritmetiky -- stejně, jako by to byla paměť.
\begin{description}
-\item[Parametr \texttt{addr}] -- v případě, že není nulový -- určí, na jakou adresu by měla být paměť mapována.
-\item[Parametr \texttt{length}] udává velikost mapované paměti v násobcích velikosti paměťové stránky.
-\item[Parametr \texttt{prot}] obsahuje příznaky definující, zda bude mapovaná paměť pro čtení/zápis, apod.
-\item[Parametr \texttt{flags}] pomocí příznaku určuje, zda se mají změny zapisovat pouze do \textit{lokální kopie} (příznak \texttt{MAP\_PRIVATE}) nebo zda mají být zapisovány do původního souboru/zařízení (příznak \texttt{MAP\_SHARED}).
-\item[Parametr \texttt{fd}] obsahuje \textit{filedescriptor} na zařízení, které má být namapováno (v tomto případě \texttt{/dev/uio0}).
-\item[Parametr \texttt{offset}] určuje, zda se zaný soubor/zařízení začne mapovat od posunuté adresy. V případě UIO ovladače je možné jako offset používat násobky velikosti paměťové stránky -- tento offset určí, který z regionů zpřístupněných jadernou částí ovladače má být namapován.
+\item[Parametr \texttt{addr}]~\\V případě, že není nulový, určí na jakou adresu by měla být paměť mapována.
+\item[Parametr \texttt{length}]~\\ Udává velikost mapované paměti v násobcích velikosti paměťové stránky.
+\item[Parametr \texttt{prot}]~\\ Obsahuje příznaky definující, zda bude mapovaná paměť pro čtení/zápis, apod.
+\item[Parametr \texttt{flags}]~\\ Pomocí příznaku určuje, zda se mají změny zapisovat pouze do \textit{lokální kopie} (příznak \texttt{MAP\_PRIVATE}) nebo zda mají být zapisovány do původního souboru/zařízení (příznak \texttt{MAP\_SHARED}).
+\item[Parametr \texttt{fd}]~\\ Obsahuje \textit{filedescriptor} na zařízení, které má být namapováno (v tomto případě file\-desc\-riptor vrácený voláním \texttt{open("/dev/uio0", ... );}).
+\item[Parametr \texttt{offset}]~\\ Určuje, zda se zaný soubor/zařízení začne mapovat od posunuté adresy. V případě UIO ovladače je možné jako offset používat násobky velikosti paměťové stránky -- tento offset určí, který z regionů zpřístupněných jadernou částí ovladače má být namapován.
\end{description}
-Příklad, jak takové volání může vypadat (bez ošetření chybných stavů):
+Příklad, jak takové volání může vypadat (bez ošetření chybových stavů):
\begin{verbatim}
-1 | #define BAR2_offset (1 * sysconf(_SC_PAGESIZE))
-2 | void* mf624_BAR2 = NULL;
-3 | int device_fd = open("/dev/uio0", O_RDWR);
-4 |
-5 | mf624_BAR2 = mmap(0, 1 * sysconf(_SC_PAGESIZE),
-6 | PROT_READ | PROT_WRITE, MAP_SHARED,
-7 | device_fd, BAR2_offset);
+ 1 | #define BAR2_offset (1 * sysconf(_SC_PAGESIZE))
+ 2 |
+ 3 | void* mf624_BAR2 = NULL;
+ 4 | int device_fd = open("/dev/uio0", O_RDWR);
+ 5 |
+ 6 | mf624_BAR2 = mmap(0, 1 * sysconf(_SC_PAGESIZE),
+ 7 | PROT_READ | PROT_WRITE, MAP_SHARED,
+ 8 | device_fd, BAR2_offset);
\end{verbatim}
S adresou vrácenou voláním \texttt{mmap()} však není možné vždy ihned pracovat. Může se stát, že mapovaný region PCI zařízení (reprezentovaný zařízením \texttt{/dev/uio0}, na které je \texttt{mmap()} volán) je menší než je velikost celé stránky, \texttt{mmap()} však vrací ukazatel zarovnaný na velikost stránky. Je tedy potřeba se v rámci této stránky posunout na tu část, která odpovídá požadovanému regionu.
-Jak velký je potřeba udělat \textit{posun} pomůže zjistit soubor \texttt{/sys/class/uio/uio0/maps/map1/addr}\footnote{Pro názornost uvádím konkrétní cestu -- jedná se tedy o \textit{druhý} paměťový region zažízení \textit{uio0}.} -- ten obsahuje fyzickou adresu požadovaného regionu. Z té je možné následujícím trikem získat ukazatel, se kterým je již možné pracovat (nejnižší bity totiž budou zachovány z fyzické adresy):
+Jak velký je potřeba udělat \textit{posun} pomůže zjistit soubor \texttt{/sys/class/uio/uio0/maps/map1/addr}\footnote{Pro názornost je uvedena konkrétní cesta -- jedná se tedy o \textit{druhý} paměťový region zažízení \textit{uio0}.} -- ten obsahuje fyzickou adresu požadovaného regionu. Z té je možné následujícím trikem získat ukazatel, se kterým je již možné pracovat (nejnižší bity totiž budou zachovány z fyzické adresy):
\begin{verbatim}
mf624_BAR2 += (BAR2_phys_addr & (sysconf(_SC_PAGESIZE) - 1));
| \-- Fyzická adresa
\end{verbatim}
\subsection{Přístup k paměti zařízení}
-Podobně jakou u jaderného modulu komunikujícího s pamětí zařízení, je potřeba i v uživatelském prostoru k této paměti přistupovat pomocí speciálních funkcí.
-FIXME
+Jelikož se jedná o paměť zařízení, je potřeba i v uživatelském prostoru k této paměti přistupovat pomocí speciálních funkcí. Ty jsou popsány v kapitole \ref{iofce}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\newpage
\section{Comedi ovladač}
UIO ovladač dává uživateli možnost využít 100 \% všech funkcí zařízení -- daní za to je pomalejší odezva než v případě plnohodnotného jaderného ovladače.
Pro ovladače měřících karet existuje v Linuxu subsystém -- tzv. Comedi (Control and Measurement Device Interface).
-\subsection{Registrace Comedi ovladače}
-V případě Comedi ovladače se namísto volání \texttt{module\_init()} a \texttt{module\_exit()} zavolá makro \texttt{COMEDI\_INITCLEANUP()}, kterému se jako parametr předá struktura \texttt{struct comedi\_driver} (toto makro však ve skutečnosti volá výše zmíněné standardní funkce).
+\subsection{Registrace ovladače}
\subsection{Struktura \texttt{struct comedi\_driver}}
Tato struktura obsahuje ukazatele na funkce, které jsou zavolány při nahrání resp. uvolnění ovladače.
Příklad základní registrace zařízení:
\begin{verbatim}
- 1 | static struct pci_device_id mf624_pci_table[] = {
- 2 | { /* ... */ },
- 3 | { 0 }
- 4 | };
- 5 | MODULE_DEVICE_TABLE(pci, mf624_pci_table);
- 6 |
- 7 | static struct comedi_driver driver_mf624 = {
- 8 | driver_name: "mf624",
- 9 | module: THIS_MODULE,
-10 | attach: mf624_attach,
-11 | detach: mf624_detach,
-12 | };
-13 |
-14 | COMEDI_INITCLEANUP(driver_mf624);
+
\end{verbatim}
\subsection{Funkce \texttt{attach}}
\begin{figure}[h!]
\begin{center}
\includegraphics[width=120mm]{img/svorkovnice.jpg}
- \caption{Svorkovnice TB620.}
+ \caption{Svorkovnice TB620}
\label{svorkovnice}
\end{center}
\end{figure}