Heim  >  Artikel  >  System-Tutorial  >  Detaillierte Erläuterung des Linux-USB-Treiber-Workflows

Detaillierte Erläuterung des Linux-USB-Treiber-Workflows

王林
王林nach vorne
2024-02-09 18:40:26637Durchsuche

Der Linux-Kernel-Treiber ist eine der wichtigsten Komponenten des Linux-Systems. Sie sind für die Kommunikation mit Hardwaregeräten verantwortlich, damit das Betriebssystem die Hardware korrekt identifizieren und verwenden kann. Allerdings ist die Entwicklung von Linux-Kernel-Treibern keine leichte Aufgabe. In diesem Artikel werden wir uns mit der Implementierungsmethode des Linux-Kernel-Treibers befassen und den Lesern ein umfassendes Verständnis und eine Anleitung bieten.

详解Linux USB驱动工作流程

1. USB-Host

Bei Linux-Treibern ist die unterste Ebene des USB-Treibers die USB-Host-Controller-Hardware. Darüber befindet sich der USB-Host-Controller-Treiber. Über dem Host-Controller befindet sich die USB-Kernebene und die obere Ebene ist der USB-Gerätetreiber Schicht (Legen Sie die U-Disk, die Maus, den USB-zu-Seriell-Anschluss und andere Gerätetreiber auf dem Host ein).

Daher umfasst der zu implementierende USB-Treiber in der hostseitigen Hierarchie zwei Kategorien: USB-Host-Controller-Treiber und USB-Gerätetreiber. Ersterer steuert das angeschlossene USB-Gerät und letzterer steuert, wie das USB-Gerät mit dem kommuniziert Gastgeber. Der USB-Kern des Linux-Kernels ist für die Hauptarbeit der USB-Treiberverwaltung und Protokollverarbeitung verantwortlich. Der USB-Kern zwischen dem Host-Controller-Treiber und dem Gerätetreiber ist sehr wichtig. Zu seinen Funktionen gehören: Durch die Definition einiger Datenstrukturen, Makros und Funktionen stellt er eine Programmierschnittstelle für den Gerätetreiber nach oben und eine Programmierschnittstelle für den USB-Host-Controller bereit Treiber abwärts; Globale Variablen verwalten die USB-Geräteinformationen des gesamten Systems; vollständige Geräte-Hot-Plug-Steuerung, Bus-Datenübertragungssteuerung usw.

2. USB-Gerät

Der USB-Gerätetreiber im Linux-Kernel ist in drei Ebenen unterteilt: UDC-Treiber, Gadget-API und Gadget-Treiber. Der UDC-Treiber greift direkt auf die Hardware zu, steuert die zugrunde liegende Kommunikation zwischen dem USB-Gerät und dem Host und stellt der oberen Schicht Rückruffunktionen für hardwarebezogene Vorgänge bereit. Die aktuelle Gadget-API ist ein einfacher Wrapper für die Rückruffunktionen des UDC-Treibers. Der Gadget-Treiber steuert speziell die Implementierung von USB-Gerätefunktionen und ermöglicht es dem Gerät, Eigenschaften wie „Netzwerkverbindung“, „Drucker“ oder „USB-Massenspeicher“ anzuzeigen. Er verwendet die Gadget-API, um UDC zur Implementierung der oben genannten Funktionen zu steuern. Die Gadget-API isoliert den UDC-Treiber der unteren Schicht vom Gadget-Treiber der oberen Schicht und ermöglicht so die Trennung der Implementierung von Funktionen von der zugrunde liegenden Kommunikation beim Schreiben eines Treibers auf der USB-Geräteseite in einem Linux-System.

3. In der Organisationsstruktur des USB-Geräts ist es von oben nach unten in vier Ebenen unterteilt: Gerät (Gerät), Konfiguration (Konfiguration), Schnittstelle (Schnittstelle) und Endpunkt (Endpunkt). Das USB-Geräteprogramm ist an die Schnittstelle gebunden.

Eine kurze Beschreibung dieser vier Ebenen lautet wie folgt:
Geräte haben normalerweise eine oder mehrere Konfigurationen
Konfigurationen verfügen oft über eine oder mehrere Schnittstellen
Die Schnittstelle hat keinen oder mehr als einen Endpunkt

详解Linux USB驱动工作流程

