8.2. USB-Subsystem

Bei moderner Hardware geht der Trend zunehmend hin zu intelligenteren Bussystemen, zu PCI und USB. Während PCI ein paralleles Bussystem ist, welches vornehmlich die Verteilung der Ressourcen am Bus verwaltet und dem Entwickler noch den direkten Zugriff auf die Hardware erlaubt, handelt es sich beim Universal Serial Bus (USB) um ein serielles Bussystem mit Baumstruktur. An der Wurzel dieses Baumes sitzt der Host, die Abzweigungen stellen die Hubs dar und die Blätter schließlich sind die Geräte (siehe Abbildung Physikalische Struktur des USB).

Abbildung 8-6. Physikalische Struktur des USB

Das Besondere an diesem Bussystem ist, dass ein Treiber mit seinem zugeordneten Gerät über eine Paketschnittstelle kommuniziert, anstatt direkte Hardwarezugriffe durchzuführen. Für die Hardwarezugriffe ist der USB-Hostcontroller zuständig, der selbst wiederum durch das USB-Subsystem (USB-Core) bedient wird.

Damit gibt es drei unterschiedliche Treiberebenen:

  1. Die Ansteuerung des USB-Hostcontrollers (Hostcontroller-Treiber).

  2. Die Ansteuerung des Gerätes (Hostseite, Hosttreiber).

  3. Die Bedienung von USB innerhalb eines Slaves (Geräteseite, Gadget-Treiber).

Der Entwickler ist wohl hauptsächlich mit der Programmierung eines Treibers auf Hostseite konfrontiert. Entwickler eingebetteter System müssen sich daneben des Öfteren mit der Geräteseite – man spricht hier von den Gadget-Treibern – auseinander setzen. In nur sehr seltenen Fällen ist der Treiber für den Hostcontroller selbst zu konzipieren.

In den folgenden Abschnitten soll in die Treiberentwicklung zur Ansteuerung des Gerätes (Hosttreiber) eingeführt werden. Da die zugehörige USB-Treiberschnittstelle viel zu komplex ist, um an dieser Stelle einen auch nur halbwegs erschöpfenden Überblick geben zu können, werden nur die wesentlichen Basismechanismen, die Initialisierung und ein einfacher Datenaustausch vorgestellt. Die Aspekte Hostcontroller-, Devicecontroller-und Gadget-Treiber bleiben komplett außen vor. Dokumentation zur Entwicklung eines Gadget-Treibers findet sich unter [Brownell2003]. Darüber hinaus wird gegenwärtig auch die Entwicklung von USB-Treibern im User-Space praktiziert. Weitere Erläuterungen zu diesem Aspekt finden sich unter [Erdfelt2003].

8.2.1. USB programmtechnisch betrachtet

Der Universal Serial Bus (USB) stellt ein serielles Bussystem dar, über welches Peripheriekomponenten mit dem Rechner verbunden werden können. Bei einer Datenübertragungsrate von 1,5 MBit/s (lowspeed), 12 MBit/s (fullspeed) oder 480 MBit/s (highspeed) eignet sich der USB zum Anschluss von Mäusen, Tastaturen, Digitalkameras, Scannern, Druckern oder Filmkameras.

Abbildung 8-7. Softwaremodell des USB-Subsystems

Das hostseitige USB-Subsystem des Linux-Kernels besteht aus zwei Schichten (siehe Softwaremodell des USB-Subsystems):

  1. Der Low-level-Treiberschicht (Hostcontroller) und

  2. der Core-Schicht.

Die Low-level-Treiber bedienen den jeweiligen USB-Controller in einem Rechner. Sie stellen Funktionen zum Empfangen und Versenden der USB-Pakete an die Peripheriegeräte zur Verfügung.

Das USB-Coresystem ist für die Identifikation der Hardware und für die Auswahl des richtigen USB-Hosttreibers zuständig.

