EHCI menschlich auf Russisch

Bild

Einführung


Ich begrüße alle. Heute möchte ich meine Erfahrungen teilen und meiner Meinung nach auf den ersten Blick einen einfachen Standard für USB 2.0-Host-Controller klar erläutern.

Zunächst können Sie sich vorstellen, dass ein USB 2.0-Anschluss nur aus 4 Pins besteht, von denen zwei einfach Daten übertragen (z. B. ein COM-Anschluss), aber tatsächlich ist nicht alles so, und sogar das Gegenteil. Der USB-Controller erlaubt es uns grundsätzlich nicht, Daten wie über einen normalen COM-Port zu übertragen. EHCI ist ein ziemlich komplizierter Standard, der eine zuverlässige und schnelle Datenübertragung von der Software zum Gerät selbst und in die entgegengesetzte Richtung ermöglicht.

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 EHCI?


Nun, fangen wir an. EHCI - Enhanced Host Controller Interface (Enhanced Host Controller Interface) dient zur Übertragung von Daten und Steuerungsanforderungen an USB-Geräte und in die andere Richtung. In 99% der Fälle handelt es sich um eine Verbindung zwischen einer Software und einem physischen Gerät. EHCI arbeitet als PCI-Gerät und verwendet dementsprechend MMIO (Memory-Mapped-IO) zur Steuerung des Controllers (ja, ich weiß, dass einige PCI-Geräte Ports verwenden, aber hier habe ich alles verallgemeinert). Die Dokumentation von Intel beschreibt nur das Funktionsprinzip, und es gibt keine Hinweise auf alle Algorithmen, die zumindest in Pseudocode geschrieben sind. EHCI verfügt über zwei Arten von MMIO-Registern: Capability und Operational. Die ersteren dienen dazu, die Eigenschaften der Steuerung zu erhalten, während die letzteren dazu dienen, diese zu steuern. Eigentlich werde ich das Wesentliche der Verbindung zwischen Software und dem EHCI-Controller anhängen:

Bild

Jeder EHCI-Controller verfügt über mehrere Anschlüsse, von denen jeder an ein beliebiges USB-Gerät angeschlossen werden kann. Bitte beachten Sie auch, dass EHCI eine verbesserte Version von UHCI ist, die einige Jahre zuvor ebenfalls von Intel entwickelt wurde. Aus Gründen der Abwärtskompatibilität ist jeder UHCI / OHCI-Controller mit einer niedrigeren Version als EHCI ein Begleiter von EHCI. Sie haben beispielsweise eine USB-Tastatur (und die meisten Tastaturen des Jahres waren bisher genau so), die mit USB 1.1 funktioniert (beachten Sie, dass die maximale Geschwindigkeit von USB 1.1 12 Megabit pro Sekunde beträgt und FullSpeed ​​USB 2.0 über Bandbreite verfügt bis zu 480 Mbit / s) und Sie haben einen Computer mit einem USB 2.0-Anschluss. Wenn Sie die Tastatur an den Computer anschließen, funktioniert der EHCI-Host-Controller in keiner Weise mit USB 1.1. Dieses Modell ist in der folgenden Abbildung dargestellt:

Bild

Außerdem möchte ich Sie für die Zukunft sofort warnen, dass Ihr Treiber aufgrund einer solch absurden Situation möglicherweise nicht richtig funktioniert: Sie haben UHCI initialisiert und dann EHCI, während Sie zwei identische Geräte hinzugefügt haben, das Port Owner Control-Bit auf das Portregister gesetzt und dann UHCI funktioniert nicht mehr, da EHCI den Port automatisch auf sich selbst zieht und der Port auf UHCI nicht mehr reagiert. Diese Situation muss überwacht werden.

Schauen wir uns auch ein Diagramm an, das die EHCI-Architektur selbst zeigt:

Bild

Rechts steht über die Warteschlange - etwas später darüber.

EHCI-Controller-Register


