7.2. Das neue Gerätemodell

Das Gerätemodell ist eine brandneue Komponente des Kernel 2.6 und steckt noch in der Entwicklung. Da sich bisher erst wenige Applikationen darauf abstützen, darf es nicht verwundern, wenn sich die Schnittstellen zum Gerätemodell seit Drucklegung des Buches geändert haben. Wenn Sie also in Ihren eigenen Treibern das neue Modell unterstützen möchten, könnte es erforderlich werden, schon mal selbst im Linux-Quellcode über Variablen, Interfaces und Aufrufsemantik zu recherchieren.

Dass Linus Torvalds das Gerätemodell trotz des unfertigen Zustandes in den Kernel 2.6 aufgenommen hat, liegt daran, dass die Entwickler hohe Erwartungen an die neue Komponente stellen. Es hilft die Gerätelandschaft zu strukturieren und zu präsentieren, es hilft beim Power-Management und ebenso – als Ersatz für das Device-Filesystem (siehe Kapitel Device-Filesystem) – bei der Verwaltung der Gerätedateien [Kroah2003].

Das Gerätemodell bildet ab, wie die Prozessoren eines Systems mit seinen Controller-Bausteinen und diese wiederum mit den Peripheriekarten und mit sonstiger Hardware zusammenhängen. Allerdings wird nicht nur die Hardwarestruktur modelliert, sondern auch die zugehörigen Softwarekomponenten, also die Gerätetreiber. Anhand der innerhalb des Gerätemodells angesammelten Information ist das System in der Lage, Powermanagement zu betreiben. Man denke allein an die Reihenfolge, in der die einzelnen Komponenten abgeschaltet werden sollten: Zunächst müssen die Geräte an einem Bus runtergefahren werden, bevor der Bus selbst und schließlich der Prozessor in einen Stromspar-Zustand versetzt werden.

