
Présentation
Je souhaite la bienvenue à tous. Aujourd'hui, je veux partager mon expérience et toujours, à mon avis, expliquer clairement à ce sujet, à première vue, une norme simple pour le contrôleur hôte USB 2.0.
Au départ, vous pouvez imaginer qu'un port USB 2.0 n'est qu'à 4 broches, dont deux transmettent simplement des données (comme, par exemple, un port COM), mais en fait, tout n'est pas le cas, et même le contraire. Le contrôleur USB, en principe, ne nous permet pas de transférer des données comme via un port COM standard. EHCI est une norme assez complexe qui permet un transfert de données fiable et rapide du logiciel vers l'appareil lui-même, et dans la direction opposée.
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 fer, et vous utilisez toute sa puissance exclusivement à vos propres fins.
Qu'est-ce que EHCI?
Eh bien, commençons. EHCI - Enhanced Host Controller Interface, est conçu pour transférer des données et des demandes de contrôle vers des périphériques USB, et dans l'autre sens, et dans 99% des cas, c'est un lien entre n'importe quel logiciel et un périphérique physique. EHCI fonctionne comme un périphérique PCI et utilise en conséquence MMIO (Memory-Mapped-IO) pour contrôler le contrôleur (oui, je sais que certains périphériques PCI utilisent des ports, mais ici j'ai tout généralisé). La documentation d'Intel ne décrit que le principe de fonctionnement, et il n'y a aucune indication sur tous les algorithmes écrits au moins en pseudo-code. EHCI dispose de 2 types de registres MMIO: Capability et Operational. Les premiers servent à obtenir les caractéristiques du contrôleur, tandis que les seconds servent à le contrôler. En fait, je vais attacher l'essence même de la connexion entre le logiciel et le contrôleur EHCI:

Chaque contrôleur EHCI possède plusieurs ports, chacun pouvant être connecté à n'importe quel périphérique USB. Veuillez également noter que EHCI est une version améliorée de UHCI, qui a également été développée par Intel quelques années plus tôt. Pour des raisons de compatibilité descendante, tout contrôleur UHCI / OHCI qui a une version inférieure à EHCI sera un compagnon d'EHCI. Par exemple, vous avez un clavier USB (et la plupart des claviers de l'année jusqu'à présent ont été comme ça) qui fonctionne sur USB 1.1 (notez que la vitesse maximale d'USB 1.1 est de 12 mégabits par seconde, et FullSpeed USB 2.0 a une bande passante jusqu'à 480 Mbps), et si vous avez un ordinateur avec un port USB 2.0, lorsque vous connectez le clavier à l'ordinateur, le contrôleur hôte EHCI fonctionnera de quelque manière que ce soit avec USB 1.1. Ce modèle est illustré dans le diagramme suivant:

De plus, pour l'avenir, je tiens à vous avertir immédiatement que votre pilote peut ne pas fonctionner correctement en raison d'une situation aussi absurde: vous avez initialisé UHCI, puis EHCI, tout en ajoutant deux périphériques identiques, définissez le bit de contrôle du propriétaire du port sur le registre de port, puis UHCI a cessé de fonctionner, car EHCI fait automatiquement glisser le port sur lui-même, et le port sur UHCI ne répond plus, cette situation doit être surveillée.
Examinons également un diagramme montrant l'architecture EHCI elle-même:

À droite est écrit sur la file d'attente - à leur sujet un peu plus tard.
Registres du contrôleur EHCI
Pour commencer, je tiens à préciser une fois de plus que grâce à ces registres, vous contrôlerez votre appareil, ils sont donc très importants - et sans eux, la programmation EHCI est impossible.
Vous devez d'abord obtenir l'adresse MMIO qui est donnée à ce contrôleur, à offset + 0x10 sera l'adresse de nos registres tant attendus. Il y a une chose: tout d'abord, les registres de capacité vont, et seulement après eux - Opérationnels, donc, au décalage 0 (à partir de l'adresse précédente, que nous avons reçue au décalage 0x10 par rapport au début du MMIO de notre EHCI), il y a un octet - la longueur des registres de capacité.
Registres de capacité
Au décalage 2, le registre
HCIVERSION est
localisé - le numéro de révision de ce HC, qui prend 2 octets et contient la version BCD de la révision (ce BCD peut être trouvé sur Wikipedia).
Au décalage +4, le registre
HCSPARAMS est
localisé , sa taille est de 2 mots, il contient les paramètres structurels de l'appareil et ses bits affichent ce qui suit:
- Bit 16 - Indicateurs de port - LED disponibles pour les périphériques USB connectés.
- Bits 15:12 - le numéro du contrôleur compagnon affecté à ce contrôleur
- Bits 11: 8 - le nombre de ports sur le contrôleur compagnon
- Bit 7 - Règles de routage des ports - montre comment ces ports sont mappés aux ports compagnons
- Bit 4 - Contrôle de l'alimentation du port - indique s'il est nécessaire de mettre sous tension chaque port, 0 - l'alimentation est fournie automatiquement
- Bits 3: 0 - le nombre de ports pour ce contrôleur.
- Au décalage +8 se trouve le registre HCCPARAMS - il affiche les paramètres de compatibilité, ses bits signifient ce qui suit:
- Bit 2 - disponibilité de la file d'attente asynchrone,
- Bit 1 - Disponibilité de la file d'attente périodique (séquentielle)
- Compatibilité bits 0 - 64 bits
Registres d'opération
Au décalage 0, le registre
USBCMD est le registre de commande du contrôleur, ses bits signifient ce qui suit:
- Bits 23:16 - Interrupt Threshold Control - montre combien de micro-trames seront utilisées pour une trame régulière. Le plus grand, le plus rapide, mais s'il est supérieur à 8, les micro-images seront traitées à la même vitesse que pour le 8.
- Bit 6 - interruption après chaque transaction dans la file d'attente asynchrone,
- Bit 5 - est la file d'attente asynchrone utilisée
- Bit 4 - utilisation de la file d'attente séquentielle,
- Bits 3: 2 - la taille de FrameList'a (plus à ce sujet plus tard). 0 signifie 1024 éléments, 1 - 512, 2 - 256, 3 - réservés
- Bit 1 - défini pour réinitialiser le contrôleur hôte.
- Bit 0 - Marche / Arrêt
.
Ensuite, au décalage +4, il y a le registre
USBSTS - l'état du contrôleur hôte,
- Le bit 15 indique si une file d'attente asynchrone est utilisée.
- Le bit 14 indique si une file d'attente séquentielle est utilisée,
- Bit 13 - indique qu'une file d'attente asynchrone vide a été détectée,
- Le bit 12 est défini sur 1, si une erreur s'est produite lors du traitement de la transaction, le contrôleur hôte arrêtera toutes les files d'attente.
- Le bit 4 est défini sur 1, si une erreur grave se produit, le contrôleur hôte arrête toutes les files d'attente.
- Bit 3 FrameList (Register) Rollover - défini sur 1 lorsque le contrôleur hôte a traité l'intégralité de la FrameList.
- Bit 1 - Interruption d'erreur USB - Dois-je générer une interruption d'erreur?
- Bit 0 - Interruption USB - défini après un traitement de transaction réussi, si IOC a été installé dans TD
Pas fatigué? Vous pouvez vous verser une mouette solide et amener le foie, nous sommes au tout début!
Au décalage +8, il y a un registre
USBINTR - le registre d'activation d'interruption
Afin de ne pas écrire pendant longtemps, et encore plus, pour ne pas lire pendant longtemps, les valeurs des bits de ce registre peuvent être trouvées dans la spécification, un lien vers celui-ci sera laissé ci-dessous. Ici, j'écris juste 0, car Je n'ai absolument aucune envie d'écrire des gestionnaires, des interruptions de carte, etc., donc je pense que cela est presque complètement inutile.
À l'offset +12 (0x0C), se trouve le registre
FRINDEX , dans lequel se trouve simplement le numéro de trame actuel, et je tiens à noter que les 4 derniers bits affichent le numéro de micro-trame, dans les 28 bits supérieurs le numéro de trame (également la valeur n'est pas nécessairement inférieure à la taille de frameList Mais si vous avez besoin d'un index, il vaut mieux le prendre avec un masque de 0x3FF (ou 0x1FF, etc.).
Le registre
CTRLDSSEGMENT est à offset + 0x10; il montre au contrôleur hôte les 32 bits les plus significatifs de l'adresse de la feuille de trame.
Le registre
PERIODICLISTBASE a un décalage de + 0x14, vous pouvez y mettre les 32 bits inférieurs de la feuille de trame, notez que l'adresse doit être alignée sur la taille de la page mémoire (4096).
Le registre
ASYNCLISTADDR a un décalage de + 0x18, vous pouvez y mettre l'adresse de la file d'attente asynchrone, notez qu'elle doit être alignée à la limite de 32 octets, alors qu'elle doit être dans les quatre premiers gigaoctets de mémoire physique.
Le registre
CONFIGFLAG indique si le périphérique est configuré. Vous devez définir le bit 0 après avoir terminé la configuration de l'appareil, il a un décalage de + 0x40.
Passons aux registres de ports. Chaque port a son propre registre d'état de commande, chaque registre de port est décalé
+ 0x44 + (PortNumber - 1) * 4 , ses bits signifient ce qui suit:
- Bit 12 - alimentation du port, 1 - l'alimentation est fournie, 0 - non.
- Le bit 8 - Port Rest - est défini pour réinitialiser l'appareil.
- Bit 3 - Port Enable / Disable Change - défini lors du changement de l'état de "l'inclusion" du port.
- Bit 2 - port activé / désactivé.
- Bit 1 - Modifier l'état de la connexion, est défini sur 1, par exemple, si vous avez connecté ou déconnecté un périphérique USB.
- Bit 0 - état de la connexion, 1 - connecté, 0 - non.
Passons maintenant au jus lui-même.
Structures de transfert de données et de requêtes
L'organisation d'une structure de traitement des demandes comprend des files d'attente et des descripteurs de transfert (TD).
Pour le moment, nous ne considérerons que 3 structures.
Liste séquentielle
La liste séquentielle (Périodique, Péréodique) est organisée comme suit:

Comme vous pouvez le voir dans le diagramme, le traitement commence par l'obtention de la trame souhaitée à partir du cadre de feuille, chacun de ses éléments occupe 4 octets et a la structure suivante:

Comme vous pouvez le voir sur l'image, le transfert d'adresse / descripteur de file d'attente est aligné à la limite de 32 octets, le bit 0 signifie que le contrôleur hôte ne traitera pas cet élément, les bits 3: 1 indiquent le type de ce que le contrôleur hôte traitera: 0 - TD isosynchrone (iTD), 1 - tour, 2 et 3 dans cet article, je ne considérerai pas.
File d'attente asynchrone
Le contrôleur hôte traite cette file d'attente uniquement lorsque la trame séquentielle est vide ou que le contrôleur hôte a traité l'intégralité de la liste série.
Une file d'attente asynchrone est un pointeur vers une file d'attente qui contient d'autres files d'attente qui doivent être traitées. Schéma:

qTD (descripteur de transfert d'élément de file d'attente)
Ce TD a la structure suivante:
Pointeur qTD suivant - un pointeur vers la continuation de la file d'attente pour le traitement (pour l'exécution horizontale), bit 0 Le pointeur qTD suivant indique qu'il n'y a plus de file d'attente.
Jeton qTD - jeton TD, affiche les paramètres de transfert de données:
- Bit 31 - Basculement des données (plus à ce sujet plus tard)
- Bits 30:16 - la quantité de données à transférer, après l'achèvement de la transaction, leur valeur diminue de la quantité de données transférées.
- Bit 15 - IOC - Interruption à la fin - provoque une interruption une fois le traitement des descripteurs terminé.
- Les bits 14:12 indiquent le numéro du tampon actuel vers / à partir duquel les données sont échangées, plus à ce sujet plus tard.
- Bits 11:10 - le nombre d'erreurs autorisées. Ce tableau indique quand le nombre d'erreurs diminue:

Note de bas de page 1 - la détection de Babble ou Stall arrête automatiquement l'exécution de la tête de file d'attente. Référence 3 - Les erreurs de tampon de données sont des problèmes avec l'hôte. Ils ne tiennent pas compte des tentatives de périphérique. - 9: 8 - Code PID - type de jeton: 0 - jeton à l'entrée (de l'hôte à l'appareil), 1 - jeton à la sortie (de l'appareil à l'hôte), 2 - jeton «SETUP»
- Les bits 7: 0 indiquent l'état TD:
Le bit 7 indique que le TD est dans un état actif (c'est-à-dire que le contrôleur hôte traite ce TD)
Bit 6 - Halted - indique qu'une erreur s'est produite et que l'exécution de TD s'est arrêtée.
Bit 4 - Babble Detected - la quantité de données que nous avons envoyées à l'appareil, ou par tour, est inférieure à ce que nous transmettons, c'est-à-dire, par exemple, l'appareil nous a envoyé 100 octets de données, et nous lisons seulement 50 octets, puis 50 autres Le bit Halted sera également défini si ce bit est défini sur 1.
Bit 3 - Erreur de transaction - Une erreur s'est produite lors de la transaction.
qTD Buffer Page Pointer List - l'un des 5 tampons. Il contient un lien vers l'endroit où en mémoire la transaction doit être effectuée (envoyer des données à l'appareil / recevoir des données de l'appareil), toutes les adresses dans les tampons, sauf la première, doivent être alignées sur la taille de la page (4096 octets).
Responsable de ligne
La tête de file d'attente a la structure suivante:
Pointeur de lien horizontal de tête de file d'attente - pointeur vers la file d'attente suivante, les bits 2: 1 ont les valeurs suivantes selon le type de file d'attente:
Capacités / caractéristiques des terminaux - caractéristiques des files d'attente:
- Les bits 26:16 contiennent la taille de paquet maximale pour la transmission
- Bit 14: Data Toggle Control - indique où le contrôleur hôte doit prendre la valeur initiale de Data Toggle, 0 - ignore le bit DT dans qTD, enregistre le bit DT pour la tête de file d'attente.
- Bit 13:12 - caractéristiques de la vitesse de transmission:

- Bits 11: 8 - le numéro du noeud final auquel la demande est adressée
- Bits 6: 0 - adresse de l'appareil
Capacités de point de terminaison: Queue Head DWord 2 - suite du mot double précédent:
- Bits 29:23 - Numéro de concentrateur
- Bits 22:16 - Adresse du hub
Pointeur de lien qTD actuel - pointeur vers le qTD actuel.
Nous passons au plus intéressant.
Pilote EHCI
Commençons par les requêtes que l'EHCI peut répondre. Il existe 2 types de demandes: Contrôle - à la commande, et Bulk - aux points de terminaison, pour l'échange de données, par exemple, la grande majorité des lecteurs flash USB (USB MassStorage) utilisent le type de transfert de données Bulk / Bulk / Bulk. La souris et le clavier utilisent également des demandes groupées pour le transfert de données.
Initialisez EHCI et configurez les files d'attente asynchrones et séquentielles:
En fait, le code pour réinitialiser le port à son état d'origine:
volatile u32 *reg = &hc->opRegs->ports[port];
Demande de contrôle à l'appareil:
static void EhciDevControl(UsbDevice *dev, UsbTransfer *t) { EhciController *hc = (EhciController *)dev->hc; UsbDevReq *req = t->req;
Code de traitement des files d'attente:
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) ....
Et maintenant la demande de point final (demande en masse)
static void EhciDevIntr(UsbDevice *dev, UsbTransfer *t) { EhciController *hc = (EhciController *)dev->hc;
Je pense que le sujet est assez intéressant, sur Internet en russe il n'y a presque pas de documentation, de descriptions et d'articles sur ce sujet, et s'il y en a, c'est très flou. Si le sujet du travail avec le développement matériel et OS est intéressant, alors il y a beaucoup à dire.
Docks:
spécification