¿Cómo maneja el kernel de Linux las IRQ compartidas?

14

Según lo que he leído hasta ahora, "cuando el núcleo recibe una interrupción, se invocan todos los controladores registrados".

Entiendo que los controladores registrados para cada IRQ se pueden ver a través de /proc/interrupts, y también entiendo que los controladores registrados provienen de los controladores que han invocado request_irqpasar una devolución de llamada aproximadamente del formulario:

irqreturn_t (*handler)(int, void *)

En base a lo que sé, cada una de estas devoluciones de llamada del controlador de interrupciones asociadas con la IRQ particular debe invocarse, y depende del controlador determinar si la interrupción debe ser manejada por ella. Si el controlador no debe manejar la interrupción particular, debe devolver la macro del núcleo IRQ_NONE.

Lo que tengo problemas para entender es cómo se espera que cada controlador determine si debe manejar la interrupción o no. Supongo que pueden realizar un seguimiento interno si se supone que esperan una interrupción. Si es así, no sé cómo podrían lidiar con la situación en la que varios conductores detrás del mismo IRQ esperan una interrupción.

La razón por la que estoy tratando de entender estos detalles es porque estoy jugando con el kexecmecanismo para volver a ejecutar el kernel en medio de la operación del sistema mientras juego con los pines de reinicio y varios registros en un puente PCIe, así como un PCI descendente dispositivo. Y al hacerlo, después de un reinicio, recibo pánico en el kernel u otros controladores quejándose de que están recibiendo interrupciones a pesar de que no se realizó ninguna operación.

Cómo el manejador decidió que la interrupción debería ser manejada por él es el misterio.

Editar: en caso de que sea relevante, la arquitectura de la CPU en cuestión es x86.

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

Respuestas:

14

Esto está cubierto en el capítulo 10 de Controladores de dispositivos Linux , 3a edición, por Corbet et al. Está disponible de forma gratuita en línea , o puede arrojar algunos shekels O'Reilly para árboles muertos o formularios de libros electrónicos. La parte relevante a su pregunta comienza en la página 278 en el primer enlace.

Para lo que vale, aquí está mi intento de parafrasear esas tres páginas, más otros bits que he buscado en Google:

  • Cuando registra un controlador IRQ compartido, el núcleo verifica que:

    a. no existe otro controlador para esa interrupción, o

    si. todos los previamente registrados también solicitaron compartir interrupciones

    Si se aplica cualquiera de los casos, verifica que su dev_idparámetro sea único, de modo que el núcleo pueda diferenciar los múltiples controladores, por ejemplo, durante la eliminación del controlador.

  • Cuando un dispositivo de hardware PCI¹ eleva la línea IRQ, se llama al manejador de interrupciones de bajo nivel del núcleo y, a su vez, llama a todos los manejadores de interrupciones registrados, pasando cada uno de los dev_idque utilizó para registrar el manejador request_irq().

    El dev_idvalor debe ser exclusivo de la máquina. La forma común de hacerlo es pasar un puntero al dispositivo structque usa su controlador para administrar ese dispositivo. Dado que este puntero debe estar dentro del espacio de memoria de su controlador para que sea útil para el controlador, es ipso facto exclusivo de ese controlador.²

    Si hay varios controladores registrados para una interrupción determinada, todos se llamarán cuando cualquiera de los dispositivos eleve esa línea de interrupción compartida. Si no fue el dispositivo de su conductor el que hizo esto, el controlador de interrupción de su controlador recibirá un dev_idvalor que no le pertenece. El controlador de interrupciones de su conductor debe regresar inmediatamente cuando esto suceda.

    Otro caso es que su controlador está administrando múltiples dispositivos. El controlador de interrupciones del controlador obtendrá uno de los dev_idvalores conocidos por el controlador. Se supone que su código sondea cada dispositivo para averiguar cuál provocó la interrupción.

    El ejemplo Corbet et al. dar es el de un puerto paralelo de PC. Cuando afirma la línea de interrupción, también establece el bit superior en su primer registro de dispositivo. (Es decir, inb(0x378) & 0x80 == truesuponiendo una numeración de puerto de E / S estándar). Cuando su controlador detecta esto, se supone que debe hacer su trabajo, luego borre la IRQ escribiendo el valor leído desde el puerto de E / S de nuevo al puerto con la parte superior poco despejado

    No veo ninguna razón para que ese mecanismo en particular sea especial. Un dispositivo de hardware diferente podría elegir un mecanismo diferente. Lo único importante es que para que un dispositivo permita interrupciones compartidas, tiene que tener alguna forma para que el controlador lea el estado de la interrupción del dispositivo y alguna forma de borrar la interrupción. Tendrá que leer la hoja de datos de su dispositivo o el manual de programación para averiguar qué mecanismo utiliza su dispositivo en particular.

  • Cuando su controlador de interrupciones le dice al núcleo que manejó la interrupción, eso no impide que el núcleo continúe llamando a otros controladores registrados para esa misma interrupción. Esto es inevitable si va a compartir una línea de interrupción cuando usa interrupciones activadas por nivel.

    Imagine que dos dispositivos afirman la misma línea de interrupción al mismo tiempo. (O al menos, tan cerca en el tiempo que el núcleo no tiene tiempo para llamar a un controlador de interrupciones para borrar la línea y, por lo tanto, ver la segunda afirmación como separada). El núcleo debe llamar a todos los controladores para esa línea de interrupción, para dar a cada uno una oportunidad de consultar su hardware asociado para ver si necesita atención. Es muy posible que dos controladores diferentes manejen con éxito una interrupción dentro del mismo paso a través de la lista de controladores para una interrupción dada.

    Debido a esto, es imperativo que su controlador le informe al dispositivo que está logrando borrar su afirmación de interrupción en algún momento antes de que regrese el controlador de interrupción. No me queda claro qué sucede de otra manera. La línea de interrupción afirmada continuamente dará como resultado que el kernel llame continuamente a los manejadores de interrupción compartidos, o enmascarará la capacidad del kernel para ver nuevas interrupciones para que los manejadores nunca sean llamados. De cualquier manera, desastre.