Die Hosttreiber selbst bedienen schließlich die über USB angeschlossenen Geräte, also Scanner, Modems, Drucker, Webcams oder Digital-Kameras. Diese Treiber nutzen die Funktionalität, welche das USB-Coresystem zur Verfügung stellt. Sie registrieren sich mit der Angabe, für welche Geräte (zum Beispiel durch Angabe einer Vendor- und Device-ID) sie zuständig sind.

Abbildung 8-8. Datentransfer zwischen USB-Controller und Geräten

Per USB werden Pakete von einem Controller zu den einzelnen Peripheriegeräten übertragen (siehe Abbildung Datentransfer zwischen USB-Controller und Geräten). Dabei sind 4 Übertragungsarten definiert:

  1. Kontroll-Transfer (control)

    Dieses sind kurze Pakete, die Konfigurationsinformationen zum Gerät bzw. Statusinformationen vom Gerät holen.

  2. Bulk-Transfer (bulk)

    Diese Pakete werden für den »normalen« Datenaustausch zwischen Controller und Gerät genutzt.

  3. Interrupt-Transfer (int)

    Diese Art des Datenaustausches basiert auf einem periodischen Datentransfer zwischen Host und Device.

  4. Isochronous-Transfer (iso)

    Hier findet die Übertragung in Realzeit mit fester Bandbreite statt.

Auch wenn die Geräte physikalisch in einer Baum-Struktur angeordnet sind, liegt logisch ein Bus vor, bei dem der Host die einzelnen Geräte direkt ansprechen kann. Jedes Gerät bekommt – wenn es an den USB angeklemmt wird – eine eindeutige Adresse. Das Gerät selbst definiert innerhalb des Gerätes Interfaces und diese wiederum Endpunkte. Die Software auf dem Host kommuniziert nun mit dem Gerät, indem es Daten aus einem Speicherblock an den Endpunkt eines Interfaces schickt oder Daten von dem Endpunkt des Interfaces in den Speicherblock übergeben bekommt.

Zur Adressierung sind damit notwendig:

  1. die Gerätekennung,

  2. die Interfacenummer und

  3. die Nummer des Endpunktes.

Das Management des Gerätes wird über die Kontrollmeldungen abgewickelt. Der USB-Standard [USB2.0] hat festgelegt, dass jedes Gerät die folgenden Managementdienste (definiert in <linux/usb_ch9.h>) unterstützen muss:

USB_REQ_GET_STATUS
USB_REQ_CLEAR_FEATURE
USB_REQ_SET_FEATURE
USB_REQ_SET_ADDRESS
USB_REQ_GET_DESCRIPTOR
USB_REQ_SET_DESCRIPTOR
USB_REQ_GET_CONFIGURATION
USB_REQ_SET_CONFIGURATION
USB_REQ_GET_INTERFACE
USB_REQ_SET_INTERFACE
USB_REQ_SYNCH_FRAME

Diese Dienste können zumindest teilweise an das Gerät, das Interface oder einen Endpunkt gerichtet sein. Alle Details zu den Parametern und den Returnwerten der Dienste finden sich in [USB2.0].

Abbildung 8-9. Datenstrukturen und Funktionen eines USB-Treibers ohne die Funktionen zum Datenaustausch

