MyToyOS: Acesso direto à memória

Continuando a série sobre controladores, além das requisições de interrupções alguns dispositivos podem pedir para o processador que este libere os seus barramentos de endereço e dados para que o próprio dispositivo lide com blocos de dados contidos na memória… É o caso de HDs, por exemplo, quando pedimos para que alguns setores sejam lidos o circuito do HD pode colocar esses dados diretamente na memória, sem a interferência do processador…

Da mesma forma que ocorre com as interrupções, o processador tem apenas um sinal de pedido de liberação do barramento (DRQ, de DMA Request, ou HREQ, de Hold [Bus] Request). No entanto, podemos ter n dispositivos que podem pedir que o processador entregue o controle. Como fazer para lidar com todos eles? Novamente entra a figura de um controlador, o DMAC (Direct Memory Access Controller).

Vou esboçar aqui o Intel 8237A (datasheet pode ser baixado aqui), que é o controlador de DMA legado da arquitetura dos PCs. Esse chip tem sérias limitações porque acompanha os PCs desde sua primeira concepção, na época em que usávamos o processador 8086, de 16 bits (a mesma coisa acontece com o PIC, descrito nos artigos anterior)… Por exemplo: Esse circuito integrado só permite transferências de blocos de dados de, no máximo, 64 KiB e o endereço base também está limitado em 16 bits. Isso significa que, se usarmos o DMAC legado (que ainda está presente de forma emulada) nos sistemas atuais, o buffer onde os dados serão transferidos só podem ser localizados na memória baixa.

Em outro artigo descreverei o novo DMAC, que permite transferências com tamanhos de 32 bits… Este “novo” DMAC está presente em chipsets especializados e depende da arquitetura, por isso o DMAC tradicional, diferente do que acontece com o PIC e o APIC, ainda é o mais usado…

Como o DMA funciona?

Desconsiderando o DMAC, um dispositivo coloca, no sinal HREQ, um sinal de nível 1 pedindo ao processador que este libere o barramento (coloque os barramentos de dados, endereço e controle, em estado de alta impedância). O processador, quando resolver atender o pedido, coloca nível 0 no sinal HACK# (Hold Acknowledge) e abre os circuitos dos barramentos… Neste ponto o dispositivo tem total controle do acesso à memória. Quando o sinal HREQ for zerado, o processador assume, de volta, o controle do barramento.

E como o DMAC ajuda?

