summaryrefslogtreecommitdiff
path: root/kernel-devel/uio-driver/handout_uio-driver_de.tex
blob: 02662619703f303b78eca6f020804731e1f2fa08 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
\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}