Der Anwender kann mit dem Gerätemodell über das Sys-Filesystem (auch Driver-Filesystem genannt) in Berührung kommen. Dazu muss er das Sys-Filesystem – falls das noch nicht geschehen ist – in das Verzeichnis /sys/ einhängen ((root)# mount -t sysfs sysfs /sys).

Abbildung 7-6. Repräsentierung des Gerätemodells im Sys-Filesystem

Das Sys-Filesystem ist wie das Proc-Filesystem virtuell: Seine Verzeichnisse und Dateien werden dynamisch erzeugt und befinden sich nicht auf einem Hintergrundspeicher. Der durch das Sys-Filesystem aufgespannte Verzeichnisbaum spiegelt die Struktur der Hardware und der zugehörigen Software innerhalb des Systems wider. Das Sys-Filesystem kategorisiert diese Informationen in Form von Verzeichnissen und Dateien sowohl nach ihrer physikalischen Art (z.B. Gerät oder Bus) als auch nach Geräteklassen (z.B. Input). Verzeichnisse spiegeln dabei Objekte wider. Die zugehörigen Objektattribute, beispielsweise Herstellername, Versionsnummer, Gerätezustand oder die Übertragungsstatistik einer Netzwerkkarte sind als Dateien dargestellt. Diese Dateien lassen sich gleich den Dateien des Proc-Filesystems lesen und schreiben (siehe Abbildung Repräsentierung des Gerätemodells im Sys-Filesystem). Darüber hinaus bietet das Sys-Filesystem aber auch Schnittstellen an, wie beispielsweise diejenige zum Download von Firmware [Sainz2003].

Entsprechend den unterschiedlichen Kategorien können einzelne Geräte und Treiber im Verzeichnisbaum des Sys-Filesystems an mehreren Stellen auftauchen. Eine Netzwerkkarte beispielsweise, die über PCI angeschlossen ist, muss sowohl unterhalb der Kategorie »Bus«, als auch unterhalb der Kategorie »Geräteklasse/Netzwerk« einsortiert werden. Dies alles leistet das Gerätemodell. Mehrfacheinträge werden dabei als symbolische Links ausgeprägt.

Die Integration eines Gerätes oder eines Treibers in das Gerätemodell vollzieht sich in den meisten Fällen implizit. Die Subsysteme des Kernels übernehmen die Einbindung, beispielsweise das Subsystem für blockorientierte Treiber oder das PCI-Subsystem. In diesen Fällen muss der Treiberentwickler zunächst nichts machen.

Der Entwickler ist jedoch gefordert, wenn

  1. Geräte nicht an ein standardisiertes Bussystem angeschlossen sind,

  2. Powermanagement-Funktionen der Geräte durch den Treiber unterstützt werden sollen,

  3. Status- und Kontrollzugriffe über so genannte Attribute ermöglicht werden sollen. Diese Attributdateien können sowohl für Treiber als auch für Geräte erstellt werden,

  4. neue Bussysteme integriert werden sollen oder

  5. neue Geräteklassen definiert werden.

Abbildung 7-7. Unterstützung des Gerätemodells innerhalb eines Treibers

Der Treiberprogrammierer hat folgende Schritte durchzuführen (siehe Abbildung Unterstützung des Gerätemodells innerhalb eines Treibers): Er erzeugt ein den Treiber repräsentierendes Objekt, initialisiert es und übergibt es dem Linux-Gerätemodell. Das gleiche Vorgehen ist für die vom Treiber identifizierten (und unterstützten) Geräte erforderlich. Um nun noch sicherzustellen, dass Treiber und Gerät miteinander in Beziehung stehen, so dass später im Sys-Filesystem zu jedem Gerät der zugehörige Treiber gefunden wird, muss der Entwickler Treiber und Gerät miteinander verknüpfen. Soll der Treiber darüber hinaus eine eigene Geräteklasse anlegen wollen, muss auch hierfür ein Objekt angelegt und dem Linux-Gerätemodell übergeben werden.

7.2.1. Treiber anmelden

Das Linux-Gerätemodell definiert Treiber als originäre Objekte der Bussysteme. Entsprechend dieser Festlegung tauchen sie im Sys-Filesystem auch unterhalb der Kategorie »bus/<busname>« auf. »Busname« steht hier beispielsweise für »pci«, »usb«, »ide«, »pnp«, »pcmcia«, »eisa«, »i2c«, »mca« oder auch »platform«.

Üblicherweise braucht sich der Programmierer nicht um die Anmeldung des Treibers beim Linux-Gerätemodell kümmern. Registriert sich nämlich der Treiber bei einem Bus-Subsystem, zum Beispiel bei PCI, übernimmt dieses die Anmeldung beim Gerätemodell, so dass daraufhin ein Eintrag im Sys-Filesystem unter /sys/bus/pci/ erfolgt.

Treiber für virtuelle Geräte und Treiber für Geräte, die an proprietären Bussen oder direkt am Prozessorbus angeschlossen werden, müssen jedoch selbst für eine Anmeldung beim Linux-Gerätemodell sorgen. In einem solchen Fall wird er sich unterhalb des »Plattform-Busses« einhängen oder aber zunächst ein eigenes Bussystem definieren (siehe Neue Bussysteme anlegen). Der Plattform-Bus ist als Container für all diejenigen Treiber gedacht, die sich nicht unterhalb der übrigen Busse einsortieren lassen.

Für die Anmeldung beim Linux-Gerätemodell instanziert der Treiber eine Datenstruktur vom Typ struct device_driver (Header-Datei <linux/device.h>):

struct device_driver speaker_driver = {
    .name = "SpeakerDriver",
    .bus  = &platform_bus_type,
};

Die Felder name und bus der Datenstruktur müssen ausgefüllt werden! Solange kein eigenes Bussystem definiert wurde, wird für das Feld bus der Plattform-Bus gewählt. Das zugehörige Symbol »platform_bus_type« findet sich ebenfalls in der Header-Datei <linux/device.h>.

Beispiel Anmeldung eines Treibers beim Gerätemodell zeigt, wie durch Aufruf der Funktion driver_register die Anmeldung beim Gerätemodell im Rahmen der Treiberinitialisierung stattfindet. Die Funktion gibt im fehlerfreien Fall »0« zurück, im Fehlerfall einen negativen Wert.

Beispiel 7-8. Anmeldung eines Treibers beim Gerätemodell

/* vim: set ts=4: */
#include <linux/fs.h>
#include <linux/version.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>

MODULE_LICENSE("GPL");

static struct file_operations fops;

static struct device_driver speaker_driver = {
    .name = "SpeakerDriver",
    .bus = &platform_bus_type,
};

static int __init drv_init(void)
{
    if(register_chrdev(240, "MyDevice", &fops) == 0) {
        if( driver_register(&speaker_driver)==0 )
            return 0; // Alles ok!
        unregister_chrdev(240,"MyDevice");
    }
    return -EIO;
}

static void __exit drv_exit(void)
{
    driver_unregister(&speaker_driver);
    unregister_chrdev(240,"MyDevice");
}

module_init( drv_init );
module_exit( drv_exit );

Im Sys-Filesystem erscheint bei erfolgreicher Anmeldung das neue Verzeichnis unterhalb des Plattform-Busses (/bus/platform/SpeakerDriver).

Natürlich muss sich ein Treiber beim Linux-Gerätemodell auch wieder abmelden. Dazu dient die Funktion driver_unregister (siehe Beispiel Anmeldung eines Treibers beim Gerätemodell).

7.2.2. Geräte anmelden

Ähnlich wie die Treiber werden auch die Geräte implizit beim Gerätemodell angemeldet. Für die Anmeldung sind wiederum die Bus-Subsysteme verantwortlich. Erkennt beispielsweise das PCI-Subsystem eine Hardware, registriert es diese beim Linux-Gerätemodell.

Sind aber die Treiber für die Geräteerkennung zuständig, müssen diese auch die Registrierung der Geräte beim Linux-Gerätemodell durchführen. So wie der Bus »platform« als Container für alle sonstigen Treiber gedacht ist, dient »legacy« als Container für die zugehörigen Geräte. Das entsprechende Verzeichnis befindet sich unterhalb des Verzeichnisses »devices«: /sys/devices/legacy/.

Um ein Gerät anzumelden, ist eine Datenstruktur vom Typ struct platform_device zu definieren. Hierbei sind insbesondere drei Felder auszufüllen: Der Name des Gerätes, die Geräte-Identifikationsnummer und die Adresse einer Release-Funktion. Der Name zusammen mit der Identifikationsnummer ergibt den Namen des Directories, unter dem die Geräteinformationen später zu finden sein werden – und in dem die zugehörigen Attributdateien liegen. Vom Linux Device Model-Subsystem werden die Attribute name und power automatisch vergeben. Die Release-Funktion wird aufgerufen, wenn das Gerätemodell das Geräteobjekt nicht mehr benötigt. Der zugehörige Speicher darf also erst nach Aufruf dieser Funktion wieder freigegeben werden. Das lässt sich am einfachsten per Completion-Objekt bewerkstelligen (siehe Abschnitt Bis zum »Ende«).

static void speaker_release( struct device *dev )
{
    complete( &dev_object_is_free );
}

struct platform_device speaker_dev = {
    .name  = "Speaker",
    .id    = 0,
    .dev = {
        .release = speaker_release,
    }
};

Die definierte Datenstruktur wird durch Aufruf von platform_device_register dem Gerätemodell übergeben. Die korrespondierende Abmeldefunktion heißt platform_device_unregister, ihr einziger Aufrufparameter ist ebenfalls die zuvor beschriebene Datenstruktur.

    platform_device_unregister( &speaker_dev );

Das Erstellen von Treiber- und Geräteobjekten ist nicht komplex – allerdings stehen beide Objekte noch in keinerlei Beziehung zueinander. Das Gerätemodell weiß also noch nicht, dass das Gerät durch unseren Treiber bedient wird und umgekehrt, dass dieser Treiber für das soeben definierte Gerät verantwortlich ist. Um die Verbindung herzustellen, gibt es in der Datenstruktur für das Gerät (struct device) das Feld »driver«. In dieses Feld wird die Adresse des Treiberobjektes eingetragen. Anschließend wird die Funktion device_bind_driver aufgerufen:

    platform_geraet.standard_geraete_parameter.driver = &treiber_object;
    device_bind_driver( &platform_geraet.standard_geraete_parameter );

Im Sys-Filesystem werden durch den Aufruf die symbolischen Links vom Verzeichnis /sys/bus/platform/driver/SpeakerDriver hin zu /sys/devices/legacy/SpeakerDev erstellt.

7.2.3. Geräteklassen

Im neuen Gerätemodell werden Treiber und Geräte auch in Geräteklassen kategorisiert. So gehört beispielsweise der Ethernet-Adapter zur Klasse der Netzwerkgeräte, die Tastatur dagegen zur Gruppe der Eingabegeräte. Die Klassen »input«, »net«, »misc«, »graphics«, »sound«, »pcmcia_socket«, »usb«, »usb_host«, »i2c_adapter«, »i2c_device«, »tty«, »scsi_host«, »scsi_device« und »firmware« sind bereits vordefiniert. Mit wenigen Zeilen Code kann der Treiberentwickler diese Liste jedoch um eigene Klassen erweitern (siehe Eigene Klassen erzeugen).

7.2.3.1. Vordefinierte Klassen verwenden

Innerhalb der Geräteklassen wird für jedes Gerät (Device) ein Verzeichnis erstellt, welches jeweils einen Link auf den wirklichen Geräteeintrag (z.B. unter /sys/devices/legacy/) und einen Link auf den zugehörigen Treiber (z.B. unter /sys/bus/platform/drivers/) enthält. Ist eine Geräteklasse in Form eines Kernel-Subsystems realisiert, in das sich der Treiber einklinkt, erstellt das Subsystem im Gerätemodell die Einträge. Dies geschieht zum Beispiel, wenn sich ein Treiber beim Netzwerksubsystem anmeldet.

Bei selbst definierten Klassen und Klassen wie input und pcmcia-socket liegt der Fall anders. Unterstützt der Treiber ein Gerät dieser Klasse, muss er selbst Hand anlegen, um die Einträge im Gerätemodell vorzunehmen. Eine derart vordefinierte Klasse ist entweder vom Datentyp struct class oder vom Datentyp struct class_simple (eingeführt mit Kernel 2.6.2).

Gehört ein Gerät zu einer Klasse, die über struct class repräsentiert wird, muss im Treiber ein Objekt vom Typ struct class_device definiert werden. Dieses habe beispielsweise den Namen class_entry. Es fungiert als Container, um dem Gerätemodell das zugehörige Geräte- und Treiberobjekt zu übergeben. Außerdem dient es zur Namensgebung für das zugehörige Verzeichnis. Entsprechend ist es zu initialisieren:

  1. Zunächst werden Geräte- und Treiberobjekt miteinander verknüpft. Dazu wird die Adresse des Treiberobjektes mydriver oder im Fall von PCI pci_drv.driver an das Geräteobjekt mydevice angehängt.

  2. Jetzt reicht es aus, in class_entry das Geräteobjekt einzutragen (class_entry->dev=&mydevice respektive class_entry->dev=&pci_dev->dev).

  3. Im Feld class_entry.class muss noch die Klasse (beziehungsweise das zugehörige Verzeichnis) angegeben werden, unter dem der neue Eintrag erfolgt; die Variable, die die Klasse repräsentiert, muss global bekannt sein. Im Fall der im Beispiel Eigene Klassen im Gerätemodell vorgestellten Game-Klasse ist dies beispielsweise die Variable game_class.

  4. Zuletzt ist class_entry noch mit dem Namen des Gerätes – respektive des Verzeichnisses, unter dem die Einträge später zu finden sein werden – zu initialisieren. Dazu wird der gewünschte Verzeichnisname in das Feld class_id kopiert.

Die so initialisierte Struktur wird mit class_device_register an das Gerätemodell übergeben:

static struct class_device class_entry;
...
    mydevice->driver = &mydriver;
    class_entry.dev   = &mydevice;
    class_entry.class = &game_class;
    strlcpy( (void*)&class_entry.class_id, "MyDev", 6 );
    class_device_register( &class_entry );

Dieser Code ist üblicherweise Teil der Geräteinitialisierung. Wird er ausgeführt, legt das Gerätemodell das Verzeichnis »MyDev« unterhalb von /sys/class/GameClass an und setzt symbolische Links auf die entsprechenden Geräte- und Treiberobjekte.

Um das Gerät beim Klassen-Objekt abzumelden, wird die Funktion class_device_unregister aufgerufen.

Gehört ein Gerät zu einer Klasse, die über struct class_simple repräsentiert wird, reicht es aus, im Treiber die Funktion class_simple_device_add aufzurufen. Der Funktion werden dabei ein Zeiger auf das Klassen-Objekt, die zum Gerät gehörende Gerätenummer sowie das Geräteobjekt selbst übergeben. Zudem muss der Name des Verzeichnisses, welches unterhalb der Klasse im Sys-Filesystem erstellt werden soll, in Form eines Formatstrings mit den zugehörigen Parametern angegeben werden. Das folgende Codefragment ordnet ein Gerät der Input-Klasse (input_class) zu:

    class_simple_device_add( input_class, MKDEV(240,0),
        button_dev.dev, "keyb%d", 0 );

Die Funktion class_simple_device_remove meldet das Gerät wieder bei der Klasse ab:

    class_simple_device_remove( MKDEV(240,0) );

7.2.3.2. Eigene Klassen erzeugen

Eigene Klassen sind entweder durch ein Objekt vom Typ struct class oder vom Typ struct class_simple repräsentiert.

In den meisten Fällen dürfte die Realisierung einer eigenen Klasse über die »Simple-Class« einfacher sein. Hierfür stehen (seit Kernel 2.6.2) die Funktionen class_simple_create und class_simple_destroy zur Verfügung. Die erste Funktion erzeugt das Klassen-Objekt, unterhalb dem Geräte eingehängt werden können (siehe Vordefinierte Klassen verwenden). Die zweite Funktion schließlich entfernt das Klassen-Objekt wieder aus dem Gerätemodell.

struct class_simple *my_class;
...
    my_class = class_simple_create( THIS_MODULE, "MyClass");
...
    class_simple_destroy( my_class );

Um eine Standard-Klasse zu erzeugen, wird ein Objekt vom Typ struct class definiert:

struct class new_class = {
    .name     = "NewClass",
    .hotplug  = new_class_hotplug,
    .release  = new_class_release,
};

Die beiden Methoden .hotplug und .release werden aufgerufen, falls sich ein Treiber bei der neuen Klasse an- bzw. wieder abmeldet.

int new_class_hotplug( struct class_device *dev, char **envp, 
        int num_envp, char *buffer, int buffer_size)
void new_class_release( struct class_device *dev )

Das Objekt »new_class« wird durch Aufruf der Funktion class_register dem Linux-Gerätemodell übergeben. Dieses legt unterhalb von /sys/class/ ein Verzeichnis mit dem in »new_class« angegebenen Namen an.

    class_register( &new_class );

Damit andere Treiber die neue Klasse nutzen können, muss das Klassenobjekt »new_class« global sein.

Beispiel Eigene Klassen im Gerätemodell zeigt das Anlegen und das Entfernen einer solchen Klasse.

Beispiel 7-9. Eigene Klassen im Gerätemodell

#include <linux/fs.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>

MODULE_LICENSE("GPL");

static void game_device_release( struct device *dev );
int game_class_hotplug( struct class_device *dev, char **envp,
                       int num_envp, char *buffer, int buffer_size);
void game_class_release( struct class_device *dev );

static struct device_driver game_driver = {
    .name = "GameDriver",
    .bus  = &platform_bus_type,
};
struct platform_device game_device = {
    .name  = "GameDevice",
    .id    = 0,
    .dev   = {
        .release = game_device_release,
    }
};
static struct class game_class = {
    .name    = "GameClass",
    .hotplug = game_class_hotplug,
    .release = game_class_release,
};

static struct class_device game_class_device = {
    .class = &game_class,
};
static struct file_operations fops;
static DECLARE_COMPLETION( dev_obj_is_free );

static void game_device_release( struct device *dev )
{
    complete( &dev_obj_is_free );
}

int game_class_hotplug( struct class_device *dev, char **envp,
    int num_envp, char *buffer, int buffer_size)
{
    printk("game_class_hotplug( %p )\n", dev );
    return 0;
}
void game_class_release( struct class_device *dev )
{
    printk("game_class_release( %p )\n", dev );
}

static int __init mod_init(void)
{
    if(register_chrdev(240, "gamedevice", &fops) == 0) {
        driver_register(&game_driver);
        platform_device_register( &game_device );
        game_device.dev.driver = &game_driver;
        device_bind_driver( &game_device.dev );
        class_register( &game_class );
        game_class_device.dev = &game_device.dev;
        strlcpy( (void *)&game_class_device.class_id,
            "GameClassDevice", 16 );
        class_device_register( &game_class_device );
        return 0;
    }
    return -EIO;
}

static void __exit mod_exit(void)
{
    class_device_unregister( &game_class_device );
    class_unregister( &game_class );
    device_release_driver( &game_device.dev );
    platform_device_unregister( &game_device );
    driver_unregister(&game_driver);
    unregister_chrdev(240,"gamedevice");
    wait_for_completion( &dev_obj_is_free );
}

module_init( mod_init );
module_exit( mod_exit );

7.2.4. Attributdateien erstellen

Richtig spannend wird das Gerätemodell erst, sobald die einzelnen Objekte mit Attributen in Form von Attributdateien versehen werden. Wie beim Proc-Filesystem lassen sich diese Attributdateien mit Standardkommandos wie echo oder cat lesen und schreiben. Wenn beispielsweise das Attribut »Version« realisiert worden wäre, könnte die zu einem Treiber gehörige Versionsnummer über das Kommando »# cat version« ausgelesen werden.

Attributdateien lassen sich in drei Schritten realisieren:

  1. Es werden eine Lese- und wenn sinnvoll eine Schreibfunktion erstellt.

  2. Es wird ein Objekt definiert, welches die Adressen der Lese- und Schreibfunktion ebenso aufnimmt wie die zugehörigen Zugriffsrechte auf die zu erstellende Attributdatei.

  3. Das Gerätemodell wird mit der Erstellung der Attributdatei beauftragt.

Die Lesefunktion bekommt zwei Parameter übergeben. Abhängig davon, ob eine Attributdatei für ein Treiberobjekt, für ein Geräteobjekt oder für eine Geräteklasse erstellt werden soll, ist der erste Parameter vom Typ struct device_driver, struct device oder struct class.

Die Aufgabe der Lesefunktion besteht darin, die internen Daten ASCII-kodiert an die Speicherstelle abzulegen, die der Funktion im zweiten Parameter übergeben wird. Da der zugehörige Speicher Teil des Kernel-Space ist, können die Daten direkt – ohne Umweg über copy_to_user – geschrieben werden. Das Gerätemodell wird die Daten dann später selbst in den User-Space kopieren.

Zur ASCII-Kodierung der Daten stehen die bekannten Funktionen strlcpy oder auch snprintf zur Verfügung. Es ist darauf zu achten, dass der zugehörige Speicherbereich die Größe einer Speicherseite (auf einem x86-System also 4096 Bytes) hat. Das Sys-Filesystem ist nicht dazu ausgelegt, umfangreiche Daten zu publizieren!

Im Folgenden wird die Implementierung einer Lesefunktion exemplarisch vorgestellt:

static ssize_t driver_version( struct device_driver *driver, char *buffer )
{
    strcpy(buffer,"V1.0");
    return strlen(buffer)+1;
}

Die Funktion gibt die Anzahl der in den Buffer kopierten Bytes zurück.

Die Schreibfunktion bekommt zusätzlich zu den Parametern der Lesefunktion noch einen Parameter für die Anzahl der zu schreibenden Daten (der Daten im Buffer) mit. Auch hier liegen die Daten bereits im Kernel-Space vor, brauchen also nicht per copy_from_user kopiert werden; einfache, direkte Kopieroperationen reichen aus.

static ssize_t write_freq( struct device *dev, const char *buf, size_t count )
{
    frequenz = simple_strtoul( buf, NULL, 0 );
    return strlen(buf)+1;
}

Die Schreibfunktion gibt die Anzahl der von der Applikation erfolgreich geschriebenen (aus Sicht unserer Funktion übernommenen) Bytes zurück.

Zur Aufnahme der Adressen von Lese- und Schreibfunktion und der Zugriffsrechte auf die zu erzeugende Attributdatei wird ein Objekt vom Typ struct driver_attribute, struct device_attribute beziehungsweise struct class_attribute erzeugt. Dies sollte über die folgenden Makros (definiert in <linux/device.h>) geschehen:

static DRIVER_ATTR( version, S_IRUGO, driver_version, NULL );
static DEVICE_ATTR( freq ,S_IRUGO|S_IWUGO, read_freq, write_freq );
static CLASS_ATTR( version, S_IRUGO, class_version, NULL );

Erklärungsbedürftig ist insbesondere der erste Parameter des Makros. Der Name des Übergabeobjektes setzt sich nämlich aus drei Komponenten zusammen:

  1. dem Namensvorsatz »driver«oder »device«,

  2. dem Wort »attr« und

  3. dem ersten Parameter.

Im ersten Fall würde demnach unser Übergabeobjekt »driver_attr_version«, im zweiten Fall »device_attr_freq« und im dritten Fall »class_attr_version« lauten.

Der zweite Parameter bestimmt die Zugriffsrechte (siehe Tabelle Definitionen für Zugriffsrechte). Wird nur eine Lesefunktion zur Verfügung gestellt, dürfen die Zugriffsrechte maximal den lesenden Zugriff erlauben.

Der dritte respektive vierte Parameter des Makros stellen die Adressen der Lese- und der Schreibfunktion dar.

Der letzte Schritt nach der Erstellung der Lese- und Schreibfunktion und der Definition des Übergabeobjektes ist die Beauftragung des Gerätemodells mit der Erstellung der Attributdatei. Hierzu dient die Funktion driver_create_file, device_create_file oder class_create_file.

    driver_create_file( &speaker_driver, &driver_attr_version );
    device_create_file( &speaker_dev.dev, &dev_attr_freq );
    class_create_file( &new_class, &class_attr_version );

Direkt nach dem Aufruf kann eine Applikation bereits auf die Attributdatei zugreifen und damit für den Aufruf der zugehörigen Lese- und Schreibfunktion sorgen. Beispiel Unterstützung des Gerätemodells im Treiber zeigt vollständig, wie ein Treiber- und Geräteobjekt erstellt wird, wie beide miteinander verknüpft werden sowie wie Attributdateien anzulegen sind.

Beispiel 7-10. Unterstützung des Gerätemodells im Treiber

#include <linux/fs.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>

#define DRIVER_MAJOR 240

MODULE_LICENSE("GPL");

static struct file_operations fops;
static DECLARE_COMPLETION( dev_obj_is_free );
static int frequenz; // Zustandsvariable des Gerätes

static void mydevice_release( struct device *dev )
{
    complete( &dev_obj_is_free );
}

struct platform_device mydevice = {
    .name  = "MyDevice",
    .id    = 0,
    .dev = {
        .release = mydevice_release,
    }
};

static struct device_driver mydriver = {
    .name = "MyDevDrv",
    .bus = &platform_bus_type,
};

static ssize_t read_freq( struct device *dev, char *buf )
{
    snprintf(buf, 256, "frequenz: %d", frequenz ); 
    return strlen(buf)+1;
}

static ssize_t write_freq( struct device *dev, const char *buf, size_t count )
{
    frequenz = simple_strtoul( buf, NULL, 0 );
    return strlen(buf)+1;
}

static DEVICE_ATTR( freq ,S_IRUGO|S_IWUGO, read_freq, write_freq );

static int __init drv_init(void)
{
    if(register_chrdev(DRIVER_MAJOR, "MyDevice", &fops) == 0) {
        driver_register(&mydriver);           // register the driver
        platform_device_register( &mydevice );// register the device
        mydevice.dev.driver = &mydriver;      // now tie them together
        device_bind_driver( &mydevice.dev );  // links the driver to the device
        device_create_file( &mydevice.dev, &dev_attr_freq );
        return 0;
    }
    return -EIO;
}

static void __exit drv_exit(void)
{
    device_remove_file( &mydevice.dev, &dev_attr_freq );
    device_release_driver( &mydevice.dev );
    platform_device_unregister( &mydevice );
    driver_unregister(&mydriver);
    unregister_chrdev(DRIVER_MAJOR,"MyDevice");
    wait_for_completion( &dev_obj_is_free );
}

module_init( drv_init );
module_exit( drv_exit );

Zum Aufräumen müssen die zuvor angelegten Attributdateien wieder gelöscht werden. Die dazu aufzurufenden Funktionen lauten driver_remove_file, device_remove_file und class_remove_file. Ihre Anwendung könnte folgendermaßen aussehen:

    driver_remove_file( &speaker_driver, &driver_attr_version );
    device_remove_file( &speaker_dev.dev, &dev_attr_freq );
    class_remove_file( &new_class, &class_attr_version );

Nicht nur für Plattform-Geräte, sondern auch für die Treiber und Geräte der Bussysteme lassen sich Attributdateien zur Verfügung stellen. So enthält beispielsweise die Datenstruktur struct pci_driver ein struct device_driver und die Datenstruktur struct pci_dev ein struct device, also die Objekte, die zur Erstellung der Attributdateien notwendig sind. Während der Treiberinitialisierung werden die Attributdateien des Treibers, während der Geräteinitialisierung diejenigen des Gerätes angelegt:

    driver_create_file( &pcidrv.driver, &driver_attr_mytext );
    device_create_file( &pcidev->dev, &dev_attr_mytext );

7.2.5. Neue Bussysteme anlegen

Nicht nur neue Klassen, sondern auch neue Bussysteme lassen sich im Gerätemodell sehr einfach anlegen. Dazu wird ein Objekt vom Typ struct bus_type definiert und mit Hilfe der Funktion bus_register dem Linux-Gerätemodell übergeben:

static struct bus_type can = {
    .name = "CAN",
};
...
static int __init my_module_init(void)
{
    ...
    bus_register( &can );
...


Lizenz