UHCI, ou o primeiro USB



Bom dia, querido leitor! Me pediram para escrever sobre UHCI - bem, eu escrevo.

Você pode achar este artigo útil se, por exemplo, você não tiver habilidades suficientes de escrita para drivers e leitura de documentação para um hardware. Um exemplo simples: você deseja gravar seu sistema operacional para um mini PC, para que algumas distribuições do Windows ou de outro Linux não façam o download do ferro, e você use todo o seu poder exclusivamente para seus próprios fins.

O que é UHCI?


Penso que, para não falar novamente sobre o que e por quê, basta deixar um link para o meu artigo anterior sobre EHCI. Pique aqui
UHCI - Universal Host Controller Interface, opera como um dispositivo PCI, mas, diferentemente do EHCI, usa portas em vez de MMIO (Memory-Mapped-IO).



Termos a serem usados ​​a seguir


  • Driver USB (USBD) - o próprio driver USB
  • HC (Host Controller) - um controlador host ou apenas o nosso UHCI
  • Host Controller Driver (HCD) - um driver que conecta hardware e USBD
  • Dispositivo USB - o próprio dispositivo USB

Tipos de transferência de dados


Isócrono - transmissão isossíncrona, que possui uma determinada frequência de transferência de dados. Pode ser usado, por exemplo, para microfones USB, etc.

Interrupção - Transferências de dados pequenas e espontâneas de um dispositivo. O tipo de transmissão de interrupção suporta dispositivos que requerem um intervalo de serviço previsível, mas não fornecem necessariamente um fluxo de dados previsível. Geralmente usado para dispositivos como teclados e dispositivos apontadores que podem não fornecer dados por longos períodos de tempo, mas exigem uma resposta rápida quando eles têm dados para enviar.

Controle - Tipo de transmissão de informações sobre o status, status e configuração do dispositivo. O tipo de transferência de controle é usado para fornecer um canal de controle do host para os dispositivos USB. As transmissões de controle sempre consistem em uma fase de configuração e zero ou mais fases de dados seguidas por uma fase de status. É imperativo que a transferência de controle para um determinado terminal seja processada no modo FIFO. Se o controle for passado para o mesmo terminal, a intercalação pode levar a um comportamento imprevisível.

Bulk - tipo de transferência de matrizes de dados. Usado, por exemplo, em dispositivos MassStorage.



É assim que se parece a distribuição de tempo de 1 ms - processamento de um quadro.

Distribuição de tempo


O controlador host suporta a entrega de dados em tempo real, gerando um pacote Start Of Frame (SOF) a cada 1 ms. Um pacote SOF é gerado quando o contador SOF no controlador host expira (Figura 3). O controlador host inicializa o contador SOF por um tempo de quadro de 1 ms. Pequenas alterações podem ser feitas nesse valor (e, portanto, no período de tempo do quadro) programando o registro de alterações SOF. Esse recurso permite que você faça pequenas alterações no período do quadro, se necessário, para manter a sincronização em tempo real em todo o sistema USB.

O controlador host inclui o número do quadro em cada pacote SOF. Esse número de quadro determina exclusivamente o período do quadro em tempo real. A condição de fim de quadro (EOF) ocorre no final do intervalo de 1 ms quando o controlador host inicia o próximo tempo de quadro, gerando outro pacote SOF com o número de quadro correspondente. Durante o período do quadro, os dados são transmitidos como pacotes de informações. O período do quadro é rigorosamente imposto pelo controlador host, e os pacotes de dados no quadro atual não podem ir além do EOF (consulte o Capítulo 11 na especificação USB). O controlador host suporta a sincronização da transmissão de dados entre quadros em tempo real, vinculando o número do quadro para executar uma entrada específica na lista de quadros. O contador de quadros do controlador host gera um número de quadro (valor de 11 bits) e o inclui em cada pacote SOF. O contador é programado através de registradores e cada período de quadro é incrementado. O controlador host usa os 10 bits inferiores do número de quadros como um índice na lista de quadros com 1024 quadros, armazenados na memória do sistema. Assim, como o contador de quadros controla a seleção de uma entrada da lista de quadros, o controlador host processa cada entrada da lista em um determinado período de quadros. O controlador host se expande para a próxima entrada na lista de quadros para cada novo quadro. Isso garante que as transmissões isócronas sejam executadas em um quadro específico.

