Como o kernel do Linux lida com IRQs compartilhados?

14

De acordo com o que li até agora, "quando o kernel recebe uma interrupção, todos os manipuladores registrados são chamados".

Entendo que os manipuladores registrados para cada IRQ podem ser visualizados via /proc/interrupts, e também entendo que os manipuladores registrados são provenientes dos drivers que invocaram a request_irqpassagem em um retorno de chamada aproximadamente do formulário:

irqreturn_t (*handler)(int, void *)

Com base no que eu sei, cada um desses retornos de chamada do manipulador de interrupção associados ao IRQ específico deve ser chamado, e cabe ao manipulador determinar se a interrupção deve realmente ser tratada por ele. Se o manipulador não deve lidar com a interrupção específica, ele deve retornar a macro do kernel IRQ_NONE.

O que estou tendo problemas para entender é como é esperado que cada driver determine se deve lidar com a interrupção ou não. Suponho que eles possam acompanhar internamente se deveriam esperar uma interrupção. Nesse caso, não sei como eles seriam capazes de lidar com a situação em que vários drivers por trás do mesmo IRQ estão esperando uma interrupção.

A razão pela qual estou tentando entender esses detalhes é porque estou mexendo com o kexecmecanismo para reexecutar o kernel no meio da operação do sistema enquanto brinco com os pinos de redefinição e vários registradores em uma ponte PCIe, bem como uma PCI a jusante. dispositivo. E, ao fazer isso, após uma reinicialização, eu estou tendo pânico no kernel ou outros drivers reclamando que estão recebendo interrupções, mesmo que nenhuma operação esteja ocorrendo.

Como o manipulador decidiu que a interrupção deveria ser tratada por ele é o mistério.

Edit: Caso seja relevante, a arquitetura da CPU em questão é x86.

bsirang
fonte
1
stackoverflow.com/questions/14371513/for-a-shared-interrupt-line-how-do-i-find-which-interrupt-handler-to-usec
Ciro Santilli新疆改造中心法轮功六四事件

Respostas:

14

Isso é abordado no capítulo 10 dos Linux Device Drivers , 3ª edição, de Corbet et al. Está disponível gratuitamente on - line , ou você pode usar o shekels O'Reilly para encontrar árvores mortas ou formulários de e-books. A parte relevante para sua pergunta começa na página 278 no primeiro link.

Pelo que vale, aqui está minha tentativa de parafrasear essas três páginas, além de outros bits que pesquisei no Google:

  • Quando você registra um manipulador de IRQ compartilhado, o kernel verifica se:

    uma. não existe outro manipulador para essa interrupção ou

    b. todos os registrados anteriormente também solicitaram o compartilhamento de interrupções

    Se um dos casos se aplicar, ele verifica se seu dev_idparâmetro é único, para que o kernel possa diferenciar os vários manipuladores, por exemplo, durante a remoção do manipulador.

  • Quando um dispositivo de hardware PCI¹ aumenta a linha de IRQ, o manipulador de interrupção de baixo nível do kernel é chamado e, por sua vez, chama todos os manipuladores de interrupção registrados, passando cada um de volta o que dev_idvocê usou para registrar o manipulador request_irq().

    O dev_idvalor precisa ser exclusivo da máquina. A maneira mais comum de fazer isso é passar um ponteiro para o dispositivo que structo driver usa para gerenciar esse dispositivo. Como esse ponteiro deve estar no espaço de memória do driver para ser útil ao motorista, é ipso facto exclusivo desse driver.²

    Se houver vários drivers registrados para uma determinada interrupção, todos eles serão chamados quando qualquer um dos dispositivos aumentar a linha de interrupção compartilhada. Se não foi o dispositivo do seu motorista que fez isso, o manipulador de interrupções do seu motorista receberá um dev_idvalor que não pertence a ele. O manipulador de interrupções do seu motorista deve retornar imediatamente quando isso acontecer.

    Outro caso é que seu driver está gerenciando vários dispositivos. O manipulador de interrupções do driver obterá um dos dev_idvalores conhecidos pelo driver. Seu código deve pesquisar cada dispositivo para descobrir qual deles causou a interrupção.

    O exemplo Corbet et al. give é o de uma porta paralela do PC. Quando afirma a linha de interrupção, também define o bit superior em seu primeiro registro de dispositivo. (Ou seja, inb(0x378) & 0x80 == trueassumindo a numeração da porta de E / S padrão.) Quando o manipulador detectar isso, ele deve fazer seu trabalho e, em seguida, limpe o IRQ escrevendo o valor lido da porta de E / S na porta com a parte superior. pouco limpo.

    Não vejo nenhuma razão para que esse mecanismo específico seja especial. Um dispositivo de hardware diferente pode escolher um mecanismo diferente. A única coisa importante é que, para um dispositivo permitir interrupções compartilhadas, é necessário que haja alguma maneira de o driver ler o status de interrupção do dispositivo e alguma maneira de limpar a interrupção. Você precisará ler a folha de dados ou o manual de programação do seu dispositivo para descobrir qual mecanismo o seu dispositivo específico usa.

  • Quando o manipulador de interrupção diz ao kernel que manipulou a interrupção, isso não impede que o kernel continue chamando outros manipuladores registrados para a mesma interrupção. Isso é inevitável se você deseja compartilhar uma linha de interrupção ao usar interrupções acionadas por nível.

    Imagine dois dispositivos afirmarem a mesma linha de interrupção ao mesmo tempo. (Ou pelo menos, tão perto no tempo que o kernel não tem tempo para chamar um manipulador de interrupções para limpar a linha e, assim, ver a segunda asserção como separada.) O kernel deve chamar todos os manipuladores para essa linha de interrupção, para fornecer a cada uma chance de consultar seu hardware associado para ver se ele precisa de atenção. É bem possível para dois drivers diferentes manipularem com êxito uma interrupção na mesma passagem pela lista de manipuladores para uma determinada interrupção.

    Por esse motivo, é imperativo que seu driver informe ao dispositivo que está conseguindo limpar sua declaração de interrupção algum tempo antes do retorno do manipulador de interrupções. Não está claro para mim o que acontece de outra maneira. A linha de interrupção continuamente declarada resultará no kernel chamando continuamente os manipuladores de interrupção compartilhados ou mascarará a capacidade do kernel de ver novas interrupções para que os manipuladores nunca sejam chamados. De qualquer maneira, desastre.


