UHCI, ou le tout premier USB



Bonjour, cher lecteur! On m'a demandé d'écrire sur UHCI - eh bien, j'écris.

Cet article peut vous être utile si, par exemple, vous n'avez pas les compétences suffisantes en écriture pour les pilotes et en lecture de documentation pour un matériel. Un exemple simple: vous voulez écrire votre système d'exploitation pour un mini-PC, afin que certaines distributions Windows ou Linux ne téléchargent pas de matériel, et vous utilisez toute sa puissance exclusivement à vos propres fins.

Qu'est-ce que l'UHCI?


Je pense, afin de ne pas pulvériser à nouveau sur le sujet de quoi et pourquoi, il suffit de laisser un lien vers mon article précédent sur EHCI. Piquez ici
UHCI - Universal Host Controller Interface, fonctionne comme un périphérique PCI, mais, contrairement à EHCI, utilise des ports au lieu de MMIO (Memory-Mapped-IO).



Termes à utiliser ci-après


  • Pilote USB (USBD) - le pilote USB lui-mĂŞme
  • HC (Host Controller) - un contrĂ´leur hĂ´te, ou tout simplement notre UHCI
  • Host Controller Driver (HCD) - un pilote qui connecte le matĂ©riel et l'USBD
  • PĂ©riphĂ©rique USB - PĂ©riphĂ©rique USB lui-mĂŞme

Types de transfert de données


Isochronous - transmission isosynchrone, qui a une fréquence donnée de transfert de données. Il peut être utilisé, par exemple, pour des microphones USB, etc.

Interruption - Petits transferts de données spontanés à partir d'un appareil. Le type de transmission d'interruption prend en charge les dispositifs qui nécessitent un intervalle de service prévisible mais ne fournissent pas nécessairement un flux de données prévisible. Couramment utilisé pour les périphériques tels que les claviers et les périphériques de pointage qui peuvent ne pas fournir de données pendant de longues périodes, mais nécessitent une réponse rapide lorsqu'ils ont des données à envoyer.

Contrôle - Type de transmission d'informations sur l'état, l'état et la configuration de l'appareil. Le type de transfert de contrôle est utilisé pour fournir un canal de contrôle de l'hôte aux périphériques USB. Les transmissions de contrôle se composent toujours d'une phase de configuration et de zéro ou plusieurs phases de données suivies d'une phase d'état. Il est impératif que le transfert de contrôle vers un point d'extrémité donné soit traité en mode FIFO. Si le contrôle est transmis au même point de terminaison, l'entrelacement peut entraîner un comportement imprévisible.

En vrac - type de transfert de tableaux de données. Utilisé, par exemple, dans les appareils MassStorage.



Voici Ă  quoi ressemble la distribution temporelle 1 ms - traitement d'une image.

Distribution du temps


Le contrôleur hôte prend en charge la livraison de données en temps réel en générant un paquet de début de trame (SOF) toutes les 1 ms. Un paquet SOF est généré lorsque le compteur SOF du contrôleur hôte expire (figure 3). Le contrôleur hôte initialise le compteur SOF pour une durée de trame de 1 ms. De petites modifications peuvent être apportées à cette valeur (et donc à la période de temps de trame) en programmant le registre de changement SOF. Cette fonction vous permet d'apporter des modifications mineures à la période de trame, si nécessaire, pour maintenir la synchronisation en temps réel sur l'ensemble du système USB.

Le contrôleur hôte inclut le numéro de trame dans chaque paquet SOF. Ce numéro de trame détermine uniquement la période de trame en temps réel. La condition de fin de trame (EOF) se produit à la fin de l'intervalle de temps de 1 ms lorsque le contrôleur hôte démarre la prochaine trame, générant un autre paquet SOF avec le numéro de trame correspondant. Pendant la période de trame, les données sont transmises sous forme de paquets d'informations. La période de trame est strictement appliquée par le contrôleur hôte, et les paquets de données dans la trame actuelle ne peuvent pas aller au-delà de l'EOF (voir le chapitre 11 dans la spécification USB). Le contrôleur hôte prend en charge la synchronisation de la transmission de données entre les trames en temps réel, reliant le numéro de trame pour effectuer une entrée spécifique dans la liste des trames. Le compteur de trames du contrôleur hôte génère un numéro de trame (valeur 11 bits) et l'inclut dans chaque paquet SOF. Le compteur est programmé via des registres et chaque période de trame est incrémentée. Le contrôleur hôte utilise les 10 bits inférieurs du numéro de trame comme index dans la liste de trames avec 1024 trames, qui est stockée dans la mémoire système. Ainsi, puisque le compteur de trames contrôle la sélection d'une entrée dans la liste de trames, le contrôleur hôte traite chaque entrée de la liste dans une période de trame donnée. Le contrôleur hôte se développe à l'entrée suivante dans la liste de trames pour chaque nouvelle trame. Cela garantit que les transmissions isochrones sont effectuées dans une trame spécifique.

