Wie geht der Linux-Kernel mit gemeinsam genutzten IRQs um?

14

Nach dem, was ich bisher gelesen habe: "Wenn der Kernel einen Interrupt empfängt, werden alle registrierten Handler aufgerufen."

Ich verstehe, dass die registrierten Handler für jeden IRQ über angezeigt werden können /proc/interrupts, und ich verstehe auch, dass die registrierten Handler von den Treibern stammen, die die request_irqÜbergabe eines Rückrufs in etwa in der Form aufgerufen haben :

irqreturn_t (*handler)(int, void *)

Basierend auf dem, was ich weiß, sollte jeder dieser Interrupt-Handler-Rückrufe, die dem bestimmten IRQ zugeordnet sind, aufgerufen werden, und es ist Sache des Handlers, zu bestimmen, ob der Interrupt tatsächlich von ihm behandelt werden soll. Wenn der Handler den bestimmten Interrupt nicht verarbeiten soll, muss er das Kernelmakro zurückgeben IRQ_NONE.

Ich habe Probleme zu verstehen, wie jeder Treiber bestimmen soll, ob er den Interrupt behandeln soll oder nicht. Ich nehme an, sie können intern den Überblick behalten, wenn sie einen Interrupt erwarten sollen. Wenn ja, weiß ich nicht, wie sie mit der Situation umgehen können, in der mehrere Treiber hinter demselben IRQ einen Interrupt erwarten.

Der Grund, warum ich versuche, diese Details zu verstehen, ist, dass ich mit dem kexecMechanismus herumspiele, um den Kernel mitten im Systembetrieb erneut auszuführen, während ich mit den Reset-Pins und verschiedenen Registern auf einer PCIe-Brücke sowie einer nachgeschalteten PCI spiele Gerät. Und dabei bekomme ich nach einem Neustart entweder Kernel-Panics oder andere Treiber, die sich beschweren, dass sie Interrupts empfangen, obwohl keine Operation stattgefunden hat.

Wie der Handler entschieden hat, dass der Interrupt von ihm behandelt werden soll, ist das Rätsel.

Bearbeiten: Falls relevant, handelt es sich um die betreffende CPU-Architektur x86.

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

Antworten:

14

Dies wird in Kapitel 10 von Linux Device Drivers , 3. Auflage, von Corbet et al. Es ist kostenlos online verfügbar , oder Sie werfen einige Schekel O'Reillys Weg für tote Baum- oder E-Book-Formulare. Der für Ihre Frage relevante Teil beginnt auf Seite 278 im ersten Link.

