Pular para o conteúdo

PIC e APIC

Dispositivos de hardware sinalizam a CPU de forma assíncrona através de linhas de interrupção, e algo precisa decidir quais dessas linhas de fato chegam à CPU, em qual prioridade, e como qual vetor de interrupção. No hardware compatível com PC mais antigo, esse papel pertencia ao 8259 Programmable Interrupt Controller (PIC); em qualquer máquina construída para multiprocessamento, ele pertence em vez disso ao Advanced Programmable Interrupt Controller (APIC), dividido em um Local APIC por CPU e um I/O APIC compartilhado.

Um único 8259 lida com oito linhas de interrupção, numeradas IRQ0 a IRQ7. Máquinas compatíveis com IBM PC conectam duas delas em uma cascata master/slave (a saída do slave alimenta uma das próprias linhas de entrada do master, IRQ2 por convenção), estendendo o total para quinze linhas utilizáveis, IRQ0 a IRQ15, com a própria IRQ2 não mais disponível para um dispositivo, já que é consumida pela cascata.

Ambos os chips são programados através de um par de portas de I/O cada um: 0x20/0x21 para o master, 0xA0/0xA1 para o slave. A inicialização envia uma sequência de Initialization Command Words (ICW1 a ICW4) estabelecendo, entre outras coisas, para qual vetor de interrupção a IRQ0 de cada chip é mapeada: crítico em x86, porque o mapeamento padrão de fábrica do PIC coloca IRQ0 a IRQ7 nos vetores 8 a 15, colidindo diretamente com os próprios vetores de exceção reservados da CPU, 0 a 31. Todo kernel compatível com PC remapeia o PIC durante a inicialização, comumente para os vetores 32 e 40 para master e slave respectivamente, especificamente para evitar que uma interrupção de hardware e uma exceção de CPU jamais reivindiquem o mesmo número de vetor.

Depois da inicialização, Operation Command Words (OCW1 a OCW3) controlam o comportamento do dia a dia: OCW1 define a máscara de interrupção, um bit por linha, permitindo que o software desabilite IRQs individuais sem tocar na flag global de interrupção da CPU; um comando End-Of-Interrupt, enviado à porta de comando depois de atender uma interrupção, diz ao PIC que ele pode entregar mais interrupções de prioridade igual ou menor. Omiti-lo deixa o PIC acreditando que uma interrupção ainda está sendo atendida, e ele não entregará outra. Uma interrupção espúria (IRQ7 no master ou IRQ15 no slave, chegando sem nenhuma linha de fato ativada) é um artefato conhecido do design do 8259 sob condições específicas de timing, não uma falha de hardware, e uma IRQ15 espúria em particular ainda exige um EOI enviado ao master (mas não ao slave, que nunca de fato levantou nada), um detalhe fácil de inverter.

O modelo do 8259 tem um controlador de interrupção entregando a uma CPU, o que é fundamentalmente incompatível com um sistema que tem mais de uma CPU: não há como rotear uma determinada interrupção para qual CPU deveria tratá-la, ou fazer com que CPUs diferentes tratem interrupções diferentes. A arquitetura do APIC divide o tratamento de interrupções em duas peças cooperantes para resolver isso. Um Local APIC existe uma vez por núcleo de CPU, integrado ao próprio processador, e é o que de fato entrega um vetor de interrupção àquele núcleo específico, além de lidar com as próprias fontes locais de interrupção daquele núcleo (o timer do APIC, e interrupções entre processadores descritas abaixo). Um I/O APIC (um ou mais por sistema, externo a qualquer CPU específica) recebe interrupções de dispositivos e, através de uma tabela de redirecionamento programável, decide para qual vetor e qual Local APIC (ou qual subconjunto de CPUs) cada linha de entrada deve ser entregue.

O Local APIC de cada CPU é controlado através de um conjunto de registradores mapeados em memória, cujo endereço-base físico é lido da model-specific register IA32_APIC_BASE, por convenção 0xFEE00000, a menos que o firmware o tenha realocado. Registradores-chave incluem o APIC ID (identificando a qual CPU este Local APIC pertence, usado como destino ao rotear uma interrupção), o registrador End-Of-Interrupt (escrito, não lido, para confirmar a interrupção atual; diferente do comando dedicado do PIC, qualquer valor escrito aqui é suficiente), o registrador Spurious Interrupt Vector (que também contém o bit de habilitação global do APIC), e os registradores de configuração e contagem do APIC Timer, fornecendo um timer de propósito geral local a cada CPU, independente de qualquer timer de sistema compartilhado.