4. Die grundlegendste Form der USB-Kommunikation erfolgt über Endpunkte (USB-Endpunkte werden in vier Typen unterteilt: Interrupt, Bulk, ISO und Control, jeweils mit unterschiedlichen Verwendungszwecken). USB-Endpunkte können Daten nur in eine Richtung übertragen Host zum Gerät oder vom Gerät zum Host Der Endpunkt kann als Einwegpipeline betrachtet werden. Der Treiber registriert das Treiberobjekt beim USB-Subsystem und ermittelt später anhand der Hersteller- und Geräteidentifikation, ob die Hardware installiert wurde. Der USB-Kern verwendet eine Liste (eine Struktur, die die Hersteller-ID und die Geräte-ID enthält), um zu bestimmen, welcher Treiber für ein Gerät verwendet werden soll, und das Hotplug-Skript verwendet diese, um zu bestimmen, wann ein bestimmtes Gerät an das System angeschlossen werden soll automatisch ausgeführt.

5. Datenstruktur

1) USB-Gerät: entsprechende Datenstruktur struct usb_device

2) Konfiguration: struct usb_host_config (Es kann immer nur eine Konfiguration wirksam werden)

3) USB-Schnittstelle: struct usb_interface (Der USB-Kern übergibt es an den USB-Gerätetreiber, und der USB-Gerätetreiber ist für die anschließende Steuerung verantwortlich. Eine USB-Schnittstelle stellt eine Grundfunktion dar, und jeder USB-Treiber steuert eine Schnittstelle. Also eine physische Hardwaregeräte erfordern möglicherweise mehr als einen Treiber)

.

4) Endpunkt: struct usb_host_endpoint, die darin enthaltenen tatsächlichen Endpunktinformationen befinden sich in einer anderen Struktur: struct usb_endpoint_descriptor (Endpunktdeskriptor, enthält alle USB-spezifischen Daten).

6. USB-Endpunktklassifizierung

Die einfachste Form der USB-Kommunikation erfolgt über einen sogenannten Endpunkt. Ein USB-Endpunkt kann Daten nur in eine Richtung übertragen (vom Host zum Gerät (Ausgabeendpunkt genannt) oder vom Gerät zum Host (Eingabeendpunkt genannt)). Ein Endpunkt kann als Einwegrohr betrachtet werden.

Es gibt 4 verschiedene Arten von USB-Endpunkten mit jeweils unterschiedlichen Datenübertragungsmethoden:

1) KONTROLLE
Kontrollendpunkte werden verwendet, um den Zugriff auf verschiedene Teile eines USB-Geräts zu steuern. Sie werden normalerweise verwendet, um das Gerät zu konfigurieren, Geräteinformationen abzurufen, Befehle an das Gerät zu senden oder Gerätestatusberichte zu erhalten. Diese Endpunkte sind normalerweise kleiner. Jedes USB-Gerät verfügt über einen Steuerungsendpunkt namens „Endpunkt 0“, der vom USB-Kern zum Konfigurieren des Geräts verwendet wird, wenn es angeschlossen ist. Das USB-Protokoll stellt sicher, dass dem Steuerungsendpunkt immer genügend Bandbreite zur Verfügung steht, um Daten an das Gerät zu übertragen.

2) UNTERBRECHUNG
Immer wenn der USB-Host Daten vom Gerät anfordert, überträgt der Interrupt-Endpunkt eine kleine Datenmenge mit einer festen Rate. Dies ist die primäre Datenübertragungsmethode für USB-Tastaturen und -Mäuse. Es wird auch verwendet, um Daten an USB-Geräte zu übertragen und so das Gerät zu steuern. Wird normalerweise nicht zur Übertragung großer Datenmengen verwendet. Das USB-Protokoll stellt sicher, dass dem Interrupt-Endpunkt immer genügend Bandbreite zur Verfügung steht, um Daten an das Gerät zu übertragen.

3) Batch BULK
Massenendpunkte werden zur Übertragung großer Datenmengen verwendet. Diese Endpunkte sind normalerweise viel größer als Interrupt-Endpunkte. Sie werden häufig in Situationen verwendet, in denen kein Datenverlust auftreten kann. Das USB-Protokoll garantiert nicht, dass die Übertragung innerhalb eines bestimmten Zeitrahmens abgeschlossen wird. Wenn auf dem Bus nicht genügend Platz vorhanden ist, um das gesamte BULK-Paket zu senden, wird es zur Übertragung in mehrere Pakete aufgeteilt. Diese Endpunkte werden häufig auf Druckern, USB-Massenspeichern und USB-Netzwerkgeräten verwendet.

4) ISOCHRON
Isochrone Endpunkte übertragen auch große Datenmengen in Stapeln, die Zustellung dieser Daten kann jedoch nicht garantiert werden. Diese Endpunkte werden in Geräten verwendet, die Datenverluste bewältigen können und eher auf die Aufrechterhaltung eines kontinuierlichen Datenflusses angewiesen sind. Wie Audio- und Videogeräte und so weiter.

