UHCI oder der allererste USB



Guten Tag, lieber Leser! Ich wurde gebeten, über UHCI zu schreiben - nun, ich schreibe.

Sie können diesen Artikel nützlich finden, wenn Sie beispielsweise nicht über ausreichende Schreibfähigkeiten für Treiber und das Lesen von Dokumentation für eine Hardware verfügen. Ein einfaches Beispiel: Sie möchten Ihr Betriebssystem für einen Mini-PC schreiben, damit einige Windows- oder andere Linux-Distributionen kein Eisen herunterladen, und Sie nutzen die gesamte Leistung ausschließlich für Ihre eigenen Zwecke.

Was ist UHCI?


Ich denke, um nicht noch einmal auf das Thema was und warum zu sprühen, hinterlasse einfach einen Link zu meinem vorherigen Artikel über EHCI. Poke hier
UHCI - Universal Host Controller Interface, arbeitet als PCI-Gerät, verwendet jedoch im Gegensatz zu EHCI Ports anstelle von MMIO (Memory-Mapped-IO).



Begriffe, die im Folgenden verwendet werden


  • USB-Treiber (USBD) - der USB-Treiber selbst
  • HC (Host Controller) - ein Host Controller oder nur unser UHCI
  • Host Controller Driver (HCD) - ein Treiber, der Hardware und USBD verbindet
  • USB-Gerät - USB-Gerät selbst

Arten der Datenübertragung


Isochrone - isosynchrone Übertragung mit einer bestimmten Datenübertragungsfrequenz. Es kann beispielsweise für USB-Mikrofone usw. verwendet werden.

Interrupt - Kleine, spontane Datenübertragung von einem Gerät. Der Interrupt-Übertragungstyp unterstützt Geräte, die ein vorhersagbares Serviceintervall erfordern, aber nicht unbedingt einen vorhersagbaren Datenstrom bereitstellen. Wird häufig für Geräte wie Tastaturen und Zeigegeräte verwendet, die möglicherweise über einen längeren Zeitraum keine Daten bereitstellen, jedoch eine schnelle Antwort erfordern, wenn Daten gesendet werden müssen.

Steuerung - Art der Übertragung von Informationen über den Gerätestatus, den Status und die Konfiguration. Der Steuerungstransfertyp wird verwendet, um einen Steuerkanal vom Host zu USB-Geräten bereitzustellen. Steuerübertragungen bestehen immer aus einer Einrichtungsphase und null oder mehr Datenphasen, gefolgt von einer Statusphase. Es ist unbedingt erforderlich, dass die Steuerübertragung an einen bestimmten Endpunkt im FIFO-Modus verarbeitet wird. Wenn die Steuerung an denselben Endpunkt übergeben wird, kann das Verschachteln zu unvorhersehbarem Verhalten führen.

Bulk - Art der Übertragung von Datenarrays. Wird beispielsweise in MassStorage-Geräten verwendet.



So sieht die Zeitverteilung von 1 ms aus - Verarbeitung eines Frames.

Zeitverteilung


Der Host-Controller unterstützt die Datenübertragung in Echtzeit, indem alle 1 ms ein SOF-Paket (Start Of Frame) generiert wird. Ein SOF-Paket wird generiert, wenn der SOF-Zähler im Host-Controller abläuft (Abbildung 3). Der Host-Controller initialisiert den SOF-Zähler für eine Frame-Zeit von 1 ms. Durch Programmieren des SOF-Änderungsregisters können kleine Änderungen an diesem Wert (und damit an der Rahmenzeitdauer) vorgenommen werden. Mit dieser Funktion können Sie bei Bedarf geringfügige Änderungen am Frame-Zeitraum vornehmen, um die Echtzeitsynchronisation im gesamten USB-System aufrechtzuerhalten.

Der Host-Controller enthält die Rahmennummer in jedem SOF-Paket. Diese Bildnummer bestimmt eindeutig die Bilddauer in Echtzeit. Die End-of-Frame-Bedingung (EOF) tritt am Ende des Zeitintervalls von 1 ms auf, wenn der Host-Controller die nächste Frame-Zeit startet und ein weiteres SOF-Paket mit der entsprechenden Frame-Nummer generiert. Während der Rahmenperiode werden Daten als Informationspakete übertragen. Der Frame-Zeitraum wird vom Host-Controller strikt eingehalten, und die Datenpakete im aktuellen Frame dürfen nicht über den EOF hinausgehen (siehe Kapitel 11 in der USB-Spezifikation). Der Host-Controller unterstützt die Synchronisation der Datenübertragung zwischen Frames in Echtzeit und verknüpft die Frame-Nummer, um einen bestimmten Eintrag in der Frame-Liste vorzunehmen. Der Frame-Zähler des Host-Controllers generiert eine Frame-Nummer (11-Bit-Wert) und enthält diese in jedem SOF-Paket. Der Zähler wird über Register programmiert und jede Rahmenperiode wird inkrementiert. Der Host-Controller verwendet die unteren 10 Bits der Frame-Nummer als Index in der Frame-Liste mit 1024 Frames, die im Systemspeicher gespeichert sind. Da der Rahmenzähler die Auswahl eines Eintrags aus der Liste der Rahmen steuert, verarbeitet der Host-Controller jeden Eintrag in der Liste in einer bestimmten Rahmenperiode. Der Host-Controller wird für jeden neuen Frame zum nächsten Eintrag in der Frame-Liste erweitert. Dies stellt sicher, dass isochrone Übertragungen in einem bestimmten Rahmen ausgeführt werden.