Figure 3:



Structure UHCI


Tout est exactement le mĂŞme qu'avec EHCI. Exemples de demandes Ă  HC:



Configurer et accéder à UHCI


Et donc, comme je l'ai dit plus tĂ´t, UHCI fonctionne via des ports, donc Ă  partir de PCI, nous devons trouver la base des registres UHCI.



Au décalage 0x20, il y a 4 octets - IO Base. Concernant IO Base, nous pouvons utiliser les registres suivants:



Registres UHCI


  • USBCMD est un registre pour contrĂ´ler HC. Bits:
    • Le bit 6 est un indicateur que le pĂ©riphĂ©rique est configurĂ© et initialisĂ© avec succès.
    • Bit 1 - RĂ©initialisation HC. RĂ©glez pour rĂ©initialiser HC.
    • Bit 0 - Run / Stop. Affiche l'Ă©tat HC. 1 - fonctionne, 0 - non.
  • USBSTS - Registre d'Ă©tat. Bits:
    • Bit 5 - HC arrĂŞtĂ©. Une erreur s'est produite ou le contrĂ´leur a rĂ©ussi la rĂ©initialisation HC.
    • Bit 4 - Erreur de processus du contrĂ´leur hĂ´te. Le bit est mis Ă  1 lorsqu'une erreur critique s'est produite et que HC ne peut pas continuer la mise en file d'attente et TD.
    • Bit 3 - Erreur du système hĂ´te. Erreur PCI.
    • Bit 1 - Interruption d'erreur. Indique qu'une erreur s'est produite et que le HC a gĂ©nĂ©rĂ© une interruption.
    • Bit 0 - Interrompre. Indique que HC a gĂ©nĂ©rĂ© une interruption.
  • USBINTR - Registre des paramètres d'interruption. Bits:
    • Le bit 2 - IOC - Interruption Ă  la fin - gĂ©nère une interruption Ă  la fin de la transaction.
  • FRNUM - NumĂ©ro de la trame actuelle (Take it & 0x3FF pour la valeur correcte).
  • FLBASEADD - Frame List Base Address - adresse de la liste des trames.
  • PORTSC - Statut et contrĂ´le des ports - registre d'Ă©tat et de contrĂ´le des ports. Bits:
    • Bit 9 - Port Reset - 1 - port Ă  rĂ©initialiser.
    • Bit 8 - indique qu'un pĂ©riphĂ©rique basse vitesse est connectĂ© au port
    • Bit 3 - Indique que l'Ă©tat du port sur a changĂ©
    • Bit 2 - Indique si le port est activĂ©
    • Bit 1 - indique que l'Ă©tat de l'appareil est connectĂ© au port
    • Bit 0 - indique que l'appareil est connectĂ© au port.

Structures


Pointeur de liste d'images




Descrptor de transfert




CONTRÔLE ET ÉTAT TD
. Bits:
  • Bits 28-27 - compteur d'erreurs, similaire Ă  EHCI.
    • Bit 26 - 1 = appareil Ă  basse vitesse, 0 = appareil Ă  pleine vitesse.
    • Bit 25 - 1 = TD isosynchrone
    • Bit 24 - IOC
    • Bits 23-16 - Statut:
    • Bit 23 - Indique qu'il s'agit d'un TD actif
    • Bit 22 - au point mort
    • Bit 21 - Erreur de tampon de donnĂ©es
    • Bit 20 - Babble dĂ©tectĂ©
    • Bit 19 - NAK
  • Bits 10–0: nombre d'octets transmis par le contrĂ´leur hĂ´te.

Jeton TD

  • Bits 31:21 - Max Packet Len, similaire Ă  EHCI
  • Bit 19 - Basculement des donnĂ©es, similaire Ă  EHCI
  • Bits 18:15 - NumĂ©ro du point final
  • Bits 18:14 - adresse de l'appareil
  • Bits 7: 0 - PID. In = 0x69, Out = 0xE1, Setup = 0x2D

TĂŞte de file d'attente




Code


Initialisez et configurez HC:

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); 

Demandes de point de terminaison et de contrĂ´le:

 // ------------------------------------------------------------------------------------------------ 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/fr429422/


All Articles