Kontroll- und Batch-Endpunkte werden für die asynchrone Datenübertragung verwendet, während Interrupt- und isochrone Endpunkte periodisch sind. Das bedeutet, dass diese Endpunkte so eingerichtet sind, dass sie kontinuierlich zu einem festen Zeitpunkt Daten übertragen und der USB-Core die entsprechende Bandbreite für sie reserviert.

7. Endpunkt

struct usb_host_endpoint{  struct usb_endpoint_descriptor desc;//端点描述符  struct list_head urb_list;//此端点的URB对列,由USB核心维护  void *hcpriv;  struct ep_device *ep_dev; /* For sysfs info */  unsigned char*extra;/* Extra descriptors */  int extralen;  int enabled;};

Wenn der USB-Gerätetreiber usb_submit_urb aufruft, um eine Urb-Anfrage zu senden, wird int usb_hcd_link_urb_to_ep(struct usb_hcd *hcd, struct urb *urb) aufgerufen, um diesen Urb am Ende von urb_list hinzuzufügen. (hcd: Host Controller Driver, entsprechende Datenstruktur struct usb_hcd)

8.urb

Alle USB-Kommunikationen erfolgen im Anforderungs->Antwortmodus und USB-Geräte senden keine Daten aktiv an den Host. Daten schreiben: Der USB-Gerätetreiber sendet eine Urb-Anfrage an das USB-Gerät, und das USB-Gerät muss keine Daten zurückgeben. Daten lesen: Der USB-Gerätetreiber sendet eine Urb-Anfrage an das USB-Gerät und das USB-Gerät muss Daten zurückgeben.

USB-Gerätetreiber kommuniziert mit allen USB-Geräten über urb. urb wird mit der Struktur struct urb (include/linux/usb.h) beschrieben.
urb sendet oder empfängt Daten asynchron an einen bestimmten Endpunkt eines bestimmten USB-Geräts. Ein USB-Gerätetreiber kann je nach den Anforderungen des Treibers mehrere Urbs einem einzelnen Endpunkt zuweisen oder einen einzelnen Urb für mehrere verschiedene Endpunkte wiederverwenden. Jeder Endpunkt im Gerät verwaltet eine URB-Warteschlange, sodass mehrere URBs an denselben Endpunkt gesendet werden können, bevor die Warteschlange geleert wird.

Der typische Lebenszyklus einer Stadt ist wie folgt:
(1) Erstellt; (2) Ein bestimmter Endpunkt, der einem bestimmten USB-Gerät zugewiesen ist
(3) An USB-Kern übermittelt
(4) Vom USB-Kern an einen bestimmten USB-Host-Controller-Treiber für ein bestimmtes Gerät übermittelt
(5) Vom USB-Host-Controller-Treiber verarbeitet und an das Gerät übertragen
(6) Nachdem die oben genannten Vorgänge abgeschlossen sind, benachrichtigt der USB-Host-Controller-Treiber den USB-Gerätetreiber.

urb kann auch jederzeit vom Treiber, der es übermittelt hat, abgebrochen werden; wenn das Gerät entfernt wird, kann urb vom USB-Kern abgebrochen werden. Urbs werden dynamisch erstellt und enthalten einen internen Referenzzähler, sodass sie automatisch freigegeben werden können, wenn der letzte Benutzer sie freigibt.

8.1 Urb einreichen

Sobald der Urb ordnungsgemäß erstellt und initialisiert wurde, kann er an den USB-Kern übermittelt werden, um ihn an das USB-Gerät zu senden. Dies wird durch Aufrufen der Funktion sb_submit_urb erreicht.

int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
Parameter:
struct urb *urb: Zeiger auf den übermittelten urb
gfp_t mem_flags: Verwendet dieselben Parameter, die an den kmalloc-Aufruf übergeben wurden, um dem USB-Kern mitzuteilen, wie Speicherpuffer rechtzeitig zugewiesen werden sollen

Da die Funktion usb_submit_urb jederzeit aufgerufen werden kann (auch aus einem Interrupt-Kontext), muss die Variable mem_flags korrekt gesetzt werden. Je nachdem, wann usb_submit_urb aufgerufen wird, sind nur 3 gültige Werte verfügbar:

GFP_ATOMIC
Dieser Wert sollte verwendet werden, solange die folgenden Bedingungen erfüllt sind:
1) Der Anrufer befindet sich in einem Urb-End-Handler, Interrupt-Handler, einer unteren Hälfte, einem Tasklet oder einer Timer-Callback-Funktion.
2) Der Aufrufer hält eine Spin-Sperre oder eine Lese-/Schreibsperre. Beachten Sie, dass dieser Wert nicht erforderlich ist, wenn eine Semaphore gehalten wird.
3) current->state ist nicht TASK_RUNNING. Sofern der Treiber den aktuellen Status nicht selbst geändert hat, sollte der Status immer TASK_RUNNING sein.

