Comment le noyau Linux gère-t-il les IRQ partagées?

14

Selon ce que j'ai lu jusqu'à présent, "lorsque le noyau reçoit une interruption, tous les gestionnaires enregistrés sont invoqués".

Je comprends que les gestionnaires enregistrés pour chaque IRQ peuvent être consultés via /proc/interrupts, et je comprends également que les gestionnaires enregistrés proviennent des pilotes qui ont appelé à request_irqpasser un rappel à peu près de la forme:

irqreturn_t (*handler)(int, void *)

D'après ce que je sais, chacun de ces rappels de gestionnaire d'interruption associé à l'IRQ particulier devrait être invoqué, et c'est au gestionnaire de déterminer si l'interruption doit en effet être gérée par lui. Si le gestionnaire ne doit pas gérer l'interruption particulière, il doit renvoyer la macro du noyau IRQ_NONE.

Ce que j'ai du mal à comprendre, c'est comment chaque pilote est censé déterminer s'il doit gérer l'interruption ou non. Je suppose qu'ils peuvent suivre en interne s'ils sont censés s'attendre à une interruption. Si c'est le cas, je ne sais pas comment ils pourraient faire face à la situation dans laquelle plusieurs pilotes derrière le même IRQ attendent une interruption.

La raison pour laquelle j'essaie de comprendre ces détails est parce que je joue avec le kexecmécanisme pour ré-exécuter le noyau au milieu du fonctionnement du système tout en jouant avec les broches de réinitialisation et divers registres sur un pont PCIe ainsi qu'un PCI en aval dispositif. Et ce faisant, après un redémarrage, je reçois soit des paniques du noyau, soit d'autres pilotes se plaignant de recevoir des interruptions même si aucune opération n'a eu lieu.

La façon dont le gestionnaire a décidé que l'interruption devait être gérée est le mystère.

Edit: Dans le cas où c'est pertinent, l'architecture CPU en question l'est x86.

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

Réponses:

14

Ceci est couvert dans le chapitre 10 de Linux Device Drivers , 3rd edition, par Corbet et al. Il est disponible gratuitement en ligne , ou vous pouvez lancer la manière de certains shekels O'Reilly pour les formulaires d'arbre mort ou d'ebook. La partie pertinente à votre question commence à la page 278 du premier lien.

Pour ce que ça vaut, voici ma tentative de paraphraser ces trois pages, ainsi que d'autres bits que j'ai recherchés:

  • Lorsque vous enregistrez un gestionnaire IRQ partagé, le noyau vérifie que:

    une. aucun autre gestionnaire n'existe pour cette interruption, ou

    b. toutes les personnes précédemment enregistrées ont également demandé un partage d'interruption

    Si l'un ou l'autre cas s'applique, il vérifie alors que votre dev_idparamètre est unique, afin que le noyau puisse différencier les multiples gestionnaires, par exemple lors de la suppression du gestionnaire.

  • Lorsqu'un périphérique matériel PCI¹ lève la ligne IRQ, le gestionnaire d'interruption de bas niveau du noyau est appelé, et à son tour appelle tous les gestionnaires d'interruption enregistrés, en renvoyant chacun le nom dev_idutilisé pour enregistrer le gestionnaire via request_irq().

    La dev_idvaleur doit être unique à la machine. La façon la plus courante de procéder consiste à passer un pointeur sur le périphérique que structvotre pilote utilise pour gérer ce périphérique. Étant donné que ce pointeur doit se trouver dans l'espace mémoire de votre pilote pour être utile au pilote, il est ipso facto unique à ce pilote.²

    Si plusieurs pilotes sont enregistrés pour une interruption donnée, ils seront tous appelés lorsque l' un des périphériques déclenche cette ligne d'interruption partagée. Si ce n'est pas le périphérique de votre pilote qui a fait cela, le gestionnaire d'interruption de votre pilote recevra une dev_idvaleur qui ne lui appartient pas. Le gestionnaire d'interruption de votre chauffeur doit immédiatement revenir lorsque cela se produit.

    Un autre cas est que votre pilote gère plusieurs périphériques. Le gestionnaire d'interruption du pilote obtiendra l'une des dev_idvaleurs connues du pilote. Votre code est censé interroger chaque appareil pour savoir lequel a déclenché l'interruption.

    L'exemple Corbet et al. donner est celui d'un port parallèle PC. Lorsqu'il affirme la ligne d'interruption, il définit également le bit supérieur dans son premier registre de périphérique. (Autrement dit, inb(0x378) & 0x80 == trueen supposant une numérotation de port d'E / S standard.) Lorsque votre gestionnaire le détecte, il est censé faire son travail, puis effacer l'IRQ en écrivant la valeur lue du port d'E / S sur le port avec le haut peu effacé.

    Je ne vois aucune raison pour laquelle un mécanisme particulier est spécial. Un périphérique matériel différent pourrait choisir un mécanisme différent. La seule chose importante est que pour qu'un périphérique autorise les interruptions partagées, il doit avoir un moyen pour le pilote de lire l'état d'interruption du périphérique, et un moyen d' effacer l'interruption. Vous devrez lire la fiche technique ou le manuel de programmation de votre appareil pour savoir quel mécanisme utilise votre appareil particulier.

  • Lorsque votre gestionnaire d'interruption indique au noyau qu'il a géré l'interruption, cela n'empêche pas le noyau de continuer à appeler d'autres gestionnaires enregistrés pour cette même interruption. Cela est inévitable si vous devez partager une ligne d'interruption lorsque vous utilisez des interruptions déclenchées par le niveau.

    Imaginez deux appareils affirmant la même ligne d'interruption en même temps. (Ou du moins, si proche dans le temps que le noyau n'a pas le temps d'appeler un gestionnaire d'interruption pour effacer la ligne et ainsi voir la deuxième assertion comme distincte.) Le noyau doit appeler tous les gestionnaires de cette ligne d'interruption, pour donner à chaque une chance d'interroger son matériel associé pour voir s'il a besoin d'attention. Il est tout à fait possible pour deux pilotes différents de gérer avec succès une interruption au cours du même passage dans la liste des gestionnaires pour une interruption donnée.

    Pour cette raison, il est impératif que votre pilote indique au périphérique qu'il parvient à effacer son assertion d'interruption quelque temps avant le retour du gestionnaire d'interruption. Ce n'est pas clair pour moi ce qui se passe autrement. La ligne d'interruption affirmée en continu entraînera soit le noyau appelant en permanence les gestionnaires d'interruption partagés, soit elle masquera la capacité du noyau à voir de nouvelles interruptions afin que les gestionnaires ne soient jamais appelés. De toute façon, un désastre.