Für einen Hosttreiber ergeben sich folgende Abläufe:

  1. Während der Treiberinitialisierung meldet sich der Treiber beim USB-Subsystem an. Dabei übergibt er sowohl die Gerätekennung als auch Zeiger auf Funktionen zur Geräteinitialisierung und Gerätedeinitialisierung.

  2. Die vom Treiber zur Verfügung gestellte Funktion zur Geräteinitialisierung wird aufgerufen, sobald das USB-Subsystem ein Gerät ausgemacht hat, welches der vom Treiber übergebenen Gerätekennung entspricht.

  3. Vorgehen während der Geräteinitialisierung:

    1. Es wird festgestellt, ob das USB-Gerät wirklich vom Treiber bedient werden soll.

    2. Der Treiber meldet sich entweder bei einem sonstigen Subsystem des Kernels (z.B. Netzwerk-Subsystem) an oder nutzt für die Interaktion mit einer Applikation die USB-Major-Nummer (standardmäßig »180«) und lässt sich vom USB-Subsystem dazu eine Minor-Nummer zuteilen.

      Wird die USB-Major-Nummer genutzt, muss zunächst eine usb_class_driver Struktur angelegt werden. Mit dieser registriert der Treiber das spezifische Gerät beim Subsystem und bekommt im Erfolgsfall die Minor-Nummer mitgeteilt.

      Mit Kenntnis der dynamisch zugeteilten Minor-Nummer lässt sich nun benutzerseitig für den Zugriff auf das Gerät eine Gerätedatei anlegen.

  4. Jetzt können die Zugriffe durch die Applikation auf den Treiber erfolgen. Muss der Treiber dabei mit seinem Gerät kommunizieren, erledigt er dieses, indem er entsprechende Pakete, so genannte USB Request Blocks (URBs), verschickt.

  5. Wird das Gerät abgesteckt, informiert das USB-Subsystem den Treiber über die disconnect-Funktion. Diese beendet die gerade aktiven Zugriffe auf das Gerät und vermerkt, dass das Gerät entfernt wurde.

  6. Wird der Treiber entladen (Modul-Deinitialisierung), meldet er sich selbst beim USB-Subsystem wieder ab.

Datenstrukturen und für einen Hosttreiber zu implementierende Funktionen sind in Abbildung Datenstrukturen und Funktionen eines USB-Treibers ohne die Funktionen zum Datenaustausch dargestellt.

8.2.2. Den Treiber beim USB-Subsystem registrieren

Innerhalb der Treiberinitialisierung (die Funktion, die im Makro module_init angegeben ist) meldet sich der Treiber beim USB-Coresystem durch Aufruf der Funktion usb_register an. Alle notwendigen Informationen befinden sich dabei in einer zu übergebenen Datenstruktur vom Typ struct usb_driver (im folgenden Beispiel »usbcheck« genannt).

static int __init usbcheck_init(void)
{
    if( usb_register(&usbcheck) ) {
        printk("usbcheck: unable to register usb driver\n");
        return -EIO;
    }
    return 0;
}

Von der struct usb_driver sind insbesondere die Felder

auszufüllen. Das sieht beispielsweise folgendermaßen aus:
static struct usb_driver usbcheck = {
    .name= "usbcheck",
    .probe= usbcheck_probe,
    .disconnect= usbcheck_disconnect,
    .id_table= usbcheckids,
};

Die Merkmale (usbcheckids) eines USB-Gerätes können sein:

Herstellerkennung
Produktid
niedrigste Versionsnummer
höchste Versionsnummer
Geräteklasse
Geräteunterklasse
Geräteprotokoll
Interface-Klasse
Interface-Unterklasse
Interface-Protokoll

Die Tabelle mit der Spezifikation solcher Merkmale (id_table) muss nicht zwingenderweise angegeben werden, insbesondere sind auch nicht sämtliche Merkmale eines Gerätes zu spezifizieren. Wird keine Tabelle übergeben, wird die probe-Funktion mit jedem Gerät aufgerufen, dem bisher kein Treiber zugeordnet ist. Ansonsten erfolgt der Aufruf dann, wenn ein Gerät angesteckt ist, welches den in der Tabelle übergebenen Merkmalen entspricht. Welche Merkmale dabei überprüft werden sollen, lässt sich über das erste Element der Datenstruktur (struct usb_device_id), den match_flags, spezifizieren. Dieses Feld lässt sich bequem über Definitionen, die in der Header-Datei <linux/mod_devicetable.h> spezifiziert sind, setzen. Wird beispielsweise das Bit »USB_DEVICE_ID_MATCH_VENDOR« gesetzt, überprüft das USB-Subsystem, ob die Vendor-ID des Gerätes mit der hier gesetzten Vendor-ID übereinstimmt. Ist dies der Fall, wird der Treiber aktiviert. Sollen mehrere Merkmale überprüft werden, sind die einzelnen Bits des Feldes match_flags miteinander zu verodern.

