In che modo il kernel Linux gestisce gli IRQ condivisi?


14

Secondo quanto ho letto finora, "quando il kernel riceve un interrupt, vengono invocati tutti i gestori registrati".

Comprendo che i gestori registrati per ciascun IRQ possono essere visualizzati tramite /proc/interruptse capisco anche che i gestori registrati provengono dai driver che hanno invocato il request_irqpassaggio in una richiamata all'incirca del modulo:

irqreturn_t (*handler)(int, void *)

Sulla base di ciò che so, ciascuno di questi callback del gestore di interrupt associato al particolare IRQ dovrebbe essere invocato, e spetta al gestore determinare se l'interrupt debba effettivamente essere gestito da esso. Se il gestore non deve gestire l'interrupt particolare, deve restituire la macro del kernel IRQ_NONE.

Quello che ho difficoltà a capire è come ci si aspetta che ciascun driver determini se deve gestire l'interrupt o meno. Suppongo che possano tenere traccia internamente se dovrebbero aspettarsi un interrupt. In tal caso, non so come sarebbero in grado di affrontare la situazione in cui più driver dietro lo stesso IRQ si aspettano un interrupt.

Il motivo per cui sto cercando di capire questi dettagli è perché sto facendo confusione con il kexecmeccanismo per rieseguire il kernel nel mezzo del funzionamento del sistema mentre sto giocando con i pin di reset e vari registri su un bridge PCIe e un PCI downstream dispositivo. E nel fare ciò, dopo un riavvio, ricevo il panico del kernel o altri driver che si lamentano del fatto che stanno ricevendo interruzioni anche se non è stata eseguita alcuna operazione.

Come il gestore ha deciso che l'interrupt dovrebbe essere gestito da esso è il mistero.

Modifica: nel caso sia rilevante, l'architettura della CPU in questione è x86.


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

Risposte:


14

Questo è trattato nel capitolo 10 di Linux Device Driver , 3a edizione, di Corbet et al. È disponibile gratuitamente online , oppure puoi lanciare alcuni sicli di O'Reilly per le forme di albero morto o ebook. La parte pertinente alla tua domanda inizia a pagina 278 nel primo collegamento.

Per quello che vale, ecco il mio tentativo di parafrasare quelle tre pagine, oltre ad altre parti che ho cercato su Google:

  • Quando registri un gestore IRQ condiviso, il kernel verifica che:

    un. nessun altro gestore esiste per quell'interruzione, o

    b. anche tutti quelli precedentemente registrati hanno richiesto la condivisione degli interrupt

    Se si applica uno dei due casi, verifica che il dev_idparametro sia univoco, in modo che il kernel possa differenziare più gestori, ad esempio durante la rimozione dei gestori.

  • Quando un dispositivo hardware PCI¹ solleva la linea IRQ, viene chiamato il gestore di interrupt di basso livello del kernel, che a sua volta chiama tutti i gestori di interrupt registrati, passando ciascuno dietro il quale dev_idè stato utilizzato per registrare il gestore request_irq().

    Il dev_idvalore deve essere unico per la macchina. Il modo comune per farlo è passare un puntatore al dispositivo structutilizzato dal driver per gestirlo. Poiché questo puntatore deve trovarsi nello spazio di memoria del driver per essere utile al driver, è ipso facto unico per quel driver.

    Se sono presenti più driver registrati per un determinato interrupt, verranno tutti chiamati quando uno dei dispositivi solleva quella linea di interrupt condivisa. Se non è stato il dispositivo del tuo conducente dev_ida farlo, al gestore di interrupt del tuo conducente verrà passato un valore che non gli appartiene. Il gestore di interrupt del conducente deve restituire immediatamente quando ciò accade.

    Un altro caso è che il tuo driver sta gestendo più dispositivi. Il gestore di interrupt del driver otterrà uno dei dev_idvalori noti al driver. Il tuo codice dovrebbe eseguire il polling di ciascun dispositivo per scoprire quale ha generato l'interrupt.

    L'esempio Corbet et al. dare è quello di una porta parallela per PC. Quando asserisce la linea di interruzione, imposta anche il bit superiore nel suo primo registro del dispositivo. (Cioè inb(0x378) & 0x80 == true, supponendo la numerazione delle porte I / O standard.) Quando il gestore lo rileva, si suppone che faccia il suo lavoro, quindi cancella l'IRQ scrivendo il valore letto dalla porta I / O alla porta con la parte superiore po 'cancellato.

    Non vedo alcun motivo per cui quel particolare meccanismo sia speciale. Un dispositivo hardware diverso potrebbe scegliere un meccanismo diverso. L'unica cosa importante è che affinché un dispositivo consenta interruzioni condivise, deve avere un modo per consentire al conducente di leggere lo stato di interruzione del dispositivo e un modo per cancellare l'interruzione. Dovrai leggere la scheda tecnica del tuo dispositivo o il manuale di programmazione per scoprire quale meccanismo utilizza il tuo dispositivo specifico.

  • Quando il gestore di interrupt dice al kernel che ha gestito l'interrupt, ciò non impedisce al kernel di continuare a chiamare altri gestori registrati per quello stesso interrupt. Ciò è inevitabile se si desidera condividere una linea di interrupt quando si utilizzano interrupt attivati ​​dal livello.

    Immagina che due dispositivi affermino la stessa linea di interruzione allo stesso tempo. (O almeno, così vicino nel tempo che il kernel non ha il tempo di chiamare un gestore di interrupt per cancellare la linea e quindi vedere la seconda asserzione come separata.) Il kernel deve chiamare tutti i gestori per quella linea di interruzione, per dare a ciascuno una possibilità di interrogare il suo hardware associato per vedere se ha bisogno di attenzione. È possibile che due driver diversi gestiscano correttamente un interrupt all'interno dello stesso passaggio nell'elenco dei gestori per un determinato interrupt.

    Per questo motivo, è indispensabile che il driver comunichi al dispositivo che sta riuscendo a cancellare la sua asserzione di interruzione prima che il gestore di interruzioni ritorni. Non mi è chiaro cosa accada altrimenti. La linea di interrupt asserita continuamente determinerà che il kernel chiama continuamente i gestori di interrupt condivisi, oppure maschererà la capacità del kernel di vedere nuovi interrupt in modo che i gestori non vengano mai chiamati. Ad ogni modo, disastro.