Zunächst möchte ich noch einmal klarstellen, dass Sie über diese Register Ihr Gerät steuern, daher sind sie sehr wichtig - und ohne sie ist eine EHCI-Programmierung nicht möglich.

Zuerst müssen Sie die MMIO-Adresse erhalten, die diesem Controller zugewiesen wurde. Bei Offset + 0x10 ist dies die Adresse unserer lang erwarteten Register. Es gibt eine Sache: Erstens gehen Capability-Register und erst danach - Operational, also bei Offset 0 (von der vorherigen Adresse, die wir bei Offset 0x10 relativ zum Beginn des MMIO unseres EHCI erhalten haben), gibt es ein Byte - die Länge der Capability-Register.

Fähigkeitsregister


Bei Offset 2 befindet sich das HCIVERSION- Register - die Revisionsnummer dieses HC, die 2 Bytes benötigt und die BCD-Version der Revision enthält (welche BCD auf Wikipedia zu finden ist).
Bei Offset +4 befindet sich das HCSPARAMS- Register, seine Größe beträgt 2 Wörter, es enthält die Strukturparameter des Geräts und seine Bits zeigen Folgendes:

  • Bit 16 - Portanzeigen - Verfügbare LEDs für angeschlossene USB-Geräte.
  • Bits 15:12 - Die Nummer des Companion-Controllers, der diesem Controller zugewiesen ist
  • Bits 11: 8 - Die Anzahl der Ports auf dem Companion-Controller
  • Bit 7 - Port-Routing-Regeln - zeigt, wie diese Ports Begleit-Ports zugeordnet werden
  • Bit 4 - Port Power Control - zeigt an, ob die Stromversorgung für jeden Port eingeschaltet werden muss. 0 - Die Stromversorgung erfolgt automatisch
  • Bits 3: 0 - Die Anzahl der Ports für diesen Controller.
  • Bei Offset +8 liegt das HCCPARAMS-Register - es zeigt Kompatibilitätsparameter, seine Bits bedeuten Folgendes:
  • Bit 2 - Verfügbarkeit der asynchronen Warteschlange,
  • Bit 1 - Periodische (sequentielle) Verfügbarkeit der Warteschlange
  • Bit 0 - 64 Bit Kompatibilität

Betriebsregister


Bei Offset 0 ist das USBCMD- Register das Befehlsregister der Steuerung, seine Bits bedeuten Folgendes:

  • Bits 23:16 - Interrupt Threshold Control - zeigt an, wie viele Micro-Frames für einen regulären Frame verwendet werden. Je größer, desto schneller, aber wenn mehr als 8, werden die Micro-Frames mit der gleichen Geschwindigkeit wie für 8 verarbeitet.
  • Bit 6 - Interrupt nach jeder Transaktion in der asynchronen Warteschlange,
  • Bit 5 - ist die verwendete asynchrone Warteschlange
  • Bit 4 - Verwendung der sequentiellen Warteschlange,
  • Bits 3: 2 - die Größe von FrameList'a (dazu später mehr). 0 bedeutet 1024 Elemente, 1 - 512, 2 - 256, 3 - reserviert
  • Bit 1 - Zum Zurücksetzen des Host-Controllers setzen.
  • Bit 0 - Ausführen / Stoppen
.
Als nächstes gibt es bei Offset +4 das USBSTS- Register - den Status des Host-Controllers,

  • Bit 15 gibt an, ob eine asynchrone Warteschlange verwendet wird.
  • Bit 14 zeigt an, ob eine sequentielle Warteschlange verwendet wird.
  • Bit 13 - zeigt an, dass eine leere asynchrone Warteschlange erkannt wurde.
  • Bit 12 wird auf 1 gesetzt. Wenn während der Verarbeitung der Transaktion ein Fehler aufgetreten ist, stoppt der Host-Controller alle Warteschlangen.
  • Bit 4 wird auf 1 gesetzt. Wenn ein schwerwiegender Fehler auftritt, stoppt der Host-Controller alle Warteschlangen.
  • Bit 3 FrameList (Register) Rollover - wird auf 1 gesetzt, wenn der Host-Controller die gesamte FrameList verarbeitet hat.
  • Bit 1 - USB-Fehlerunterbrechung - Generiere ich eine Fehlerunterbrechung?
  • Bit 0 - USB-Interrupt - wird nach erfolgreicher Transaktionsverarbeitung gesetzt, wenn IOC in TD installiert wurde