Noch bequemer ist es, die Flags gemeinsam mit den Werten in die struct usb_device_id einzutragen. Dazu sind die Makros USB_DEVICE, USB_DEVICE_VER, USB_DEVICE_INFO oder USB_INTERFACE_INFO vorgesehen.

static struct usb_device_id usbcheckids[] = {
    { USB_DEVICE(USB_VENDOR_ID, USB_PRODUCT_ID) },
    { }                    /* Terminating entry */
};

MODULE_DEVICE_TABLE( &usbcheckids );

Um ein automatisches Laden des Treibers zu ermöglichen, sollte die Tabelle mit Hilfe des Makros MODULE_DEVICE_TABLE in den User-Bereich exportiert werden (siehe Kapitel Hotplug).

In der Deinitialisierungsfunktion disconnect müssen allozierte Ressourcen freigegeben werden. Danach kann sich der Treiber beim USB-Coresystem wieder abmelden. Dazu verwendet er die Funktion usb_deregister. Diese Funktion erwartet ebenfalls die Datenstruktur vom Typ struct usb_driver als Parameter.

static void __exit usbcheck_exit(void)
{
    usb_deregister(&usbcheck);	
}

8.2.3. Die Geräteinitialisierung und die -deinitialisierung

Ist ein passendes Gerät vom USB-Subsystem erkannt worden, wird die vormals übergebene probe-Funktion des Treibers aufgerufen. In dieser wird überprüft, ob das Gerät tatsächlich vom Treiber bedient werden soll. Ist dies nicht der Fall, gibt die Funktion einen Fehlercode zurück.

Soll das Gerät aber durch den Treiber bedient werden, muss zunächst eine Möglichkeit geschaffen werden, dass eine Applikation auf den Treiber zugreifen kann. Bei USB-Treibern ist es nicht gebräuchlich, sich über Ausnutzung der Funktion register_chrdev beim IO-Subsystem als normaler zeichenorientierter Gerätetreiber anzumelden. In vielen Fällen ist es erst gar nicht notwendig, weil das über USB zu bedienende Gerät über ein weiteres Subsystem (z.B. Netzwerk) angesprochen wird.

Generell hat sich das USB-Subsystem als Ganzes stellvertretend für die an ihm angesteckten Geräte bereits als ein zeichenorienterter Gerätetreiber registriert. Hierbei ist die Major-Nummer »180« verwendet worden. Falls der Treiber diese Major-Nummer mitnutzen möchte, meldet er das Gerät beim USB-Subsystem mit Hilfe der Funktion usb_register_dev als USB-Gerät an. Hierbei wird das Gerät in der Datenstruktur struct usb_class_driver genauer definiert. Diese wird (falls das Device-Filesystem aktiviert ist) genutzt, um unter dem Verzeichnis /dev/ eine Gerätedatei mit den in der Struktur enthaltenen Informationen anzulegen. Dazu gehört der Name der Datei, die Art (z.B. Character-Device) und die Minor-Nummer. Außerdem wird hier eine Struktur mit den Adressen der Funktionen angegeben, die aufgerufen werden sollen, sobald die Applikation einen entsprechenden Aufruf macht (z.B. read und write).

Im Rahmen der probe-Funktion werden weitere Initialisierungen vorgenommen. Möglicherweise werden bereits die Speicherblöcke angelegt, über die der später erfolgende Datenaustausch mit dem Gerät stattfindet (siehe Beispiel Codefragment USB-Probefunktion).