Notas al pie:

  1. Especifiqué PCI arriba porque todo lo anterior supone interrupciones disparadas por nivel , como se usa en la especificación PCI original. ISA usó interrupciones activadas por el borde , lo que hizo que el intercambio fuera complicado en el mejor de los casos, y posible incluso cuando solo es compatible con el hardware. PCIe utiliza interrupciones señalizadas por mensaje ; el mensaje de interrupción contiene un valor único que el núcleo puede usar para evitar el juego de adivinanzas round-robin requerido con el uso compartido de interrupciones PCI. PCIe puede eliminar la necesidad de compartir interrupciones. (No sé si realmente lo hace, solo que tiene el potencial de hacerlo).

  2. Todos los controladores del kernel de Linux comparten el mismo espacio de memoria, pero se supone que un controlador no relacionado debe estar en el espacio de memoria de otro. A menos que pase ese puntero, puede estar bastante seguro de que otro controlador no obtendrá ese mismo valor accidentalmente por sí solo.

Warren Young
fuente
1
Como mencionó, el controlador de interrupciones puede pasarse de una forma dev_idque no es de su propiedad. Para mí, parece que hay una posibilidad distinta de cero de que un controlador que no posee la dev_idestructura aún pueda confundirla como propia en función de cómo interpreta el contenido. Si este no es el caso, ¿qué mecanismo evitaría esto?
bsirang
Lo evita haciendo dev_idun puntero a algo dentro del espacio de memoria de su controlador. Otro controlador podría inventar un dev_idvalor que resultó ser confuso con un puntero a la memoria que posee su controlador, pero eso no sucederá porque todos cumplen las reglas. Esto es espacio de kernel, recuerde: la autodisciplina se asume como algo natural, a diferencia del código de espacio de usuario, que puede asumir alegremente que todo lo que no está prohibido está permitido.
Warren Young
De acuerdo con el capítulo diez de LDD3: "Cada vez que dos o más controladores comparten una línea de interrupción y el hardware interrumpe el procesador en esa línea, el núcleo invoca a cada controlador registrado para esa interrupción, pasando cada uno su propio dev_id" Parece que la comprensión anterior era incorrecto con respecto a si se puede pasar un controlador de interrupciones en un dev_iddispositivo que no es de su propiedad.
bsirang
Esa fue una mala lectura de mi parte. Cuando escribí eso, estaba combinando dos conceptos. He editado mi respuesta. La condición que requiere que su controlador de interrupciones regrese rápidamente es que recibe una llamada debido a una afirmación de interrupción por parte de un dispositivo que no está administrando. El valor de dev_idno te ayuda a determinar si esto ha sucedido. Tienes que preguntarle al hardware: "¿Llamaste?"
Warren Young
Sí, ahora necesito descubrir cómo lo que estoy jugando está haciendo que otros controladores crean que sus dispositivos "sonaron" después de un reinicio del kernel a través de kexec.
bsirang
4

Cuando un controlador solicita una IRQ compartida, pasa un puntero al núcleo para hacer referencia a una estructura específica del dispositivo dentro del espacio de memoria del controlador.

De acuerdo con LDD3:

Cada vez que dos o más controladores comparten una línea de interrupción y el hardware interrumpe el procesador en esa línea, el núcleo invoca a cada controlador registrado para esa interrupción, pasando cada uno su propio dev_id.

Al verificar los controladores IRQ de varios controladores, parece que prueban el hardware en sí para determinar si debe manejar o no la interrupción o el retorno IRQ_NONE.

Ejemplos

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

En el código anterior, el controlador está leyendo el USBSTSregistro para determinar si hay una interrupción al servicio.

Controlador SDHCI
  intmask = sdhci_readl(host, SDHCI_INT_STATUS);

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

Al igual que en el ejemplo anterior, el controlador está verificando un registro de estado, SDHCI_INT_STATUSpara determinar si necesita dar servicio a una interrupción.

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;

Solo un ejemplo más.

bsirang
fuente
0

Por favor visite ver este enlace :

Es una práctica habitual activar mitades inferiores o cualquier otra lógica en el controlador IRQ solo después de verificar el estado de IRQ desde un registro mapeado en memoria. Por lo tanto, el problema es resuelto por defecto por un buen programador.

Priyaranjan
fuente
El contenido de su enlace no está disponible
usuario3405291