Onde o 8259 tem fixo por hardware qual linha mapeia para qual vetor, a tabela de redirecionamento de um I/O APIC (uma entrada de 64 bits por linha de entrada, tipicamente 24 entradas) é totalmente programável: cada entrada especifica o vetor de interrupção alvo, modo de entrega, CPU ou CPUs de destino, modo de disparo (edge ou level), e polaridade, permitindo que o sistema operacional roteie uma determinada linha física de interrupção para qualquer vetor e qualquer CPU que escolher, e mude esse roteamento em tempo de execução. Um kernel descobre quantos I/O APICs existem, seus endereços-base, e como números de IRQ ISA legados correspondem a seus pinos de entrada analisando a tabela ACPI MADT (Multiple APIC Description Table), em vez de por qualquer convenção fixa, já que esse mapeamento não tem garantia de ser idêntico entre chipsets diferentes.

Um kernel que já trocou para o APIC ainda precisa lidar com o par 8259, que permanece presente e, se deixado em seu estado padrão, ainda pode gerar interrupções espúrias nas mesmas linhas que a configuração do APIC agora espera que estejam quietas. A abordagem direta mascara toda linha em ambos os PICs escrevendo 0xFF em ambos os registradores de máscara, deixando os chips fisicamente presentes mas permanentemente silenciados, em vez de tentar desabilitá-los por completo, algo para o qual o 8259 não fornece mecanismo limpo algum.

Além de rotear interrupções de dispositivos, o Local APIC fornece uma capacidade para a qual o 8259 não tem equivalente algum: uma CPU pode enviar uma interrupção diretamente para outra CPU, ou para um grupo de CPUs, escrevendo em seu próprio Interrupt Command Register. É assim que um kernel implementa um TLB shootdown (interrompendo outras CPUs especificamente para que cada uma descarte suas próprias entradas desatualizadas de cache de tradução), e é assim que uma CPU bootstrap coloca núcleos de CPU adicionais online, através de uma sequência específica de IPIs conhecida como INIT-SIPI-SIPI: um IPI INIT reseta o núcleo alvo, seguido de um ou dois Startup IPIs (SIPI) carregando o endereço físico do código que o núcleo recém-iniciado deve começar a executar, já que um processador de aplicação, de outra forma, inicia em um estado sem como ser informado sozinho por onde começar.

Habilitar o APIC sem também configurar corretamente, ou mascarar explicitamente, o PIC legado é uma fonte comum de interrupções chegando duas vezes ou nenhuma durante o bring-up: ambos os controladores podem estar simultaneamente ativos e ambos tentando entregar a mesma linha física, a menos que o PIC legado seja mascarado primeiro. Ler a MADT é efetivamente obrigatório, não opcional, para correção: presumir o mapeamento convencional de IRQ0 a IRQ15 para vetor sem verificar as entradas de interrupt source override da MADT vai funcionar em muitas máquinas por coincidência e falhar silenciosamente, de formas difíceis de distinguir de outros bugs, em outras onde o firmware remapeou uma IRQ legada para uma entrada diferente de I/O APIC.

  1. ^ Intel, 8259A Programmable Interrupt Controller Datasheet: define o modelo de programação ICW/OCW referenciado acima.
  2. ^ Intel, Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A, Capítulo 10: define o conjunto de registradores do Local APIC e o modelo de entrega de IPI.
  3. ^ Especificação ACPI, MADT (Multiple APIC Description Table): a fonte fornecida pelo firmware para informação de I/O APIC e roteamento de interrupção.
  • The IDT: a tabela através da qual um vetor de interrupção é despachado uma vez entregue.
  • System Calls: um caminho de interrupção disparado por software, não relacionado ao roteamento de hardware descrito aqui.
  • Bring-Up de Multiprocessador: a sequência INIT-SIPI-SIPI enviada como uma IPI através do Local APIC coberto aqui.