USB
O USB substituiu uma coleção de barramentos e conectores específicos de dispositivo (PS/2, serial, paralelo, ADB) por um único padrão elétrico e de protocolo genérico o suficiente para cobrir teclados, armazenamento em massa, áudio, e rede igualmente. Essa generalidade também é o que torna o USB uma das peças de hardware mais elaboradas que um kernel suporta diretamente: diferente de um dispositivo de propósito fixo com um punhado de registradores, um host USB precisa enumerar o que quer que esteja conectado, descobrir o que aquilo é através de um formato de descrição padronizado mas aberto, e rotear tráfego para ele através de um de quatro mecanismos de transferência fundamentalmente diferentes dependendo do que aquele dispositivo precisa.
Host controllers
Seção intitulada “Host controllers”O software fala com hardware USB através de um host controller, e qual interface de registradores aquele controlador expõe mudou a cada aumento importante de velocidade do USB. O UHCI (Universal Host Controller Interface), design da Intel para USB 1.1, empurra a maior parte do trabalho de escalonamento para o software, percorrendo uma lista de frames que o próprio driver constrói e liga a cada milissegundo. O OHCI (Open Host Controller Interface), um design concorrente da era 1.1 usado pela maioria dos chipsets não-Intel da época, move mais desse escalonamento para o hardware via seus próprios formatos de lista ligada, ao custo de ser uma interface de registradores completamente diferente que um driver precisa suportar separadamente do UHCI. O EHCI (Enhanced Host Controller Interface) trata o tráfego High Speed (480 Mbit/s) do USB 2.0, mas por design só esse tráfego: um controlador EHCI devolve dispositivos Low- e Full-Speed a um controlador UHCI ou OHCI companheiro sentado ao lado dele, dividindo o tráfego de uma única porta física entre duas interfaces de registradores diferentes dependendo da velocidade negociada do dispositivo. O xHCI (Extensible Host Controller Interface) unifica tudo isso: uma única interface de registradores e um único conjunto de estruturas de dados cobrem toda velocidade USB, de Low a SuperSpeed, com a maior parte do escalonamento por transferência tratada pelo próprio controlador em vez de pelo software percorrendo listas ligadas, o que também é a razão pela qual o xHCI é o único dos quatro com suporte nativo ao SuperSpeed do USB 3. Todos os quatro são identificados no barramento PCI sob a classe 0x0C (serial bus controller), subclasse 0x03 (USB), com o byte de programming interface distinguindo qual das quatro interfaces de registradores um dado controlador implementa.
Descriptors
Seção intitulada “Descriptors”Um dispositivo USB descreve a si mesmo ao host através de uma hierarquia fixa de descriptors, cada um uma pequena estrutura binária com um layout definido que o host analisa sem precisar de driver algum específico para aquele exato dispositivo só para lê-los. Um device descriptor fica no topo, um por dispositivo, carregando um par de vendor ID e product ID (o mesmo tipo de esquema de identificador que o PCI usa, ainda que extraído de um registro próprio e separado do USB), uma classe de dispositivo, e o número de configurações que o dispositivo suporta. Abaixo disso, um configuration descriptor descreve um perfil particular de energia e funcionalidade em que um dispositivo pode ser colocado; a maioria dos dispositivos expõe exatamente uma. Aninhados dentro de uma configuração estão um ou mais interface descriptors, cada um representando uma função distinta que o dispositivo expõe: uma webcam USB com um microfone embutido exporia uma interface para vídeo e outra separada para áudio, permitindo que o host associe drivers inteiramente diferentes a cada metade do mesmo dispositivo físico. Cada interface por sua vez lista um ou mais endpoint descriptors, descrevendo os canais de comunicação reais que a interface usa: uma direção (host-para-dispositivo ou dispositivo-para-host), um tipo de transferência, um tamanho máximo de pacote, e, para endpoints de interrupt, um intervalo de polling. Esse aninhamento significa que ler o conjunto completo de descriptors de um dispositivo é uma questão de percorrer uma forma de árvore conhecida, em vez de analisar um formato arbitrário, mesmo que o que a árvore de qualquer dispositivo em particular contenha varie enormemente entre um teclado e um disco rígido.
Enumeração
Seção intitulada “Enumeração”Um dispositivo é invisível ao software até que a enumeração lhe atribua uma identidade que o host consiga endereçar diretamente. Todo dispositivo se conecta ao barramento inicialmente no endereço 0, o único endereço que todo dispositivo é obrigado a responder antes da configuração, especificamente para tornar esse bootstrap possível sem um problema de ovo-e-galinha de endereçamento. O host emite uma requisição GET_DESCRIPTOR ao endereço 0 para ler os primeiros bytes do device descriptor, descobrindo o tamanho máximo de pacote do endpoint de controle padrão do dispositivo antes de se comunicar mais com ele. Em seguida emite SET_ADDRESS, movendo o dispositivo para um endereço atribuído pelo host (1 a 127) ao qual ele vai responder pelo resto da sessão, liberando o endereço 0 para o próximo dispositivo a se conectar. Com um endereço estável atribuído, o host lê o conjunto completo de device, configuration, interface, e endpoint descriptors, e finalmente emite SET_CONFIGURATION para selecionar sob qual configuração o dispositivo deve de fato operar, ponto em que os endpoints do dispositivo se tornam utilizáveis para transferências reais. Um hub, ele mesmo apenas outro dispositivo USB com uma classe de dispositivo dedicada ao propósito, repete essa dança para cada dispositivo a jusante que detecta conectando ou desconectando, o que é como uma árvore de hubs e dispositivos é enumerada sem que cada dispositivo precise de um caminho diretamente cabeado até o host controller.
Tipos de transferência
Seção intitulada “Tipos de transferência”O USB define quatro tipos de transferência, e um endpoint fica permanentemente comprometido com exatamente um deles pela vida de uma dada configuração. Control transfers são bidirecionais, confiáveis, e usadas para configuração de dispositivo e as requisições de enumeração descritas acima; todo dispositivo tem pelo menos um endpoint de controle (endpoint 0) exclusivamente para esse propósito. Interrupt transfers são sondadas pelo host em um intervalo fixo que o endpoint descriptor especifica, apesar do nome sugerir que o dispositivo empurra dados sem ser solicitado; o endpoint de um teclado USB pode especificar um intervalo de 10 ms, significando que o host pergunta por dados novos cem vezes por segundo, quer uma tecla tenha de fato mudado de estado ou não. Bulk transfers carregam grandes quantidades de dados de forma confiável mas sem garantia de tempo alguma, usando qualquer largura de banda de barramento sobrando depois que outros tipos de transferência já tomaram sua parte reservada; dispositivos de armazenamento em massa usam bulk transfers quase exclusivamente, já que uma cópia grande de arquivo se importa muito mais com correção eventual e throughput do que com latência. Isochronous transfers garantem largura de banda e timing de barramento ao custo de confiabilidade inteiramente: um pacote isochronous perdido simplesmente se foi, apropriado para streaming de áudio ou vídeo onde uma falha breve é preferível a a transferência travar para retransmitir um pacote que já se tornou irrelevante no momento em que uma nova tentativa chegaria.
Emulação legada e teclados no boot
Seção intitulada “Emulação legada e teclados no boot”Firmware e host controllers comumente implementam emulação legada USB especificamente para que um teclado USB funcione antes de qualquer driver USB-aware do sistema operacional ter carregado, interceptando os relatórios HID USB reais do dispositivo no nível de hardware ou firmware e os representando novamente à CPU como tráfego comum de controlador de teclado PS/2. É por isso que um teclado USB visivelmente funciona em uma tela de configuração de BIOS ou em um bootloader inicial que nunca ouviu falar de USB: nada nesse caminho de código está de fato falando USB, e a pilha USB do próprio sistema operacional, uma vez inicializada, tipicamente precisa desabilitar explicitamente a emulação legada daquele dispositivo antes de conseguir endereçá-lo diretamente e assumir da interface PS/2 emulada.
Notas de implementação
Seção intitulada “Notas de implementação”Escrever um driver contra os quatro tipos de host controller é consideravelmente mais trabalho do que o modelo de descriptor e tipo de transferência acima sugere, já que cada um de UHCI, OHCI, EHCI, e xHCI tem suas próprias estruturas de dados inteiramente distintas para descrever transferências pendentes ao controlador, apesar de expor a mesma semântica lógica de USB ao software acima dessa camada; um kernel hobbyist comumente implementa só UHCI ou só xHCI primeiro, aceitando compatibilidade de hardware reduzida, em vez dos quatro de uma vez. SET_ADDRESS tem um requisito de timing específico que vale a pena respeitar exatamente: o dispositivo precisa completar a fase de status daquela requisição e então tem um tempo de recuperação definido antes de ser garantido que responda em seu novo endereço, e software que emite requisições adicionais imediatamente sem essa espera arrisca um dispositivo que aparenta ter aceitado o endereço mas ainda não responde a ele. Por fim, um tamanho máximo de pacote lido dos primeiros oito bytes de um device descriptor, antes mesmo de o resto do descriptor ser conhecido, é o que uma implementação precisa antes de poder emitir com segurança a segunda requisição GET_DESCRIPTOR, completa, para esse mesmo descriptor, já que o tamanho de pacote de uma control transfer é exatamente a informação que o host ainda não tem quando fala com um dispositivo pela primeira vez no endereço 0.
Referências
Seção intitulada “Referências”- ^ USB Implementers Forum, Universal Serial Bus Specification, revisão 2.0 (define formatos de descriptor, tipos de transferência, e a sequência de enumeração)
- ^ Intel, Enhanced Host Controller Interface Specification for Universal Serial Bus (define a interface de registradores do EHCI e o handoff para o controlador companheiro)
- ^ OSDev Wiki, “Universal Serial Bus” (uma visão geral hobbyist com notas de implementação específicas por host controller)