AHCI
AHCI é a interface de registradores que a maioria dos controladores SATA expõe ao software hoje, o sucessor baseado em DMA do ATA/IDE em modo PIO, padronizada pela Intel para que um único driver escrito contra a especificação funcione em chipsets de fabricantes diferentes sem código específico de fornecedor. Localizar e mapear um controlador AHCI é um problema de enumeração PCI coberto em outro lugar; este artigo continua a partir do ponto em que a BAR5, a ABAR (AHCI Base Address Register), já está mapeada e legível, e cobre os registradores, estruturas de dados, e sequência de comando que um driver usa para mover dados através de uma porta.
Registradores genéricos da HBA
Seção intitulada “Registradores genéricos da HBA”Os primeiros 256 bytes da ABAR guardam registradores descrevendo o controlador como um todo, não uma porta individual. CAP (offset 0x00) reporta capacidades fixas em hardware: o número de portas implementadas, o número de slots de comando por porta, e se o controlador suporta recursos como spin-up escalonado ou native command queuing. GHC (0x04), o registrador Global HBA Control, guarda o bit AHCI Enable que precisa ser setado antes de qualquer registrador de porta ter comportamento definido, junto com um bit HBA Reset que retorna o controlador inteiro ao seu estado de power-on. IS (0x08) é uma máscara de bits das interrupções de porta pendentes, um bit por porta, permitindo que um driver com uma única linha de interrupção compartilhada determine qual porta a disparou sem ler o registrador de status de cada porta individualmente. PI (0x0C), o registrador Ports Implemented, é uma máscara de bits identificando quais das até 32 portas possíveis de fato existem neste controlador; um bit desligado ali significa que o bloco de registradores daquela porta, presente na memória, corresponde a hardware físico nenhum e não deve ser tocado.
Registradores de porta
Seção intitulada “Registradores de porta”Cada porta implementada recebe seu próprio bloco de registradores de 128 bytes começando no offset 0x100 mais 0x80 vezes o número da porta. PxCLB/PxCLBU guardam o endereço físico da lista de comando daquela porta, dividido entre dois registradores de 32 bits para as metades baixa e alta em um sistema de 64 bits. PxFB/PxFBU endereçam da mesma forma a área de recepção de FIS da porta, um pequeno buffer no qual o controlador escreve automaticamente as Frame Information Structures recebidas. PxCI, o registrador Command Issue, é onde um driver seta um bit para entregar um slot de comando ao controlador para execução; o próprio controlador limpa esse bit assim que o comando é concluído, dando ao software uma forma direta de sondar a conclusão sem um campo de status separado. PxIS e PxIE são os próprios registradores de status e habilitação de interrupção da porta, cobrindo condições específicas daquela porta: um comando concluído, um erro de task file, ou uma mudança de presença do dispositivo em hardware com suporte a hot-plug. PxTFD, o registrador Task File Data, espelha os registradores legados de status e erro do ATA, permitindo que código de driver que já entende os bits de status ATA (BSY, DRQ, ERR) reaproveite esse conhecimento aqui. PxSSTS, PxSCTL, e PxSERR reportam e controlam o próprio link físico SATA, independentemente do AHCI: velocidade de link negociada, se um dispositivo está atualmente detectado, e quaisquer erros de camada de link, respectivamente.
A lista de comando e a tabela de comando
Seção intitulada “A lista de comando e a tabela de comando”A lista de comando de uma porta é um array de até 32 cabeçalhos de comando, cada um com 32 bytes, um por slot de comando. Um cabeçalho de comando guarda o endereço físico de uma tabela de comando, o número de entradas PRDT que aquela tabela contém, e um punhado de flags descrevendo o comando: seu tamanho na FIS que carrega, se é uma escrita, e se usa comandos de pacote ATAPI em vez de comandos ATA comuns. A própria tabela de comando guarda a FIS de comando real a ser enviada ao dispositivo, um bloco de comando ATAPI opcional, e uma Physical Region Descriptor Table (PRDT): uma lista de pares de endereço físico e contagem de bytes descrevendo de onde na memória os dados deste comando devem ser lidos ou para onde devem ser escritos. Os dados de um único comando não precisam ocupar memória física contígua; dividi-los entre múltiplas entradas PRDT é precisamente o que permite a um driver entregar ao controlador uma lista scatter-gather construída a partir de quaisquer páginas que um buffer por acaso ocupe, em vez de exigir uma alocação física contígua para cada I/O.
struct h2d_register_fis { uint8_t fis_type; // 0x27 uint8_t pm_port : 4; uint8_t rsv0 : 3; uint8_t c : 1; // 1 = comando, 0 = controle uint8_t command; uint8_t featurel; uint8_t lba0, lba1, lba2; uint8_t device; uint8_t lba3, lba4, lba5; uint8_t featureh; uint16_t count; uint8_t icc; uint8_t control; uint32_t rsv1;};Tipos de FIS
Seção intitulada “Tipos de FIS”Frame Information Structures são a unidade de comunicação entre host e dispositivo no próprio link SATA; as tabelas de comando e áreas de recepção do AHCI são construídas em torno da troca delas, não do modelo mais antigo de task file do ATA paralelo diretamente, ainda que vários campos da FIS (LBA, contagem de setores, byte de comando) correspondam aos mesmos conceitos. Uma Register FIS - Host to Device, tipo 0x27, é o que um driver constrói para emitir um comando ATA comum: carrega o byte de comando, LBA, contagem de setores, e campos de seleção de dispositivo que um driver já precisa preencher. A resposta do controlador, uma Register FIS - Device to Host (tipo 0x34), reporta os registradores de status e erro resultantes e é escrita pelo controlador na área de recepção de FIS da porta, em vez de retornada como um valor de alguma leitura de registrador. Uma DMA Setup FIS (0x41) e PIO Setup FIS (0x5F) precedem a fase de dados de comandos DMA e PIO respectivamente, e uma Set Device Bits FIS (0xA1) permite que um dispositivo atualize status e dispare uma interrupção sem uma register FIS completa, usada intensamente por native command queuing para reportar qual de vários comandos pendentes acabou de concluir.
Iniciando uma porta
Seção intitulada “Iniciando uma porta”Uma porta começa em um estado ocioso, não configurado, e precisa ser iniciada explicitamente antes de aceitar comandos. O software primeiro configura PxCLB/PxCLBU e PxFB/PxFBU para apontar para memória fisicamente contígua alocada pelo driver, para a lista de comando e a área de recepção de FIS. PxCMD.FRE (FIS Receive Enable) é setado primeiro, dizendo ao controlador que ele pode começar a escrever FISes recebidas na área de recepção; só depois disso PxCMD.ST (Start) é setado, dizendo ao controlador para começar a processar comandos a partir da lista de comando. O controlador confirma cada um com um bit de status correspondente, PxCMD.FR e PxCMD.CR, e um driver que seta ST sem antes confirmar que FR foi setado, ou sem FRE setado de forma alguma, arrisca uma porta que aparenta aceitar comandos mas nunca sinaliza sua conclusão, já que a Register FIS carregando o status de volta do dispositivo não tem para onde ir de forma definida.
Emitindo um comando: lendo um setor
Seção intitulada “Emitindo um comando: lendo um setor”Ler dados começa com um driver localizando um slot de comando livre, um cujo bit esteja limpo tanto em PxCI quanto no próprio rastreamento de slot-ocupado do controlador, e preenchendo o cabeçalho e a tabela de comando daquele slot: o tamanho da FIS em DWORDs, a contagem de entradas PRDT, e um ponteiro para uma tabela de comando contendo uma Register FIS com o byte de comando READ DMA EXT, o LBA alvo, e a contagem de setores, junto com uma entrada PRDT apontando para o buffer de destino.
cmd_header->prdtl = 1;cmd_header->cfl = sizeof(struct h2d_register_fis) / 4;cmd_header->w = 0; // leitura, não escrita
cmd_table->prdt[0].dba = (uint32_t)(uintptr_t)dest_buffer;cmd_table->prdt[0].dbau = (uint32_t)((uintptr_t)dest_buffer >> 32);cmd_table->prdt[0].dbc = (sector_count * 512) - 1; // contagem de bytes menos 1cmd_table->prdt[0].i = 1; // interrupção na conclusão
fis->fis_type = 0x27;fis->c = 1;fis->command = 0x25; // READ DMA EXT// campos de LBA e contagem setados aquiCom o slot preparado, o driver seta o bit correspondente em PxCI, entregando o comando ao controlador. O controlador o processa de forma assíncrona, move os dados via DMA diretamente para o buffer que a PRDT descreveu, escreve uma Register FIS - Device to Host na área de recepção reportando o status de conclusão, dispara a interrupção da porta se habilitada, e limpa o bit do driver em PxCI. Um driver que faz polling em vez de usar interrupções observa esse mesmo bit ser limpo, em vez de alguma flag separada de “concluído”, já que a conclusão do comando e a limpeza do PxCI são o mesmo evento por design.
Native Command Queuing
Seção intitulada “Native Command Queuing”Native Command Queuing (NCQ) permite que uma porta tenha múltiplos comandos pendentes simultaneamente, emitidos através de PxSACT (o registrador SATA Active) e PxCI juntos, em vez de um de cada vez, permitindo que o próprio firmware de um dispositivo reordene leituras e escritas pendentes para melhor eficiência mecânica em mídia rotativa. Comandos NCQ usam um byte de comando diferente (READ FMA QUEUED/WRITE FMA QUEUED, ou suas variantes EXT) e a conclusão é reportada através de Set Device Bits FISes em vez de uma única Register FIS por comando, já que o status de mais de um comando pode precisar ser reportado próximo no tempo. Um bit de suporte a NCQ no registrador CAP do controlador, e um bit correspondente reportado pelo próprio identify-device do drive, ambos precisam ser checados antes de um driver assumir que queuing está disponível; emitir comandos NCQ para um dispositivo ou controlador que não os suporta produz resultados indefinidos em vez de uma rejeição limpa.
Notas de implementação
Seção intitulada “Notas de implementação”Setar PxCMD.ST antes de PxCMD.FRE é uma fonte comum de uma porta que silenciosamente nunca conclui um comando: a Register FIS carregando o status de conclusão não tem área de recepção alguma configurada para onde ir ainda, então o comando executa no dispositivo mas seu resultado se perde, deixando PxCI setado para sempre sem interrupção e sem indicação de erro. O campo de contagem de bytes de uma PRDT em uma tabela de comando é definido como um a menos que a contagem de bytes de fato transferida, um detalhe fácil de errar por exatamente um byte e que produz uma transferência silenciosamente curta em vez de um erro explícito. Por fim, PxSERR acumula erros de camada de link SATA independentemente de qualquer comando estar em andamento, e um driver que checa apenas PxTFD por erros após cada comando pode perder resets de link ou erros de CRC que PxSERR teria reportado diretamente, particularmente em torno de eventos de hot-plug em portas que os suportam.
Referências
Seção intitulada “Referências”- ^ Intel, Serial ATA Advanced Host Controller Interface (AHCI) 1.3.1 Specification (define todo registrador e estrutura descritos acima)
- ^ OSDev Wiki, “AHCI” (uma referência hobbyist de implementação com código de driver de exemplo)
Ver também
Seção intitulada “Ver também”- PCI: localizando e mapeando o controlador atrás dos registradores deste artigo.
- Port I/O versus Memory-Mapped I/O: o modelo mapeado em memória que todo registrador deste artigo segue.
- VFS: a camada de abstração que um driver em nível de bloco como este eventualmente alimenta.
- ATA/IDE em Modo PIO: o predecessor mais simples, baseado em polling, que o modelo de DMA desta interface substitui.
- NVMe: o sucessor baseado em pares de fila que substituiu esta interface como padrão para SSDs modernos.
- Controlador de Disquete: um driver de armazenamento legado sem relevância prática hoje, diferente deste.