Notas de rodapé:

  1. Especifiquei o PCI acima porque todos os itens acima pressupõem interrupções acionadas por nível , conforme usado na especificação original do PCI. O ISA usava interrupções acionadas por borda , o que tornava o compartilhamento complicado, na melhor das hipóteses, e possível mesmo assim somente quando suportado pelo hardware. O PCIe usa interrupções sinalizadas por mensagem ; a mensagem de interrupção contém um valor exclusivo que o kernel pode usar para evitar o jogo de adivinhação exigido com o compartilhamento de interrupção do PCI. O PCIe pode eliminar a grande necessidade de compartilhamento de interrupção. (Não sei se realmente existe, apenas que tem potencial.)

  2. Todos os drivers do kernel do Linux compartilham o mesmo espaço de memória, mas um driver não relacionado não deve estar mexendo no espaço de memória de outro. A menos que você passe esse ponteiro, você pode ter certeza de que outro motorista não terá o mesmo valor acidentalmente por si próprio.

Warren Young
fonte
1
Como você mencionou, o manipulador de interrupção pode ser passado para um dev_idque não possui. Para mim, parece que existe uma chance diferente de zero de que um driver que não possui a dev_idestrutura ainda possa confundi-la como sua com base na maneira como interpreta o conteúdo. Se não for esse o caso, que mecanismo impediria isso?
precisa saber é
Você evita isso fazendo dev_idum ponteiro para algo dentro do espaço de memória do driver. Outro piloto poderia tornar-se um dev_idvalor que aconteceu para ser confusable com um ponteiro para a memória o driver possui, mas isso não vai acontecer, porque todo mundo está jogando pelas regras. Este é o espaço do kernel, lembre-se: a autodisciplina é assumida como uma questão de disciplina, diferente do código do espaço do usuário, que pode alegremente assumir que qualquer coisa não proibida é permitida.
Warren Young
De acordo com o capítulo dez do LDD3: "Sempre que dois ou mais drivers estão compartilhando uma linha de interrupção e o hardware interrompe o processador nessa linha, o kernel invoca todos os manipuladores registrados para essa interrupção, passando cada um seu próprio dev_id" Parece o entendimento anterior estava incorreto sobre se um manipulador de interrupção pode ser transmitido de uma forma dev_idque não possui.
precisa saber é
Isso foi uma leitura errada da minha parte. Quando escrevi isso, eu estava combinando dois conceitos. Eu editei minha resposta. A condição que exige que o manipulador de interrupções retorne rapidamente é que ele é chamado devido a uma declaração de interrupção por um dispositivo que não está gerenciando. O valor de dev_idnão ajuda a determinar se isso aconteceu. Você precisa perguntar ao hardware: "Você tocou?"
Warren Young
Sim, agora eu preciso descobrir como o que estou mexendo na verdade está fazendo com que outros drivers acreditem que seus dispositivos "tocaram" após uma reinicialização do kernel via kexec.
bsirang
4

Quando um driver solicita um IRQ compartilhado, ele passa um ponteiro para o kernel para uma referência a uma estrutura específica do dispositivo no espaço de memória do driver.

De acordo com o LDD3:

Sempre que dois ou mais drivers estão compartilhando uma linha de interrupção e o hardware interrompe o processador nessa linha, o kernel invoca todos os manipuladores registrados para essa interrupção, passando cada um seu próprio dev_id.

Ao verificar os manipuladores de IRQ de vários drivers, parece que eles analisam o próprio hardware para determinar se deve lidar com a interrupção ou o retorno IRQ_NONE.

Exemplos

Driver UHCI-HCD
  status = inw(uhci->io_addr + USBSTS);
  if (!(status & ~USBSTS_HCH))  /* shared interrupt, not mine */
    return IRQ_NONE;

No código acima, o driver está lendo o USBSTSregistro para determinar se há uma interrupção no serviço.

Driver SDHCI
  intmask = sdhci_readl(host, SDHCI_INT_STATUS);

  if (!intmask || intmask == 0xffffffff) {
    result = IRQ_NONE;
    goto out;
  }

Assim como no exemplo anterior, o driver está verificando um registro de status SDHCI_INT_STATUSpara determinar se precisa reparar uma interrupção.

Ath5k Driver
  struct ath5k_softc *sc = dev_id;
  struct ath5k_hw *ah = sc->ah;
  enum ath5k_int status;
  unsigned int counter = 1000;

  if (unlikely(test_bit(ATH_STAT_INVALID, sc->status) ||
        !ath5k_hw_is_intr_pending(ah)))
    return IRQ_NONE;

Apenas mais um exemplo.

bsirang
fonte
0

Por favor, visite este link :

É uma prática usual acionar as metades inferiores ou qualquer outra lógica no manipulador de IRQ somente depois de verificar o status do IRQ a partir de um registro mapeado na memória. Portanto, o problema é resolvido por padrão por um bom programador.

Priyaranjan
fonte
O conteúdo do seu link não está disponível
user3405291 3/17/17