Für das, was es wert ist, ist hier mein Versuch, diese drei Seiten und andere Teile, die ich gegoogelt habe, zu paraphrasieren:

  • Wenn Sie einen gemeinsam genutzten IRQ-Handler registrieren, überprüft der Kernel Folgendes:

    ein. Für diesen Interrupt existiert kein anderer Handler

    b. Alle zuvor registrierten Benutzer forderten auch die gemeinsame Nutzung von Interrupts an

    In beiden Fällen wird überprüft, ob Ihr dev_idParameter eindeutig ist, damit der Kernel die mehreren Handler unterscheiden kann, z. B. beim Entfernen von Handlern.

  • Wenn ein PCI¹-Hardwaregerät die IRQ-Leitung auslöst, wird der Low-Level-Interrupt-Handler des Kernels aufgerufen, der wiederum alle registrierten Interrupt-Handler aufruft und die zurückgibt, über die dev_idSie den Handler registriert haben request_irq().

    Der dev_idWert muss für die Maschine eindeutig sein. Die übliche Vorgehensweise besteht darin, einen Zeiger an das Gerät zu übergeben, mit structdem der Treiber dieses Gerät verwaltet. Da sich dieser Zeiger im Speicher Ihres Fahrers befinden muss, damit er für den Fahrer nützlich ist, ist er ipso facto für diesen Fahrer einzigartig

    Wenn mehrere Treiber registriert für einen bestimmten Interrupt sind, werden sie alle aufgerufen werden , wenn jede der Geräte Raises , die Interrupt - Leitung geteilt. Wenn dies nicht das Gerät Ihres Fahrers war, wird dem Interrupt-Handler Ihres Fahrers ein dev_idWert übergeben, der nicht dazu gehört. In diesem Fall muss der Interrupt-Handler Ihres Fahrers unverzüglich zurückkehren.

    Ein weiterer Fall ist, dass Ihr Treiber mehrere Geräte verwaltet. Der Interrupt-Handler dev_iddes Fahrers erhält einen dem Fahrer bekannten Wert. Ihr Code soll jedes Gerät abfragen, um herauszufinden, welches den Interrupt ausgelöst hat.

    Das Beispiel Corbet et al. gib das von einem PC parallel port. Wenn es die Interruptleitung aktiviert, setzt es auch das oberste Bit in seinem ersten Geräteregister. (Unter der inb(0x378) & 0x80 == trueAnnahme einer Standard-E / A-Port-Nummerierung.) Wenn Ihr Handler dies erkennt, sollte er seine Arbeit erledigen. Löschen Sie dann den IRQ, indem Sie den vom E / A-Port gelesenen Wert an den Port mit der Spitze zurückschreiben ein bisschen gelöscht.

    Ich sehe keinen Grund dafür, dass ein bestimmter Mechanismus etwas Besonderes ist. Ein anderes Hardwaregerät könnte einen anderen Mechanismus wählen. Das einzig Wichtige ist, dass ein Gerät, das gemeinsame Interrupts zulässt, eine Möglichkeit haben muss, den Interrupt-Status des Geräts auszulesen und den Interrupt auf irgendeine Weise zu löschen . Sie müssen das Datenblatt oder Programmierhandbuch Ihres Geräts lesen, um herauszufinden, welchen Mechanismus Ihr bestimmtes Gerät verwendet.

  • Wenn Ihr Interrupt-Handler dem Kernel mitteilt, dass er den Interrupt verarbeitet hat, wird der Kernel nicht daran gehindert, weitere für denselben Interrupt registrierte Handler aufzurufen. Dies ist unvermeidlich, wenn Sie eine Interrupt-Leitung gemeinsam nutzen, wenn Sie pegelgetriggerte Interrupts verwenden.

    Stellen Sie sich zwei Geräte vor, die zur gleichen Zeit dieselbe Interrupt-Leitung aktivieren. (Oder zumindest so zeitnah, dass der Kernel keine Zeit hat, einen Interrupt-Handler aufzurufen, um die Leitung zu löschen, und dadurch die zweite Behauptung als getrennt ansieht.) Der Kernel muss alle Handler für diese Interrupt-Leitung aufrufen, um jede zu geben eine Möglichkeit, die zugehörige Hardware abzufragen, um festzustellen, ob Aufmerksamkeit erforderlich ist. Es ist durchaus möglich, dass zwei verschiedene Treiber einen Interrupt innerhalb desselben Durchlaufs durch die Handlerliste für einen bestimmten Interrupt erfolgreich verarbeiten.

    Aus diesem Grund muss Ihr Treiber dem Gerät, das es verwaltet, unbedingt mitteilen, dass die Interrupt-Bestätigung gelöscht werden soll, bevor der Interrupt-Handler zurückkehrt. Mir ist nicht klar, was sonst passiert. Die ständig aktivierte Interrupt-Zeile führt entweder dazu, dass der Kernel die gemeinsam genutzten Interrupt-Handler ständig aufruft, oder sie maskiert die Fähigkeit des Kernels, neue Interrupts zu sehen, so dass die Handler niemals aufgerufen werden. So oder so, Katastrophe.


Fußnoten:

  1. Ich habe PCI oben angegeben, da alle oben genannten Punkte pegelgetriggerte Interrupts voraussetzen , wie sie in der ursprünglichen PCI-Spezifikation verwendet wurden. ISA verwendete flankengetriggerte Interrupts, die das Teilen im besten Fall schwierig machten und selbst dann möglich waren, wenn sie von der Hardware unterstützt wurden. PCIe verwendet nachrichtengesteuerte Interrupts. Die Interrupt-Nachricht enthält einen eindeutigen Wert, den der Kernel verwenden kann, um das Round-Robin-Ratespiel zu vermeiden, das für die gemeinsame Nutzung von PCI-Interrupts erforderlich ist. Durch PCIe kann die Notwendigkeit einer Unterbrechungsfreigabe entfallen. (Ich weiß nicht, ob es tatsächlich funktioniert, nur dass es das Potenzial dazu hat.)

  2. Linux-Kerneltreiber teilen sich alle den gleichen Speicherplatz, aber ein nicht verwandter Treiber sollte nicht im Speicherplatz eines anderen herumspielen. Wenn Sie diesen Zeiger nicht herumreichen, können Sie ziemlich sicher sein, dass ein anderer Treiber diesen Wert nicht versehentlich von sich aus findet.