Notes de bas de page:

  1. J'ai spécifié PCI ci-dessus parce que tout ce qui précède suppose des interruptions déclenchées par le niveau , telles qu'utilisées dans la spécification PCI d'origine. ISA utilisait des interruptions déclenchées par les bords , ce qui rendait le partage délicat au mieux, et possible même alors uniquement s'il était pris en charge par le matériel. PCIe utilise des interruptions signalées par message ; le message d'interruption contient une valeur unique que le noyau peut utiliser pour éviter le jeu de devinettes à tour de rôle requis avec le partage d'interruption PCI. PCIe peut éliminer le besoin même de partage d'interruption. (Je ne sais pas si c'est le cas, juste qu'il a le potentiel de le faire.)

  2. Les pilotes du noyau Linux partagent tous le même espace mémoire, mais un pilote indépendant n'est pas censé contourner l'espace mémoire d'un autre. À moins que vous ne passiez ce pointeur, vous pouvez être sûr qu'un autre pilote ne parviendra pas par lui-même à la même valeur par accident.

Warren Young
la source
1
Comme vous l'avez mentionné, le gestionnaire d'interruptions peut être transmis à un dev_idqui ne lui appartient pas. Pour moi, il semble qu'il y ait une chance non nulle qu'un pilote qui ne possède pas la dev_idstructure puisse toujours la confondre avec la sienne en fonction de la façon dont il interprète le contenu. Si ce n'est pas le cas, quel mécanisme empêcherait cela?
bsirang
Vous pouvez l'empêcher en créant dev_idun pointeur sur quelque chose dans l'espace mémoire de votre pilote. Un autre pilote pourrait constituer une dev_idvaleur qui s'est avérée être confondue avec un pointeur sur la mémoire que possède votre pilote, mais cela ne se produira pas parce que tout le monde respecte les règles. Il s'agit de l'espace noyau, rappelez-vous: l'autodiscipline est supposée comme une évidence, contrairement au code de l'espace utilisateur, qui peut présumer allègrement que tout ce qui n'est pas interdit est autorisé.
Warren Young
Selon le chapitre dix de LDD3: "Chaque fois que deux pilotes ou plus partagent une ligne d'interruption et que le matériel interrompt le processeur sur cette ligne, le noyau invoque chaque gestionnaire enregistré pour cette interruption, en passant chacun son propre dev_id" Il semble que la compréhension précédente était incorrect quant à savoir si un gestionnaire d'interruption peut être passé dans un dev_idqu'il ne possède pas.
bsirang
C'était une mauvaise lecture de ma part. Quand j'ai écrit cela, je confondais deux concepts. J'ai édité ma réponse. La condition qui nécessite que votre gestionnaire d'interruptions revienne rapidement est qu'il est appelé en raison d'une assertion d'interruption par un périphérique qu'il ne gère pas. La valeur de dev_idne vous aide pas à déterminer si cela s'est produit. Vous devez demander au matériel, "Vous avez sonné?"
Warren Young
Oui, maintenant je dois comprendre comment ce que je bricole fait en sorte que les autres pilotes pensent que leurs périphériques "ont sonné" après un redémarrage du noyau via kexec.
bsirang
4

Lorsqu'un pilote demande une IRQ partagée, il transmet un pointeur au noyau vers une référence à une structure spécifique au périphérique dans l'espace mémoire du pilote.

Selon LDD3:

Chaque fois que deux pilotes ou plus partagent une ligne d'interruption et que le matériel interrompt le processeur sur cette ligne, le noyau appelle chaque gestionnaire enregistré pour cette interruption, en passant chacun son propre dev_id.

Lors de la vérification de plusieurs gestionnaires IRQ de pilotes, il apparaît qu'ils sondent le matériel lui-même afin de déterminer s'il doit gérer l'interruption ou le retour IRQ_NONE.

Exemples

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

Dans le code ci-dessus, le pilote lit le USBSTSregistre pour déterminer s'il y a une interruption de service.

Pilote SDHCI
  intmask = sdhci_readl(host, SDHCI_INT_STATUS);

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

Tout comme dans l'exemple précédent, le pilote vérifie un registre d'état SDHCI_INT_STATUSpour déterminer s'il doit réparer une interruption.

Pilote Ath5k
  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;

Encore un exemple.

bsirang
la source
0

Veuillez consulter ce lien :

C'est une pratique habituelle de déclencher les moitiés inférieures ou toute autre logique dans le gestionnaire IRQ uniquement après avoir vérifié l'état IRQ à partir d'un registre mappé en mémoire. Par conséquent, le problème est résolu par défaut par un bon programmeur.

Priyaranjan
la source
Le contenu de votre lien n'est pas disponible
user3405291