diff options
| author | Hans J. Koch <hjk@linutronix.de> | 2009-06-22 19:38:50 +0200 |
|---|---|---|
| committer | Hans J. Koch <hjk@linutronix.de> | 2009-06-22 19:38:50 +0200 |
| commit | 42cbeb39988df2958fb21020c06589cb2eb0e400 (patch) | |
| tree | 89f78445c03a922118cf0f02f49a3fbaf3918cc0 /kernel-devel/uio-driver | |
| parent | 78d3078212e26533da5213ef440306ff27d27eb3 (diff) | |
Updated English presentation and German handout in kernel-devel/uio-driver
Diffstat (limited to 'kernel-devel/uio-driver')
| -rw-r--r-- | kernel-devel/uio-driver/handout_uio-driver_de.tex | 597 | ||||
| -rw-r--r-- | kernel-devel/uio-driver/pres_uio-driver_en.tex | 7 |
2 files changed, 594 insertions, 10 deletions
diff --git a/kernel-devel/uio-driver/handout_uio-driver_de.tex b/kernel-devel/uio-driver/handout_uio-driver_de.tex index 4c391b2..546c7fe 100644 --- a/kernel-devel/uio-driver/handout_uio-driver_de.tex +++ b/kernel-devel/uio-driver/handout_uio-driver_de.tex @@ -1,17 +1,602 @@ -\documentclass{article} +\documentclass{lxarticle} \usepackage{german} \usepackage[utf8]{inputenc} +\usepackage{lxheaders} +\usepackage{lxextras} \begin{document} -\section*{Titel} +\section*{Linux-Treiber im Userspace} -\subsection*{Abschnitt1} +Das ab Linux 2.6.23 im stabilen Kernel verfügbare Userspace-I/O-Framework +(UIO) ermöglicht, bestimmte Treiber fast vollständig in den Userspace zu +verlagern. Dies vereinfacht die Entwicklung und Wartung von Treibern, +außerdem ergeben sich Möglichkeiten, die besonders Industriebetrieben +entgegen kommen. -Text +Eine im Automatisierungsbereich tätige Firma hat eine PCI-I/O-Karte +entwickelt. Sie soll in einer unter Linux laufenden Maschinensteuerung +eingesetzt werden. Ein Linux-Kerneltreiber muss her. Kein Problem, denkt +man sich. Immerhin stehen sowohl der Kernel-Sourcecode als auch die nötigen +Entwicklungstools frei zur Verfügung. So oder so ähnlich hat schon so manche +Linux-Horror-Story begonnen. -\subsection*{Abschnitt2} +Trotz benutzerfreundlicher Kernel-Subsysteme und Unterstützung durch +Mailinglisten bedeutet das Schreiben eines Kerneltreibers für Linux-Einsteiger +eine nicht zu unterschätzende Einarbeitungszeit. Industrielle I/O-Hardware ist +oft so speziell, dass die Subsysteme des Kernels dem Programmierer gerade einmal +das Erkennen der PCI-Karte abnehmen können. Gleichzeitig verfügen solche Karten +oft über sehr komplexe Funktionen, so dass der Treiber immer umfangreicher wird. +Treibercode mit mehreren tausend Codezeilen ist keine Seltenheit. -Text +Doch auch wenn diese Hürde genommen, der Treiber erfolgreich debugged und +getestet wurde, gibt es ein noch größeres Problem: Die Wartung des Treibers. +Denn ein so großer Treiber verwendet zweifellos eine große Zahl interner +Kernel-Funktionen und -Makros. Dieses Kernel-interne API ist stets in Bewegung. +Wer einen Treiber für 2.6.30 geschrieben hat, hat keinerlei Garantie, dass der +unter 2.6.31 noch fehlerfrei kompiliert. Wer seinen Code mehrere Kernel-Versionen +lang liegen lässt und dann upgraden will, hat höchst wahrscheinlich ein paar +unerfreuliche Tage vor sich. + +Das zuletzt genannte Problem löst sich in Luft auf, wenn es gelingt, die +Kernel-Entwickler, an der Spitze Linus Torvalds, dazu zu bewegen, den Code in +den stabilen Mainline-Kernel aufzunehmen. Denn dann sind die Entwickler, die +etwas am internen API ändern, verpflichtet, für die Anpassung aller betroffenen +Module zu sorgen. Für unsere Automatisierungsfirma würde das bedeuten, dass die +Linux-Community ihnen gratis diese Wartungsarbeit abnehmen würde. Sie und ihre +Kunden könnten immer die neueste Kernelversion verwenden und würden ihren Treiber +kompilierfähig vorfinden. + +Leider ist dies aus mehreren Gründen oft nicht machbar. Erstens werden die +Kernel-Entwickler die Aufnahme eines großen Treibers, der nicht von allgemeinem +Interesse ist, schlicht verweigern. Der Umfang des Kernelcodes würde sonst ins +Unermessliche wachsen. Zweitens geht der Aufnahme von Code in den Mainline-Kernel +gewöhnlich eine langwierige Review-Prozedur voraus. Dabei wird nicht nur +Coding-Style begutachtet, es kann sich durchaus auch um Kritik an grundlegenden +Vorgehensweisen handeln. Erst wenn alle Kritiker zufrieden gestellt sind, wird +der Code in den Kernel aufgenommen. Darüber können Wochen vergehen. Die wenigsten +Industriebetriebe genehmigen ihren Programmierern diesen Zusatzaufwand, da der +Treiber in ihrer Maschine ja schon läuft. + +Drittens haben manche Firmen Probleme mit der GPL. Code, der im Kernel +einkompiliert wird, muss dieser Lizenz genügen, das heißt, der Sourcecode muss +öffentlich zugänglich sein. Firmen haben in der Vergangenheit zahlreiche +(meist illegale) Tricks angewandt, um dieser Bedingung der GPL zu entgehen und +ihren Kernelcode unter Verschluss zu halten. + +\subsection*{UIO löst Probleme} + +Die geschilderte Problematik führte bereits vor längerer Zeit zu Überlegungen, +zumindest Teile eines Treibers in den Userspace zu verlagern. I/O-Hardware +besteht in der Regel aus einem Speicherbereich, in dem sich RAM oder Register +befinden. Linux ist von Haus aus in der Lage, einen solchen Speicherbereich per +\cmd{mmap()} in den Userspace zu mappen. Damit kann man bereits alle Register +der Hardware erreichen. Dies ist aber erst die halbe Miete: In der Regel +erzeugen I/O-Karten Interrupts, und diese lassen sich nur im Kernel bearbeiten. +Ausserdem muss man in einem allgemein verwendbaren System der Tatsache +Rechnung tragen, dass eventuell mehrere (gleiche oder verschiedene) I/O-Karten +im System vorhanden sein können. + +Das Userspace-I/O-Framework (UIO) löst diese Probleme einfach und elegant. +Das für den Treiber immer noch benötigte Kernelmodul beschränkt sich auf einen +minimalen Interrupt-Handler sowie das Anmelden des Treibers beim +UIO-Framework. Letzteres erzeugt ein Device-File unter /dev sowie einige +dokumentierte Standardfiles im sysfs unter /sys/class/uio. Ausserdem stellt das +UIO-Framework bereits die für den Zugriff auf das Device-File benötigten +Funktionen wie \cmd{open()}, \cmd{read()} und \cmd{mmap()} zur Verfügung. Im +kundenspezifischen Kerneltreiber wird lediglich eine Struktur vom Typ +\cmd{struct uio\_info} mit einigen Informationen gefüllt und damit die Funktion +\cmd{uio\_register\_device()} aufgerufen. Den Rest erledigt das Framework. +Durch dieses Design kann das hardwarespezifische Kernelmodul sehr klein +ausfallen: das seit Kernel 2.6.23 enthaltene Modul für eine PCI-Feldbuskarte +(Hilscher CIF) besteht aus gerade einmal 156 Zeilen, inklusive Leerzeilen und +Kommentaren. Durch die sehr überschaubare Codegröße ist ein solches Modul schnell +geschrieben, übersichtlich, wenig fehleranfällig, leicht wartbar und kann +wesentlich leichter in den Mainline-Kernel integriert werden. Der Review-Prozess +ist viel einfacher, da auch Kernelentwickler, die nicht über die passende +Hardware verfügen, das Modul bezüglich Fehlerfreiheit beurteilen können. + +Nach dem Laden des Kernelmoduls kann man vom Userspace aus das Device-File +(z.B. \cmd{/dev/uio0}) öffnen und sich per \cmd{mmap()}-Aufruf Zugriff auf +den Speicher der Hardware verschaffen. Man wird zunächst durch geeignetes +Beschreiben von Registern die Hardware initialisieren, so dass sie die +gewünschte Funktion ausführt und Interrupts generiert. Auf Interrupts wartet +man, indem man mit einem blockierenden \cmd{read()}-Aufruf einen 32-bit-Wert +aus dem Device-File liest. Der Aufruf kehrt zurück, sobald ein Interrupt +eintrifft. Der gelesene 32-bit-Wert gibt die Gesamtzahl der bisher +aufgetretenen Interrupts an. Der Wert sollte idealerweise um eins größer +sein als beim letzten Mal. Ist er noch größer, so hat man Interrupts verpasst. + +Man beachte, dass der Interrupt-Handler im Kernelmodul den Interrupt bereits +quittiert haben muss. Im Fall eines pegelgetriggerten Interrupts, wie er bei +PCI-Karten üblich ist, würde sich der Rechner sonst sang- und klanglos +aufhängen. Bei Rückkehr des blockierenden \cmd{read()}-Aufrufs kann man sich +also darauf beschränken, Daten zu lesen und zu schreiben, Statusregister zu +lesen, neue Parameter zu setzen oder ähnliche Aufgaben, die beim Auftreten +eines Interrupts anfallen. Falls die entsprechende Routine damit bis zum +Eintreffen des nächsten Interrupts nicht fertig wird, so ist das zumindest +softwaretechnisch kein Problem: Anhand des gelesenen Werts beim nächsten +\cmd{read()} erkennen Sie, wie viele Interrupts Sie verpasst haben. + +\subsection*{GPL oder nicht GPL} + +Der Linux-Kernel einschließlich aller enthaltenen Module ist unter der GPL +lizensiert. Programme im Linux-Userspace unterliegen solchen Einschränkungen +nicht. Das gilt auch für UIO-Treiber. Mit UIO existiert daher erstmals eine +Kernel-Schnittstelle, die es erlaubt, einen interruptfähigen Treiber zu +schreiben, der größtenteils nicht der GPL unterliegt (das kleine Kernelmodul +muss natürlich immer noch GPL-lizensiert werden). Diese Tatsache hat für +einigen Wirbel in der Linux-Szene gesorgt. Das UIO-Framework wurde von +Mitarbeitern der Firma Linutronix entwickelt und an Greg Kroah-Hartman, +den Maintainer des Linux driver core, weiter gereicht. Kroah-Hartman +versuchte am 13.12.2006 erstmals, UIO im Mainline-Kernel unterzubringen. +Linus Torvalds lehnte die Aufnahme damals ab, hauptsächlich aufgrund von +Missverständnissen bezüglich eines beigefügten Demo-Treibers. In der Folge +entstand eine wochenlange Diskussion, die schnell vom eigentlichen Thema +abwich und sich hauptsächlich mit der Lizensierungs-Problematik beschäftigte. + +Dabei lag es in erster Linie gar nicht in der Absicht der UIO-Entwickler, +proprietäre Treiber zu ermöglichen. Dies ist eigentlich ein Nebeneffekt, der +sich aus der Tatsache ergibt, dass man für Userspace-Software unter Linux seit +eh und je die Lizenz frei wählen darf. Dennoch mag dieser Punkt in einigen +Firmen für Erleichterung sorgen. Wer etwa bestimmtes Know-How in einem FPGA +untergebracht hat, der möchte nicht unter dem Zwang stehen, den Code des +zugehörigen Treibers öffentlich zu machen. Manchmal kann dies sogar durch den +Endkunden vertraglich untersagt sein, zum Beispiel bei Code, der für die +Einhaltung gesetzlich vorgeschriebener Werte sorgt, bei militärischen oder +sonst sicherheitsrelevanten Anwendungen. + +Bisher waren solche Firmen gezwungen, Codeänderungen durch technische Klimmzüge +zu verhindern, bewusst gegen die GPL zu verstoßen oder ganz auf Linux zu +verzichten. Durch UIO wird an dieser Stelle ein gangbarer und legaler Ausweg +geschaffen. + +\subsection*{Konventionelle Treiber...} + +Bisherige Gerätetreiber sind vollständig im Kernel integriert. Die +Schnittstelle zum Userspace besteht typischerweise aus einer Gerätedatei, auf +die mit den üblichen Funktionen wie \cmd{open()}, \cmd{close()}, \cmd{read()}, +\cmd{write()} und \cmd{ioctl()} zugegriffen werden kann. + +\begin{figure}[h] +\centering +\includegraphics[width=0.8\textwidth]{images/konventioneller-treiber_de.png} +\caption{Aufbau eines konventionellen Treibers} +\label{img:konvtreiber} +\end{figure} + + + +Alle diese Funktionen +müssen vollständig im Treiber implementiert werden und führen je nach Gerät +mehr oder weniger komplexe Operationen mit der Hardware aus. Bei industriellen +I/O-Karten liegt meist ein Schwerpunkt auf der \cmd{ioctl()}-Funktion. Die Ein- +und Ausgabe-Funktionaltät der Karte ist oft so komplex, dass man sie nicht auf +einfache \cmd{read()}- und \cmd{write()}-Operationen abbilden kann. Ausserdem +haben solche Karten in der Regel zahlreiche Parameter und Optionen, die auf +einen bestimmten Wert gesetzt oder ein- und ausgeschaltet werden wollen. +Man findet daher häufig Treiber für derartige Industrie-Karten, die 50 oder +mehr ioctl-Unterfunktionen implementieren. + +\begin{figure}[h] +\centering +\includegraphics[width=0.8\textwidth]{images/ioctl-vs-uio_de.png} +\caption{Vergleich von \cmd{ioctl()}-basierten Sytemen mit UIO} +\label{img:ioctl} +\end{figure} + +Dies führt oft dazu, dass letzlich mehr Datenaustausch mit der Karte per +\cmd{ioctl()} abgewickelt wird als über die eigentlich dafür vorgesehenen +\cmd{read()}- und \cmd{write()}-Funktionen. Auch aus Performance-Gründen ist +diese Vorgehensweise alles Andere als optimal. Wenn beispielsweise +\cmd{ioctl()} dazu verwendet wird, einen 32-Bit-Wert in ein Register der +Hardware zu schreiben, dann packt das Anwenderprogramm im Userspace zunächst +den Wert auf den Stack und ruft über die libc \cmd{ioctl()} auf. Per syscall +landet der Aufruf im Kernel. Dort sucht das Virtual File System (VFS) den +Pointer auf die für das jeweilige Gerät zuständige \cmd{ioctl()}-Funktion und +ruft diese auf. Da die übergebenen Parameter im User-Speicher liegen, muss sie +die \cmd{ioctl()}-Implementierung zunächst mit Hilfe der Kernel-Funktion +\cmd{copy\_from\_user()} in den Kernel-Speicher holen. Jetzt endlich kann der +übergebene Wert in das Hardware-Register geschrieben werden. Falls der Aufruf +auch Werte zurückgeben soll, müssen diese entsprechend erst mit +\cmd{copy\_to\_user()} in den übergebenen Puffer geschrieben werden. + +Letztlich handelt es sich bei einem derartigen Konzept um einen Missbrauch von +\cmd{ioctl()}. Die Funktion war ursprünglich nur dazu gedacht, gelegentlich +einen Parameter einer (Geräte-)Datei zu ändern. Für häufigen, schnellen +Datenaustausch ist sie denkbar ungeeignet und vor allem auch unnötig, wie wir +weiter unten sehen werden. Die Kernel-Entwickler stehen daher einer +\cmd{ioctl()}-basierten Treiberimplementierung äußerst kritisch gegenüber. +Ein Treiber für eine exotische Karte, der 50 neue \cmd{ioctl()}-Funktionen +einführen will, wird wahrscheinlich schon aus diesem Grund nicht in den +Mainline-Kernel aufgenommen. + +Ein weiteres Problem ergibt sich aus der Größe des Treibercodes. Ein paar +tausend (nicht-triviale) Zeilen sind schnell beieinander. Obwohl auch für die +Kernel-Entwicklung zahlreiche mächtige Hilfsmittel zur Fehlersuche existieren, +erfordert das Debugging doch erheblich mehr an Spezialwissen als im Userspace. +In einem Kerneltreiber können einfache Fehler den Rechner so lahm legen, dass +nur noch der Griff zum Resettaster Rettung bringt. In einem Industriebetrieb +verfügen häufig auch die ansonsten sehr qualifizierten Programmierer nur über +wenig Erfahrung mit Linux-Kernelprogrammierung. Mit den üblicherweise recht +eingeschränkten Ressourcen an Zeit und Personal entstehen dann Treiber, die +ebenso fehlerträchtig wie unübersichtlich sind und keinerlei Chance auf +Aufnahme in den offiziellen Mainline-Kernel haben. Dadurch haben die +Programmierer dann während der gesamten Laufzeit des Produkts zusätzlich das +Problem, diesen wackligen Treiber out-of-tree warten zu müssen. + +\subsection*{...und UIO-Treiber} + +Sieht man sich Gerätetreiber etwas genauer an, so stellt man fest, dass es +stets wiederkehrende Aufgabenstellungen gibt. Zum einen hat die Hardware +einen oder mehrere Speicherbereiche, auf die man zugreifen möchte. Zum +anderen erzeugt die Hardware Interrupts, auf die reagiert werden muss. Das +UIO-Framework enthält den Code für diese Standardaufgaben und vermeidet so, +dass Programmierer ständig ein nicht ganz einfaches Rad neu erfinden müssen. + +\begin{figure}[h] +\centering +\includegraphics[width=0.8\textwidth]{images/uio-treiber_de.png} +\caption{Aufbau eines konventionellen Treibers} +\label{img:konvtreiber} +\end{figure} + +Um Speicherbereiche verwenden zu können, muss man dem UIO-Framework lediglich +deren Startadresse und Länge angeben. UIO stellt dann bereits eine +\cmd{mmap()}-Funktion zur Verfügung, die es erlaubt, die Speicherbereiche +in den Userspace zu mappen. Im Falle einer PCI-Karte erhalten Sie die +Informationen über Startadresse und Länge bereits vom PCI-Subsystem. + +Zur Behandlung von Interrupts muss lediglich ein minimaler Interrupt-Handler +geschrieben werden. Um Interrupt-Sharing zu ermöglichen, sollte der Handler +erkennen, ob es die eigene Karte war, die den Interrupt ausgelöst hat. Falls +nein, gibt er einfach IRQ\_NONE zurück. Falls ja, wird er den Interrupt bei +der Hardware quittieren und dafür sorgen, dass die Interrupt-Leitung wieder +ihren inaktiven Zustand annimmt. Auch hier muss man lediglich die Nummer des +Interrupts und einen Pointer auf den Handler angeben, den Rest erledigt das +Framework. + +Zu den weiteren Dienstleistungen des UIO-Frameworks gehört das Anlegen und +Behandeln von Dateien im sysfs. Unter \cmd{/sys/class/uio} finden sich +Informationen zu allen angemeldeten UIO-Geräten. Dazu gehören der Name des +Geräts, Version seines Kernelmoduls, Adressen und Größen seiner +Speicherbereiche, Anzahl aufgetretener Interrupts und Ähnliches. Diese +(dokumentierten) sysfs-Dateien ermöglichen es dem Userspace-Teil des Treibers, +gezielt nach seinem Gerät zu suchen und beispielsweise die Größen der +Speicherbereiche korrekt zu erfragen, anstatt sich auf fest kodierte Werte +zu verlassen. + +Insgesamt enthält das UIO-Framework etwa 900 Zeilen nicht-trivialen Code, die +man für eine ähliche Funktionalität sonst selbst schreiben müsste. Dagegen +kommt das zu erstellende gerätespezifische Modul auf etwa 150 Zeilen Code, +der zudem recht einfach ist und leicht von schon existierenden Treibern +abgeleitet werden kann. Dadurch haben auch weniger erfahrene Programmierer +die Chance, ein sauberes, übersichtliches und stabiles Modul für ihre Hardware +zu produzieren. Es bleibt auch jederzeit die Möglichkeit, sich dieses Modul +von einem erfahrenen Dienstleister erstellen zu lassen. Auf diese Weise ist es +möglich, eigene Treiber zu entwickeln, ohne eine einzige Zeile Kernel-Code +schreiben zu müssen. + +Der ganze Rest, also die komplette eigentliche Funktionalität des Treibers, +wird im Userspace erledigt. Der Treiber kann als Shared Library, als +Bestandteil eines Anwenderprogramms oder in bester Unix-Manier als Dämon +realisiert werden. Die Programmierer des Treibers benötigen nicht mehr +Kenntnisse und Tools, als sie zur Programmierung ihrer Applikation ohnehin +benötigen. + +Im Beispiel oben haben wir gesehen, welchen Aufwand es bedeutet, einen +einzelnen Wert per \cmd{ioctl()} in ein Hardware-Register zu befördern. Im +Falle von UIO ist diese Operation eine einfache Zuweisung an die betreffende +Stelle des gemappten Speichers. Wenn \cmd{a} der Zeiger auf den Speicher ist +und an die relative Adresse 1000 der Wert 5 geschrieben werden soll, so +schreibt man ohne Umschweife \cmd{a[1000]=5}. Jegliches Hin- und Herkopieren +entfällt, so dass sich bei schnellen Anwendungen ein echter Performance-Vorteil +gegenüber einem Kerneltreiber mit \cmd{ioctl()}-Konzept ergibt. Es ist also +keinesfalls so, dass man für den Userspace-Komfort Geschwindigkeits-Nachteile +in Kauf nehmen müsste. + +Was sich ebenfalls verbessert, ist das Handling von Updates. Bei einem reinen +Kernel-Treiber ist ein Update des Kernels oder zumindest der Module nötig, was +in der Regel root-Rechte des Anwenders erfordert und bei Fehlern zu einem +völlig unbrauchbaren System führt. Bei einem UIO-Treiber unterscheidet sich +ein Update des Treibers durch nichts von einem Update der Anwender-Applikation. + +\subsection*{Wartungsvertrag} + +Ein Treiber will gewartet sein. Bei industriellen Geräten findet man oft die +Situation, dass ein Gerät über einen längeren Zeitraum mit der selben +Kernelversion ausgeliefert wird. Möchte man später, etwa nach 6 Monaten oder +einem Jahr, beispielsweise im Zuge eines allgemeinen Software-Updates auf einen +aktuellen Kernel wechseln, so sind oft deutliche Änderungen am Kerneltreiber +nötig. + +Konventionelle Treiber beinhalten zwangsläufig viele Aufrufe von internen +Kernelfunktionen. Im Gegensatz zur stabilen syscall-Schnittstelle kann sich +dieses interne Kernel-API jederzeit ändern. Sie müssen damit rechnen, dass +Funktionen, die ihr Treiber verwendet, plötzlich nicht mehr existieren oder +andere Parameter haben. Entsprechend hoch ist der Wartungsaufwand für einen +solchen Treiber, wenn er nicht im Mainline-Kernel enthalten ist. + +UIO macht ihnen auch hier das Leben leichter. Das Framework übernimmt die +üble Kleinarbeit, die etwa beim Umgang mit sysfs, Interrupt-Handling oder +Memory-Mapping anfällt. Es schirmt sie sozusagen von den wilden Seiten des +Kernels ab, indem es ihnen erlaubt, mit etwa einem halben Dutzend einfacher +Funktionen auszukommen. Angesichts der geringen Codegröße eines +UIO-Kernelmoduls sind selbst bei Änderungen an diesen Funktionen die +Wartungsarbeiten sehr überschaubar und schnell ausgeführt. + +Betrachtet man als Beispiel den Treiber für die Hilscher CIF-Karte +\cmd{(drivers/uio/uio\_cif.c)}, dann fällt auf, dass für die Einbindung in das +UIO-Framework lediglich zwei Funktionen (\cmd{uio\_register\_device} und +\cmd{uio\_unregister\_device}) sowie eine Datenstruktur +(\cmd{struct uio\_info}) benötigt werden. Ansonsten werden lediglich 7 +Funktionen des PCI-Subsystems sowie 6 sonstige Funktionen wie \cmd{kzalloc()} +benutzt. Alle diese Funktionen sind einfach und gängig. Das sie sich ändern, +ist möglich, aber nicht sehr wahrscheinlich. + +Dennoch sollten sie den Kernelteil ihres Treibers möglichst nicht ausserhalb +des offiziellen Mainline-Kernels pflegen wollen. Schreiben sie sauberen Code +entsprechend dem Kernel-Coding-Style und zögern sie nicht, auf der +Linux-Kernel-Mailingliste um die Aufnahme desselben zu bitten. Die Mühe lohnt +sich: Zukünftige Kernel haben die Unterstützung für ihr Produkt schon +eingebaut, Versionskonflikte oder das mühsame Kernel-Patchen entfällt. + +\subsection*{Los geht's...} + +Wie sieht nun ein UIO-Treiber konkret aus? Betrachten wir dazu einen Treiber +für eine PCI-Karte. Die folgenden Beispiele sind Auszüge aus dem bereits +erwähnten Treiber für die Hilscher CIF Feldbuskarte. Er befindet sich seit +2.6.23-rc1 im Mainline-Kernel. + +Der Treiber unterscheidet sich zunächst nur wenig von einem normalen +PCI-Kartentreiber. Anhand einer Tabelle mit PCI-ID-Nummern wird dem +PCI-Subsystem mitgeteilt, für welche Karten sich der Treiber zuständig fühlt. +Wird beim Booten eine solche Karte gefunden, so lädt das PCI-Subsystem den +Treiber und ruft dessen probe-Funktion auf. Diese muss Karte und +Treiber-Variablen initialisieren und das Ergebnis dieser Bemühungen +zurückmelden. Konnten Treiber und Karte erfolgreich initialisiert werden, so +haben sie dem System ein betriebsbereites PCI-Device hinzugefügt. + +Bei einem UIO-Treiber besteht ihre Hauptarbeit darin, eine Struktur vom Typ +\cmd{struct uio\_info} zu erzeugen und auszufüllen. Beginnen wir mit Name und +Version ihres Treibers. Dies sind beliebige strings, die später über +sysfs-Dateien abgefragt werden können: + +\begin{lstlisting} +/* Struktur anlegen */ +struct uio_info *info; +info = kzalloc(sizeof(struct uio_info), GFP_KERNEL); +/* Name und Version setzen */ +info->name = "Meine UIO Karte"; +info->version = "0.0.1"; +\end{lstlisting} + +Als Nächstes kann der von der Karte verwendete Interrupt eingetragen werden. +Freundlicherweise übergibt der Kernel der probe-Funktion einen Pointer +(hier \emph{dev} genannt) auf eine Struktur vom Typ \cmd{struct pci\_dev}, in der +derartige Angaben schon vom PCI-Subsystem eingetragen wurden. Unsere Aufgabe +ist also leicht zu erfüllen: + +\begin{lstlisting} +info->irq = dev->irq; +\end{lstlisting} + +Ausserdem kann man dem UIO-Framework mitteilen, dass man einen +"Fast"-Interrupthandler möchte und für Interrupt-Sharing vorbereitet ist: + +\begin{lstlisting} +info->irq_flags = IRQF_DISABLED | IRQF_SHARED; +\end{lstlisting} + +Die Adresse des (noch zu erstellenden) Interrupt-Handlers darf natürlich +nicht fehlen: + +\begin{lstlisting} +info->handler = mein_irq_handler; +\end{lstlisting} + +Jetzt wäre noch der Speicher der PCI-Karte anzugeben. Nehmen wir an, die Karte +habe nur einen solchen Bereich. Wir erfahren Adresse und Größe des Bereichs +wieder mit Hilfe des Pointers \emph{dev}. Zusätzlich geben wir an, dass es sich um +physikalischen Speicher handelt: + +\begin{lstlisting} +info->mem[0].addr = pci_resource_start(dev, 0); +info->mem[0].size = pci_resource_len(dev, 0); +info->mem[0].memtype = UIO_MEM_PHYS; +\end{lstlisting} + +Die hier eingetragene physikalische Adresse kann nicht direkt für Zugriffe +verwendet werden. Sie wird vom UIO-Framework beim \cmd{mmap()}-Aufruf in den +Userspace gemappt. Allerdings wird man sicherlich auch in der Interrupt-Routine +Zugriff auf den Speicher benötigen. Dazu mappen wir die Adresse in einen vom +Kernel verwendbaren Bereich: + +\begin{lstlisting} +info->mem[0].internal_addr = ioremap(pci_resource_start(dev, 0), + pci_resource_len(dev, 0) ); +\end{lstlisting} + +Damit haben wir unserer Pflicht Genüge getan und können unser Device beim +UIO-Framework registrieren: + +\begin{lstlisting} +uio_register_device(&dev->dev, info); +\end{lstlisting} + +Das war's. Ein neues UIO-Device hat das Licht der Welt erblickt. Es gibt jetzt +eine Gerätedatei \cmd{/dev/uio0} sowie verschiedene Dateien im Verzeichnis +\cmd{/sys/class/uio/uio0/}, die die in \cmd{struct uio\_info} enthaltenen Daten +wiedergeben. Natürlich wird man in der Realität dem obigen Beispielcode noch +Fehlerüberprüfungen hinzufügen, dies wurde hier der Übersichtlichkeit halber +weggelassen. + +Damit das Ganze funktioniert, muss natürlich noch der oben angegebene +Interrupt-Handler implementiert werden. Sein Gerüst sieht so aus: + +\begin{lstlisting} +static irqreturn_t mein_irq_handler(int irq, struct uio_info *dev_info) +{ + void *ptr = dev_info->mem[0].internal_addr; + + if (Interrupt_kam_nicht_von_meiner_Karte) + return IRQ_NONE; +\end{lstlisting} + + Hier sind Zugriffe mit Hilfe von ioread/iowrite möglich: + +\begin{lstlisting} + ein_byte = ioread8(ptr + MEIN_REGISTER_OFFSET); + iowrite8(ein_byte | INTERRUPT_AUS, ptr + NOCH_EIN_OFFSET); + + Hier muss sichergestellt sein, dass die Interruptleitung + wieder inaktiv ist! + + return IRQ_HANDLED; +} +\end{lstlisting} + +Jetzt ist der Kernel-Teil des Treibers komplett. Beispiele für vollständige +Treiber finden sich im Verzeichnis \cmd{drivers/uio/} in den Kernel-Quellen +oder auf dem Server des Open Source Automation Development Lab (www.osadl.org). +Ausführliche Dokumentation findet sich auch in den Kernel-Quellen in +\cmd{Documentation/DocBook/uio-howto.tmpl}; mit \cmd{make htmldocs} erstellt +man daraus eine lesbare Form. + +\subsection*{Testen mit lsuio} + +Hat man einen UIO-Kerneltreiber geladen, so führt der erste Weg ins Verzeichnis +\cmd{/sys/class/uio/uio0}, wo man überprüft, ob alle Dateien die erwarteten +Werte enthalten. Dies kann auf Dauer recht lästig sein, besonders, wenn man +mehrere UIO-Devices gleichzeitig testen will. Hier hilft ein kleines Tool +namens \cmd{lsuio}, das alle diese Werte übersichtlich auflistet. + +Der Sourcecode dieses Tools findet sich auf dem Server des OSADL. Nach dem +Auspacken des Archivs erstellt das übliche + +\begin{lstlisting} +./configure && make && make install +\end{lstlisting} + +das Tool. Dabei wird auch eine manpage installiert, die die möglichen Optionen +des Programms erläutert. Zum Beispiel sorgt die Option -m dafür, dass +\cmd{lsuio} testweise überprüft, ob der Speicher der Karte problemlos in den +Userspace gemappt werden kann. + +Der recht übersichtliche Sourcecode von \cmd{lsuio} (er besteht nur aus zwei +C-Dateien) kann im Übrigen als Beispiel für eigene Anwendungen dienen. Der +Userspace-Teil des Treibers sollte nämlich nie eine bestimmte Version des +Kerneltreibers voraussetzen. Ebenso sollte er nicht als selbstverständlich +voraussetzen, dass das eigene Device immer als \cmd{/dev/uio0} erscheint. +Durch Überprüfen der Dateien \cmd{name} und \cmd{version} in den Verzeichnissen +unterhalb von \cmd{/sys/class/uio} kann man diese Informationen auf sichere +Weise gewinnen. + +\subsection*{Weiter geht's im Userspace} + +Der Userspace-Teil des Treibers wird zunächst über das sysfs den Namen des +zuständigen Device-Files ermitteln. Im Folgenden wird der Einfachkeit halber +\cmd{/dev/uio0} angenommen. Ausserdem wird er überprüfen, ob die Version des +Kerneltreibers den Erwartungen entspricht. Wenn Alles im grünen Bereich ist, +wird \cmd{/dev/uio0} geöffnet: + +\begin{lstlisting} +int fd = open("/dev/uio0", O_RDWR); +\end{lstlisting} + +Jetzt müssen wir uns Zugriff auf den Speicher der Karte verschaffen. Nehmen +wir an, die Karte verfügt über einen 4kByte großen Bereich, auf den wir +zugreifen wollen: + +\begin{lstlisting} +void* map_addr = mmap( NULL, 4096, PROT_READ, MAP_SHARED, fd, 0); +\end{lstlisting} + +Jetzt enthält \cmd{map\_addr} einen Zeiger, mit dem direkt auf den Speicher +der Karte zugegriffen werden kann. Der Treiber wird jetzt die Register der +Karte mit sinnvollen Werten beschreiben. Zuletzt wird er dafür sorgen, dass +die Karte Interrupts generiert. + +Auf das Eintreffen von Interrupts wartet der Treiber, indem er in einer +Schleife einen blockierenden \cmd{read()}-Aufruf für das Device-File ausführt. +Dabei liest er einen vorzeichenbehafteten 32-bit-Wert. Da \cmd{read()} +standardmäßig blockiert, wenn bei \cmd{open()} kein \cmd{O\_NONBLOCK} +angegeben wurde, sieht das einfach so aus: + +\begin{lstlisting} +s32 irq_count; + +while (read(fd, &irq_count, 4)==4) { + +/* Interrupt-Aktionen */ + +} +\end{lstlisting} + +Natürlich sollte die Schleife noch eine Abbruchbedingung haben, damit das +Programm irgendwann einmal beendet werden kann. Der in \cmd{irq\_count} +gelieferte Wert ist die Gesamtzahl der aufgetretenen Interrupts. Meist wird +man sich diesen Wert merken wollen, damit man beim nächsten Interrupt +überprüfen kann, ob der Wert genau um eins größer geworden ist. + +Will man gleichzeitig auf mehrere Filedeskriptoren warten, z.B. auf +Interrupts des UIO-Device und auf das Eintreffen von Bytes in einem +Netzwerk-Socket, so kann man auch \cmd{select()} verwenden. + +Insgesamt ist der Userspace-Teil des Treibers sehr Entwickler-freundlich. +Unter der Annahme, dass man die Karte nicht kaputt programmieren kann, +steht Experimenten kaum etwas im Weg. Sie können jedenfalls keine +schlimmeren Abstürze verursachen als beim Programmieren jeder anderen +Anwender-Applikation. + +\subsection*{UIO oder nicht UIO} + +Sollen jetzt in Zukunft alle Treiber mittels UIO realisiert werden? Sicher +nicht. UIO eignet sich sehr gut für Hardware, die Interrupts erzeugt, deren +Register durch Memory-Mapping in den Userspace gebracht werden können, und +die vor allem nur wenig mit anderen Subsystemen des Kernels zu tun haben. +Letzteres ist leicht einzusehen, da die Interaktion mit anderen Subsystemen +nur innerhalb des Kernel erfolgen kann. Wollte man etwa den Treiber für +einen SATA-Controller mit UIO realisieren, so müßte dieser ja ein gültiges +Block-Device auf die Beine stellen. Hätte man den dazu nötigen Code, der +eng mit anderen Kernel-Teilen zusammenarbeiten würde, fertiggestellt, so +würde man bemerken, dass für den Userspace nicht mehr viel übrig bleibt. + +Auch Hardware, die keine Interrupts erzeugt, ist kein idealer Kandidat für +UIO, da die saubere Handhabung von Interrupts die eigentliche Besonderheit +des Frameworks darstellt. Das reine Mapping von Speicher in den Userspace +kann man auch ohne UIO haben. Allerdings bietet eine Implementierung mit Hilfe +von UIO immer noch den Vorteil, eine standardisierte und dokumentierte +sysfs-Schnittstelle gratis dazu zu bekommen. + +Eine weitere Einschränkung besteht darin, dass UIO in seiner aktuellen Version +kein DMA unterstützt. Die Entwickler planen, dies in einer zukünftigen Version +nachzureichen, falls sich Anwendungen dafür finden. Ein naheliegendes Beispiel +sind natürlich Treiber für Grafikkarten, allerdings hat sich hier durch die +weite Verbreitung von X.org bereits ein anderer Standard etabliert, der im +Übrigen teilweise ähnliche Ansätze wie UIO aufweist. Aber auch I/O-Karten, +beispielsweise schnelle A/D-Wandler, könnten DMA benötigen. Die Zukunft wird +zeigen, ob an dieser Stelle Nachfrage besteht. + +\subsection*{Fazit} + +Durch das seit Kernel-Version 2.6.23 in Linux verfügbare +Userspace I/O-Framework wird es besonders für Industriebetriebe erheblich +einfacher, Treiber für ihre I/O-Karten zu schreiben und zu warten. Der +Kernel-Teil des Treibers beschränkt sich auf ein absolutes Minimum, und das +UIO-Framework entkoppelt ihn wirkungsvoll von der Vielzahl an internen +Kernelfunktionen, die herkömmliche Treiber verwenden müssen. Die Entwicklung der +eigentlichen Treiber-Funktionalität erfolgt bequem im Userspace, unter Verwendung +der für die Applikationsentwicklung gewohnten Tools und Bibliotheken. + +Hat man sich einmal für UIO entschieden, so sollte man unbedingt anstreben, den +Kernelteil des Treibers im Mainline-Kernel unterzubringen. Auf diese Weise +erspart man sich und seinen Kunden das ständige Patchen des Kernels und die +Pflege eines out-of-tree Moduls. + +Verhilft man seinem Kernel zusätzlich zu Echtzeit-Fähigkeiten, indem man den +Realtime-Preemption-Patch einspielt, so erhält man ein einfach zu +programmierendes und vollständig aus Open Source-Software bestehendes System +für die Automatisierungstechnik. \end{document} diff --git a/kernel-devel/uio-driver/pres_uio-driver_en.tex b/kernel-devel/uio-driver/pres_uio-driver_en.tex index 8468894..ca9ff44 100644 --- a/kernel-devel/uio-driver/pres_uio-driver_en.tex +++ b/kernel-devel/uio-driver/pres_uio-driver_en.tex @@ -6,7 +6,6 @@ \usepackage{graphicx} \title{The Userspace I/O Framework (UIO)} -\author{Hans-Jürgen Koch} \institute{Linutronix GmbH} \begin{document} @@ -69,12 +68,12 @@ % ----- Slide "In-kernel driver" ------------------ \begin{frame} -\includegraphics[width=11cm]{images/konventioneller-treiber.png} +\includegraphics[width=11cm]{images/konventioneller-treiber_en.png} \end{frame} % ----- Slide "UIO driver" ------------------ \begin{frame} -\includegraphics[width=11cm]{images/uio-treiber.png} +\includegraphics[width=11cm]{images/uio-treiber_en.png} \end{frame} % ----- Slide "How UIO works" -------------------------- @@ -210,7 +209,7 @@ while (read(fd, &irq_cnt, 4)) { % ----- Slide "ioctl vs. UIO" ------------------ \begin{frame} -\includegraphics[width=10cm]{images/ioctl-vs-uio.png} +\includegraphics[width=10cm]{images/ioctl-vs-uio_en.png} \end{frame} % ----- Slide "Publish your driver!" -------------- |