Beispiel 8-4. Codefragment USB-Probefunktion

...
struct usb_class_driver class_descr = {
    .name = "usbcheck%d",
    .fops = &usb_fops,
    .mode = S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH,
    .minor_base = 16,
};
...
static int usbcheck_probe(struct usb_interface *interface,
    const struct usb_device_id *id)
{
    dev = interface_to_usbdev(interface);
    if(dev->descriptor.idVendor==0x05a9 && dev->descriptor.idProduct==0xa511) {
        if( usb_register_dev( interface, &class_descr ) )
            return -EIO;
        printk("got minor= %d\n", interface->minor );
        return 0;
    }
    return -ENODEV;
}

Die Gerätedeinitialisierung wird aufgerufen, falls entweder das USB-Gerät abgeklemmt wurde oder aber der Treiber entladen wird. Die entsprechende Funktion ist bei der Anmeldung des Treibers im Element .disconnect dem USB-Subsystem übergeben worden. Im einfachsten Fall meldet sich der Treiber bei dem Subsystem wieder ab, bei dem er sich während der Geräteinitialisierung registriert hat. Im Beispiel hat der Treiber das identifizierte Gerät beim USB-Subsystem selbst angemeldet, daher reicht zum Abmelden der Aufruf von usb_deregister_dev aus. Zu beachten ist, dass die Funktion erst dann beendet werden darf, wenn alle ausstehenden USB-Transfers abgeschlossen sind. Synchron ablaufende Transfers, beispielsweise der Aufruf von usb_control_msg, werden dazu über Semaphore geschützt, asynchron ablaufende Transfers über ein Completion-Objekt.

static void usbcheck_disconnect( struct usb_interface *iface )
{
    down( &ulock );
    usb_deregister_dev( iface, &class_descr );
    up( &ulock );
}

8.2.4. Auf das USB-Gerät zugreifen

Um auf ein USB-Gerät zugreifen zu können, muss der Treiber Datenpakete an dieses schicken. Ein solches Paket wird USB Request Block (URB) genannt. Im URB werden alle für den USB-Coretreiber notwendigen Parameter eingetragen: so zum Beispiel die Transferart (»Control«, »Bulk«, »Interrupt«, »Isochronous«), die Zieladresse (Device, Interface, Endpoint), die zu übertragenden Daten und die Länge dieser Daten, ein Timeout für die Übertragung und die Adresse einer Funktion, die aufgerufen werden soll, falls die Übertragung abgeschlossen worden ist.

In der Header-Datei <linux/usb.h> gibt es für die Transferarten »Control«, »Bulk« und »Int« jeweils eine Hilfsfunktion (usb_fill_control_urb, usb_fill_bulk_urb und usb_fill_int_urb) zum Ausfüllen der URB-Datenstruktur. Zuvor muss der URB jedoch mit Hilfe der Funktion usb_alloc_urb angelegt werden. Ist ein solcher URB angelegt und initialisiert, kann er mit Hilfe der Funktion usb_submit_urb an das Gerät gesendet werden. Soll die gestartete Bearbeitung des URB abgebrochen werden, kann dies über die Funktion usb_unlink_urb erfolgen. Ist die Bearbeitung eines URBs durch den USB-Core abgeschlossen, wird die im URB angegebene Funktion (Element complete) aufgerufen. Diese kann das Ergebnis übernehmen und den URB wieder freigeben (Funktion usb_free_urb).

Für die Transferarten »Control« und »Bulk« gibt es Hilfsfunktionen, die das Anlegen eines URBs, das Ausfüllen und das Verschicken dem Programmierer abnehmen: usb_control_msg und usb_bulk_msg. Beide Funktionen warten zudem auf die Antwort, arbeiten also synchron. Daher dürfen sie auch nicht im Interrupt-Kontext verwendet werden. Um Control- und Bulk-Transfers asynchron ablaufen zu lassen, sind die URBs durch den Programmierer zu allozieren, zu initialisieren und über die Funktion usb_submit_urb zu verschicken.