Nicht müde? Sie können sich eine starke Möwe einschenken und die Leber bringen, wir stehen ganz am Anfang!

Bei Offset +8 gibt es ein USBINTR- Register - das Interrupt-Aktivierungsregister
Um nicht lange zu schreiben und noch mehr, um nicht lange zu lesen, finden Sie die Werte der Bits dieses Registers in der Spezifikation. Ein Link dazu bleibt unten. Hier schreibe ich einfach 0, weil Ich habe absolut keine Lust, Handler, Map-Interrupts usw. zu schreiben, daher halte ich das für fast völlig sinnlos.

Bei Offset +12 (0x0C) befindet sich das FRINDEX- Register, in dem die aktuelle Frame-Nummer einfach liegt, und ich möchte darauf hinweisen, dass die letzten 4 Bits die Micro-Frame-Nummer und in den oberen 28 Bits die Frame-Nummer anzeigen (der Wert ist auch nicht unbedingt kleiner als die FrameList-Größe Wenn Sie jedoch einen Index benötigen, ist es besser, ihn mit einer Maske von 0x3FF (oder 0x1FF usw.) zu verwenden.

Das CTRLDSSEGMENT- Register hat den Offset + 0x10 und zeigt dem Host-Controller die höchstwertigen 32 Bits der Adresse des Rahmenblatts an.

Das PERIODICLISTBASE- Register hat einen Versatz von + 0x14. Sie können die unteren 32 Bits des Rahmenblatts darin einfügen. Beachten Sie, dass die Adresse an der Größe der Speicherseite (4096) ausgerichtet sein sollte.

Das ASYNCLISTADDR- Register hat einen Offset von + 0x18. Sie können die Adresse der asynchronen Warteschlange darin einfügen. Beachten Sie, dass sie an der Grenze von 32 Byte ausgerichtet sein muss, während sie sich in den ersten vier Gigabyte des physischen Speichers befinden muss.

Das CONFIGFLAG- Register zeigt an, ob das Gerät konfiguriert ist. Sie müssen Bit 0 setzen, nachdem Sie das Geräte-Setup abgeschlossen haben. Es hat einen Offset von + 0x40.

Gehen wir weiter zu den Portregistern. Jeder Port hat ein eigenes Befehlsstatusregister, jedes Portregister ist versetzt + 0x44 + (Portnummer - 1) * 4 , seine Bits bedeuten Folgendes:

  • Bit 12 - Portstromversorgung, 1 - Stromversorgung, 0 - Nr.
  • Bit 8 - Port Rest - wird gesetzt, um das Gerät zurückzusetzen.
  • Bit 3 - Port Enable / Disable Change - wird gesetzt, wenn der Status der "Einbeziehung" des Ports geändert wird.
  • Bit 2 - Port ein / aus.
  • Bit 1 - Ändern Sie den Verbindungsstatus. Wird beispielsweise auf 1 gesetzt, wenn Sie ein USB-Gerät angeschlossen oder getrennt haben.
  • Bit 0 - Verbindungsstatus, 1 - verbunden, 0 - Nr.

Kommen wir nun zum Saft selbst.

Datenübertragungs- und Abfragestrukturen


Das Organisieren einer Struktur zum Verarbeiten von Anforderungen umfasst Warteschlangen und Übertragungsdeskriptoren (TDs).

Im Moment werden wir nur 3 Strukturen betrachten.

Sequenzielle Liste


Die sequentielle Liste (periodisch, Pereodisch) ist wie folgt organisiert:

Bild

Wie Sie im Diagramm sehen können, beginnt die Verarbeitung mit dem Erhalten des gewünschten Rahmens aus dem Blattrahmen. Jedes seiner Elemente belegt 4 Bytes und hat die folgende Struktur:

Bild

Wie Sie in der Abbildung sehen können, ist die Übertragung von Warteschlangenadresse / Deskriptor an der Grenze von 32 Bytes ausgerichtet. Bit 0 bedeutet, dass der Host-Controller dieses Element nicht verarbeitet. Bits 3: 1 geben den Typ an, den der Host-Controller verarbeitet: 0 - isosynchrones TD (iTD), 1 - Runde, 2 und 3 in diesem Artikel werde ich nicht berücksichtigen.

Asynchrone Warteschlange


Der Host-Controller verarbeitet diese Warteschlange nur, wenn der sequentielle Frame leer ist oder der Host-Controller die gesamte serielle Liste verarbeitet hat.

Eine asynchrone Warteschlange ist ein Zeiger auf eine Warteschlange, die andere Warteschlangen enthält, die verarbeitet werden müssen. Schema:

Bild

qTD (Queue Element Transfer Descriptor)


Dieser TD hat die folgende Struktur:

Bild

Nächster qTD-Zeiger - Ein Zeiger auf die Fortsetzung der Warteschlange zur Verarbeitung (für die horizontale Ausführung), Bit 0. Nächster qTD-Zeiger zeigt an, dass keine weitere Warteschlange vorhanden ist.
qTD-Token - TD-Token, zeigt Datenübertragungsparameter an:

  • Bit 31 - Data Toggle (dazu später mehr)
  • Bits 30:16 - Die zu übertragende Datenmenge verringert sich nach Abschluss der Transaktion um die übertragene Datenmenge.
  • Bit 15 - IOC - Interrupt On Complete - Unterbrechung verursachen, nachdem die Deskriptorverarbeitung abgeschlossen ist.
  • Die Bits 14:12 zeigen die Nummer des aktuellen Puffers, zu dem / von dem Daten ausgetauscht werden, dazu später mehr.
  • Bits 11:10 - die Anzahl der zulässigen Fehler. Diese Tabelle zeigt, wann die Fehlerzahl abnimmt:

    Bild

    Fußnote 1 - Wenn Sie entweder Babble oder Stall erkennen, wird die Ausführung des Warteschlangenkopfs automatisch gestoppt. Fußnote 3 - Datenpufferfehler sind Probleme mit dem Host. Sie berücksichtigen keine Gerätewiederholungen.
  • 9: 8 - PID-Code - Token-Typ: 0 - Token zum Eingang (vom Host zum Gerät), 1 - Token zum Ausgang (vom Gerät zum Host), 2 - Token „SETUP“
  • Die Bits 7: 0 zeigen den TD-Status an:
    Bit 7 zeigt an, dass sich der TD in einem aktiven Zustand befindet (d. H. Der Host-Controller verarbeitet diesen TD).
    Bit 6 - Angehalten - zeigt an, dass ein Fehler aufgetreten ist und die TD-Ausführung gestoppt wurde.
    Bit 4 - Babble Detected - Die Datenmenge, die wir an das Gerät oder pro Umdrehung gesendet haben, ist geringer als die von uns übertragene, d. H. Das Gerät hat uns beispielsweise 100 Datenbytes gesendet, und wir lesen nur 50 Byte und dann weitere 50 Byte Das angehaltene Bit wird auch gesetzt, wenn dieses Bit auf 1 gesetzt ist.
    Bit 3 - Transaktionsfehler - Während der Transaktion ist ein Fehler aufgetreten.

qTD Buffer Page Pointer List - einer von 5 Puffern. Es enthält einen Link zu dem Ort, an dem die Transaktion im Speicher durchgeführt werden soll (Daten an das Gerät senden / Daten vom Gerät empfangen). Alle Adressen in den Puffern mit Ausnahme der ersten sollten an der Größe der Seite ausgerichtet sein (4096 Byte).

Leiter der Leitung


Der Warteschlangenkopf hat die folgende Struktur:

Bild

Horizontaler Verbindungszeiger für Warteschlangenkopf - Zeiger auf die nächste Warteschlange, Bits 2: 1 haben je nach Warteschlangentyp die folgenden Werte:

Bild

Endpunktfunktionen / -merkmale - Warteschlangenmerkmale:

  • Die Bits 26:16 enthalten die maximale Paketgröße für die Übertragung
  • Bit 14: Data Toggle Control - Zeigt an, wo der Host-Controller den anfänglichen Data Toggle-Wert 0 annehmen soll. Ignoriert das DT-Bit in qTD und speichert das DT-Bit für den Warteschlangenkopf.
  • Bit 13:12 - Übertragungsrateneigenschaften: Bild
  • Bits 11: 8 - Die Nummer des Endpunkts, an den die Anforderung gesendet wird
  • Bits 6: 0 - Geräteadresse

Endpunktfunktionen: Queue Head DWord 2 - Fortsetzung des vorherigen Doppelworts:

  • Bits 29:23 - Hub-Nummer
  • Bits 22:16 - Hub-Adresse

Aktueller qTD-Link-Zeiger - Zeiger auf den aktuellen qTD.

Wir gehen zu den interessantesten über.

EHCI-Treiber


Beginnen wir mit den Fragen, die der EHCI erfüllen kann. Es gibt zwei Arten von Anforderungen: Steuerung - a la Befehl und Bulk - an Endpunkte, für den Datenaustausch. Beispielsweise verwendet die überwiegende Mehrheit der USB-Flash-Laufwerke (USB MassStorage) den Datenübertragungstyp Bulk / Bulk / Bulk. Maus und Tastatur verwenden auch Massenanforderungen für die Datenübertragung.

Initialisieren Sie EHCI und konfigurieren Sie asynchrone und sequentielle Warteschlangen:

// Base I/O Address PciBar bar; PciGetBar(&bar, id, 0); EhciController *hc = VMAlloc(sizeof(EhciController)); hc->capRegs = (EhciCapRegs *)(uintptr_t)bar.u.address; hc->opRegs = (EhciOpRegs *)(uintptr_t)(bar.u.address + hc->capRegs->capLength); // Read the Command register //    uint cmd = ROR(usbCmdO); // Write it back, setting bit 2 (the Reset bit) //   ,   2(Reset) // and making sure the two schedule Enable bits are clear. //  ,  2   WOR(usbCmdO, 2 | cmd & ~(CMD_ASE | CMD_PSE)); // A small delay here would be good. You don't want to read //     ,     // the register before it has a chance to actually set the bit //   ,         ROR(usbCmdO); // Now wait for the controller to clear the reset bit. //      Reset while (ROR(usbCmdO) & 2); // Again, a small delay here would be good to allow the // reset to actually become complete. //   ROR(usbCmdO); // wait for the halted bit to become set //    Halted    while (!(ROR(usbStsO) & STS_HCHALTED)); //     ,        // ,           128  hc->frameList = (u32 *)VMAlloc(1024 * sizeof(u32) + 8192 * 4); hc->frameList = (((uint)hc->frameList) / 16384) * 16384 + 16384; hc->qhPool = (EhciQH *)VMAlloc(sizeof(EhciQH) * MAX_QH + 8192 * 4); hc->tdPool = (EhciTD *)VMAlloc(sizeof(EhciTD) * MAX_TD + 8192 * 4); hc->qhPool = (((uint)hc->qhPool) / 16384) * 16384 + 16384; hc->tdPool = (((uint)hc->tdPool) / 16384) * 16384 + 16384; // Asynchronous queue setup //    EhciQH *qh = EhciAllocQH(hc); //     ,      // ,    qh->qhlp = (u32)(uintptr_t)qh | PTR_QH; //  ,  ,     qh->ch = QH_CH_H; qh->caps = 0; qh->curLink = 0; qh->nextLink = PTR_TERMINATE; qh->altLink = 0; qh->token = 0; //    for (uint i = 0; i < 5; ++i) { qh->buffer[i] = 0; qh->extBuffer[i] = 0; } hc->asyncQH = qh; // Periodic list queue setup //    qh = EhciAllocQH(hc); //     qh->qhlp = PTR_TERMINATE; qh->ch = 0; qh->caps = 0; qh->curLink = 0; qh->nextLink = PTR_TERMINATE; qh->altLink = 0; qh->token = 0; //   for (uint i = 0; i < 5; ++i) { qh->buffer[i] = 0; qh->extBuffer[i] = 0; } qh->transfer = 0; qh->qhLink.prev = &qh->qhLink; qh->qhLink.next = &qh->qhLink; hc->periodicQH = qh; //        for (uint i = 0; i < 1024; ++i) hc->frameList[i] = PTR_QH | (u32)(uintptr_t)qh; kprintf("FrameList filled. Turning off Legacy BIOS support..."); // Check extended capabilities //  BIOS Legacy support uint eecp = (RCR(hccParamsO) & HCCPARAMS_EECP_MASK) >> HCCPARAMS_EECP_SHIFT; if (eecp >= 0x40) { // Disable BIOS legacy support uint legsup = PciRead32(id, eecp + USBLEGSUP); kprintf("."); if (legsup & USBLEGSUP_HC_BIOS) { PciWrite32(id, eecp + USBLEGSUP, legsup | USBLEGSUP_HC_OS); kprintf("."); for (;;) { legsup = PciRead32(id, eecp + USBLEGSUP); kprintf("."); if (~legsup & USBLEGSUP_HC_BIOS && legsup & USBLEGSUP_HC_OS) { break; } } } } kprintf("Done\n"); // Disable interrupts //   //hc->opRegs->usbIntr = 0; MWIR(ehcibase, usbIntrO, 0); // Setup frame list //     //hc->opRegs->frameIndex = 0; WOR(frameIndexO, 0); //hc->opRegs->periodicListBase = (u32)(uintptr_t)hc->frameList; WOR(periodicListBaseO, (u32)(uintptr_t)hc->frameList); //       //hc->opRegs->asyncListAddr = (u32)(uintptr_t)hc->asyncQH; WOR(asyncListAddrO, (u32)(uintptr_t)hc->asyncQH); //    0 //hc->opRegs->ctrlDsSegment = 0; WOR(ctrlDsSegmentO, 0); // Clear status //   //hc->opRegs->usbSts = ~0; WOR(usbStsO, ~0); // Enable controller //  , 8 -,  //     //hc->opRegs->usbCmd = (8 << CMD_ITC_SHIFT) | CMD_PSE | CMD_ASE | CMD_RS; WOR(usbCmdO, (8 << CMD_ITC_SHIFT) | CMD_PSE | CMD_ASE | CMD_RS); while (ROR(usbStsO)&STS_HCHALTED); // Configure all devices to be managed by the EHCI // ,   //hc->opRegs->configFlag = 1; WOR(configFlagO, 1);\ // Probe devices //   EhciProbe(hc); 

Eigentlich der Code zum Zurücksetzen des Ports auf seinen ursprünglichen Zustand:

  volatile u32 *reg = &hc->opRegs->ports[port]; //    ,  100 *reg|=(1<<12)|(1<<20); Wait(100); //  ,  50  EhciPortSet(reg, PORT_RESET | (1<<12) | (1<<20) | (1<<6)); Wait(50); EhciPortClr(reg, PORT_RESET); // Wait 100ms for port to enable (TODO - what is appropriate length of time?) //  100    ,   , //  100    uint status = 0; for (uint i = 0; i < 10; ++i) { // Delay Wait(10); // Get current status //    status = *reg; // Check if device is attached to port //      if (~status & PORT_CONNECTION) break; // Acknowledge change in status //    -    if (status & (PORT_ENABLE_CHANGE | PORT_CONNECTION_CHANGE)) { EhciPortClr(reg, PORT_ENABLE_CHANGE | PORT_CONNECTION_CHANGE); continue; } // Check if device is enabled //    ,    if (status & PORT_ENABLE) break; } return status; 

Steueranforderung an das Gerät:

 static void EhciDevControl(UsbDevice *dev, UsbTransfer *t) { EhciController *hc = (EhciController *)dev->hc; UsbDevReq *req = t->req; // Determine transfer properties //    uint speed = dev->speed; uint addr = dev->addr; uint maxSize = dev->maxPacketSize; uint type = req->type; uint len = req->len; // Create queue of transfer descriptors //   TDs EhciTD *td = EhciAllocTD(hc); if (!td) return; EhciTD *head = td; EhciTD *prev = 0; // Setup packet //   uint toggle = 0; uint packetType = USB_PACKET_SETUP; uint packetSize = sizeof(UsbDevReq); EhciInitTD(td, prev, toggle, packetType, packetSize, req); prev = td; // Data in/out packets packetType = type & RT_DEV_TO_HOST ? USB_PACKET_IN : USB_PACKET_OUT; u8 *it = (u8 *)t->data; u8 *end = it + len; //EhciPrintTD(td); while (it < end) { td = EhciAllocTD(hc); if (!td) return; toggle ^= 1; packetSize = end - it; if (packetSize > maxSize) packetSize = maxSize; EhciInitTD(td, prev, toggle, packetType, packetSize, it); it += packetSize; prev = td; } // Status packet //   td = EhciAllocTD(hc); if (!td) return; toggle = 1; packetType = type & RT_DEV_TO_HOST ? USB_PACKET_OUT : USB_PACKET_IN; EhciInitTD(td, prev, toggle, packetType, 0, 0); // Initialize queue head //   : EhciQH *qh = EhciAllocQH(hc); EhciInitQH(qh, t, head, dev->parent, false, speed, addr, 0, maxSize); // Wait until queue has been processed //       EhciInsertAsyncQH(hc->asyncQH, qh); EhciWaitForQH(hc, qh); } 

Code für die Warteschlangenverarbeitung:

  if (qh->token & TD_TOK_HALTED) { t->success = false; t->complete = true; } else if (qh->nextLink & PTR_TERMINATE) if (~qh->token & TD_TOK_ACTIVE) { if (qh->token & TD_TOK_DATABUFFER) kprintf(" Data Buffer Error\n"); if (qh->token & TD_TOK_BABBLE) kprintf(" Babble Detected\n"); if (qh->token & TD_TOK_XACT) kprintf(" Transaction Error\n"); if (qh->token & TD_TOK_MMF) kprintf(" Missed Micro-Frame\n"); t->success = true; t->complete = true; } if (t->complete) .... 

Und jetzt die Endpunktanforderung (Massenanforderung)

 static void EhciDevIntr(UsbDevice *dev, UsbTransfer *t) { EhciController *hc = (EhciController *)dev->hc; // Determine transfer properties //    uint speed = dev->speed; uint addr = dev->addr; uint maxSize = t->endp->desc->maxPacketSize; uint endp = t->endp->desc->addr & 0xf; EhciTD *td = EhciAllocTD(hc); if (!td) { t->success = false; t->complete = true; return; } EhciTD *head = td; EhciTD *prev = 0; // Data in/out packets uint toggle = t->endp->toggle; uint packetType = t->endp->desc->addr & 0x80 ? USB_PACKET_IN : USB_PACKET_OUT; uint packetSize = t->len; EhciInitTD(td, prev, toggle, packetType, packetSize, t->data); // Initialize queue head //    EhciQH *qh = EhciAllocQH(hc); EhciInitQH(qh, t, head, dev->parent, true, speed, addr, endp, maxSize); //printQh(qh); // Schedule queue //    EhciInsertPeriodicQH(hc->periodicQH, qh); } 

Ich finde das Thema sehr interessant, im Internet auf Russisch gibt es fast keine Dokumente, Beschreibungen und Artikel zu diesem Thema, und wenn ja, ist es sehr verschwommen. Wenn das Thema der Arbeit mit Hardware- und Betriebssystementwicklung interessant ist, gibt es viel zu erzählen.

Docks: Spezifikation

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


All Articles