Note:

  1. Ho specificato PCI sopra perché tutto quanto sopra presuppone interruzioni attivate dal livello , come usato nelle specifiche PCI originali. ISA utilizzava interrupt trigger-edge , il che rendeva la condivisione nella migliore delle ipotesi complicata e possibile anche solo quando supportata dall'hardware. PCIe utilizza interrupt segnalati da messaggi ; il messaggio di interruzione contiene un valore univoco che il kernel può usare per evitare il gioco di indovinazioni round robin richiesto con la condivisione di interruzioni PCI. PCIe può eliminare la necessità di una condivisione degli interrupt. (Non so se lo fa davvero, solo che ha il potenziale per.)

  2. I driver del kernel Linux condividono tutti lo stesso spazio di memoria, ma non si suppone che un driver non correlato si stia muovendo nello spazio di memoria di un altro. A meno che non si passi quel puntatore, si può essere abbastanza sicuri che un altro driver non produrrà accidentalmente lo stesso valore da solo.


1
Come hai già detto, al gestore di interrupt può essere passato un valore di dev_idcui non è proprietario. A me sembra che ci sia una possibilità diversa da zero che un driver che non possiede la dev_idstruttura possa ancora confonderla come propria in base a come interpreta il contenuto. Se così non fosse, quale meccanismo impedirebbe questo?
bsirang,

Lo impedisci facendo dev_idun puntatore a qualcosa nello spazio di memoria del tuo driver. Un altro pilota potrebbe costituire un dev_idvalore che è accaduto di essere confondibile con un puntatore alla memoria il driver possiede, ma questo non succederà, perché ognuno sta giocando secondo le regole. Questo è lo spazio del kernel, ricorda: l'autodisciplina è assunta come una cosa ovvia, a differenza del codice dello spazio utente, che può presumibilmente supporre che sia permesso qualsiasi cosa non proibita.
Warren Young,

Secondo il capitolo dieci di LDD3: "Ogni volta che due o più driver condividono una linea di interruzione e l'hardware interrompe il processore su quella linea, il kernel invoca ogni gestore registrato per tale interruzione, passando ciascuno il proprio dev_id" Sembra la comprensione precedente era errato riguardo al fatto che un gestore di interrupt potesse essere passato in un di dev_idcui non era proprietario.
bsirang,

È stata una lettura errata da parte mia. Quando l'ho scritto, stavo fondendo due concetti. Ho modificato la mia risposta. La condizione che richiede al gestore di interrupt di tornare rapidamente è che viene chiamato a causa di un'asserzione di interruzione da parte di un dispositivo che non gestisce. Il valore di dev_idnon ti aiuta a determinare se questo è successo. Devi chiedere all'hardware "Hai suonato?"
Warren Young,

Sì, ora ho bisogno di capire come ciò con cui sto armeggiando in realtà induce altri driver a credere che i loro dispositivi "suonino" dopo un riavvio del kernel tramite kexec.
bsirang,

4

Quando un driver richiede un IRQ condiviso, passa un puntatore al kernel a un riferimento a una struttura specifica del dispositivo all'interno dello spazio di memoria del driver.

Secondo LDD3:

Ogni volta che due o più driver condividono una linea di interruzione e l'hardware interrompe il processore su quella linea, il kernel invoca tutti i gestori registrati per quella interruzione, passando ciascuno il proprio dev_id.

Dopo aver controllato diversi gestori IRQ dei driver, sembra che sondino l'hardware stesso per determinare se deve gestire o meno l'interrupt o il return IRQ_NONE.

Esempi

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

Nel codice sopra, il driver sta leggendo il USBSTSregistro per determinare se c'è un interrupt al servizio.

Driver SDHCI
  intmask = sdhci_readl(host, SDHCI_INT_STATUS);

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

Proprio come nell'esempio precedente, il driver sta controllando un registro di stato, SDHCI_INT_STATUSper determinare se deve servire un interrupt.

Driver 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;

Solo un altro esempio.


0

Si prega di visitare controllare questo link :

È una pratica abituale innescare le metà inferiori o qualsiasi altra logica nel gestore IRQ solo dopo aver verificato lo stato IRQ da un registro mappato in memoria. Quindi il problema è risolto di default da un buon programmatore.


Il contenuto del tuo link non è disponibile
user3405291
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.