Das Verschicken einer Get-Status-Anfrage kann damit folgendermaßen kodiert werden:

result = usb_control_msg(dev,
    usb_rcvctrlpipe(dev, 0),
    USB_REQ_GET_STATUS,
    USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE,
    0,
    0,
    pstatus,
    sizeof(*pstatus),
    5*HZ);

Der erste Parameter (»dev«) adressiert das USB-Gerät. Das Interface ist per Konfiguration auf dem Gerät ausgewählt, so dass nur noch der Endpunkt bestimmt werden muss. Dieser wird in Form einer so genannten Pipe angegeben – ein Bitfeld, das neben der Geräteadresse, der Endpunktadresse (Nummer) und der Transferrichtung noch die Transferart kodiert. Dem Programmierer steht ein Set von Makros zur Verfügung, um die Kodierung einer Pipe nicht selbst vornehmen zu müssen: der Name des Makros setzt sich zusammen aus der Vorsilbe usb_, der Transferrichtung snd oder rcv und der Transferart ctrl, bulk, iso oder int. Für den obigen Fall bildet sich das Makro damit zu usb_rcvctrlpipe.

Als Nächstes wird das vom Gerät auszuführende Kommando angegeben. Neben den Standardkommandos dürften vor allem gerätespezifische Kommandos (USB_TYPE_VENDOR) für den Entwickler interessant sein. Die Art des Kommandos wird zusammen mit der Transferrichtung und einer Adressatenauswahl (für das Kommando) im folgenden Parameter kodiert. Als Transferrichtung gibt es nur USB_DIR_IN und USB_DIR_OUT (aus Sicht des Hosts). Adressaten für das Kommando sind USB_RECIP_MASK, USB_RECIP_DEVICE, USB_RECIP_INTERFACE, USB_RECIP_ENDPOINT oder USB_RECIP_OTHER.

Danach folgt – wie im USB-Standard verankert – ein möglicherweise zu übertragender Parameter und ein Index, der im Fall eines Kommandos an das Gerät den Wert »0« hat. Wird ein Kommando an ein Interface abgesetzt, wird hier die Interface-Nummer angegeben und bei einem Kommando an den Endpunkt die Endpunktnummer. Als Nächstes folgt die Adresse des Speicherbereiches, in den das Ergebnis abgelegt wird, sowie die Längenangabe des dort zur Verfügung stehenden Speichers. Der Speicherbereich selbst muss per kmalloc angefordert worden sein, da das USB-Subsystem den Datentransfer unter Umständen per DMA vornimmt. Der letzte Parameter schließlich ist der Timeout-Wert für den Request.

Tabelle USB-Sendefunktionen für Standardkommandos zeigt die für das Versenden der Standardkommandos vorgefertigten Funktionen.

Die Nutzung des so genannten Bulk-Transfers ist ähnlich dem des Control-Transfers gestaltet. Über die Funktion usb_bulk_msg wird das Paket verschickt und gleichzeitig auf Antwort gewartet. Allerdings werden hier nur sechs Parameter erwartet: das Gerät, die Pipe, der Speicherblock mit den Daten, die Länge des Speicherblocks, die Adresse einer Variablen, die das Ergebnis aufnimmt, und ein Timeout-Wert:

result = usb_bulk_msg( dev, usb_rcvbulkpipe( dev, 0 ), buffer, count, &count, HZ*2 );

Auch bei dieser Funktion muss der Datenbereich (»buffer«) DMA-fähig und dazu am einfachsten per kmalloc angefordert worden sein. Im Erfolgsfall gibt die Funktion die Anzahl der empfangenen Bytes zurück, ansonsten einen negativen Wert, der dem Fehlercode entspricht.

Abbildung 8-10. Asynchrone USB-Kommunikation

