
Introduccion
Doy la bienvenida a todos. Hoy quiero compartir mi experiencia y aún, en mi opinión, explicar claramente sobre, a primera vista, un estándar simple para el controlador de host USB 2.0.
Inicialmente, puede imaginar que un puerto USB 2.0 tiene solo 4 pines, dos de los cuales simplemente transmiten datos (como, por ejemplo, un puerto COM), pero de hecho, no todo es así, e incluso todo lo contrario. El controlador USB, en principio, no nos permite transferir datos como a través de un puerto COM normal. EHCI es un estándar bastante complejo que permite la transferencia de datos confiable y rápida del software al dispositivo en sí, y en la dirección opuesta.
Puede encontrar este artículo útil si, por ejemplo, no tiene suficientes habilidades de escritura para los controladores y la lectura de la documentación de un hardware. Un ejemplo simple: desea escribir su sistema operativo para una mini PC, de modo que alguna distribución de Windows u otra distribución de Linux no descargue hardware, y use toda su potencia exclusivamente para sus propios fines.
¿Qué es el EHCI?
Bueno, empecemos. EHCI - Interfaz de controlador de host mejorada, está diseñada para transferir datos y controlar solicitudes a dispositivos USB, y en la otra dirección, y en el 99% de los casos es un enlace entre cualquier software y un dispositivo físico. EHCI funciona como un dispositivo PCI y, en consecuencia, utiliza MMIO (Memory-Mapped-IO) para controlar el controlador (sí, sé que algunos dispositivos PCI usan puertos, pero aquí generalicé todo). La documentación de Intel describe solo el principio de funcionamiento, y no hay pistas sobre todos los algoritmos escritos al menos en pseudocódigo. EHCI tiene 2 tipos de registros MMIO: Capacidad y Operativo. Los primeros sirven para obtener las características del controlador, mientras que los segundos sirven para controlarlo. En realidad, adjuntaré la esencia misma de la conexión entre el software y el controlador EHCI:

Cada controlador EHCI tiene varios puertos, cada uno de los cuales se puede conectar a cualquier dispositivo USB. Además, tenga en cuenta que EHCI es una versión mejorada de UHCI, que también fue desarrollada por Intel unos años antes. Por compatibilidad con versiones anteriores, cualquier controlador UHCI / OHCI que tenga una versión inferior a EHCI será un compañero de EHCI. Por ejemplo, tiene un teclado USB (y la mayoría de los teclados del año hasta ahora han sido así) que funciona en USB 1.1 (tenga en cuenta que la velocidad máxima de USB 1.1 es de 12 megabits por segundo, y FullSpeed USB 2.0 tiene ancho de banda tanto como 480 Mbps), y tiene una computadora con un puerto USB 2.0, cuando conecta el teclado a la computadora, el controlador host EHCI funcionará de cualquier manera con USB 1.1. Este modelo se muestra en el siguiente diagrama:

Además, para el futuro quiero advertirle de inmediato que su controlador puede no funcionar correctamente debido a una situación tan absurda: inicializó UHCI y luego EHCI, mientras agregaba dos dispositivos idénticos, establezca el bit de Control del propietario del puerto en el registro del puerto y UHCI dejó de funcionar, debido a que EHCI arrastra automáticamente el puerto sobre sí mismo, y el puerto en UHCI deja de responder, esta situación necesita ser monitoreada.
Además, veamos un diagrama que muestra la propia arquitectura EHCI:

A la derecha está escrito sobre la cola, sobre ellos un poco más tarde.
Registros del controlador EHCI
Para empezar, quiero aclarar una vez más que a través de estos registros controlarás tu dispositivo, por lo tanto, son muy importantes, y sin ellos la programación EHCI es imposible.
Primero debe obtener la dirección MMIO que se le da a este controlador, en el desplazamiento + 0x10 será la dirección de nuestros registros tan esperados. Hay una cosa: primero, los registros de capacidad van, y solo después de ellos: operativos, por lo tanto, en el desplazamiento 0 (de la dirección anterior, que recibimos en el desplazamiento 0x10 en relación con el inicio de nuestro MMIO de EHCI), hay un byte: la longitud de los registros de capacidad.
Registros de capacidad
En el desplazamiento 2, se encuentra el registro
HCIVERSION : el número de revisión de este HC, que toma 2 bytes y contiene la versión BCD de la revisión (qué BCD se puede encontrar en Wikipedia).
En el desplazamiento +4, se encuentra el registro
HCSPARAMS , su tamaño es de 2 palabras, contiene los parámetros estructurales del dispositivo y sus bits muestran lo siguiente:
- Bit 16 - Indicadores de puerto - LED disponibles para dispositivos USB conectados.
- Bits 15:12: el número del controlador complementario asignado a este controlador
- Bits 11: 8: la cantidad de puertos en el controlador complementario
- Bit 7: reglas de enrutamiento de puertos: muestra cómo estos puertos se asignan a puertos complementarios
- Bit 4: control de alimentación del puerto: indica si es necesario encender la alimentación de cada puerto; 0: la alimentación se suministra automáticamente
- Bits 3: 0: el número de puertos para este controlador.
- En el desplazamiento +8 se encuentra el registro HCCPARAMS: muestra los parámetros de compatibilidad, sus bits significan lo siguiente:
- Bit 2: disponibilidad de cola asíncrona,
- Bit 1: disponibilidad de cola periódica (secuencial)
- Bit 0 - compatibilidad de 64 bits
Registros de operaciones
En el desplazamiento 0, el registro
USBCMD es el registro de comando del controlador, sus bits significan lo siguiente:
- Bits 23:16 - Control de umbral de interrupción: muestra cuántos micro cuadros se usarán para un cuadro normal. Cuanto más grande, más rápido, pero si es más de 8, los micro-cuadros se procesarán a la misma velocidad que para 8.
- Bit 6: interrupción después de cada transacción en la cola asíncrona,
- Bit 5: es la cola asincrónica utilizada
- Bit 4 - uso de cola secuencial,
- Bits 3: 2: el tamaño de FrameList'a (más sobre eso más adelante). 0 significa 1024 elementos, 1 - 512, 2 - 256, 3 - reservado
- Bit 1: se establece para restablecer el controlador de host.
- Bit 0 - Ejecutar / Parar
.
A continuación, en el desplazamiento +4, está el registro
USBSTS : el estado del controlador host,
- El bit 15 indica si se está utilizando una cola asincrónica.
- El bit 14 indica si se está utilizando una cola secuencial,
- Bit 13: indica que se ha detectado una cola asíncrona vacía,
- El bit 12 se establece en 1, si se produjo un error al procesar la transacción, el controlador host detendrá todas las colas.
- El bit 4 se establece en 1, si se produce un error grave, el controlador host detiene todas las colas.
- Bit 3 FrameList (Registro) Rollover: se establece en 1 cuando el controlador host procesó toda la FrameList.
- Bit 1 - Interrupción de error USB - ¿Genero una interrupción de error?
- Bit 0: interrupción de USB: se establece después del procesamiento exitoso de la transacción, si IOC se instaló en TD
No cansado? Puedes servirte una gaviota fuerte y traer el hígado, ¡estamos al principio!
En el desplazamiento +8, hay un registro
USBINTR : el registro de habilitación de interrupción
Para no escribir durante mucho tiempo, y aún más, para que no lea durante mucho tiempo, los valores de los bits de este registro se pueden encontrar en la especificación, se dejará un enlace a continuación. Aquí solo escribo 0, porque No tengo ningún deseo de escribir controladores, interrupciones de mapas, etc., por lo que creo que esto es casi completamente inútil.
En el desplazamiento +12 (0x0C) se encuentra el registro
FRINDEX , en el que simplemente se encuentra el número de cuadro actual, y quiero señalar que los últimos 4 bits muestran el número de micro-cuadro, en los 28 bits superiores el número de cuadro (el mismo valor no es necesariamente menor que el tamaño de la lista de cuadros Pero si necesita un índice, es mejor tomarlo con una máscara de 0x3FF (o 0x1FF, etc.).
El registro
CTRLDSSEGMENT está en offset + 0x10; muestra al controlador host los 32 bits más significativos de la dirección de la hoja de cuadro.
El registro
PERIODICLISTBASE tiene un desplazamiento de + 0x14, puede colocar los 32 bits inferiores de la hoja de marco, tenga en cuenta que la dirección debe estar alineada con el tamaño de la página de memoria (4096).
El registro
ASYNCLISTADDR tiene un desplazamiento de + 0x18, puede poner la dirección de la cola asincrónica en él, tenga en cuenta que debe estar alineado en el límite de 32 bytes, mientras que debe estar en los primeros cuatro gigabytes de memoria física.
El registro
CONFIGFLAG indica si el dispositivo está configurado. Debe establecer el bit 0 después de completar la configuración del dispositivo, tiene un desplazamiento de + 0x40.
Pasemos a los registros de puertos. Cada puerto tiene su propio registro de estado de comando, cada registro de puerto está desplazado
+ 0x44 + (PortNumber - 1) * 4 , sus bits significan lo siguiente:
- Bit 12 - alimentación de puerto, 1 - se suministra alimentación, 0 - no.
- El Bit 8 - Port Rest - está configurado para restablecer el dispositivo.
- Bit 3: cambio de activación / desactivación de puerto: se establece al cambiar el estado de la "inclusión" del puerto.
- Bit 2 - puerto activado / desactivado.
- Bit 1: cambie el estado de la conexión; se establece en 1, por ejemplo, si conectó o desconectó un dispositivo USB.
- Bit 0 - estado de conexión, 1 - conectado, 0 - no.
Ahora pasemos al jugo mismo.
Transferencia de datos y estructuras de consulta
Organizar una estructura para procesar solicitudes incluye colas y descriptores de transferencia (TD).
Por el momento, consideraremos solo 3 estructuras.
Lista secuencial
La lista secuencial (periódica, pereódica) se organiza de la siguiente manera:

Como puede ver en el diagrama, el procesamiento comienza con la obtención del marco deseado del marco de la hoja, cada uno de sus elementos ocupa 4 bytes y tiene la siguiente estructura:

Como puede ver en la imagen, la transferencia de la dirección / descriptor de la cola está alineada en el límite de 32 bytes, el bit 0 significa que el controlador del host no procesará este elemento, los bits 3: 1 indican el tipo de lo que procesará el controlador del host: 0 - TD isosíncrono (iTD), 1 - turno, 2 y 3 en este artículo que no consideraré.
Cola asincrónica
El controlador de host procesa esta cola solo cuando el marco secuencial está vacío o el controlador de host ha procesado toda la lista de serie.
Una cola asíncrona es un puntero a una cola que contiene otras colas que necesitan procesamiento. Esquema:

qTD (Descriptor de transferencia de elemento de cola)
Este TD tiene la siguiente estructura:
Siguiente puntero qTD : un puntero a la continuación de la cola para el procesamiento (para ejecución horizontal), bit 0 Siguiente puntero qTD indica que no hay más colas.
Token qTD: token TD, muestra los parámetros de transferencia de datos:
- Bit 31 - Alternar datos (más sobre eso más adelante)
- Bits 30:16: la cantidad de datos a transferir, después de la finalización de la transacción, su valor disminuye en la cantidad de datos transferidos.
- Bit 15 - IOC - Interrupción al finalizar - causa la interrupción después de completar el procesamiento del descriptor.
- Los bits 14:12 muestran el número del búfer actual con el que se intercambian datos, más sobre esto más adelante.
- Bits 11:10: la cantidad de errores permitidos. Esta tabla muestra cuándo disminuye el recuento de errores:

Nota 1: la detección de Babble o Stall detiene automáticamente la ejecución del encabezado de la cola. Nota al pie 3: los errores del búfer de datos son problemas con el host. No tienen en cuenta los reintentos del dispositivo. - 9: 8 - Código PID - tipo de token: 0 - token a la entrada (del host al dispositivo), 1 - token a la salida (del dispositivo al host), 2 - token "SETUP"
- Los bits 7: 0 indican el estado de TD:
El bit 7 indica que el TD está en un estado activo (es decir, el controlador host procesa este TD)
Bit 6 - Detenido - indica que ha ocurrido un error y que la ejecución de TD se ha detenido.
Bit 4: balbuceo detectado: la cantidad de datos que enviamos al dispositivo, o por revolución, es inferior a la que transmitimos, es decir, por ejemplo, el dispositivo nos envió 100 bytes de datos, y leemos solo 50 bytes, y luego otros 50 El bit detenido también se establecerá si este bit se establece en 1.
Bit 3: error de transacción: se produjo un error durante la transacción.
Lista de punteros de la página de búfer qTD : cualquiera de los 5 búferes. Contiene un enlace a donde en la memoria se debe realizar la transacción (enviar datos al dispositivo / recibir datos del dispositivo), todas las direcciones en los buffers, excepto la primera, deben estar alineadas con el tamaño de la página (4096 bytes).
Jefe de línea
La cola de la cola tiene la siguiente estructura:
Puntero de enlace horizontal del encabezado de la cola: puntero a la siguiente cola, los bits 2: 1 tienen los siguientes valores según el tipo de cola:
Capacidades / características de punto final - características de la cola:
- Los bits 26:16 contienen el tamaño máximo de paquete para la transmisión
- Bit 14: Control de alternancia de datos: muestra dónde el controlador del host debe tomar el valor de alternancia de datos inicial, 0: ignora el bit DT en qTD, guarda el bit DT para la cabeza de la cola.
- Bit 13:12 - características de velocidad de transmisión:

- Bits 11: 8: el número del punto final al que se realiza la solicitud
- Bits 6: 0 - dirección del dispositivo
Capacidades de punto final: Queue Head DWord 2 - continuación de la palabra doble anterior:
- Bits 29:23 - Número de concentrador
- Bits 22:16 - Dirección del concentrador
Puntero de enlace qTD actual : puntero al qTD actual.
Pasamos a lo más interesante.
Conductor EHCI
Comencemos con las consultas que el EHCI puede cumplir. Hay 2 tipos de solicitudes: Control - a la orden, y Bulk - a puntos finales, para el intercambio de datos, por ejemplo, la gran mayoría de las unidades flash USB (USB MassStorage) usan el tipo de transferencia de datos Bulk / Bulk / Bulk. El mouse y el teclado también usan solicitudes masivas para la transferencia de datos.
Inicialice EHCI y configure colas asíncronas y secuenciales:
En realidad, el código para restablecer el puerto a su estado original:
volatile u32 *reg = &hc->opRegs->ports[port];
Solicitud de control al dispositivo:
static void EhciDevControl(UsbDevice *dev, UsbTransfer *t) { EhciController *hc = (EhciController *)dev->hc; UsbDevReq *req = t->req;
Código de procesamiento de cola:
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) ....
Y ahora la solicitud de punto final (solicitud masiva)
static void EhciDevIntr(UsbDevice *dev, UsbTransfer *t) { EhciController *hc = (EhciController *)dev->hc;
Creo que el tema es bastante interesante, en Internet en ruso casi no hay documentos, descripciones y artículos sobre este tema, y si lo hay, es muy borroso. Si el tema de trabajar con el desarrollo de hardware y sistema operativo es interesante, entonces hay mucho que contar.
Muelles:
especificación