Externe Bus-Systeme

In jüngerer Zeit ist mit den Interface-Bus-Systemen eine ganze Klasse von externen Bus-Systemen hinzugekommen. Dazu gehören USB, FireWire und IEEE1284 (ein Parallel-Port-basierter externer Bus). Diese Interfaces ähneln älterer und nicht so externer Technologie wie PCMCIA/CardBUS und sogar SCSI.

Konzeptionell sind diese Busse weder vollständige Interface-Busse (wie PCI) noch dumme Kommunikationskanäle (wie serielle Ports). Es ist schwer, die Software zu klassifizieren, mit der die Fähigkeiten dieser Busse ausgenutzt werden, weil sie normalerweise in zwei Ebenen aufgeteilt wird: in den Treiber für den Hardware-Controller (wie Treiber für PCI-SCSI-Adapter oder die in “the Section called Die PCI-Schnittstelle” eingeführten PCI-Controller) einerseits und in die Treiber für die jeweiligen “Client”-Geräte andererseits (ähnlich wie sd.c generische SCSI-Festplatten steuert und sogenannte PCI-Treiber für in den Bus eingesteckte Karten zuständig sind).

Es gibt aber noch ein anderes Problem mit diesen neuen Bussen. Mit Ausnahme von USB ist die Unterstützung entweder noch nicht ausgereift oder muß überarbeitet werden (letzteres trifft insbesondere auf das SCSI-Kernel-Subsystem zu, das nach Aussage mehrerer der besten Kernel-Hacker alles andere als optimal ist).

USB

USB, der Universal Serial Bus, ist der einzige externe Bus, der derzeit so ausgereift ist, daß es sich lohnt, ihn hier zu besprechen. Topologisch ist ein USB-Subsystem nicht als Bus gestaltet, sondern eher als Baum aus mehreren Punkt-zu-Punkt-Verbindungen. Die Verbindungen sind vierdrahtige Kabel (Masse, Spannung und zwei Signalleitungen), die ein Gerät und einen Hub verbinden (wie bei Twisted Pair-Ethernet). Computer der PC-Klasse sind normalerweise mit einem “Wurzel-Hub” ausgerüstet und enthalten zwei Buchsen für externe Verbindungen. Sie können hier entweder Geräte oder weitere Hubs anschließen.

Aus technologischer Sicht ist der Bus nicht weiter aufregend, ist handelt sich um eine Single-Master-Implementation, in der der Host-Computer die diversen Geräte der Reihe nach abfragt. Trotz dieser eingebauten Beschränkung hat der Bus eine Reihe interessanter Merkmale. So bietet er beispielsweise einem Gerät die Möglichkeit, für seine Übertragungen eine feste Bandbreite anzufordern, um Video- und Audio-I/O verläßlich zu unterstützen. Außerdem arbeitet USB als reiner Kommunikationskanal zwischen dem Gerät und dem Host und verlangt keine spezielle Bedeutung oder Struktur der gelieferten Daten.[1]

Dadurch unterscheidet sich USB von SCSI und anderen standardisierten seriellen Medien.

Diese Merkmale machen USB gemeinsam mit der eingebauten Hot-Plug-Fähigkeit zu einer praktischen und billigen Lösung, wenn es um das Verbinden (und das Aufheben von Verbindungen) mehrerer Geräte mit einem Computer geht, ohne das System herunterfahren, das Gehäuse öffnen und über die Schrauben und Kabel fluchen zu müssen. USB wird im PC-Markt beliebt, bleibt aber für Hochgeschwindigkeitsgeräte ungeeignet, weil die maximale Übertragungsrate 12 MByte pro Sekunde beträgt.

USB wird von der Version 2.2.18 (und neuer) sowie von den 2.4.x-Versionen des Linux-Kernels unterstützt. Der USB-Controller gehört immer zu einer von zwei Arten, und beide werden vom Standard-Kernel mit einem Treiber unterstützt.

Einen USB-Treiber schreiben

Was “Client”-Gerätetreiber angeht, ist der Ansatz bei USB ähnlich wie beim pci_driver-Layout: Der Gerätetreiber registriert sein Treiber-Objekt beim USB-Subsystem und verwendet später die Hersteller- und Geräte-Bezeichner, um das Ankoppeln "seiner" Hardware zu erkennen.

Die relevante Datenstruktur ist struct usb_driver und wird typischerweise folgendermaßen verwendet:


#include <linux/usb.h>