Der Interrupt- und Isochronous-Transfer wird asynchron, also ohne auf das Ergebnis implizit zu warten, abgewickelt. Hierbei wird ein URB entsprechend der gewünschten Transportart vorbereitet und eine Callback-Funktion übergeben, die vom USB-Subsystem aufgerufen wird, sobald der Auftrag abgewickelt wurde. Im Regelfall wird zur Synchronisation ein Completion-Objekt eingesetzt (siehe Bis zum »Ende«). In der Callback-Funktion wird die Funktion complete aufgerufen. An anderer Stelle wird dann auf wait_for_completion gewartet.

Unter der Voraussetzung, dass Sie ein USB-Gerät zur Hand haben, können Sie die Grundmechanismen des USB-Subsystems austesten. Hierzu können Sie den in Beispiel Treibergerüst zum Test des USB-Subsystems abgedruckten Beispielcode verwenden. Sie müssen nur im Quelltext die Hersteller-ID (#define USB_VENDOR_ID) und die Produkt-ID (#define USB_DEVICE_ID) Ihres Gerätes mit den dort verwendeten Werten (»0x05a9« und »0xa511«) austauschen. Außerdem müssen Sie sicherstellen, dass der für das Gerät ursprünglich vorgesehene Treiber nicht geladen wird. Am besten gehen Sie folgendermaßen vor:

  1. Stellen Sie sicher, dass Ihr Kernel USB unterstützt: Dazu muss USB-Support im Kernel konfiguriert sein. Falls der USB-Support über Module erfolgt, stellen Sie durch Aufruf von »lsmod« fest, ob die Hostcontroller-Treiber mit den Namen ehci_hcd, uhci_hcd oder ohci_hcd geladen sind. Falls nicht, müssen Sie diese mit »modprobe uhci_hcd«, »modprobe ehci_hcd« oder »modprobe ohci_hcd« laden.

  2. Identifizieren Sie die Hersteller- und Geräte-ID Ihrer Peripherie. Hierbei kann das Programm »lsusb« behilflich sein. Das Programm »lsusb« bezieht seine Informationen aus dem USB-Filesystem, welches per »mount -t usbfs none /proc/bus/usb« eingehängt werden kann (falls nicht ohnehin bereits geschehen).

    Stecken Sie Ihr Gerät an den USB-Bus und rufen Sie »lsusb« auf. In der Ausgabe sollten Sie Ihre Peripherie wiederfinden:

    (root)# lsusb
    Bus 004 Device 001: ID 0000:0000
    Bus 003 Device 032: ID 05a9:a511 OmniVision Technologies, Inc. OV511+ WebCam
    Bus 003 Device 001: ID 0000:0000
    Bus 002 Device 001: ID 0000:0000
    Bus 001 Device 001: ID 0000:0000
    (root)#

    Im Beispiel wollen wir die Webcam zum Test der Mechanismen innerhalb des USB-Subsystems verwenden. Der Ausgabe ist zu entnehmen, dass die Webcam eine Hersteller-ID von »0x05a9« und eine Produkt-ID von »0xa511« hat. Die IDs Ihrer Geräte müssen Sie in das Codegerüst von Beispiel Treibergerüst zum Test des USB-Subsystems eintragen.

  3. Damit das Hotplug-System den ursprünglich vorgesehenen Treiber nicht einfach lädt, muss dieser in die Datei /etc/hotplug/blacklist eingetragen werden. Diesen Treiber können Sie über die Datei /lib/modules/<kernelversion>/modules.usbmap ausfindig machen.

  4. Wenn Sie den Quellcode modifiziert und den Treiber übersetzt haben, laden Sie diesen (»insmod usbcheck.ko«). Falls Sie das Device-Filesystem aktiviert haben, wird vom USB-Subsystem automatisch eine passende Gerätedatei angelegt. Falls nicht, legen Sie die Gerätedatei selbst an. Bei USB ist die Major-Nummer grundsätzlich »180«. Die Minor-Nummer hat unser Treiber in den Syslogs ausgegeben, innerhalb des Treibers wird die Minor-Nummer 16 angefordert.

       (root)# mknod devicefile c 180 16
       (root)#

  5. Der Treiber ist so implementiert, dass – falls Sie lesend auf die Gerätedatei devicefile zugreifen – vom Gerät der Status zurückgegeben wird:

       (root)# cat <devicefile
       status = 0
       status = 0
       ...

    Falls Sie das Gerät jetzt abstecken, bricht »cat« mit einem Ein-/Ausgabefehler ab.

Beispiel 8-5. Treibergerüst zum Test des USB-Subsystems

#include <linux/fs.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <asm/uaccess.h>

MODULE_LICENSE( "GPL" );

#define USB_VENDOR_ID 0x05a9
#define USB_DEVICE_ID 0xa511

struct usb_device *dev;

static DECLARE_MUTEX( ulock );

static ssize_t usbcheck_read( struct file *instanz, char *buffer, size_t count,
        loff_t *ofs )
{
    char pbuf[20];
    __u16 *status = kmalloc( sizeof(__u16), GFP_KERNEL );

    down( &ulock ); // Jetzt nicht disconnecten...
    if( usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), USB_REQ_GET_STATUS,
                 USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE,
                0, 0, status, sizeof(*status), 5*HZ) < 0 ) {
        count = -EIO;
        goto read_out;
    }
    snprintf( pbuf, sizeof(pbuf), "status=%d\n", *status );
    if( strlen(pbuf) < count )
        count = strlen(pbuf);
    count -= copy_to_user(buffer,pbuf,count);
