Open Source im professionellen Einsatz

Kernel- und Treiberprogrammierung mit dem Kernel 2.6 - Folge 37

Kern-Technik

,

Das Firmware-Subsystem fristet ein Dasein als Mauerblümchen. Zu Unrecht, denn hinter dem eleganten Kernel- und User-Interface steckt sehr moderne Betriebssystem-Technologie.

Ein heikles Thema war zur Laufzeit nachzuladende Firmware bei Linux schon immer. Für Hardware-nahe Software liegt oft kein Quellcode vor. Folglich - so die Konsequenz aus der GPL - darf ein Entwickler sie nicht direkt in den Linux-Kernel respektive einen Linux-Treiber integrieren. Dabei setzen Hardwarehersteller oft auf Firmware: Intels Centrino-WLAN-Adapter funken erst, wenn der auf dem Adapter eingesetzte Prozessor seine Firmware geladen hat. Und ohne Extra-Software weigert sich der DVB-T-Empfänger standhaft, aktuelle Bilder zu präsentieren.

Für Hersteller und Kunden ist auswechselbare Systemsoftware vorteilhaft: Fehler lassen sich bei bereits ausgelieferten und im Einsatz befindlichen Komponenten ausbessern und sie aktiviert auch auf älterer Hardware neue Funktionen.

Entsprechend den Lizenzbestimmungen haben Entwickler aber schon seit Längerem die Methode fallen gelassen, die Firmware als obskuren Hexstring im Quellcode des zugehörigen Gerätetreibers zu verstecken. Stattdessen lädt der Kernel das Firmware-Image aus dem Userspace nach und kopiert es dann in die zugehörige Hardware. Anfänglich hatte jeder Entwickler für sich dazu eine eigene Schnittstelle geschaffen und implementiert, doch bereits in den Kerneln der Reihe 2.4 führte Torvalds eine einfach zu nutzende, standardisierte Schnittstelle für Firmware ein.

Für den Anwender ist die Schnittstelle erfreulich einfach: Er legt die Firmware-Datei an zentraler Stelle ab, typisch sind »/lib/firmware« oder »/usr/local/lib/firmware«. Sobald Linux den passenden Treiber lädt, übergibt es ihm das Firmware-Image. Der Treiber sorgt für den Transport in die Hardware.

Im Detail betrachtet sind die Abläufe etwas komplexer, da Kernel und Userland harmonisch zusammenspielen müssen, bis sich die richtigen Daten in der Hardware befinden.

Zwischen Kern und Userland

Der Ablauf beginnt, sobald ein Benutzer einen Gerätetreiber für eine Hardware lädt, die eine Firmware benötigt (siehe Abbildung 1). Der Treiber teilt dem Kernel den Namen der an zentraler Stelle (»/lib/firmware«) im Filesystem abgelegten Firmware-Datei mit. Das Firmware-Subsystem des Kernels legt daraufhin dynamisch im Sys-Filesystem ein Verzeichnis an. Es repräsentiert den Treiber und enthält die virtuellen Dateien »loading« und »data«.

Abbildung 1: Die Funktion zum Laden der Firmware löst innerhalb des Betriebssystems umfangreiche Aktivitäten zwischen Treiber, Firmware-Subsystem und dem kontrollierenden Userprogramm aus.

Abbildung 1: Die Funktion zum Laden der Firmware löst innerhalb des Betriebssystems umfangreiche Aktivitäten zwischen Treiber, Firmware-Subsystem und dem kontrollierenden Userprogramm aus.

Der Treiber informiert den Udev-Daemon (»udevd«), der Gerätedateien automatisch anlegt [1]. Der Daemon erkennt am übermittelten »uevent«, dass eine Firmware zu laden ist, und startet das Helferprogramm »/lib/udevd/firmware.sh« oder »/lib/udevd/firmware_helper« (siehe Kasten "Ubuntu und die Firmware"). Diese Software übergibt über die virtuellen Dateien »loading« und »data« das Firmware-Image an den Kernel.

Ubuntu und die
Firmware

Ubuntu verwendet auch in der neuesten Version 7.10 nicht das den Orginalquellen beiliegende Skript »firmware.sh«, sondern den älteren C-Code »firmware_helper.c«. Anders als in den Orginalquellen von »firmware_helper.c« vorgesehen, ermöglicht das von den Ubuntu-Entwicklern modifizierte Programm, für unterschiedliche Kernel auch unterschiedliche Firmware-Versionen zu verwenden. Kernel-spezifische Firmware-Images erwartet es in einem Verzeichnis unterhalb von »/lib/firmware«, das den Namen der Kernelversion trägt - bei Ubuntu 7.10 beispielsweise »/lib/firmware/2.6.22-14-generic«. Firmware, die Kernel-unabhängig geladen werden soll, findet sich weiterhin im Verzeichnis »/lib/firmware«.