O 8237A possui 4 entradas de requisição de DMA, nomeadas de DRQ0 até DRQ3. Da mesma forma como ocorre com o PIC, esses DMA são priorizados (se dois ocorrerem, um deles será atendido antes do outro). Em essência, o DMAC pedirá ou enviará dados de ou para o dispositivo, bem como de ou para a memória e, quando acabar, informa o fim do processo (sinal EOP# ou End Of Process) e, por sua vez, libera os barramentos de volta para o processador.

O DMAC também substitui, do ponto de vista do dispositivo, os sinais HREQ e HACK#, do processador, pelos sinais DRQn e DACKn#, onde n corresponde ao canal de DMA usado pelo dispositivo. É fácil perceber que o 8237A funciona, então, como um “agente”, do ponto de vista dos dispositivos.

De acordo com a especificação, a transferências da memória e para a própria memória também é possível. Mas, ao que parece, isso não foi implementado nos PCs… Não dá para pedir pro DMAC copiar, por demanda de software, um bloco de memória de um lugar para o outro. Em teoria isso seria uma boa ideia para aliviar alguma pressão em loops de cópias de dados, por parte do processador. Na prática, o 8237A é bem lento, permitindo transferências com uma taxa máxima de 2,5 MiB/s (existem versões com taxas de 5 MiB/s, mas, hoje em dia, isso não é lá muito rápido!).

Outro detalhe interessante são os modos de operação do DMAC… Alguns dispositivos são bem lentos e, por isso, aceitam apenas transferências simples, uma palavra (byte?) por vez… Outros, são rápidos, e permitem a transferência de um bloco de dados inteiro, mas ainda existem outros que precisam de algumas requisições para completar a transferência (transferem por demanda). Esses diferentes modos de operação o 8237A aceita e o PC está preparado para elas.

PCs adicionam um detalhe ao endereço base de transferência:

Note que, até aqui, o endereço base de transferência tem apenas 16 bits de tamanho. Isso limitaria o uso do DMAC a um único segmento da memória. Não é assim que funciona nos PCs… O padrão ISA adiciona o conceito de “página de DMA”, estendendo o endereço físico em 8 bits… Assim, o endereço base tem 24 bits de tamanho.

Isso não significa que podemos transferir, via DMA, blocos de 16 MiB! Os blocos continuam limitados a 64 KiB, e a página é fixa, mesmo quando há um overflow (ou underflow) no endereço. Por exemplo, se a página for 0xB8 (como é o caso com os antigos circuitos de vídeo VGA, no modo texto), se o DMAC transferir um byte do endereço físico 0xB8FFFF, o próximo endereço não será 0xB90000, mas 0xB80000. A página não muda.

O que é preciso para programar o DMAC?

O DMAC tem 12 tipos diferentes de registradores. Neles podemos ajustar o endereço base da transferência, a quantidade de bytes a serem copiados e o modo como esses dados serão transferidos (em bloco? um byte por vez?). Existem, ainda, registradores de status e máscara de requisições (do mesmo jeito que ocorre no PIC, exceto que apenas 3 bits são usados – os dois bits inferiores selecionam o canal e o bit 2 ajusta a máscara).

Para programar um canal do DMAC precisamos informar o endereço base (veja abaixo), a quantidade de bytes a serem transferidos, o modo de transferência e desmascarar o canal… O dispositivo requisitando a transferência vai indicar a transferência via sinal DRQn (uma vez que o DMAC responda com o DACKn).

É costumeiro que o dispositivo, depois de terminar a transferência envie uma interrupção ao processador para que o tratador saiba que existem dados colocados no buffer, pelo DMAC… É o caso de transferências feitas por HDs, sem o uso do modo PIO, por exemplo (por isso as IRQs 14 e 15 são alocadas para as duas controladoras possíveis de HD!)… É claro, as IRQs podem ocorrer por outro motivo e, neste caso, o tratador poderá ler o status do respectivo canal do DMAC para determinar o término da transferência…

As portas de I/O:

Existem dois DMACs no seu PC. As portas de 0x00 até 0x0F são usadas para o DMAC1, já as portas 0xC0 até 0xCF para o DMAC2… O primeiro DMAC lida com os canais de 0 até 3, o segundo, com os canais de 4 até 5, onde o canal 4 não é usável (o DMAC2 é escravo do DMAC1 via canal 4). Eis uma listagem para o DMAC1:

Porta Tamanho Registrador
0x00 word Start Address (Channel 0)
0x01 word Count (Channel 0)
0x02 word Start Address (Channel 1)
0x03 word Count (Channel 1)
0x04 word Start Address (Channel 2)
0x05 word Count (Channel 2)
0x06 word Start Address (Channel 3)
0x07 word Count (Channel 3)
0x08 byte Status(R)/Command(W)
0x09 byte Request
0x0a byte Single Channel Mask
0x0b byte Mode
0x0c byte Flip-Flop Reset
0x0d byte Intermediate(R)/Master Reset(W)
0x0e byte Mask Reset
0x0f byte Multi Channel Mask

Um cuidado ao lidar com o 8237A é que os registradores de 16 bits devem ser acessados 8 bits de cada vez, enviando (ou lendo) o LSB, primeiro e depois o MSB. Mas é importante que antes escrevamos qualquer valor no registrador Flip-flop reset. Isso deve ser feito porque o 8237A não muda o estado do flip-flop interno e, se não o fizermos “manualmente”, a próxima escrita num registrador de 16 bits ficará escrevendo apenas o MSB. Ao escrever em Flip-flop reset este flip-flop interno é resetado.

A porta Master Reset coloca o DMAC num estado inicial, como se tivéssemos feito um Power on reset. Não é interessante mexer nele. A porta Mask reset zera as máscaras de requisição de todos os canais do DMAC. Isso quer dizer que todos os canais estarão “desmascarados”…

As portas Single Channel Mask e Multi Channel Mask ligam ou desligam a máscara de requisições. A primeira o faz com apenas um canal, a última com todos os quatro, ao mesmo tempo. A porta Single Channel Mask recebe, nos dois primeiros bits (bits 0 e 1), o número do canal (de 0 a 3), tanto para o DMAC1, quanto para o DMAC2. e o bit 2 indica se a requisição para esse canal deve ser mascarada ou não. No caso de Multi Channel Mask, os bits de 0 a 3 são as máscaras de cada canal correspondnte.

Os registradores Request e Command são inúteis… O registrador Status devolve, nos 4 primeiros bits o estado da contagem… Para cada byte transferido a contagem decrementa. Se ela chega a zero, o bit correspondente ao canal estará setado, indicando um “término de contagem”. Já os quatro bits superiores indicam se existe alguma requisição de DMA pendente para o canal correspondente (bit 4 para o canal 0 até o bit 7, para o canal 3)… Ao lermos esse registrador, os bits TCn (terminal count) que estiverem setados serão automaticamente zerados.

As portas com as páginas de DMA, para o DMAC1, são as listadas abaixo e todas têm apenas 1 byte de tamanho. Note que a perfeita sequência que existe, acima, não existe aqui:

Porta Registrador
0x87 Page (Channel 0)
0x82 Page (Channel 1)
0x81 Page (Channel 2)
0x82 Page (Channel 3)

No caso do DMAC2 os endereços de I/O desses registradores assumem os valores de 0x8f (não usável), 0x8b, 0x89 e 0x8a (o mesmo que acima, mas com o bit 3 setado).

Resta-nos entender o registrador de modo… Para isso, é prudente que você consulte a documentação do 8237A. No contexto desse artigo, basta que eu diga que, geralmente, o modo usado por dispositivos são demand ou block. A não ser que o dispositivo seja bem velho, como antigos floppy drives, talvez o modo single seja útil… Ainda, nos PCs, o modo de autoinicialização não é usado por dispositivo algum e, geralmente, o endereço de transferência cresce (ao inves de ser decrementado)… No registrador de modo temos que dizer a direção que o DMAC vai trafegar os dados (do dispositivo para memória, da memória para o dispositivo ou da memória para a própria memória). As transferências Memória⇒memória geralmente não são suportadas e devem ser evitadas… Assim como no registrador Single Channel Mask, os 2 bits inferiores indicam para qual canal o modo se aplica.

ATENÇÃO: Novamente o leitor e amigo atento, Axey, vem em meu auxílio e indica que há uma diferença essencial nos datasheets da AMD (do link que forneci) e o da Intel (link aqui). No datasheet da Intel o “Terminal Count” é sinalizado quado o contador corrente vai de zero a 0xffff, ou seja, quando ha um underflow. Para o datasheet da Intel isso parece estranho, porque ao atingir zero nenhuma transferência adicional deveria ser feita, bem como decremento do contador… Considere a transferência de um bloco de 1 único byte: O DMAC faz algo assim:

...
while (current_count > 0)
{
  transfer_data(current_address++);
  current_count--;
}

No entanto, se esse for o caso, como poderíamos transferir 65536 bytes, ou exatamente 64 KiB? Segundo o datasheet da AMD seria impossível, já que o contador base tem 16 bits de tamnho. O contador máximo seria 65535… No caso da Intel, o contador base contém o valor da contagem requerida mais um byte. O que faz todo sentido… Para transferir 1 único byte teríamos que colocar 0 neste contador!

De fato, isso é descrito em ambos os datasheets, mas no da AMD eles esquecem de dizer que o terminal count é sinalizado no underflow. Afinal, não tem muito sentido programar o DMAC para transferir 0 bytes, tem?

Valeu, de novo, Axey!

Anúncios