Automatische und manuelle Konfiguration

Einige der Parameter, die ein Treiber benötigt, können von System zu System unterschiedlich sein. Beispielsweise muß ein Treiber die verwendeten I/O-Adressen oder Speicherbereiche kennen (dies ist bei gut durchdachten Bus-Interfaces kein Problem und betrifft nur ISA-Geräte). Manchmal müssen Sie Parameter an einen Treiber übergeben, um diesem dabei zu helfen, sein eigenes Gerät zu finden oder bestimmte Features ein- oder auszuschalten.

Je nach Gerät können auch noch andere Parameter außer der I/O-Adresse das Verhalten des Treibers beeinflussen, beispielsweise die Gerätemarke und die Versionsnummer. Ein Treiber muß die Werte dieser Parameter kennen, um korrekt arbeiten zu können. Einen Treiber mit den korrekten Werten zu versorgen (also ihn zu konfigurieren) ist eine der kniffligen Aufgaben, die während der Treiber-Initialisierung vollbracht werden müssen.

Im Grunde genommen gibt es nur zwei Möglichkeiten, an die korrekten Werte heranzukommen: entweder gibt der Benutzer sie explizit an oder der Treiber ermittelt sie selbst (Auto-Detection). Während Auto-Detection zweifellos die beste Variante ist, ist die Konfiguration durch den Benutzer deutlich einfacher zu implementieren. Eine sinnvolle Wahl könnte es sein, die automatische Konfiguration wo immer möglich zu implementieren, es aber zu erlauben, daß vom Benutzer übergebene Werte die Auto-Detection überschreiben. Dieser Ansatz hat noch den zusätzlichen Vorteil, daß der Treiber zunächst ohne Auto-Detection entwickelt werden kann, indem die Parameter zur Ladezeit angegeben werden. Die Auto-Detection kann dann später implementiert werden.

Viele Treiber haben auch Konfigurationsoptionen, die andere Aspekte ihrer Arbeit steuern. Beispielsweise gibt es in Treibern für SCSI-Adapter oft Optionen zur Steuerung des Tagged Command Queueings, und IDE-Treiber (Integrated Device Electronics) erlauben dem Benutzer die Steuerung der Hardware-Operationen. Selbst wenn Ihr Treiber vollständig autokonfigurierbar ist, kann es also sinnvoll sein, andere Konfigurationsoptionen für den Benutzer verfügbar zu machen.

Parameterwerte können von insmod oder modprobe zur Ladezeit zugewiesen werden; letzteres kann die Parameter auch aus einer Konfigurationsdatei (normalerweise /etc/modules.conf) auslesen. Die Befehle nehmen Integer- und String-Werte auf der Kommandozeile entgegen. Wenn Ihr Modul also einen Integer-Parameter namens skull_ival und einen String-Parameter namens skull_sval haben soll, könnten die Parameter beim Laden des Moduls mit etwa folgendem insmod-Befehl angegeben werden:



insmod skull skull_ival=666 skull_sval="das Biest"

Bevor insmod aber die Modulparameter verändern kann, muß das Modul diese verfügbar machen. Parameter werden mit dem in module.h definierten Makro MODULE_PARM deklariert. MODULE_PARM erwartet zwei Parameter: den Namen der Variablen und einen String, der den Typ angibt. Das Makro sollte außerhalb aller Funktionen stehen und gehört normalerweise in die Nähe des Anfangs der Quelldatei. Die beiden oben erwähnten Parameter könnten mit den folgenden Zeilen deklariert werden:



int skull_ival=0;
char *skull_sval;

MODULE_PARM (skull_ival, "i");
MODULE_PARM (skull_sval, "s");

Es werden derzeit fünf Typen von Modulparametern unterstützt: b, ein Byte; h, ein Short-Wert (zwei Bytes); i, ein Integer-Wert; l, ein Long-Wert, und s, ein String. Im Falle von String-Werten sollte eine Zeiger-Variable deklariert werden; insmod alloziert den Speicher für den vom Benutzer angegebenen Parameter und setzt die Variable entsprechend um. Ein dem Typ vorangestellter Integer-Wert gibt ein Array der angegebenen Länge an; zwei Zahlen, die von einem Bindestrich getrennt sind, geben eine minimale und eine maximale Anzahl von Werten an. Wenn Sie die Beschreibung dieses Features durch den Autor lesen möchten, verweisen wir Sie auf die Header-Datei <linux/module.h>.