Das definierte Protokoll sieht vor, dass die Software zunächst eine »1« in die Datei »loading« schreibt und danach die Firmware-Daten in die virtuelle Datei »data« kopiert. Das Firmware-Subsystem übernimmt die Daten und legt sie in einen mitwachsenden, zusammenhängenden Bereich des Kernelspeichers ab. Nachdem »firmware.sh« alle Daten kopiert hat, signalisiert das Programm das Ende der Aktion, indem es eine »0« in »loading« schreibt (Abbildung 2).

Eine »-1« dagegen signalisiert den Abbruch des Ladevorgangs. Das Firmware-Subsystem entfernt daraufhin die virtuellen Dateien und das angelegte Verzeichnis wieder und übergibt dem Treiber die Adresse des Speicherbereichs, in dem das Firmware-Image nun liegt.

Der Treiber sorgt anschließend noch dafür, die Daten der Hardware zu übergeben und den Speicher wieder freizugeben. Der Kernel überwacht den ganzen Vorgang: Wenn der Vorgang nicht binnen 60 Sekunden abgeschlossen ist und das Image im Speicher liegt, sorgt der Kernel für einen Abbruch.

Glücklicherweise sind die beschriebenen Vorgänge alle bereits im Firmware-Subsystem implementiert. Der Treiberprogrammierer bedient ein recht einfaches Interface. Es besteht im Wesentlichen aus den beiden Funktionen »request_firmware()« und »release_firmware()« und den beiden Objekten »struct firmware« und »struct device« (siehe Kasten "Schnittstellenfunktionen im Kernel"). Das Device-Objekt ist bei den meisten Treibern ohnehin implizit vorhanden. Wenn eine Karte beispielsweise per PCI angebunden ist, stellt der Kernel ein Objekt vom Typ »struct pci_dev« bereit, das seinerseits im Element »dev« das gesuchte Device-Objekt enthält.

Schnittstellenfunktionen im
Kernel

Um das Firmware-Subsystem zu veranlassen, das zum Gerät »device« gehörende und unter dem Namen »name« abgelegte Firmware-Image in einen Speicherbereich des Kernels zu kopieren, bietet es eine Schnittstelle an:

intrequest_firmware(conststructfirmware**fw, constchar*name, structdevice*device);

Sowohl das den Speicherbereich beschreibende Objekt vom Typ »struct firmware« als auch der entsprechende Speicher reserviert das Firmware-Subsystem dynamisch. Das Objekt »struct firmware« enthält die Startadresse des Speicherbereichs, in dem das Firmware-Image liegt, und die zugehörige Länge. Die Adresse des Objektes ist in »fw« gespeichert.

Während des Ladens der Firmware legt sich der zugehörige Thread schlafen. Bei erfolgreichem Laden der Firmware gibt die Funktion »0«, sonst einen negativen Fehlercode zurück. »EINVAL« bedeutet, dass die Adresse für die Aufnahme der Speicherdaten des Firmware-Image ungültig war, »ENOMEM«, dass kein Speicher im Kernel für die Firmware-Struktur oder für die Aufnahme des Firmware-Image ist. Bei »ENOENT« findet die Funktion das Firmware-Image nicht. Die folgende Funktion gibt die mit »fw« assoziierten Ressourcen wieder frei:

voidrelease_firmware(conststructfirmware*fw);

So veranlasst das Firmware-Subsystem, einen Kernel-Thread zu starten und in ihm die Kernelfunktion »request_firmware()« aufzurufen:

int request_firmware_nowait(struct module *mod, int uevent, const char *name, struct device *device, void*context, void(*cont)(conststructfirmware*fw,void*context));

Der Parameter »mod« spezifiziert das zugehörige Modul, typischerweise das Makro »THIS_MODULE«, und »name« das zu ladende Firmware-Image. Der Parameter »device« gibt das zugehörige Objekt im Gerätemodell (Sys-Filesystem) an. Nach einem erfolgreichen oder erfolglosen Versuch, die Firmware zu laden, ruft das Subsystem die Callback-Funktion »cont« auf. Neben der Spezifikation, wo das Firmware-Image im Speicher zu finden ist, bekommt die Funktion den frei definierbaren Parameter »context« übergeben.

Beim erfolgreichen Starten des Kernel-Thread gibt die Funktion »0«, sonst einen negativen Fehlercode zurück. »ENOMEM« signalisiert, dass kein Speicher im Kernel vorhanden ist, »EFAULT«, dass das Modul bereits entladen ist.

