NVMe
NVMe (Non-Volatile Memory Express) é o protocolo de armazenamento que substituiu SATA/AHCI como padrão em SSDs modernos, construído nativamente em torno de PCIe em vez de emular a semântica de registrador ATA que o AHCI ainda expõe por baixo. Onde o AHCI organiza trabalho em torno de um número fixo de portas e slots de comando por porta, o NVMe é construído em torno de pares de fila, um modelo de comando inteiramente diferente projetado desde o início para o paralelismo que armazenamento flash de fato consegue sustentar.
O conjunto de registradores do controlador
Seção intitulada “O conjunto de registradores do controlador”Localizar um controlador NVMe através do PCI funciona exatamente como localizar qualquer outro dispositivo: ler sua BAR0, que é sempre mapeada em memória para NVMe (não há alternativa de espaço de I/O da forma que algumas outras classes de dispositivo oferecem), e mapeá-la. Os registradores encontrados ali incluem as capacidades do controlador, um registrador Controller Configuration usado para habilitá-lo, um registrador Controller Status para checar prontidão, e os registradores de doorbell que pares de fila usam para sinalizar trabalho novo.
struct nvme_regs { uint64_t cap; // capacidades uint32_t vs; // versão uint32_t intms; // interrupt mask set uint32_t intmc; // interrupt mask clear uint32_t cc; // controller configuration uint32_t rsvd; uint32_t csts; // controller status uint32_t nssr; uint32_t aqa; // atributos da fila admin uint64_t asq; // endereço-base da admin submission queue uint64_t acq; // endereço-base da admin completion queue // ... registradores de doorbell seguem, espaçamento definido por CAP.DSTRD ...};Submission e completion queues
Seção intitulada “Submission e completion queues”Toda operação, desde o exato primeiro comando Identify até uma leitura ou escrita comum, passa por um par de fila: uma submission queue (SQ) na qual o driver escreve comandos, e uma completion queue (CQ) na qual o controlador escreve resultados, cada uma um buffer circular simples em memória de host em vez de um conjunto fixo de slots de hardware. O primeiríssimo par de fila, a Admin Queue, é configurado diretamente através dos registradores ASQ/ACQ/AQA antes de qualquer outra coisa poder acontecer, e é usado exclusivamente para comandos de gerenciamento de controlador (Identify entre eles); I/O de dados comum passa por pares de fila de I/O separados, criados depois via comandos admin, e um driver pode criar vários deles, comumente um par por núcleo de CPU, para submeter e completar comandos a partir de múltiplos núcleos em paralelo sem contenção de lock compartilhado entre eles.
struct nvme_command { uint8_t opcode; uint8_t flags; uint16_t command_id; uint32_t nsid; // ID de namespace (aproximadamente, qual "drive") uint64_t rsvd2[2]; uint64_t metadata; uint64_t prp1, prp2; // endereços físicos do buffer de dados uint32_t cdw10, cdw11, cdw12, cdw13, cdw14, cdw15; // específico de opcode};Submeter um comando significa escrevê-lo no próximo slot livre da submission queue e depois escrever o novo índice de cauda no registrador de doorbell daquela fila, uma escrita mapeada em memória que o controlador sonda ou de outra forma toma conhecimento, notificando-o de que há trabalho novo pronto sem sequência de comando registrador-por-registrador alguma da forma que os registradores de porta do AHCI exigem. O controlador processa o comando assincronamente e escreve uma entrada de conclusão na completion queue correspondente, alternando um bit de fase que o driver checa para distinguir uma conclusão genuinamente nova de dados desatualizados deixados num slot de uma passada anterior pelo anel, depois avança o próprio doorbell da completion queue para reconhecê-la.
Identify
Seção intitulada “Identify”O comando Identify (opcode 0x06) é o primeiro comando real emitido depois de a admin queue ser configurada, e, de forma parecida com o próprio IDENTIFY do ATA em espírito embora estruturalmente não relacionado, retorna uma estrutura de dados grande descrevendo as capacidades do controlador e, com um valor de sub-comando diferente, a geometria de um namespace específico: seu tamanho em blocos lógicos, seu tamanho de bloco lógico, e várias flags de recurso opcionais.
struct nvme_command identify_cmd = { .opcode = 0x06, .cdw10 = 1, // CNS = 1: identifica o próprio controlador (0 = identifica um namespace) .prp1 = data_buffer_phys_addr,};Um namespace é o equivalente aproximado do NVMe a um “drive” na perspectiva do AHCI, embora o mapeamento nem sempre seja um-para-um: um único SSD NVMe físico pode expor múltiplos namespaces, cada um endereçável e dimensionado independentemente, uma capacidade sem paralelo direto na suposição mais simples de um-controlador-por-drive-físico do AHCI.
Notas de implementação
Seção intitulada “Notas de implementação”Como tanto submission quanto completion queues são buffers comuns de memória de host que o controlador lê e escreve via DMA em vez de estruturas fixas de hardware, um driver precisa alocá-las como memória fisicamente contígua e adequadamente alinhada e nunca movê-las depois de registradas com o controlador, diferente da lista de comando fixa por porta do AHCI cujo local pode ser reprogramado mais livremente entre operações. O mecanismo de bit de fase existe especificamente para permitir que uma completion queue seja lida puramente por sondagem de seu conteúdo em memória, com interrupções como uma otimização opcional empilhada por cima em vez de um requisito, o que é parte do que torna o caminho de conclusão do NVMe mais barato em profundidades de fila altas do que um design puramente orientado a interrupção seria.
Referências
Seção intitulada “Referências”- ^ NVM Express, Inc., NVM Express Base Specification: a especificação formal que define o conjunto de registradores, o modelo de fila, e o conjunto de comandos descritos acima.