Pular para o conteúdo

Port I/O versus Memory-Mapped I/O

Hardware x86 expõe dois espaços de endereçamento inteiramente separados para conversar com dispositivos, e quase todo artigo de driver desta wiki usa um ou outro sem pausar para explicar a escolha: port I/O, um espaço dedicado de 64 KB alcançado apenas através das instruções IN e OUT, e memory-mapped I/O (MMIO), onde os registradores de um dispositivo simplesmente ocupam uma faixa de endereços físicos comuns e são tocados com as mesmas instruções de load e store usadas para RAM. Ambos existem em x86; decidir qual deles um dado hardware usa é uma propriedade do design desse hardware, não algo que o software escolhe, embora um único controlador às vezes ofereça os dois para a mesma funcionalidade.

As instruções IN e OUT endereçam um espaço de portas de 16 bits, inteiramente separado do espaço de endereço de memória normal e não alcançável através de loads e stores comuns: in al, 0x60 lê um byte da porta 0x60 (a porta de dados do teclado PS/2), e nenhum ponteiro, entrada de tabela de página, ou endereço de memória está envolvido nessa operação. O espaço de configuração PCI, antes do mecanismo mapeado em memória MCFG que o PCI Express introduz, é um exemplo canônico: o software escreve um endereço empacotado em CONFIG_ADDRESS (porta de I/O 0xCF8) e lê ou escreve os dados reais através de CONFIG_DATA (0xCFC), duas portas fixas substituindo o que MMIO de outra forma exporia como uma faixa grande de registradores endereçáveis.

mov dx, 0x60
in al, dx ; lê um byte da porta 0x60

Código de ring 3 executando IN/OUT diretamente não é automaticamente proibido da forma como uma escrita de ring 3 em memória de kernel é; em vez disso, é controlado pelo IOPL (o campo I/O Privilege Level em EFLAGS) junto com o bitmap de permissão de I/O da TSS, um bit por porta, permitindo que um kernel conceda a um processo desprivilegiado específico acesso direto a portas específicas (um driver em user space para um dispositivo particular, por exemplo) sem conceder acesso irrestrito a todo o espaço de portas nem exigir que todo acesso caia no kernel primeiro.

MMIO em vez disso coloca os registradores de um dispositivo em endereços físicos fixos, mapeados no mesmo espaço de endereço que a RAM ocupa, e acessados com exatamente as mesmas instruções (MOV, e qualquer coisa construída sobre ela) que tocam memória comum. AHCI é um exemplo concreto, já documentado: uma vez que o ABAR de um controlador (seu Base Address Register do PCI) está mapeado, todo registrador descrito ali, listas de comando de porta, a área de recepção de FIS, status de interrupção, é lido e escrito como um membro de struct comum através de um ponteiro, sem instrução especial alguma envolvida.

volatile uint32_t *ghc = (volatile uint32_t *)(abar_base + 0x04);
*ghc |= (1 << 31); // define AHCI Enable

O qualificador volatile acima não é decorativo: um compilador que não sabe que um endereço mapeia para hardware, em vez de RAM comum, tem liberdade para reordenar, manter em cache num registrador, ou eliminar por completo o que parece um load ou store redundante, tudo isso otimizações corretas para memória e silenciosamente erradas para um registrador de dispositivo que pode mudar sozinho ou onde a própria escrita, não só seu valor eventual, é o que dispara um efeito colateral.

Por que a maior parte do hardware moderno escolheu MMIO

Seção intitulada “Por que a maior parte do hardware moderno escolheu MMIO”

Port I/O é arquiteturalmente específico de x86 (e um punhado de parentes próximos); um design de dispositivo baseado em MMIO se transporta em grande parte inalterado para qualquer arquitetura com um espaço de endereço físico plano, o que por si só explica boa parte da migração da indústria em direção a MMIO para novos designs de hardware. Registradores MMIO também são diretamente alcançáveis por qualquer instrução capaz de tocar memória, incluindo instruções que um compilador já gera rotineiramente, onde port I/O só é alcançável através das duas instruções dedicadas e quaisquer funções wrapper que um kernel construa ao redor delas; o PCI Express formalizou essa mudança adicionando um mecanismo de configuração inteiramente mapeado em memória (MCFG) ao lado das portas de I/O legadas, que a maioria dos controladores ainda responde para seus primeiros 256 bytes puramente por compatibilidade retroativa. Port I/O não desapareceu nem mesmo de sistemas modernos: ele sobrevive especificamente onde manter compatibilidade com décadas de software existente (as portas legadas de configuração PCI, PS/2, a UART serial clássica) supera qualquer benefício que trocar para MMIO traria para um periférico que não está sendo redesenhado de qualquer forma.

Uma faixa de endereço físico por trás de registradores MMIO precisa ser marcada como não cacheável nas tabelas de página que a mapeiam, tipicamente via o bit PCD ou uma entrada PAT selecionando um tipo de memória não cacheável; deixá-la cacheável permite que a CPU sirva silenciosamente uma leitura a partir de uma cópia em cache desatualizada em vez do valor atual do registrador do dispositivo, ou atrase uma escrita no cache indefinidamente em vez de de fato alcançar o dispositivo quando o driver espera que aconteça. Port I/O não tem preocupação equivalente com cache, já que IN/OUT são definidas para sempre alcançar o dispositivo diretamente, o que é um dos poucos aspectos em que o mecanismo mais antigo é mais simples de raciocinar corretamente do que seu substituto.

  1. ^ Intel, Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 1, Capítulo 3.1: o espaço de endereço de I/O e a família de instruções IN/OUT.
  2. ^ Intel, Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 1, Capítulo 11: os memory type range registers e o PAT, que governam como regiões MMIO são cacheadas.
  • PCI: espaço de configuração acessado através de portas de I/O legadas, com uma alternativa mapeada em memória em sistemas mais novos.
  • AHCI: uma interface de registradores inteiramente mapeada em memória, uma vez que seu BAR é localizado através do PCI.
  • Paging & Virtual Memory: onde o atributo não cacheável de uma região MMIO é de fato definido.
  • A Task State Segment: o bitmap de permissão de I/O que governa o acesso de ring 3 ao espaço de portas.