Sollte ein Treiber noch kein Device-Objekt haben, erhält er es mit Hilfe der Funktion »device_register()« und muss es aktivieren (siehe Listing 1, Zeile 33). Speicher wird reserviert und ein Objektname bereitgestellt. Die Release-Funktion in den Zeilen 7 bis 11 sorgt dafür, dass niemand den Treiber entladen kann, solange jemand noch auf das Device-Objekt zugreift, zum Beispiel über das Sys-Filesystem.

Listing 1: Anfordern von
Firmware (»fwtest.c«)

01 #include <linux/device.h>
02 #include <linux/firmware.h>
03 
04 static DECLARE_COMPLETION(obj_not_in_use);
05 static const struct firmware *fw_entry;
06 
07 static void
08 firmware_device_release(struct device *dev)
09 {
10     complete(&obj_not_in_use);
11 }
12 
13 static struct device fwtest_device = {
14     .bus_id    = "fwtest",
15     .release   = firmware_device_release,
16 };
17 
18 static void
19 fwtest_load_firmware(char *firmware, int size)
20 {
21     unsigned char buf[80+1];
22     int to_copy;
23 
24     printk("fwtest_load_firmware(void)n");
25     to_copy = min((int)(sizeof(buf)-1), size);
26     memcpy(buf, firmware, to_copy);
27     buf[to_copy] = '\0';
28     printk("firmware: \"%s\"\n", buf);
29 }
30 
31 static int __init fwtest_init(void)
32 {
33     if (device_register(&fwtest_device)) {
34         printk("device_register failedn");
35         return -EIO;
36     }
37     if (request_firmware(&fw_entry,
38          "pseudo_firmware", &fwtest_device)) {
39         printk("firmware not availablen");
40         device_unregister(&fwtest_device);
41         return -EIO;
42     }
43 
44     fwtest_load_firmware(fw_entry->data,
45                          fw_entry->size);
46 
47     release_firmware(fw_entry);
48     return 0; 
49 }
50 
51 static void __exit fwtest_exit(void)
52 {
53     device_unregister(&fwtest_device);
54     wait_for_completion(&obj_not_in_use);
55     printk("fwtest_exit()n");
56 }
57 
58 module_init(fwtest_init);
59 module_exit(fwtest_exit);
60 MODULE_LICENSE("GPL");

Die Funktion »request_firmware()« übergibt dem Kernel-Subsystem den Namen der Firmware-Datei und das Device-Objekt. Der Name der Firmware-Datei darf nicht länger als 30 Zeichen sein. Außerdem wichtig: Die Funktion blockiert so lange, bis »udevd« entweder das erfolgreiche Kopieren der Firmware-Datei oder einen Fehler meldet, beispielsweise wenn es die Datei nicht findet.

Entwickler dürfen diese Funktion also nicht innerhalb einer Interrupt-Service-Routine, eines Tasklets oder eines Timers verwenden. Diese Einschränkung ist aber nicht gravierend, da ohnehin typischerweise der Treiber die Firmware anfordert, wenn er geladen wird, also in der durch das Makro »mod_init()« angegebenen Funktion. Wem das nicht reicht, für den gibt es einen bisher noch nicht erwähnten asynchronen Modus.

Treiber-Angelegenheit

Konnte eines der erwähnten Helferprogramme die Firmware in den Kernel transportieren, gibt die Funktion »request_firmware()« die Adresse des »struct firmware«-Objekts zurück (siehe Abbildung 2). Dieses Objekt enthält die Startadresse der Daten und die zugehörige Länge. Meist wird der Treiber jetzt die Firmware in das Gerät kopieren. Dazu greift er mit Hilfe der Funktionen »in[bwl]« oder »out[bwl]« bei Port-mapped-IO beziehungsweise »read[bwl]« oder »write[bwl]« bei Memory-mapped-IO direkt auf die Register der Hardware zu. Dann ruft er die Funktion »release_firmware()« auf. Sie gibt den Speicherbereich wieder frei, in den der Treiber die Firmware temporär geladen hat.

Abbildung 2: Das Firmware-Subsystem reserviert Kernelspeicher für das Image und ein Objekt, das es beschreibt. Die Größe des Speichers wird &ndash; wenn es sein muss durch Umkopieren &ndash; dynamisch als ein Vielfaches der Pagesize angepasst.

Abbildung 2: Das Firmware-Subsystem reserviert Kernelspeicher für das Image und ein Objekt, das es beschreibt. Die Größe des Speichers wird &ndash; wenn es sein muss durch Umkopieren &ndash; dynamisch als ein Vielfaches der Pagesize angepasst.