GFP_NOIO
Der Treiber wird während der Block-I/O-Verarbeitung verwendet. Er sollte auch während der Fehlerbehandlung für alle Speichertypen verwendet werden.

GFP_KERNEL
Alle anderen Situationen, die nicht in die zuvor genannten Situationen fallen

Nachdem urb erfolgreich an den USB-Kern übermittelt wurde, kann auf kein Mitglied der urb-Struktur zugegriffen werden, bis die Endverarbeitungsroutinenfunktion aufgerufen wird

8.2 URB-Endverarbeitungsroutine

Wenn usb_submit_urb erfolgreich aufgerufen wird und die Kontrolle über den urb an den USB-Kern überträgt, gibt die Funktion 0 zurück; andernfalls wird ein negativer Fehlercode zurückgegeben. Wenn die Funktion erfolgreich aufgerufen wird, wird die End-Handler-Routine einmal aufgerufen, wenn der urb beendet wird .Wenn diese Funktion aufgerufen wird, schließt der USB-Kern den Urb ab und gibt seine Kontrolle an den Gerätetreiber zurück.

Es gibt nur 3 Situationen, um urb zu beenden und die Endverarbeitungsroutine aufzurufen:
(1)urb wurde erfolgreich an das Gerät gesendet und das Gerät gibt die korrekte Bestätigung zurück. Wenn ja, wird die Statusvariable in urb auf 0.
gesetzt (2) Ein Fehler tritt auf und der Fehlerwert wird in der Statusvariablen in der Urb-Struktur aufgezeichnet.
(3) URB-Verbindung zum USB-Kern aufheben. Dies geschieht entweder, wenn der Treiber den USB-Kern anweist, ein übermitteltes UB abzubrechen, indem er usb_unlink_urb oder usb_kill_urb aufruft, oder wenn ein URB an ihn übermittelt wurde und das Gerät aus dem System entfernt wird.

9. Erkennung und Trennung

In der Struktur struct usb_driver gibt es zwei Funktionen, die der USB-Kern zum richtigen Zeitpunkt aufruft:
(1) Wenn das Gerät angeschlossen ist und der USB-Kern denkt, dass der Treiber damit umgehen kann (der USB-Kern verwendet eine Liste (eine Struktur, die die Hersteller-ID und die Gerätenummern-ID enthält), um zu bestimmen, welcher Treiber für ein Gerät verwendet werden soll). , wird die Probe-Funktion aufgerufen. Die Probe-Funktion prüft die ihr übergebenen Geräteinformationen und stellt fest, ob der Treiber tatsächlich für dieses Gerät geeignet ist.
(2) Wenn das Gerät aus bestimmten Gründen entfernt wird oder der Treiber das Gerät nicht mehr steuert, rufen Sie die Trennfunktion auf und führen Sie die entsprechende Bereinigung durch.

Sowohl die Rückruffunktionen für die Erkennung als auch für die Trennung werden im USB-Hub-Kernel-Thread-Kontext aufgerufen, daher ist es zulässig, dass sie in den Ruhezustand versetzt werden. Um die USB-Erkennungszeit zu verkürzen, wird die meiste Arbeit erledigt, wenn das Gerät so oft eingeschaltet ist Dies liegt daran, dass sich der USB-Kern in einem Thread befindet. Das Hinzufügen und Entfernen von USB-Geräten wird im Thread gehandhabt, sodass jeder langsame Gerätetreiber die Erkennungszeit für USB-Geräte verlängern kann.

9.1 Erkennungsfunktionsanalyse
In der Probe-Callback-Funktion sollte der USB-Gerätetreiber alle lokalen Strukturen initialisieren, die er möglicherweise zur Verwaltung des USB-Geräts verwendet, und alle erforderlichen Geräteinformationen in den lokalen Strukturen speichern, da dies zu diesem Zeitpunkt normalerweise einfacher ist, um zu kommunizieren Mit dem Gerät prüft der USB-Treiber normalerweise die Endpunktadresse und Puffergröße des Geräts.

In diesem Artikel stellen wir die Implementierungsmethode des Linux-Kerneltreibers im Detail vor, einschließlich des Kerneltreiber-Frameworks, des Schreibens von Kernelmodulen, der Registrierung und Aufhebung der Registrierung von Gerätetreibern usw. Wir glauben, dass die Leser durch das Studium dieses Artikels ein tieferes Verständnis der Implementierungsprinzipien des Linux-Kernel-Treibers erlangen und mehr Referenz und Hilfe für ihre eigene Entwicklungsarbeit erhalten können.

Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung des Linux-USB-Treiber-Workflows. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:lxlinux.net. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen