\input{confighandout} \subsection{Linux-Treiber im Userspace} Das ab Linux 2.6.23 im stabilen Kernel verf\"ugbare Userspace-I/O-Framework (UIO) erm\"oglicht, bestimmte Treiber fast vollst\"andig in den Userspace zu verlagern. Dies vereinfacht die Entwicklung und Wartung von Treibern, au\ss erdem ergeben sich M\"oglichkeiten, die besonders Industriebetrieben entgegen kommen. Eine im Automatisierungsbereich t\"atige 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\"otigen Entwicklungstools frei zur Verf\"ugung. So oder so \"ahnlich hat schon so manche Linux-Horror-Story begonnen. Trotz benutzerfreundlicher Kernel-Subsysteme und Unterst\"utzung durch Mailinglisten bedeutet das Schreiben eines Kerneltreibers f\"ur Linux-Einsteiger eine nicht zu untersch\"atzende 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\"onnen. Gleichzeitig verf\"ugen solche Karten oft \"uber sehr komplexe Funktionen, so dass der Treiber immer umfangreicher wird. Treibercode mit mehreren tausend Codezeilen ist keine Seltenheit. Doch auch wenn diese H\"urde genommen, der Treiber erfolgreich debugged und getestet wurde, gibt es ein noch gr\"o\ss eres Problem: Die Wartung des Treibers. Denn ein so gro\ss er Treiber verwendet zweifellos eine gro\ss e Zahl interner Kernel-Funktionen und -Makros. Dieses Kernel-interne API ist stets in Bewegung. Wer einen Treiber f\"ur 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\"asst und dann upgraden will, hat h\"ochst wahrscheinlich ein paar unerfreuliche Tage vor sich. Das zuletzt genannte Problem l\"ost 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 \"andern, verpflichtet, f\"ur die Anpassung aller betroffenen Module zu sorgen. F\"ur unsere Automatisierungsfirma w\"urde das bedeuten, dass die Linux-Community ihnen gratis diese Wartungsarbeit abnehmen w\"urde. Sie und ihre Kunden k\"onnten immer die neueste Kernelversion verwenden und w\"urden ihren Treiber kompilierf\"ahig vorfinden. Leider ist dies aus mehreren Gr\"unden oft nicht machbar. Erstens werden die Kernel-Entwickler die Aufnahme eines gro\ss en Treibers, der nicht von allgemeinem Interesse ist, schlicht verweigern. Der Umfang des Kernelcodes w\"urde sonst ins Unermessliche wachsen. Zweitens geht der Aufnahme von Code in den Mainline-Kernel gew\"ohnlich 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\"uber k\"onnen Wochen vergehen. Die wenigsten Industriebetriebe genehmigen ihren Programmierern diesen Zusatzaufwand, da der Treiber in ihrer Maschine ja schon l\"auft. Drittens haben manche Firmen Probleme mit der GPL. Code, der im Kernel einkompiliert wird, muss dieser Lizenz gen\"ugen, das hei\ss t, der Sourcecode muss \"offentlich zug\"anglich 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. \subsubsection{UIO l\"ost Probleme} Die geschilderte Problematik f\"uhrte bereits vor l\"angerer Zeit zu \"Uberlegungen, 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\"onnen. Das Userspace-I/O-Framework (UIO) l\"ost diese Probleme einfach und elegant. Das f\"ur den Treiber immer noch ben\"otigte Kernelmodul beschr\"ankt 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\"ur den Zugriff auf das Device-File ben\"otigten Funktionen wie \cmd{open()}, \cmd{read()} und \cmd{mmap()} zur Verf\"ugung. Im kundenspezifischen Kerneltreiber wird lediglich eine Struktur vom Typ \cmd{struct uio\_info} mit einigen Informationen gef\"ullt 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\"ur eine PCI-Feldbuskarte (Hilscher CIF) besteht aus gerade einmal 156 Zeilen, inklusive Leerzeilen und Kommentaren. Durch die sehr \"uberschaubare Codegr\"o\ss e ist ein solches Modul schnell geschrieben, \"ubersichtlich, wenig fehleranf\"allig, leicht wartbar und kann wesentlich leichter in den Mainline-Kernel integriert werden. Der Review-Prozess ist viel einfacher, da auch Kernelentwickler, die nicht \"uber die passende Hardware verf\"ugen, das Modul bez\"uglich Fehlerfreiheit beurteilen k\"onnen. Nach dem Laden des Kernelmoduls kann man vom Userspace aus das Device-File (z.B. \cmd{/dev/uio0}) \"offnen und sich per \cmd{mmap()}-Aufruf Zugriff auf den Speicher der Hardware verschaffen. Man wird zun\"achst durch geeignetes Beschreiben von Registern die Hardware initialisieren, so dass sie die gew\"unschte Funktion ausf\"uhrt 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\"uck, sobald ein Interrupt eintrifft. Der gelesene 32-bit-Wert gibt die Gesamtzahl der bisher aufgetretenen Interrupts an. Der Wert sollte idealerweise um eins gr\"o\ss er sein als beim letzten Mal. Ist er noch gr\"o\ss 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 \"ublich ist, w\"urde sich der Rechner sonst sang- und klanglos aufh\"angen. Bei R\"uckkehr des blockierenden \cmd{read()}-Aufrufs kann man sich also darauf beschr\"anken, Daten zu lesen und zu schreiben, Statusregister zu lesen, neue Parameter zu setzen oder \"ahnliche Aufgaben, die beim Auftreten eines Interrupts anfallen. Falls die entsprechende Routine damit bis zum Eintreffen des n\"achsten Interrupts nicht fertig wird, so ist das zumindest softwaretechnisch kein Problem: Anhand des gelesenen Werts beim n\"achsten \cmd{read()} erkennen Sie, wie viele Interrupts Sie verpasst haben. \subsubsection{GPL oder nicht GPL} Der Linux-Kernel einschlie\ss lich aller enthaltenen Module ist unter der GPL lizensiert. Programme im Linux-Userspace unterliegen solchen Einschr\"ankungen nicht. Das gilt auch f\"ur UIO-Treiber. Mit UIO existiert daher erstmals eine Kernel-Schnittstelle, die es erlaubt, einen interruptf\"ahigen Treiber zu schreiben, der gr\"o\ss tenteils nicht der GPL unterliegt (das kleine Kernelmodul muss nat\"urlich immer noch GPL-lizensiert werden). Diese Tatsache hat f\"ur 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\"achlich aufgrund von Missverst\"andnissen bez\"uglich eines beigef\"ugten Demo-Treibers. In der Folge entstand eine wochenlange Diskussion, die schnell vom eigentlichen Thema abwich und sich haupts\"achlich mit der Lizensierungs-Problematik besch\"aftigte. Dabei lag es in erster Linie gar nicht in der Absicht der UIO-Entwickler, propriet\"are Treiber zu erm\"oglichen. Dies ist eigentlich ein Nebeneffekt, der sich aus der Tatsache ergibt, dass man f\"ur Userspace-Software unter Linux seit eh und je die Lizenz frei w\"ahlen darf. Dennoch mag dieser Punkt in einigen Firmen f\"ur Erleichterung sorgen. Wer etwa bestimmtes Know-How in einem FPGA untergebracht hat, der m\"ochte nicht unter dem Zwang stehen, den Code des zugeh\"origen Treibers \"offentlich zu machen. Manchmal kann dies sogar durch den Endkunden vertraglich untersagt sein, zum Beispiel bei Code, der f\"ur die Einhaltung gesetzlich vorgeschriebener Werte sorgt, bei milit\"arischen oder sonst sicherheitsrelevanten Anwendungen. Bisher waren solche Firmen gezwungen, Code\"anderungen durch technische Klimmz\"uge zu verhindern, bewusst gegen die GPL zu versto\ss en oder ganz auf Linux zu verzichten. Durch UIO wird an dieser Stelle ein gangbarer und legaler Ausweg geschaffen. \subsubsection{Konventionelle Treiber...} Bisherige Ger\"atetreiber sind vollst\"andig im Kernel integriert. Die Schnittstelle zum Userspace besteht typischerweise aus einer Ger\"atedatei, auf die mit den \"ublichen 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\"ussen vollst\"andig im Treiber implementiert werden und f\"uhren je nach Ger\"at 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\"at 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\"aufig Treiber f\"ur 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\"uhrt oft dazu, dass letzlich mehr Datenaustausch mit der Karte per \cmd{ioctl()} abgewickelt wird als \"uber die eigentlich daf\"ur vorgesehenen \cmd{read()}- und \cmd{write()}-Funktionen. Auch aus Performance-Gr\"unden 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\"achst den Wert auf den Stack und ruft \"uber die libc \cmd{ioctl()} auf. Per syscall landet der Aufruf im Kernel. Dort sucht das Virtual File System (VFS) den Pointer auf die f\"ur das jeweilige Ger\"at zust\"andige \cmd{ioctl()}-Funktion und ruft diese auf. Da die \"ubergebenen Parameter im User-Speicher liegen, muss sie die \cmd{ioctl()}-Implementierung zun\"achst mit Hilfe der Kernel-Funktion \cmd{copy\_from\_user()} in den Kernel-Speicher holen. Jetzt endlich kann der \"ubergebene Wert in das Hardware-Register geschrieben werden. Falls der Aufruf auch Werte zur\"uckgeben soll, m\"ussen diese entsprechend erst mit \cmd{copy\_to\_user()} in den \"ubergebenen Puffer geschrieben werden. Letztlich handelt es sich bei einem derartigen Konzept um einen Missbrauch von \cmd{ioctl()}. Die Funktion war urspr\"unglich nur dazu gedacht, gelegentlich einen Parameter einer (Ger\"ate-)Datei zu \"andern. F\"ur h\"aufigen, schnellen Datenaustausch ist sie denkbar ungeeignet und vor allem auch unn\"otig, wie wir weiter unten sehen werden. Die Kernel-Entwickler stehen daher einer \cmd{ioctl()}-basierten Treiberimplementierung \"au\ss erst kritisch gegen\"uber. Ein Treiber f\"ur eine exotische Karte, der 50 neue \cmd{ioctl()}-Funktionen einf\"uhren will, wird wahrscheinlich schon aus diesem Grund nicht in den Mainline-Kernel aufgenommen. Ein weiteres Problem ergibt sich aus der Gr\"o\ss e des Treibercodes. Ein paar tausend (nicht-triviale) Zeilen sind schnell beieinander. Obwohl auch f\"ur die Kernel-Entwicklung zahlreiche m\"achtige Hilfsmittel zur Fehlersuche existieren, erfordert das Debugging doch erheblich mehr an Spezialwissen als im Userspace. In einem Kerneltreiber k\"onnen einfache Fehler den Rechner so lahm legen, dass nur noch der Griff zum Resettaster Rettung bringt. In einem Industriebetrieb verf\"ugen h\"aufig auch die ansonsten sehr qualifizierten Programmierer nur \"uber wenig Erfahrung mit Linux-Kernelprogrammierung. Mit den \"ublicherweise recht eingeschr\"ankten Ressourcen an Zeit und Personal entstehen dann Treiber, die ebenso fehlertr\"achtig wie un\"ubersichtlich sind und keinerlei Chance auf Aufnahme in den offiziellen Mainline-Kernel haben. Dadurch haben die Programmierer dann w\"ahrend der gesamten Laufzeit des Produkts zus\"atzlich das Problem, diesen wackligen Treiber out-of-tree warten zu m\"ussen. \subsubsection{...und UIO-Treiber} Sieht man sich Ger\"atetreiber 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\"ochte. Zum anderen erzeugt die Hardware Interrupts, auf die reagiert werden muss. Das UIO-Framework enth\"alt den Code f\"ur diese Standardaufgaben und vermeidet so, dass Programmierer st\"andig ein nicht ganz einfaches Rad neu erfinden m\"ussen. \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\"onnen, muss man dem UIO-Framework lediglich deren Startadresse und L\"ange angeben. UIO stellt dann bereits eine \cmd{mmap()}-Funktion zur Verf\"ugung, die es erlaubt, die Speicherbereiche in den Userspace zu mappen. Im Falle einer PCI-Karte erhalten Sie die Informationen \"uber Startadresse und L\"ange bereits vom PCI-Subsystem. Zur Behandlung von Interrupts muss lediglich ein minimaler Interrupt-Handler geschrieben werden. Um Interrupt-Sharing zu erm\"oglichen, sollte der Handler erkennen, ob es die eigene Karte war, die den Interrupt ausgel\"ost hat. Falls nein, gibt er einfach IRQ\_NONE zur\"uck. Falls ja, wird er den Interrupt bei der Hardware quittieren und daf\"ur 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\"ort das Anlegen und Behandeln von Dateien im sysfs. Unter \cmd{/sys/class/uio} finden sich Informationen zu allen angemeldeten UIO-Ger\"aten. Dazu geh\"oren der Name des Ger\"ats, Version seines Kernelmoduls, Adressen und Gr\"o\ss en seiner Speicherbereiche, Anzahl aufgetretener Interrupts und \"Ahnliches. Diese (dokumentierten) sysfs-Dateien erm\"oglichen es dem Userspace-Teil des Treibers, gezielt nach seinem Ger\"at zu suchen und beispielsweise die Gr\"o\ss en der Speicherbereiche korrekt zu erfragen, anstatt sich auf fest kodierte Werte zu verlassen. Insgesamt enth\"alt das UIO-Framework etwa 900 Zeilen nicht-trivialen Code, die man f\"ur eine \"ahliche Funktionalit\"at sonst selbst schreiben m\"usste. Dagegen kommt das zu erstellende ger\"atespezifische 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, \"ubersichtliches und stabiles Modul f\"ur ihre Hardware zu produzieren. Es bleibt auch jederzeit die M\"oglichkeit, sich dieses Modul von einem erfahrenen Dienstleister erstellen zu lassen. Auf diese Weise ist es m\"oglich, eigene Treiber zu entwickeln, ohne eine einzige Zeile Kernel-Code schreiben zu m\"ussen. Der ganze Rest, also die komplette eigentliche Funktionalit\"at des Treibers, wird im Userspace erledigt. Der Treiber kann als Shared Library, als Bestandteil eines Anwenderprogramms oder in bester Unix-Manier als D\"amon realisiert werden. Die Programmierer des Treibers ben\"otigen nicht mehr Kenntnisse und Tools, als sie zur Programmierung ihrer Applikation ohnehin ben\"otigen. Im Beispiel oben haben wir gesehen, welchen Aufwand es bedeutet, einen einzelnen Wert per \cmd{ioctl()} in ein Hardware-Register zu bef\"ordern. 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\"allt, so dass sich bei schnellen Anwendungen ein echter Performance-Vorteil gegen\"uber einem Kerneltreiber mit \cmd{ioctl()}-Konzept ergibt. Es ist also keinesfalls so, dass man f\"ur den Userspace-Komfort Geschwindigkeits-Nachteile in Kauf nehmen m\"usste. Was sich ebenfalls verbessert, ist das Handling von Updates. Bei einem reinen Kernel-Treiber ist ein Update des Kernels oder zumindest der Module n\"otig, was in der Regel root-Rechte des Anwenders erfordert und bei Fehlern zu einem v\"ollig unbrauchbaren System f\"uhrt. Bei einem UIO-Treiber unterscheidet sich ein Update des Treibers durch nichts von einem Update der Anwender-Applikation. \subsubsection{Wartungsvertrag} Ein Treiber will gewartet sein. Bei industriellen Ger\"aten findet man oft die Situation, dass ein Ger\"at \"uber einen l\"angeren Zeitraum mit der selben Kernelversion ausgeliefert wird. M\"ochte man sp\"ater, etwa nach 6 Monaten oder einem Jahr, beispielsweise im Zuge eines allgemeinen Software-Updates auf einen aktuellen Kernel wechseln, so sind oft deutliche \"Anderungen am Kerneltreiber n\"otig. Konventionelle Treiber beinhalten zwangsl\"aufig viele Aufrufe von internen Kernelfunktionen. Im Gegensatz zur stabilen syscall-Schnittstelle kann sich dieses interne Kernel-API jederzeit \"andern. Sie m\"ussen damit rechnen, dass Funktionen, die ihr Treiber verwendet, pl\"otzlich nicht mehr existieren oder andere Parameter haben. Entsprechend hoch ist der Wartungsaufwand f\"ur einen solchen Treiber, wenn er nicht im Mainline-Kernel enthalten ist. UIO macht ihnen auch hier das Leben leichter. Das Framework \"ubernimmt die \"uble Kleinarbeit, die etwa beim Umgang mit sysfs, Interrupt-Handling oder Memory-Mapping anf\"allt. 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\"o\ss e eines UIO-Kernelmoduls sind selbst bei \"Anderungen an diesen Funktionen die Wartungsarbeiten sehr \"uberschaubar und schnell ausgef\"uhrt. Betrachtet man als Beispiel den Treiber f\"ur die Hilscher CIF-Karte \cmd{(drivers/uio/uio\_cif.c)}, dann f\"allt auf, dass f\"ur 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\"otigt werden. Ansonsten werden lediglich 7 Funktionen des PCI-Subsystems sowie 6 sonstige Funktionen wie \cmd{kzalloc()} benutzt. Alle diese Funktionen sind einfach und g\"angig. Das sie sich \"andern, ist m\"oglich, aber nicht sehr wahrscheinlich. Dennoch sollten sie den Kernelteil ihres Treibers m\"oglichst nicht ausserhalb des offiziellen Mainline-Kernels pflegen wollen. Schreiben sie sauberen Code entsprechend dem Kernel-Coding-Style und z\"ogern sie nicht, auf der Linux-Kernel-Mailingliste um die Aufnahme desselben zu bitten. Die M\"uhe lohnt sich: Zuk\"unftige Kernel haben die Unterst\"utzung f\"ur ihr Produkt schon eingebaut, Versionskonflikte oder das m\"uhsame Kernel-Patchen entf\"allt. \subsubsection{Los geht's...} Wie sieht nun ein UIO-Treiber konkret aus? Betrachten wir dazu einen Treiber f\"ur eine PCI-Karte. Die folgenden Beispiele sind Ausz\"uge aus dem bereits erw\"ahnten Treiber f\"ur die Hilscher CIF Feldbuskarte. Er befindet sich seit 2.6.23-rc1 im Mainline-Kernel. Der Treiber unterscheidet sich zun\"achst nur wenig von einem normalen PCI-Kartentreiber. Anhand einer Tabelle mit PCI-ID-Nummern wird dem PCI-Subsystem mitgeteilt, f\"ur welche Karten sich der Treiber zust\"andig f\"uhlt. Wird beim Booten eine solche Karte gefunden, so l\"adt das PCI-Subsystem den Treiber und ruft dessen probe-Funktion auf. Diese muss Karte und Treiber-Variablen initialisieren und das Ergebnis dieser Bem\"uhungen zur\"uckmelden. Konnten Treiber und Karte erfolgreich initialisiert werden, so haben sie dem System ein betriebsbereites PCI-Device hinzugef\"ugt. Bei einem UIO-Treiber besteht ihre Hauptarbeit darin, eine Struktur vom Typ \cmd{struct uio\_info} zu erzeugen und auszuf\"ullen. Beginnen wir mit Name und Version ihres Treibers. Dies sind beliebige strings, die sp\"ater \"uber sysfs-Dateien abgefragt werden k\"onnen: \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\"achstes kann der von der Karte verwendete Interrupt eingetragen werden. Freundlicherweise \"ubergibt 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\"ullen: \begin{lstlisting} info->irq = dev->irq; \end{lstlisting} Ausserdem kann man dem UIO-Framework mitteilen, dass man einen "Fast"-Interrupthandler m\"ochte und f\"ur 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\"urlich nicht fehlen: \begin{lstlisting} info->handler = mein_irq_handler; \end{lstlisting} Jetzt w\"are noch der Speicher der PCI-Karte anzugeben. Nehmen wir an, die Karte habe nur einen solchen Bereich. Wir erfahren Adresse und Gr\"o\ss e des Bereichs wieder mit Hilfe des Pointers \emph{dev}. Zus\"atzlich 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\"ur 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\"otigen. 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\"uge getan und k\"onnen 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\"atedatei \cmd{/dev/uio0} sowie verschiedene Dateien im Verzeichnis \cmd{/sys/class/uio/uio0/}, die die in \cmd{struct uio\_info} enthaltenen Daten wiedergeben. Nat\"urlich wird man in der Realit\"at dem obigen Beispielcode noch Fehler\"uberpr\"ufungen hinzuf\"ugen, dies wurde hier der \"Ubersichtlichkeit halber weggelassen. Damit das Ganze funktioniert, muss nat\"urlich noch der oben angegebene Interrupt-Handler implementiert werden. Sein Ger\"ust 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\"oglich: \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\"ur vollst\"andige 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\"uhrliche 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. \subsubsection{Testen mit lsuio} Hat man einen UIO-Kerneltreiber geladen, so f\"uhrt der erste Weg ins Verzeichnis \cmd{/sys/class/uio/uio0}, wo man \"uberpr\"uft, ob alle Dateien die erwarteten Werte enthalten. Dies kann auf Dauer recht l\"astig sein, besonders, wenn man mehrere UIO-Devices gleichzeitig testen will. Hier hilft ein kleines Tool namens \cmd{lsuio}, das alle diese Werte \"ubersichtlich auflistet. Der Sourcecode dieses Tools findet sich auf dem Server des OSADL. Nach dem Auspacken des Archivs erstellt das \"ubliche \begin{lstlisting} ./configure && make && make install \end{lstlisting} das Tool. Dabei wird auch eine manpage installiert, die die m\"oglichen Optionen des Programms erl\"autert. Zum Beispiel sorgt die Option -m daf\"ur, dass \cmd{lsuio} testweise \"uberpr\"uft, ob der Speicher der Karte problemlos in den Userspace gemappt werden kann. Der recht \"ubersichtliche Sourcecode von \cmd{lsuio} (er besteht nur aus zwei C-Dateien) kann im \"Ubrigen als Beispiel f\"ur eigene Anwendungen dienen. Der Userspace-Teil des Treibers sollte n\"amlich nie eine bestimmte Version des Kerneltreibers voraussetzen. Ebenso sollte er nicht als selbstverst\"andlich voraussetzen, dass das eigene Device immer als \cmd{/dev/uio0} erscheint. Durch \"Uberpr\"ufen der Dateien \cmd{name} und \cmd{version} in den Verzeichnissen unterhalb von \cmd{/sys/class/uio} kann man diese Informationen auf sichere Weise gewinnen. \subsubsection{Weiter geht's im Userspace} Der Userspace-Teil des Treibers wird zun\"achst \"uber das sysfs den Namen des zust\"andigen Device-Files ermitteln. Im Folgenden wird der Einfachkeit halber \cmd{/dev/uio0} angenommen. Ausserdem wird er \"uberpr\"ufen, ob die Version des Kerneltreibers den Erwartungen entspricht. Wenn Alles im gr\"unen Bereich ist, wird \cmd{/dev/uio0} ge\"offnet: \begin{lstlisting} int fd = open("/dev/uio0", O_RDWR); \end{lstlisting} Jetzt m\"ussen wir uns Zugriff auf den Speicher der Karte verschaffen. Nehmen wir an, die Karte verf\"ugt \"uber einen 4kByte gro\ss 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\"alt \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\"ur 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\"ur das Device-File ausf\"uhrt. Dabei liest er einen vorzeichenbehafteten 32-bit-Wert. Da \cmd{read()} standardm\"a\ss 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\"urlich 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\"achsten Interrupt \"uberpr\"ufen kann, ob der Wert genau um eins gr\"o\ss 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\"onnen jedenfalls keine schlimmeren Abst\"urze verursachen als beim Programmieren jeder anderen Anwender-Applikation. \subsubsection{UIO oder nicht UIO} Sollen jetzt in Zukunft alle Treiber mittels UIO realisiert werden? Sicher nicht. UIO eignet sich sehr gut f\"ur Hardware, die Interrupts erzeugt, deren Register durch Memory-Mapping in den Userspace gebracht werden k\"onnen, 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\"ur einen SATA-Controller mit UIO realisieren, so m\"u\ss te dieser ja ein g\"ultiges Block-Device auf die Beine stellen. H\"atte man den dazu n\"otigen Code, der eng mit anderen Kernel-Teilen zusammenarbeiten w\"urde, fertiggestellt, so w\"urde man bemerken, dass f\"ur den Userspace nicht mehr viel \"ubrig bleibt. Auch Hardware, die keine Interrupts erzeugt, ist kein idealer Kandidat f\"ur 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\"ankung besteht darin, dass UIO in seiner aktuellen Version kein DMA unterst\"utzt. Die Entwickler planen, dies in einer zuk\"unftigen Version nachzureichen, falls sich Anwendungen daf\"ur finden. Ein naheliegendes Beispiel sind nat\"urlich Treiber f\"ur Grafikkarten, allerdings hat sich hier durch die weite Verbreitung von X.org bereits ein anderer Standard etabliert, der im \"Ubrigen teilweise \"ahnliche Ans\"atze wie UIO aufweist. Aber auch I/O-Karten, beispielsweise schnelle A/D-Wandler, k\"onnten DMA ben\"otigen. Die Zukunft wird zeigen, ob an dieser Stelle Nachfrage besteht. \subsubsection{Fazit} Durch das seit Kernel-Version 2.6.23 in Linux verf\"ugbare Userspace I/O-Framework wird es besonders f\"ur Industriebetriebe erheblich einfacher, Treiber f\"ur ihre I/O-Karten zu schreiben und zu warten. Der Kernel-Teil des Treibers beschr\"ankt sich auf ein absolutes Minimum, und das UIO-Framework entkoppelt ihn wirkungsvoll von der Vielzahl an internen Kernelfunktionen, die herk\"ommliche Treiber verwenden m\"ussen. Die Entwicklung der eigentlichen Treiber-Funktionalit\"at erfolgt bequem im Userspace, unter Verwendung der f\"ur die Applikationsentwicklung gewohnten Tools und Bibliotheken. Hat man sich einmal f\"ur 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\"andige Patchen des Kernels und die Pflege eines out-of-tree Moduls. Verhilft man seinem Kernel zus\"atzlich zu Echtzeit-F\"ahigkeiten, indem man den Realtime-Preemption-Patch einspielt, so erh\"alt man ein einfach zu programmierendes und vollst\"andig aus Open Source-Software bestehendes System f\"ur die Automatisierungstechnik. \input{tailhandout}