USB em registradores: endpoint isócrono usando o exemplo de um dispositivo de áudio

imagem<imagem com placa e fones de ouvido>

Nível ainda mais baixo (avr-vusb): habr.com/ru/post/460815

USB em registros: STM32L1 / STM32F1

USB em registros: endpoint em massa usando o exemplo de armazenamento

em massa USB em registros: ponto de extremidade de interrupção no exemplo HID



Hoje, vamos olhar para o último tipo de ponto de extremidade, isócrono. Ele é projetado para transferir dados críticos para o tempo de entrega, mas não garante seu sucesso. O exemplo mais clássico são os dispositivos de áudio: alto-falantes, microfones.



Estranhamente, esse tipo de ponto final acabou sendo o que mais bombeia o cérebro (e isso depois de tudo que vi com stm'kami!). No entanto, hoje faremos um dispositivo de áudio e ao mesmo tempo terminaremos ligeiramente o núcleo da biblioteca USB. Como de costume, os códigos-fonte estão disponíveis:

github.com/COKPOWEHEU/usb/tree/main/4.Audio_L1

github.com/COKPOWEHEU/usb/tree/main/4.Audio_F1



Refinamento do kernel



É necessário refinar o kernel porque STM só pode ter pontos isócronos com buffer duplo, ou seja, grosso modo, é impossível tornar 0x01 isócrono e 0x81 controle. Ou seja, é claro que é possível escrever isso no descritor USB, mas isso não mudará o interior do controlador, e o endereço real do ponto simplesmente será diferente daquele visível do lado de fora. O que, é claro, aumentará o risco de erros, para não sermos pervertidos nessa direção.



Deve-se notar que o buffer duplo ocorre não apenas para pontos isócronos, mas também para bulk, e ele liga e funciona de forma diferente. Se os pontos isócronos habilitarem o buffer automaticamente, simplesmente porque eles não podem fazer de outra forma, para a configuração em massa correspondente, você deve usar o bit USB_EP_KIND especial, que deve ser definido junto com a configuração do tipo de ponto real.



Por si só, buffering significa que se antes um ponto correspondia a um buffer de transmissão e outro de recepção, agora ambos os buffers funcionarão tanto para transmissão quanto para recepção, e só funcionarão juntos. Como resultado, a configuração de um ponto de buffer é muito diferente do usual, porque você precisa configurar não um buffer, mas dois. Portanto, não esculpiremos condições desnecessárias na inicialização usual, mas criaremos uma função separada usb_ep_init_double () com base nela.



A recepção e a transmissão de pacotes não diferem tanto, embora tenha levado muito mais tempo primeiro para tentar entender como deveria funcionar de acordo com a lógica ST, depois ajustar o feitiço da Internet para fazê-lo funcionar. Como mencionado anteriormente, se para um ponto comum dois buffers são independentes e diferem na direção da troca, para um com buffer eles são iguais e diferem apenas no deslocamento. Portanto, vamos mudar um pouco as funções usb_ep_write e usb_ep_read para que aceitem não um número de ponto, mas um número de deslocamento. Ou seja, se antes essas funções assumiam a existência de oito pontos duplos, agora - 16 pontos únicos. Assim, o número da nova “meia-linha” para escrita é apenas o número do usual, multiplicado por dois, e para usb_ep_read deve-se também somar um (veja a alocação de buffers no PMA). Na realidade,isso é feito pelas funções inline usb_ep_write e usb_ep_read para pontos regulares. Mas vamos dar uma olhada mais de perto na lógica do buffer.



De acordo com a documentação, um buffer de tal ponto está disponível para hardware, o segundo para software. Em seguida, eles mudam e novamente não interferem um com o outro. Para o ponto OUT, o sinalizador no lado do hardware é o bit USB_EP_DTOG_RX, que precisa ser lido para entender qual dos buffers acabou de escrever e de onde o software pode ler, respectivamente. Quando ele lê seu buffer, você precisa empurrar o bit USB_EP_DTOG_TX, que na verdade muda os buffers. Não tenho certeza se é isso que significa, mas pelo menos funciona.



Uma situação simétrica deveria ser com pontos IN. Mas, na prática, descobriu-se que você precisa verificar e puxar USB_EP_DTOG_RX. Por que não TX ainda não entendi ... Obrigado ao usuário kuzulis pelo link para github.com/dmitrystu/libusb_stm32/edit/master/src/usbd_stm32f103_devfs.c



Devido à função inline, nenhuma sobrecarga especial foi adicionada, além da inicialização. Mas você pode, se desejar, jogá-lo fora com os sinalizadores do vinculador. Ou você não precisa jogá-lo fora: ele não ocupa muito espaço e é chamado apenas durante a inicialização. Este não é um HAL para você, onde as funções não são apenas pesadas, mas também ligam umas para as outras o tempo todo.



Como resultado, os terminais aprenderam a trabalhar no modo de buffer ... se você não respirar muito com eles.



Para o usuário, a diferença é pequena: em vez de usb_ep_init, use usb_ep_init_double, e em vez de usb_ep_write e usb_ep_read, use usb_ep_write_double e usb_ep_read_double, respectivamente.



Dispositivo AudioDevice



E agora, quando descobrimos o rake técnico, vamos passar para a coisa mais interessante - configurar um dispositivo de áudio.



De acordo com o padrão USB, um dispositivo de áudio é um conjunto de entidades conectadas entre si em uma determinada topologia, por onde passa o sinal de áudio. Cada entidade tem seu próprio número exclusivo (bTerminalID, também conhecido como UnitID), pelo qual outras entidades ou terminais podem se conectar a ela, o host também o usa se quiser alterar alguns parâmetros. E ele é considerado a única saída desta entidade. Mas pode não haver nenhuma entrada (se for um terminal de entrada), ou pode haver mais de um (bSourceID). Na verdade, ao escrever os números das entidades das quais a atual recebe um sinal de áudio para o array bSourceID, descrevemos toda a topologia, que como resultado pode ser muito rápida. Por exemplo, darei a topologia de uma placa de som USB adquirida (os números mostram bTerminalID / UnitID):



lsusb e sua descriptografia
Bus 001 Device 014: ID 0d8c:013c C-Media Electronics, Inc. CM108 Audio Controller

#   
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               1.10
  bDeviceClass            0 
  bDeviceSubClass         0 
  bDeviceProtocol         0 
  bMaxPacketSize0         8
  idVendor           0x0d8c C-Media Electronics, Inc.
  idProduct          0x013c CM108 Audio Controller
  bcdDevice            1.00
  iManufacturer           1 
  iProduct                2 
  iSerial                 0 
  bNumConfigurations      1
  
#  
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength       0x00fd
    bNumInterfaces          4  #   
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              100mA
    
# 0 -  
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           0
      bInterfaceClass         1 Audio
      bInterfaceSubClass      1 Control Device
      bInterfaceProtocol      0 
      iInterface              0 
      AudioControl Interface Descriptor:
        bLength                10
        bDescriptorType        36
        bDescriptorSubtype      1 (HEADER)
        bcdADC               1.00
        wTotalLength       0x0064
        bInCollection           2  # !    (2)
        baInterfaceNr(0)        1  #   
        baInterfaceNr(1)        2  # 
 
#####  #####
# 1 InputTerminal (USB,  ) 
      AudioControl Interface Descriptor:
        bLength                12
        bDescriptorType        36
        bDescriptorSubtype      2 (INPUT_TERMINAL)
        bTerminalID             1  #    
        wTerminalType      0x0101 USB Streaming
        bAssocTerminal          0
        bNrChannels             2  #    
        wChannelConfig     0x0003  #   -    
          Left Front (L)
          Right Front (R)
        iChannelNames           0 
        iTerminal               0 
        
# 2 InputTerminal ()
      AudioControl Interface Descriptor:
        bLength                12
        bDescriptorType        36
        bDescriptorSubtype      2 (INPUT_TERMINAL)
        bTerminalID             2
        wTerminalType      0x0201 Microphone
        bAssocTerminal          0
        bNrChannels             1
        wChannelConfig     0x0001
          Left Front (L)
        iChannelNames           0 
        iTerminal               0 
        
# 6 OutputTerminal (),     9
      AudioControl Interface Descriptor:
        bLength                 9
        bDescriptorType        36
        bDescriptorSubtype      3 (OUTPUT_TERMINAL)
        bTerminalID             6
        wTerminalType      0x0301 Speaker
        bAssocTerminal          0
        bSourceID               9  #    
        iTerminal               0 
        
# 7 OutputTerminal (USB),     8
      AudioControl Interface Descriptor:
        bLength                 9
        bDescriptorType        36
        bDescriptorSubtype      3 (OUTPUT_TERMINAL)
        bTerminalID             7
        wTerminalType      0x0101 USB Streaming
        bAssocTerminal          0
        bSourceID               8
        iTerminal               0 
        
# 8 Selector,      10
      AudioControl Interface Descriptor:
        bLength                 7
        bDescriptorType        36
        bDescriptorSubtype      5 (SELECTOR_UNIT)
        bUnitID                 8
        bNrInPins               1  #        
        baSourceID(0)          10  #   
        iSelector               0 
        
# 9 Feature,     15
      AudioControl Interface Descriptor:
        bLength                10
        bDescriptorType        36
        bDescriptorSubtype      6 (FEATURE_UNIT)
        bUnitID                 9
        bSourceID              15
        bControlSize            1
        bmaControls(0)       0x01
          Mute Control
        bmaControls(1)       0x02
          Volume Control
        bmaControls(2)       0x02
          Volume Control
        iFeature                0 
        
# 10 Feature,     2
      AudioControl Interface Descriptor:
        bLength                 9
        bDescriptorType        36
        bDescriptorSubtype      6 (FEATURE_UNIT)
        bUnitID                10
        bSourceID               2
        bControlSize            1
        bmaControls(0)       0x43
          Mute Control
          Volume Control
          Automatic Gain Control
        bmaControls(1)       0x00
        iFeature                0 
        
# 13 Feature,     2
      AudioControl Interface Descriptor:
        bLength                 9
        bDescriptorType        36
        bDescriptorSubtype      6 (FEATURE_UNIT)
        bUnitID                13
        bSourceID               2
        bControlSize            1
        bmaControls(0)       0x03
          Mute Control
          Volume Control
        bmaControls(1)       0x00
        iFeature                0 
        
# 15 Mixer,     1  13
      AudioControl Interface Descriptor:
        bLength                13
        bDescriptorType        36
        bDescriptorSubtype      4 (MIXER_UNIT)
        bUnitID                15
        bNrInPins               2  #   
        baSourceID(0)           1  #   
        baSourceID(1)          13
        bNrChannels             2
        wChannelConfig     0x0003
          Left Front (L)
          Right Front (R)
        iChannelNames           0 
        bmControls(0)        0x00
        iMixer                  0 
#####   #####

#  1 () -    
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       0
      bNumEndpoints           0
      bInterfaceClass         1 Audio
      bInterfaceSubClass      2 Streaming
      bInterfaceProtocol      0 
      iInterface              0 
      
#  1 () -     
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       1
      bNumEndpoints           1
      bInterfaceClass         1 Audio
      bInterfaceSubClass      2 Streaming
      bInterfaceProtocol      0 
      iInterface              0 
      AudioStreaming Interface Descriptor:
        bLength                 7
        bDescriptorType        36
        bDescriptorSubtype      1 (AS_GENERAL)
        bTerminalLink           1
        bDelay                  1 frames
        wFormatTag         0x0001 PCM
      AudioStreaming Interface Descriptor:
        bLength                14
        bDescriptorType        36
        bDescriptorSubtype      2 (FORMAT_TYPE)
        bFormatType             1 (FORMAT_TYPE_I)
        bNrChannels             2
        bSubframeSize           2
        bBitResolution         16
        bSamFreqType            2 Discrete
        tSamFreq[ 0]        48000
        tSamFreq[ 1]        44100
      Endpoint Descriptor:
        bLength                 9
        bDescriptorType         5
        bEndpointAddress     0x01  EP 1 OUT
        bmAttributes            9
          Transfer Type            Isochronous
          Synch Type               Adaptive
          Usage Type               Data
        wMaxPacketSize     0x00c8  1x 200 bytes
        bInterval               1
        bRefresh                0
        bSynchAddress           0
        AudioStreaming Endpoint Descriptor:
          bLength                 7
          bDescriptorType        37
          bDescriptorSubtype      1 (EP_GENERAL)
          bmAttributes         0x01
            Sampling Frequency
          bLockDelayUnits         1 Milliseconds
          wLockDelay         0x0001
          
#  2 () - 
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        2
      bAlternateSetting       0
      bNumEndpoints           0
      bInterfaceClass         1 Audio
      bInterfaceSubClass      2 Streaming
      bInterfaceProtocol      0 
      iInterface              0 
      
#  2 ()
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        2
      bAlternateSetting       1
      bNumEndpoints           1
      bInterfaceClass         1 Audio
      bInterfaceSubClass      2 Streaming
      bInterfaceProtocol      0 
      iInterface              0 
      AudioStreaming Interface Descriptor:
        bLength                 7
        bDescriptorType        36
        bDescriptorSubtype      1 (AS_GENERAL)
        bTerminalLink           7
        bDelay                  1 frames
        wFormatTag         0x0001 PCM
      AudioStreaming Interface Descriptor:
        bLength                14
        bDescriptorType        36
        bDescriptorSubtype      2 (FORMAT_TYPE)
        bFormatType             1 (FORMAT_TYPE_I)
        bNrChannels             1
        bSubframeSize           2
        bBitResolution         16
        bSamFreqType            2 Discrete
        tSamFreq[ 0]        48000
        tSamFreq[ 1]        44100
      Endpoint Descriptor:
        bLength                 9
        bDescriptorType         5
        bEndpointAddress     0x82  EP 2 IN
        bmAttributes            9
          Transfer Type            Isochronous
          Synch Type               Adaptive
          Usage Type               Data
        wMaxPacketSize     0x0064  1x 100 bytes
        bInterval               1
        bRefresh                0
        bSynchAddress           0
        AudioStreaming Endpoint Descriptor:
          bLength                 7
          bDescriptorType        37
          bDescriptorSubtype      1 (EP_GENERAL)
          bmAttributes         0x01
            Sampling Frequency
          bLockDelayUnits         0 Undefined
          wLockDelay         0x0000
#####    #####

#  3 "    " ( )
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        3
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 
      bInterfaceProtocol      0 
      iInterface              0 
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.00
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength      60
         Report Descriptors: 
           ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x87  EP 7 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0004  1x 4 bytes
        bInterval               2

      
      









imagem



Vamos fazer algo mais simples (peguei o branco a partir daqui ):



imagem



Aqui você pode ver dois ramos de propagação de sinal independentes: ou de USB por um "recurso" para um "alto-falante" ou de um "microfone" por outro "recurso ”Para USB. O microfone e o alto-falante não são apenas colocados entre aspas: eles não estão na minha placa de depuração, então, em vez do próprio som, usaremos botões e LEDs. Porém, nada de novo. "Recursos" no meu caso não fazem nada e são adicionados mais para a beleza.



Deve ser esclarecido de imediato que o sinal neste modelo é considerado composto por um ou mais canais lógicos. Ou seja, se, por exemplo, eu mudar um alto-falante mono para um estéreo, a topologia em si permanecerá inalterada, apenas o formato do sinal mudará.



Não investiguei profundamente as diferenças entre os tipos de "recursos" e outras entidades, mas não desdenharei recontar uma parte da documentação.



1. Terminal de entrada

Como o nome sugere, é através dele que o sinal de áudio entra no dispositivo de áudio. Pode ser USB, pode ser um microfone comum, um microfone tipo headset ou até mesmo um conjunto de microfones.



2. Terminal de saída

Também é bastante óbvio - aquele através do qual o som sai do nosso dispositivo. Pode ser o mesmo USB, pode ser um alto-falante, um fone de ouvido, um alto-falante no monitor, alto-falantes de várias frequências e vários outros dispositivos.



3. Unidade Misturadora

Ele pega vários sinais de entrada, amplifica cada um em uma quantidade predeterminada e adiciona o resultado ao canal de saída. Se desejar, você pode definir o ganho para zero, o que o reduzirá para a próxima entidade.



4. Unidade seletora

Recebe vários sinais de entrada e redireciona um deles para a saída.



5. Filtro (unidade de recurso)

Recebe um único sinal de entrada, altera os parâmetros de som (volume, tom, etc.) e o envia para a saída. Naturalmente, todos esses parâmetros são aplicados a todo o sinal da mesma maneira, sem a interação de canais lógicos dentro dele.



6. Unidade de processamento

Mas isso já permite que você manipule canais lógicos individuais dentro de cada entrada. Além disso, permite que você faça com que o número de canais lógicos na saída não seja igual ao número na entrada.



7. Unidade de extensão

Todo o conjunto de entidades fora do padrão, de modo que a fantasia doentia dos fabricantes de equipamentos era livre. Conseqüentemente, tanto o comportamento quanto as configurações dependerão dessa mesma fantasia.



Algumas entidades têm parâmetros como ganho ou número de canal, que podem ser influenciados pelo host usando consultas setFeature / getFeature no número da entidade. Mas aqui, para ser honesto, eu realmente não entendo como verificar isso. Provavelmente, você precisa de algum tipo de software especial, que eu não tenho. Bem, ok, de qualquer maneira eu entrei nisso apenas para verificar todos os tipos de pontos ... na minha cabeça ...



Rake no descritor



Ao contrário dos dispositivos USB anteriores, o descritor aqui é complexo, com várias camadas e tende a assustar o Windows e torná-lo BSOD. Como vimos acima, a topologia de um dispositivo autólogo pode ser bastante complexa e extensa. Uma interface inteira se destaca por sua descrição. Obviamente, ele não conterá terminais, mas conterá uma lista de descritores de entidade e descrições de a que suas entradas estão conectadas. Não vejo muito sentido aqui, é mais fácil olhar o código e a documentação. Vou apenas observar o rake principal: aqui é descrito quais interfaces com os pontos de extremidade correspondentes se referem especificamente a este dispositivo. Por exemplo, se você quiser mudar minha configuração e remover o alto-falante de lá, você não terá apenas que deletar metade das entidades (graças às macros, pelo menos não haverá problema em calcular o comprimento do descritor), mas também reduza o campo bInCollection para 1,em seguida, remova o número da interface extra do array bInterfaceNr que o segue.



Além disso, existem interfaces responsáveis ​​pela troca de dados. No meu caso, a 1ª interface é responsável pelo microfone e a 2ª pelo alto-falante. Vale a pena prestar atenção aqui, em primeiro lugar, a duas variantes de cada uma dessas interfaces. Um com bAlternateSetting igual a 0, o segundo com 1. Eles diferem na presença de um ponto final. Ou seja, se nosso dispositivo não estiver em uso, o host simplesmente muda para aquela interface alternativa, que não está equipada com um ponto de extremidade, e não desperdiça mais largura de banda do barramento nele.



O segundo recurso das interfaces de dados é o formato do sinal de áudio. O descritor correspondente especifica o tipo de codificação, número de canais, resolução e taxa de amostragem (que é especificada por um número de 24 bits). Existem algumas opções de codificação, mas usaremos a mais simples - PCM. Na verdade, é apenas uma sequência de valores do valor instantâneo do sinal sem nenhuma codificação, e o valor é considerado um número inteiro com sinal . A resolução do sinal é definida em dois lugares (não está claro o porquê): o campo bSubFrameSize especifica o número de bytes e bBitResolution especifica o número de bits... Provavelmente, pode ser apontado que a faixa de nossa placa de som não vai até a faixa completa do tipo de dados, digamos int16_t e tem apenas 10 bits.



E, finalmente, o descritor do terminal real. Também difere ligeiramente dos habituais, pois disponibiliza, em primeiro lugar, várias opções de sincronização e, em segundo lugar, o número da entidade à qual este ponto está associado (bTerminalLink) . As opções de sincronização são escritas em bits de alta ordem diretamente no tipo de endpoint (é por isso que o ponto isócrono foi movido para a ramificação padrão na função de inicialização), mas eu não lidei com seus detalhes, então não posso te dizer alguma coisa interessante. Em vez de sincronização, usaremos um temporizador de controlador regular, que gerará interrupções aproximadamente na frequência desejada.



Ah, sim, quase esqueci de mencionar outro monte de BSODs ao testar os descritores errados. Deixe-me lembrá-lo novamente: o número de interfaces de dados deve corresponder ao número de bInCollection, e seus números devem corresponder ao array seguinte!

Texto oculto
, , . --.





A lógica do dispositivo



Como já disse, para testes não faz sentido colocar componentes articulados na placa de depuração, portanto, todos os testes serão realizados com o que já está instalado - botões e LEDs. No entanto, neste caso, isso não constitui um problema: o "microfone" pode simplesmente gerar uma sinusóide com uma frequência de, digamos, 1 kHz, e o "alto-falante" liga o LED quando o valor do limiar de som é excedido (digamos , acima de 10.000: com os 16 bits especificados de resolução, que corresponde ao intervalo -32768 ... +32767, isso é cerca de um terço).



Mas com o teste, surgiu um pequeno problema: não encontrei uma maneira fácil de redirecionar o sinal do microfone para o stdin de algum programa. Parece que antes isso era feito simplesmente lendo / dev / dsp, mas agora algo está quebrado. Porém, nada crítico, pois existem todos os tipos de bibliotecas para interação com multimídia - SDL, SFLM e outras. Na verdade em SFML eu escrevi um utilitário simples para ler de um microfone e, se necessário, visualizar o sinal.



Vou prestar atenção especial às limitações do nosso dispositivo de áudio: até onde eu entendo, uma solicitação IN isócrona é enviada uma vez por milissegundo (mas pode haver muitos OUTs), o que limita a taxa de amostragem. Digamos que o tamanho do endpoint seja de 64 bytes (levando em consideração o buffer, ele ocupa 128 bytes na memória, mas o host não sabe disso), a resolução é de 16 bits, ou seja, 32 amostras podem ser enviadas por vez . Dado um intervalo de 1 ms, obtemos um limite teórico de 32 kHz para um canal. A maneira mais fácil de contornar isso é aumentar o tamanho do ponto de extremidade. Mas aqui devemos lembrar que o tamanho do buffer PMA total é de apenas 512 bytes. Sem a tabela de distribuição de pontos, sem ep0, obtemos no máximo 440 bytes, ou seja, 220 bytes por ponto único, levando em consideração o buffer. E esse é o limite teórico.



Mas o fato de o host poder enviar várias solicitações OUT em um quadro sugere que o dispositivo pode fazer o mesmo. Resta entender como. Talvez isso seja resolvido por uma configuração de sincronização competente. Mas para mim esta questão já não me interessa: os pontos isócronos funcionam, os pontos armazenados funcionam, o dispositivo de áudio funciona - a tarefa está concluída.



Conclusão (comum para o ciclo)



Bem, conhecemos o dispositivo USB nos controladores STM32F103 e STM32L151 (e outros com uma implementação semelhante), ficamos surpresos com a lógica de algumas soluções arquitetônicas (fiquei especialmente impressionado com o registro USB_EPnR, no entanto, o buffer duplo também não está atrasado atrás), examinou todos os tipos de endpoints e os verificou, criando os dispositivos apropriados. Portanto, podemos dizer que esta série de artigos chegou a uma conclusão lógica. Embora isso, é claro, não signifique que vou abandonar os controladores ou USB: nos planos distantes, ainda tenho que lidar com dispositivos compostos (até agora parece fácil, mas pontos isócrons também não augura nada de bom) e USB em controladores de outras famílias.



All Articles