Als Beispiel könnte ein Array, das wenigstens zwei und maximal vier Werte haben muß, folgendermaßen deklariert werden:


 int skull_array[4];
 MODULE_PARM (skull_array, "2-4i");

Es gibt auch noch das Makro MODULE_PARM_DESC, mit dem der Programmierer eine Beschreibung des Parameters angeben kann. Diese Beschreibung wird in der Objektdatei gespeichert und kann dort mit einem Hilfsprogramm wie objdump oder automatisierten Werkzeugen zur Systemverwaltung angezeigt werden. Auch hierzu ein Beispiel:


 int base_port = 0x300;
 MODULE_PARM (base_port, "i");
 MODULE_PARM_DESC (base_port, "Der Basis-I/O-Port (Default 0x300)");

Alle Modulparameter sollten einen Default-Wert haben; insmod ändert den Wert nur, wenn der Benutzer das ausdrücklich anfordert. Das Modul kann explizite Parameter finden, indem es Parameter mit deren Default-Werten vergleicht. Entsprechend kann eine automatische Konfiguration folgendermaßen funktionieren: Wenn die Konfigurationsvariablen den Default-Wert haben, führe die automatische Erkennung durch; ansonsten behalte den aktuellen Wert. Damit diese Technik funktioniert, sollte der “Default”-Wert immer ein Wert sein, den der Benutzer nie selbst angeben würde.

Der folgende Code zeigt, wie skull Port-Adressen eines Gerätes automatisch ermittelt. In diesem Beispiel wird die Auto-Detection verwendet, um nach mehreren Geräten zu suchen, während die manuelle Konfiguration auf ein einziges Gerät beschränkt ist. Beachten Sie, daß wir die Funktion skull_detect schon oben gezeigt haben, während skull_init_board die treiberspezifische Initialisierung vornimmt und deswegen nicht gezeigt wird.


 
/*
 * Port-Bereiche: das Geraet kann in Schritten von 0x10 zwischen 0x280
 * und 0x300 liegen. Es verwendet 0x10 Bytes große Ports.
 */
#define SKULL_PORT_FLOOR 0x280
#define SKULL_PORT_CEIL  0x300
#define SKULL_PORT_RANGE 0x010

/*
 * Die folgende Funktion führt die Auto-Detection durch, wenn der
 * Variablen "skull_port_base" von insmod kein
 * Wert zugewiesen worden ist.
 */

static int skull_port_base=0; /* 0 erzwingt Auto-Detection */
MODULE_PARM (skull_port_base, "i");
MODULE_PARM_DESC (skull_port_base, "Base I/O port for skull");

static int skull_find_hw(void) /* gibt die Anzahl der Geraete zurück */
{
    /* base ist entweder der Wert, der beim Laden uebergeben wurde,
     * oder der erste Versuch */
    int base = skull_port_base ? skull_port_base
                             : SKULL_PORT_FLOOR;
    int result = 0;

    /* Schleife einmal durchlaufen, wenn ein Wert zugewiesen wurde,
     * ansonsten bei der Auto-Detection alle probieren */
    do {
        if (skull_detect(base, SKULL_PORT_RANGE) == 0) {
            skull_init_board(base);
            result++;
        }
        base += SKULL_PORT_RANGE; /* für naechsten Versuch vorbereiten */
    }
    while (skull_port_base == 0 && base < SKULL_PORT_CEIL);

    return result;
}

Wenn die Konfigurationsvariablen nur im Treiber verwendet werden (also nicht in der Symboltabelle des Kernels veröffentlicht werden), dann kann der Treiber-Autor dem Benutzer das Leben etwas einfacher machen, indem er das Präfix der Variablennamen (hier skull_) wegläßt. Die Präfixe haben für den Benutzer außer zusätzlicher Schreibarbeit normalerweise keine Bedeutung.

Aus Gründen der Vollständigkeit führen wir hier noch die drei anderen Makros auf, die Dokumentation in die Objektdatei schreiben:

MODULE_AUTHOR (name)

Schreibt den Namen des Autors in die Objektdatei.

MODULE_DESCRIPTION (desc)

Schreibt eine Beschreibung des Moduls in die Objektdatei.

MODULE_SUPPORTED_DEVICE (dev)

Schreibt einen Eintrag, der angibt, welche Geräte unterstützt werden, in das Modul. Kommentare in den Kernel-Quellen legen nahe, daß dieser Parameter irgendwann einmal zum automatischen Laden von Modulen verwendet werden könnte; davon wird aber derzeit noch kein Gebrauch gemacht.