Listing 1 zeigt als Beispiel den Einsatz der beiden Funktionen. Wer den Code testet, muss natürlich eine Firmware-Datei zur Verfügung stellen. Der Code verlangt hier eine Firmware, die Daten in Textform enthält. So lässt sich das Laden gut veranschaulichen. Das in Abbildung 3 demonstrierte Beispiel erzeugt die benötigte Pseudo-Firmware. Ein Anwender generiert und lädt dort ebenfalls den Treiber, der über das Kernel-Log Informatonen ausgibt. Dieses Beispiel läuft unter Ubuntu.

Abbildung 3: Bevor sich das Bespiel-Modul in den Kernel lädt, muss vorher eine Firmware im Verzeichnis »/lib/firmware« bereitstehen. Nach geglücktem Start gibt das Modul die Firmware im Kernel-Log aus.

Abbildung 3: Bevor sich das Bespiel-Modul in den Kernel lädt, muss vorher eine Firmware im Verzeichnis »/lib/firmware« bereitstehen. Nach geglücktem Start gibt das Modul die Firmware im Kernel-Log aus.

Wie bereits erwähnt, bietet das Firmware-Subsytem auch die Möglichkeit, Firmware asynchron, also auch aus Interrup-Service-Routinen, Tasklets oder einem Timer heraus zu laden. In diesem Fall ruft der Gerätetreiber die Funktion »request_firmware_nowait()« statt »request_firmware()« auf (siehe Listing 2). Bevor die Funktion die Kontrolle wieder an die aufrufende Funktion zurückgibt, startet sie für den Entwickler transparent einen Kernel-Thread. In dessen Kontext ruft sie zunächst »request_firmware()« und danach eine vom Programmierer bereitgestellte Callback-Funktion auf. Die Funktion kann das inzwischen in den Kernel kopierte Firmware-Image zum Beispiel zur Hardware übertragen.

Listing 2: Asynchrones Laden
(»fwasync.c«)

01 #include <linux/device.h>
02 #include <linux/firmware.h>
03 
04 static DECLARE_COMPLETION(obj_not_in_use);
05 
06 static void
07 firmware_device_release(struct device *dev)
08 {
09     complete(&obj_not_in_use);
10 }
11 static struct device fwasync_device = {
12     .bus_id    = "fwasync",
13     .release   = firmware_device_release,
14 };
15 
16 static void fwasync_cont(const struct
17           firmware *fw, void *private_data)
18 {
19     unsigned char buf[80+1];
20     int to_copy;
21 
22     if (fw == NULL) {
23         printk("fwasync_cont:
24                 firmware load failedn");
25         return;
26     }
27     to_copy = min((sizeof(buf)-1), fw->size);
28     memcpy(buf, fw->data, to_copy);
29     buf[to_copy] = '';
30     printk("firmware(%d): "%s"n",
31            (int)fw->size, buf);
32 }
33 
34 static int __init fwasync_init(void)
35 {
36     if (device_register(&fwasync_device)) {
37         printk("device_register failedn");
38         return -EIO;
39     }
40     if (request_firmware_nowait(THIS_MODULE,
41           FW_ACTION_NOHOTPLUG,
42           "pseudo_firmware", &fwasync_device,
43           "private_data", fwasync_cont)) {
44         printk("fwasync:
45           request_firmware_nowait failedn");
46         device_unregister( &fwasync_device );
47         return -EIO;
48     }
49     return 0;
50 }
51 
52 static void __exit fwasync_exit(void)
53 {
54     device_unregister( &fwasync_device );
55     wait_for_completion( &obj_not_in_use );
56 }
57 
58 module_init(fwasync_init);
59 module_exit(fwasync_exit);
60 MODULE_LICENSE("GPL");

Hierbei ist aber zu beachten, dass nach Abschluss der Callback-Funktion der Kernel noch den Speicher, in dem sich das Firmware-Image befindet, wieder freigibt. Der Entwickler darf also, wenn er asynchron lädt, die Release-Funktion »release_firmware()« nicht aufrufen. Das Subsystem geht beim asynchronen Modus davon aus, dass nach Ablauf der Callback-Funktion das Image der Firmware nicht mehr benötigt wird, und räumt es selbst auf.

Diesen Artikel als PDF kaufen

Als digitales Abo

Als PDF im Abo bestellen

comments powered by Disqus

Ausgabe 07/2013

Preis € 6,40

Insecurity Bulletin

Insecurity Bulletin

Im Insecurity Bulletin widmet sich Mark Vogelsberger aktuellen Sicherheitslücken sowie Hintergründen und Security-Grundlagen. mehr...

Linux-Magazin auf Facebook