static struct usb_driver sample_usb_driver = {
        name:        "sample",
        probe:       sample_probe,
        disconnect:  sample_disconnect,
};

int init_module(void)
{
    /* nur registrieren, gibt 0 oder einen Fehler-Code zurück */
    return usb_register(&sample_usb_driver);
}

void cleanup_module(void)
{
    usb_deregister(&sample_usb_driver);
}

Die in der Datenstruktur deklarierte probe-Funktion wird vom USB-Subsystem im Kernel immer dann aufgerufen, wenn ein neues Gerät mit dem System verbunden wird (oder wenn ein Treiber geladen wird und sich noch herrenlose Geräte am Bus befinden).

Jedes Gerät identifiziert sich selbst, indem es dem System die Bezeichner für Hersteller, Gerät und Klasse mitteilt, ähnlich wie bei PCI-Geräten. Die Aufgabe von sample_probe ist es daher, sich diese Informationen anzuschauen und gegebenenfalls ein Gerät für sich zu beanspruchen.

Um ein Gerät zu beanspruchen, gibt die Funktion einen von NULL verschiedenen Zeiger zurück, der zur Identifikation des Geräts verwendet wird. Dies wird normalerweise ein Zeiger auf die gerätespezifische Datenstruktur sein, die dem Gerätetreiber als Ganzes zugrundeliegt.

Um Informationen mit dem Gerät auszutauschen, müssen Sie dem USB-Subsystem dann mitteilen, wie Sie kommunizieren wollen. Dazu wird eine Struktur struct urb (für USB Request Block) ausgefüllt an usb_submit_urb übergeben. Dieser Schritt wird normalerweise in der open-Methode, die zur Gerätedatei gehört, oder in einer äquivalenten Funktion durchgeführt.

Beachten Sie, daß nicht jeder USB-Treiber seine eigenen Gerätedateien samt Anfordern von Major-Nummern und so weiter implementieren muß. Geräte, die in eine Klasse fallen, für die der Kernel verallgemeinerte Unterstützung anbietet, haben keine eigenen Gerätedateien und melden ihre Informationen auf andere Weise.

Ein Beispiel solcher verallgemeinerter Verwaltung sind Eingabegeräte. Wenn Ihr USB-Gerät ein Eingabegerät (wie ein Grafiktablett) ist, allozieren Sie keine Major-Nummern, sondern registrieren Ihre Hardware durch Aufrufen von input_register_device. In diesem Fall ist der open-Callback Ihres Eingabegeräts dafür zuständig, durch einen Aufruf von usb_submit_urb die Kommunikation aufzubauen.

Ein USB-Eingabe-Treiber muß sich daher auf mehrere andere Systembestandteile verlassen, von denen die meisten wiederum Module sein können. Die Modul-Stapel-Architektur für USB-Eingabegeräte-Treiber ist in Abbildung 15-3 zu sehen.

Abbildung 15-3. Für USB-Eingabegeräte verwendete Module

Einen vollständigen USB-Gerätetreiber finden Sie in den Beispieldateien auf dem O'Reilly-FTP-Server. Es handelt sich dabei um einen stark vereinfachten Tastatur- und Maus-Treiber, an dem Sie sehen können, wie man einen vollständigen USB-Treiber aufbaut. Um diesen einfach zu halten, verwendet er nicht das Eingabe-Subsystem, um Ereignisse zu melden, sondern schreibt Meldungen mit printk. Sie brauchen mindestens eine USB-Tastatur oder eine USB-Maus, um den Treiber auszuprobieren.

> > > Über USB gibt es derzeit eine ganze Menge an Dokumentation, darunter zwei Artikel von einem der Autoren, deren Stil und technisches Niveau dem von Linux-Gerätetreiber ähnelt. Diese Artikel enthalten sogar einen vollständigeren Beispiel-USB-Gerätetreiber, der das Eingabe-Subsystem des Kernels verwendet und auch auf alternative Weise betrieben werden kann, wenn Sie gerade keine USB-Geräte parat haben. Sie finden den Artikel unter http://www.linux.it/kerneldocs.

Fußnoten

[1]

Ein wenig Struktur gibt es allerdings schon, aber diese läßt sich im wesentlichen auf die Anforderung reduzieren, daß jede Kommunikation in eine von wenigen vordefinierten Klassen passen muß. So wird eine Tastatur keine Bandbreite reservieren, während eine Kamera das durchaus tun kann.