Figura 3:



Estrutura UHCI


Tudo é exatamente igual ao EHCI. Solicitações de exemplo para HC:



Configurar e acessar UHCI


E assim, como eu disse anteriormente, o UHCI funciona através de portas, portanto, do PCI, precisamos descobrir a base dos registros UHCI.



No deslocamento 0x20, existem 4 bytes - IO Base. Em relação ao IO Base, podemos usar os seguintes registros:



Registradores UHCI


  • USBCMD é um registro para controlar o HC. Bits:
    • Bit 6 é um sinalizador que o dispositivo está configurado e inicializado com sucesso.
    • Bit 1 - Redefinição HC. Defina para redefinir HC.
    • Bit 0 - Executar / Parar. Exibe o status HC. 1 - obras, 0 - não.
  • USBSTS - Registro de status. Bits:
    • Bit 5 - HC parado. Ocorreu um erro ou o controlador concluiu com êxito a redefinição do HC.
    • Bit 4 - Erro no processo do controlador host. O bit é definido como 1 quando ocorre um erro crítico e o HC não pode continuar na fila e no TD.
    • Bit 3 - Erro no sistema host. Erro de PCI.
    • Bit 1 - Erro de interrupção. Indica que ocorreu um erro e o HC gerou uma interrupção.
    • Bit 0 - Interromper. Indica que HC gerou uma interrupção.
  • USBINTR - Registro das configurações de interrupção. Bits:
    • O Bit 2 - COI - Interromper na conclusão - gera uma interrupção no final da transação.
  • FRNUM - Número do quadro atual (pegue & 0x3FF para o valor correto).
  • FLBASEADD - Endereço base da lista de quadros - endereço da lista de quadros.
  • PORTSC - Status e controle da porta - registro de status e controle de porta. Bits:
    • Bit 9 - Redefinição de porta - 1 - porta a redefinir.
    • Bit 8 - indica que um dispositivo de baixa velocidade está conectado à porta
    • Bit 3 - Indica que a porta no estado mudou
    • Bit 2 - Indica se a porta está ativada
    • Bit 1 - indica que o status do dispositivo está conectado à porta
    • Bit 0 - indica que o dispositivo está conectado à porta.

Estruturas


Ponteiro da lista de quadros




Descritor de transferência




TD CONTROLE E ESTADO
. Bits:
  • Bits 28-27 - contador de erros, semelhante ao EHCI.
    • Bit 26 - 1 = Dispositivo de baixa velocidade, 0 = Dispositivo de velocidade total.
    • Bit 25 - 1 = TD isossíncrono
    • Bit 24 - COI
    • Bits 23-16 - Status:
    • Bit 23 - Indica que é um TD ativo
    • Bit 22 - Parado
    • Bit 21 - Erro no Buffer de Dados
    • Bit 20 - Babble Detected
    • Bit 19 - NAK
  • Bits 10–0: O número de bytes transmitidos pelo controlador host.

TD Token

  • Bits 31:21 - Max Packet Len, semelhante ao EHCI
  • Bit 19 - Alternância de dados, semelhante ao EHCI
  • Bits 18:15 - Número do ponto final
  • Bits 18:14 - endereço do dispositivo
  • Bits 7: 0 - PID. Entrada = 0x69, Saída = 0xE1, Instalação = 0x2D

Cabeça da fila




Código


Inicialize e configure o 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); 

Solicitações de terminal e controle:

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


All Articles