Warren Young
quelle
1
Wie Sie bereits erwähnt haben, wird der Interrupt-Handler möglicherweise an einen anderen übergeben dev_id, als er besitzt. Meiner Meinung nach besteht die Möglichkeit, dass ein Treiber, der die dev_idStruktur nicht besitzt , sie immer noch als seine eigene verwechselt, je nachdem, wie sie den Inhalt interpretiert. Wenn dies nicht der Fall ist, welcher Mechanismus würde dies verhindern?
bsirang
Sie verhindern dies, indem Sie dev_ideinen Zeiger auf etwas im Speicher Ihres Fahrers setzen. Ein anderer Treiber könnte einen dev_idWert ausmachen , der zufällig mit einem Zeiger auf den Speicher Ihres Treibers verwechselt werden kann, aber das wird nicht passieren, weil jeder nach den Regeln spielt. Dies ist Kernel-Space, denken Sie daran: Selbstdisziplin wird selbstverständlich vorausgesetzt, im Gegensatz zu User-Space-Code, der leichtfertig davon ausgehen kann, dass alles erlaubt ist, was nicht verboten ist.
Warren Young
Gemäß Kapitel zehn LDD3: „Jedes Mal , wenn zwei oder mehr Autofahrer auf dieser Linie eine Interrupt - Leitung und die Hardware - Interrupts der Prozessor teilen, ruft der Kernel jeden Handler für diesen Interrupt registriert, wobei jeder seine eigene dev_id passing“ Es scheint , wie die bisherigen Verständnis falsch war, ob ein Interrupt-Handler in einem übergeben werden darf dev_id, der ihm nicht gehört.
bsirang
Das war meinerseits eine Fehlinterpretation. Als ich das schrieb, verschmolz ich zwei Konzepte. Ich habe meine Antwort bearbeitet. Die Bedingung, die eine schnelle Rückkehr Ihres Interrupt-Handlers erfordert, besteht darin, dass er aufgrund einer Interrupt-Bestätigung durch ein nicht verwaltetes Gerät aufgerufen wird. Der Wert von dev_idhilft Ihnen nicht festzustellen, ob dies geschehen ist. Sie müssen die Hardware fragen: "Sie haben angerufen?"
Warren Young
Ja, jetzt muss ich herausfinden, wie das, was ich bastele, andere Treiber dazu veranlasst, zu glauben, dass ihre Geräte nach einem Neustart des Kernels über "klingelten" kexec.
bsirang
4

Wenn ein Treiber einen gemeinsam genutzten IRQ anfordert, übergibt er einen Zeiger an den Kernel, der auf eine gerätespezifische Struktur im Speicherbereich des Treibers verweist.

Nach LDD3:

Wenn zwei oder mehr Treiber eine Interrupt-Leitung gemeinsam nutzen und die Hardware den Prozessor in dieser Leitung unterbricht, ruft der Kernel jeden für diesen Interrupt registrierten Handler auf und übergibt jedem seine eigene dev_id.

Bei der Überprüfung der IRQ-Handler mehrerer Treiber scheint es, dass sie die Hardware selbst prüfen, um zu bestimmen, ob sie den Interrupt oder die Rückgabe behandeln soll oder nicht IRQ_NONE.

Beispiele

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

Im obigen Code liest der Fahrer das USBSTSRegister, um festzustellen, ob eine Unterbrechung für die Wartung vorliegt .

SDHCI-Treiber
  intmask = sdhci_readl(host, SDHCI_INT_STATUS);

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

Wie im vorherigen Beispiel überprüft der Treiber ein Statusregister, SDHCI_INT_STATUSum festzustellen, ob ein Interrupt bedient werden muss.

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

Nur noch ein Beispiel.

bsirang
quelle
0

Bitte besuchen Sie diesen Link :

Es ist eine übliche Praxis, Bottom-Halves oder eine andere Logik im IRQ-Handler erst dann auszulösen, wenn der IRQ-Status aus einem Speicherregister überprüft wurde. Daher wird das Problem standardmäßig von einem guten Programmierer gelöst.

Priyaranjan
quelle
Der Inhalt Ihres Links ist nicht verfügbar
user3405291