read_out:
    up( &ulock );
    kfree( status );
    return count;
}

static int usbcheck_open( struct inode *devicefile, struct file *instanz )
{
    return 0;
}

static struct file_operations usb_fops = {
    .owner = THIS_MODULE,
    .open  = usbcheck_open,
    .read  = usbcheck_read,
};

static struct usb_device_id usbid [] = {
    { USB_DEVICE(USB_VENDOR_ID, USB_DEVICE_ID), },
    { }                 /* Terminating entry */
};

struct usb_class_driver class_descr = {
    .name = "usbcheck",
    .fops = &usb_fops,
    .mode = S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH,
    .minor_base = 16,
};

static int usbcheck_probe(struct usb_interface *interface,
    const struct usb_device_id *id)
{
    dev = interface_to_usbdev(interface);
    printk("usbcheck: 0x%4.4x|0x%4.4x, if=%p\n", dev->descriptor.idVendor,
        dev->descriptor.idProduct, interface );
    if(dev->descriptor.idVendor==USB_VENDOR_ID
        && dev->descriptor.idProduct==USB_DEVICE_ID) {
        if( usb_register_dev( interface, &class_descr ) ) {
            return -EIO;
        }
        printk("got minor= %d\n", interface->minor );
        return 0;
    }
    return -ENODEV;
}

static void usbcheck_disconnect( struct usb_interface *iface )
{
    down( &ulock ); // Ausstehende Auftraege muessen abgearbeitet sein...
    usb_deregister_dev( iface, &class_descr );
    up( &ulock );
}

static struct usb_driver usbcheck = {
    .name= "usbcheck%d",
    .id_table= usbid,
    .probe= usbcheck_probe,
    .disconnect= usbcheck_disconnect,
};

static int __init usbcheck_init(void)
{
    if( usb_register(&usbcheck) ) {
        printk("usbcheck: unable to register usb driver\n");
        return -EIO;
    }
    return 0;
}

static void __exit usbcheck_exit(void)
{
    usb_deregister(&usbcheck);  
}

module_init(usbcheck_init);
module_exit(usbcheck_exit);
/* vim: set ts=4 sw=4 aw ic: */


Lizenz