Dispositivos PCI (e) mapeados em memória terão BARs (registradores de endereço base) que permitem que o host saiba quanta memória deve ser alocada para o dispositivo. O BIOS (e o SO depois) alocarão o espaço de memória solicitado para o dispositivo de destino - não que seja um endereço de memória , os bits físicos não estão sendo alocados. Além disso, isso geralmente será um endereço de memória física , não um endereço de memória virtual. O kernel Linux arbitra o acesso a esses dispositivos com funções como mmap () que permitem o mapeamento de memória física para endereços de memória virtual.
Por exemplo, digamos que você tenha um dispositivo PCIe que controle 8 LEDs. Como criador do dispositivo, posso solicitar uma barra muito pequena de 1K de espaço de endereço. O BIOS pode me fornecer um endereço físico em um sistema de 32 bits de 0xE000_0000 a 0xE000_0400 (nota lateral: você deve ver aqui agora porque os sistemas de 32 bits e GPUs com VRAM grande não funcionam bem juntos).
Agora, se eu escrever digito 0xF0 na localização de memória 0xE000_0000 com uma gravação de um byte, isso ativará os LEDs de 7 a 4 e deixará de 3 a 0 desligado. É isso - é simples E / S mapeada na memória. Somando-se às camadas de abstração, no Linux, você utilizaria seu excelente subsistema PCI e gravaria um driver que seja carregado para uma determinada cadeia de ID de fornecedor + ID de dispositivo. Você poderia então escrever um aplicativo do espaço do usuário que chama o driver do kernel, onde uma chamada mmap () pode mapear a memória no espaço do usuário, permitindo que o aplicativo userspace faça leituras / gravações em algum endereço de memória virtual que será traduzido e acabará no espaço da memória física onde pertence.
x86 tem espaço de E / S, e o comportamento é bem parecido, exceto baixo nível no kernel, as instruções outb / outw / outl (e suas entradas primas) serão usadas para escrever / ler a partir de I / O espaço, versus instruções de leitura / gravação de memória. Novamente, um aplicativo userspace deve estar se comunicando através de ioctls () e um segmento de memória mapeado, já que o kernel é responsável pela segurança / acesso à memória assim.
Para a sua segunda pergunta, uma espécie de combinação com o que foi dito acima, mas um moderno driver PCIe para Linux dependerá do subsistema PCI para muitas das tarefas domésticas de baixo nível. Você define principalmente quais identificações de fornecedor / dispositivo você é responsável e, em seguida, escreve uma função .probe () que trava seus IRQs e faz a configuração do dispositivo - sua memória é entregue a você.