Abbildung 3:



UHCI-Struktur


Alles ist genau das gleiche wie bei EHCI. Beispielanfragen an HC:



UHCI konfigurieren und darauf zugreifen


Wie ich bereits sagte, arbeitet UHCI über Ports. Daher müssen wir über PCI die Basis der UHCI-Register ermitteln.



Bei Offset 0x20 gibt es 4 Bytes - IO Base. In Bezug auf die E / A-Basis können wir die folgenden Register verwenden:



UHCI-Register


  • USBCMD ist ein Register zur Steuerung von HC. Bits:
    • Bit 6 ist ein Flag, mit dem das Gerät erfolgreich konfiguriert und initialisiert wurde.
    • Bit 1 - HC Reset. Zum Zurücksetzen von HC einstellen.
    • Bit 0 - Ausführen / Stoppen. Zeigt den HC-Status an. 1 - funktioniert, 0 - nein.
  • USBSTS - Statusregister. Bits:
    • Bit 5 - HC angehalten. Ein Fehler ist aufgetreten oder der Controller hat den HC-Reset erfolgreich abgeschlossen.
    • Bit 4 - Prozessfehler des Host-Controllers. Das Bit wird auf 1 gesetzt, wenn ein kritischer Fehler aufgetreten ist und HC die Warteschlange und TD nicht fortsetzen kann.
    • Bit 3 - Hostsystemfehler. PCI-Fehler.
    • Bit 1 - Fehlerunterbrechung. Zeigt an, dass ein Fehler aufgetreten ist und der HC einen Interrupt generiert hat.
    • Bit 0 - Interrupt. Zeigt an, dass HC einen Interrupt generiert hat.
  • USBINTR - Register der Interrupt-Einstellungen. Bits:
    • Bit 2 - IOC - Interrupt bei Abschluss - erzeugt am Ende der Transaktion einen Interrupt.
  • FRNUM - Nummer des aktuellen Frames (nimm es & 0x3FF für den richtigen Wert).
  • FLBASEADD - Frame List Base Address - Adresse der Liste der Frames.
  • PORTSC - Portstatus und -steuerung - Status- und Portsteuerungsregister. Bits:
    • Bit 9 - Port Reset - 1 - Port zum Zurücksetzen.
    • Bit 8 - zeigt an, dass ein Gerät mit niedriger Geschwindigkeit an den Port angeschlossen ist
    • Bit 3 - Zeigt an, dass sich der Port-On-Status geändert hat
    • Bit 2 - Zeigt an, ob der Port aktiviert ist
    • Bit 1 - zeigt an, dass der Status des Geräts mit dem Port verbunden ist
    • Bit 0 - zeigt an, dass das Gerät an den Port angeschlossen ist.

Strukturen


Rahmenlistenzeiger




Deskriptor übertragen




TD STEUERUNG UND STATUS
. Bits:
  • Bits 28-27 - Fehlerzähler, ähnlich wie bei EHCI.
    • Bit 26 - 1 = Gerät mit niedriger Geschwindigkeit, 0 = Gerät mit voller Geschwindigkeit.
    • Bit 25-1 = Isosynchrones TD
    • Bit 24 - IOC
    • Bits 23-16 - Status:
    • Bit 23 - Zeigt an, dass es sich um einen aktiven TD handelt
    • Bit 22 - Blockiert
    • Bit 21 - Datenpufferfehler
    • Bit 20 - Babble erkannt
    • Bit 19 - NAK
  • Bits 10–0: Die Anzahl der vom Host-Controller übertragenen Bytes.

TD Token

  • Bits 31:21 - Max Packet Len, ähnlich wie EHCI
  • Bit 19 - Data Toggle, ähnlich wie EHCI
  • Bits 18:15 - Endpunktnummer
  • Bits 18:14 - Geräteadresse
  • Bits 7: 0 - PID. In = 0x69, Out = 0xE1, Setup = 0x2D

Warteschlangenkopf




Code


HC initialisieren und konfigurieren:

PciBar bar; PciGetBar(&bar, id, 4); if (~bar.flags & PCI_BAR_IO) { // Only Port I/O supported return; } unsigned int ioAddr = bar.u.port; UhciController *hc = VMAlloc(sizeof(UhciController)); hc->ioAddr = ioAddr; hc->frameList = VMAlloc(1024 * sizeof(u32) + 8292); hc->frameList = ((int)hc->frameList / 4096) * 4096 + 4096; hc->qhPool = (UhciQH *)VMAlloc(sizeof(UhciQH) * MAX_QH + 8292); hc->qhPool = ((int)hc->qhPool / 4096) * 4096 + 4096; hc->tdPool = (UhciTD *)VMAlloc(sizeof(UhciTD) * MAX_TD + 8292); hc->tdPool = ((int)hc->tdPool / 4096) * 4096 + 4096; memset(hc->qhPool, 0, sizeof(UhciQH) * MAX_QH); memset(hc->tdPool, 0, sizeof(UhciTD) * MAX_TD); memset(hc->frameList, 0, 4 * 1024); // Frame list setup UhciQH *qh = UhciAllocQH(hc); qh->head = TD_PTR_TERMINATE; qh->element = TD_PTR_TERMINATE; qh->transfer = 0; qh->qhLink.prev = &qh->qhLink; qh->qhLink.next = &qh->qhLink; hc->asyncQH = qh; for (uint i = 0; i < 1024; ++i) hc->frameList[i] = 2 | (u32)(uintptr_t)qh; IoWrite16(hc->ioAddr + REG_INTR, 0); IoWrite16(hc->ioAddr + REG_CMD, IoRead16(hc->ioAddr + REG_CMD)&(~1)); unsigned short cfg = PciRead16(id, 4); PciWrite16(id, 4, cfg & (~1)); PciWrite16(id, 0x20, (short)-1); unsigned short size = ~(PciRead16(id, 0x20)&(~3)) + 1; PciWrite16(id, 0x20, hc->ioAddr); PciWrite16(id, 4, cfg | 5); // Disable Legacy Support IoWrite16(hc->ioAddr + REG_LEGSUP, 0x8f00); // Disable interrupts IoWrite16(hc->ioAddr + REG_INTR, 0); // Assign frame list IoWrite16(hc->ioAddr + REG_FRNUM, 0); IoWrite32(hc->ioAddr + REG_FRBASEADD, (int)hc->frameList); IoWrite16(hc->ioAddr + REG_SOFMOD, 0x40); // Clear status IoWrite16(hc->ioAddr + REG_STS, 0xffff); // Enable controller IoWrite16(hc->ioAddr + REG_CMD, 0x1); // Probe devices UhciProbe(hc, size); 

Endpunkt- und Steuerungsanforderungen:

 // ------------------------------------------------------------------------------------------------ static void UhciDevControl(UsbDevice *dev, UsbTransfer *t) { UhciController *hc = (UhciController *)dev->hc; UsbDevReq *req = t->req; // Determine transfer properties uint speed = dev->speed; uint addr = dev->addr; uint endp = 0; uint maxSize = dev->maxPacketSize; uint type = req->type; uint len = req->len; // Create queue of transfer descriptors UhciTD *td = UhciAllocTD(hc); if (!td) { return; } UhciTD *head = td; UhciTD *prev = 0; // Setup packet uint toggle = 0; uint packetType = TD_PACKET_SETUP; uint packetSize = sizeof(UsbDevReq); UhciInitTD(td, prev, speed, addr, endp, toggle, packetType, packetSize, req); prev = td; // Data in/out packets packetType = type & RT_DEV_TO_HOST ? TD_PACKET_IN : TD_PACKET_OUT; u8 *it = (u8 *)t->data; u8 *end = it + len; while (it < end) { td = UhciAllocTD(hc); if (!td) { return; } toggle ^= 1; packetSize = end - it; if (packetSize > maxSize) { packetSize = maxSize; } UhciInitTD(td, prev, speed, addr, endp, toggle, packetType, packetSize, it); it += packetSize; prev = td; } // Status packet td = UhciAllocTD(hc); if (!td) { return; } toggle = 1; packetType = type & RT_DEV_TO_HOST ? TD_PACKET_OUT : TD_PACKET_IN; UhciInitTD(td, prev, speed, addr, endp, toggle, packetType, 0, 0); // Initialize queue head UhciQH *qh = UhciAllocQH(hc); UhciInitQH(qh, t, head); // Wait until queue has been processed UhciInsertQH(hc, qh); UhciWaitForQH(hc, qh); } // ------------------------------------------------------------------------------------------------ static void UhciDevIntr(UsbDevice *dev, UsbTransfer *t) { UhciController *hc = (UhciController *)dev->hc; // Determine transfer properties uint speed = dev->speed; uint addr = dev->addr; uint endp = t->endp->desc->addr & 0xf; // Create queue of transfer descriptors UhciTD *td = UhciAllocTD(hc); if (!td) { t->success = false; t->complete = true; return; } UhciTD *head = td; UhciTD *prev = 0; // Data in/out packets uint toggle = t->endp->toggle; uint packetType = TD_PACKET_IN; //Here for compiler, on some last expression hadn't worked if (t->endp->desc->addr & 0x80) packetType = TD_PACKET_IN; else packetType = TD_PACKET_OUT; uint packetSize = t->len; UhciInitTD(td, prev, speed, addr, endp, toggle, packetType, packetSize, t->data); // Initialize queue head UhciQH *qh = UhciAllocQH(hc); UhciInitQH(qh, t, head); // Schedule queue UhciInsertQH(hc, qh); if(t->w) UhciWaitForQH(hc, qh); } 

Source: https://habr.com/ru/post/de429422/


All Articles