¡Descarga Hadware material de estudio y más Apuntes en PDF de Procesamiento de Señales Digitales solo en Docsity!
LECCIÓN 3: INTERRUPCIONES HARDWARE
- LECCIÓN 3: INTERRUPCIONES HARDWARE
- 3.1 Introducción.............................................................................................................
- Programables (arquitectura i386). 3.2 Sistema de Interrupciones basados en Controladores de Interrupciones
- 3.3 Interrupciones hardware en Linux...........................................................................
- 3.3.1 Estructuras de datos para soportar el sistema de interrupciones hardware
- irqaction
- irq_desc....................................................................................................................................................
- Tabla descriptora de interrupciones idt_table.......................................................................................
- 3.4 Procesado de interrupciones
- 3.5 Procedimientos de Inicialización
- init_IRQ()................................................................................................................................................
- request_irq.............................................................................................................................................
- setup_irq................................................................................................................................................
- free_irq...................................................................................................................................................
LECCIÓN 3: INTERRUPCIONES HARDWARE
3.1 Introducción
Las interrupciones hardware son producidas por varias fuentes, por ejemplo del
teclado, cada vez que se presiona una tecla y se suelta se genera una interrupción.
Otras interrupciones son originadas por el reloj, la impresora, el puerto serie, el
disco, etcétera.
Una interrupción de tipo hardware es una señal eléctrica producida por un dispositivo
físico del ordenador. Esta señal informa a la CPU que el dispositivo requiere su
atención. La CPU parará el proceso que está ejecutando para atender la
interrupción. Cuando la interrupción termina, la CPU reanuda la ejecución en donde
fue interrumpida, pudiendo ejecutar el proceso parado originalmente o bien otro
proceso.
Existe un hardware específico, para que los dispositivos puedan interrumpir lo que
está haciendo la CPU. La propia CPU, tiene entradas específicas para ser
interrumpida INT, cuando se activa esta entrada INT, la CPU para lo que está
haciendo y activa la salida para reconocer la interrupción INTA, y comienza a
ejecutar el código especial que maneja la interrupción. Algunas CPU´s disponen de
un conjunto especial de registros, que solo son utilizados en el modo de ejecución
de interrupciones, lo que facilita el trabajo de tratar las interrupciones.
La placa base del computador utiliza un controlador para decodificar las
interrupciones que no son mas que señales eléctricas producidas por los
dispositivos, coloca en el bus de datos información de que dispositivo interrumpió y
activa la entrada INT de interrupción de la CPU. Este chip controlador protege a la
CPU y la aísla de los dispositivos que interrumpen, además de proporcionar
flexibilidad al diseño del sistema. El controlador de interrupciones tiene un registro
de estado para permitir o inhibir las interrupciones en el sistema.
3.2 Sistema de Interrupciones basados en Controladores de
Interrupciones Programables (arquitectura i386).
Existe diverso hardware para implementar un controlador de interrupciones, los
computadores IBM PC o compatibles, utilizan el controlador de interrupciones
programable de Intel 82C59A-2 Cmos o sus chips compatibles. Este controlador ha
sido utilizado desde los comienzos del IBM PC, y es bien conocido el espacio de
direccionamiento de sus registros en la arquitectura ISA. Incluso en chips más
modernos se ha mantenido la misma localización.
Los IRQ o i nterrupt r e q uest (Pedido de Interrupción), son las notificaciones de las
interrupciones enviadas desde los dispositivos hardware a la CPU, en respuesta a la
IRQ, la CPU salta a una dirección – una r utina de s ervicio de i nterrupción (ISR),
comúnmente llamada Interrupt handler (Manejador de interrupciones) - Que se
encuentra como una función dentro del software manejador de ese dispositivo
formando parte del núcleo. Así, una función manejadora de interrupciones es una
función del núcleo que ejecuta el servicio de esa interrupción.
Los IRQ se encuentran numerados, y cada dispositivo hardware se encuentra
asociado a un número IRQ. En la arquitectura IBM PC y compatibles, por ejemplo,
IRQ 0 se encuentra asociado al reloj o temporizador, el cual genera 100
interrupciones por segundo, disquete el 6, los discos IDE la 14 y 15. Se puede
compartir un IRQ entre varios dispositivos.
La siguiente figura, muestra las interrupciones hardware y su correspondiente
puerto en el Controlador Programable de Interrupciones (PIC). No se deben
confundir los números IRQ entradas al controlador con los números de la
interrupción que son las entradas en la tabla de interrupciones. Los PIC se pueden
programar para generar diversos números de interrupción para cada IRQ.
Los Controladores también controlan la prioridad de las interrupciones. Por ejemplo,
el reloj (en IRQ 0) tiene una prioridad más alta que el teclado (IRQ 1). Si el
procesador está atendiendo una interrupción del reloj, el PIC no generará una
interrupción para el teclado hasta que ISR del reloj reajusta el PIC. Por otra parte, el
reloj puede interrumpir ISR del teclado. El PICs se puede programar para utilizar una
variedad de esquemas de la prioridad, pero no se suele hacer esto.
Se debe de tener en cuenta que el IRQ 2 del primer PIC, valida o invalida las
entradas del Segundo PIC (8 a 15).
Algunas interrupciones son fijadas por convenio en la configuración del PC, así es
que los manejadores de los dispositivos solicitan simplemente la interrupción cuando
se inicializan. Por ejemplo esto es lo que lo hace el manejador de disquete, solicita
siempre la IRQ 6.
Interrupción IRQ Descripción
00H - división por cero o desbordamiento
02H - NMI (interrupción no-enmascarable)
04H - desbordamiento (EN)
08H 0 Temporizador del sistema
09H 1 Teclado
0AH 2 Interrupción del segundo PIC
0BH 3 COM
0CH 4 COM
0DH 5 LPT
0EH 6 disquete
0FH 7 LPT
70H 8 Reloj
71H 9 I/o general
72H 10 I/o general
73H 11 I/o general
74H 12 I/o general
75H 13 Coprocesador
76H 14 Disco duro
77H 15 I/o general
3.3 Interrupciones hardware en Linux.
Una de las principales tareas del sistema de manejo de interrupciones es llevar las
diferentes interrupciones a los códigos de manejo de esas interrupciones.
Cuando se activa el contacto 6 del controlador de interrupciones, se debe reconocer
cual es la interrupción asociada a ese contacto, por ejemplo el controlador del
dispositivo disquete, por lo tanto el sistema de manejo de interrupciones debe
encaminar a la rutina que trata esta interrupción, para ello Linux proporciona un
conjunto de estructuras de datos y tablas, y un conjunto de funciones que las
inicializan y las manejan.
El sistema de interrupciones es muy dependiente de la arquitectura, Linux en la
medida de lo posible, tratará de que sea independiente de la máquina en la que
reside el sistema, para ello el sistema de interrupciones se va a implementar
mediante una serie de estructuras de datos y funciones en lenguaje C que
facilitarán la portabilidad. Veamos una presentación de las estructuras de datos
implicadas, resaltando las más importantes y describiendo los campos de cada una.
Posterior a eso se seguirá con la inicialización de estas estructuras que soportan las
interrupciones, esta parte es fuertemente dependiente de la arquitectura, nos
basaremos en i386. Finalmente se expondrá el flujo dentro del núcleo que sigue una
interrupción hardware, desde que se origina en el dispositivo hardware hasta que se
atiende por el manejador de la interrupción.
3.3.1 Estructuras de datos para soportar el sistema de interrupciones hardware
Estudiaremos las estructuras de datos del sistema de interrupciones:
irqaction almacena la dirección de la función de manejo de interrupciones.
irq_chip contiene las funciones que manejan un controlador de interrupciones
particular, es dependiente de la arquitectura.
irq_desc vector con una entrada para cada una de las interrupciones que pueden
ser atendidas.
Estructuras de datos para el sistema de interrupciones
SA_INTERRUPT Indica que esta interrupción puede ser interrumpida por
otra.
SA_SAMPLE_RANDOM Esta interrupción puede ser considerada de
naturaleza aleatoria, esto puede ser útil cuando se necesita una semilla de
aleatoriedad, etc.
SA_SHIRQ Esta IRQ puede ser compartida por diferentes struct irqaction
mask Este campo no se usa en la arquitectura i
name Un nombre asociado con el dispositivo que genera la interrupción. Como una
misma IRQ puede ser compartida por diferentes dispositivos, esto nos puede ayudar
a distinguir los dispositivos. Se puede leer en el fichero /proc/interrupts.
dev_id Todos y cada uno de los dispositivos implementados en Linux tiene un
número identificador único grabado por el fabricante. De esta forma, este campo
define qué dispositivo genera la interrupción. Estos identificadores se encuentran
definidos en ficheros cabeceras.
#define PCI_DEVICE_ID_S3_868 0x
#define PCI_DEVICE_ID_S3_928 0x88b
next Este campo contiene un puntero que apunta a la próxima struct irqaction en
la cola, esto sólo tiene sentido cuando la IRQ se encuentra compartida, así pues, en
la mayoría de los casos estará a NULL.
irq Numero de la línea IRQ.
dir Indica un puntero al descriptor del directorio /proc/irq/n asociado con la irq n.
irq_chip
Es una estructuras dependiente de la arquitectura, para Intel está definida en el
fichero include/linux/irq.h
su código es:
/* Describe el decodificador de interrupciones a bajo nivel y las funciones que lo
manejan */
98 struct irq_chip { 99 const char name; 100 unsigned int (startup)(unsigned int irq); 101 void (shutdown)(unsigned int irq); 102 void (enable)(unsigned int irq); 103 void (disable)(unsigned int irq); 104 105 void (ack)(unsigned int irq); 106 void (mask)(unsigned int irq); 107 void (mask_ack)(unsigned int irq); 108 void (unmask)(unsigned int irq); 109 void (eoi)(unsigned int irq); 110 111 void (end)(unsigned int irq); 112 void (set_affinity)(unsigned int irq, cpumask_t dest); 113 int (*retrigger)(unsigned int irq);
114 int (set_type)(unsigned int irq, unsigned int flow_type); 115 int (set_wake)(unsigned int irq, unsigned int on); 116 117 /* Currently used only by UML, might disappear one day./ 118 #ifdef CONFIG_IRQ_RELEASE_METHOD 119 void (release)(unsigned int irq, void dev_id); 120 #endif 121 / 122 * For compatibility, ->typename is copied into ->name. 123 * Will disappear. 124 */ 125 const char *typename; 126 };
Esta estructura contiene todas las operaciones específicas de un determinado
decodificador de interrupciones (8259) tales como el activado o desactivado de irqs,
etc. Los campos se detallan a continuación:
name Un nombre para /proc/interrupts
startup Activa una entrada irq para que pueda interrumpir.
shutdown Deshabilita una entrada irq.
enable y disable Igual que startup/shutdown respectivamente.
ack Comienzo de una nueva interrupción.
mask umask Pone quita una máscara para una interrupción.
eoi end Final de una interrupción.
set-affinity Para trabajar con varias CPUs (smp).
retrigger Vuelve a enviar una petición irq a la cpu.
set_type Define el modo de disparo (nivel, flanco) de la Irq.
set_wake Permite despertar a la unidad de alimentación al recibir una irq.
irq_desc
Es un vector de estructuras de tipo irq_desc_t , descriptor de interrupciones
dependiente de la arquitectura, para Intel está definido en el fichero
incluye/linux/irq.h. Por cada interrupción hardware del sistema habrá un elemento
en el array irq_desc[ ].
Su código es el siguiente:
struct irq_desc { 156 unsigned int irq; 157 irq_flow_handler_t handle_irq; 158 struct irq_chip *chip; 159 struct msi_desc *msi_desc; 160 void *handler_data; 161 void *chip_data; 162 struct irqaction action; / IRQ action list / 163 unsigned int status; / IRQ status / 164 165 unsigned int depth; / nested irq disables / 166 unsigned int wake_depth; / nested wake enables / 167 unsigned int irq_count; / For detecting broken IRQs */ 168 unsigned int irqs_unhandled;
#define IRQ_PENDING 4 /* IRQ pendiente, ha sido reconocida por el PIC
pero no ha sido atendida por el núcleo */
#define IRQ_REPLAY 8 /* IRQ reintenta pero todavía no atendida */
#define IRQ_AUTODETECT 16 /* IRQ esta siendo auto detectada */
#define IRQ_WAITING 32 /* IRQ no lista para auto detección */
Tabla descriptora de interrupciones idt_table
La tabla descriptora de las interrupciones IDT, es una tabla de 256 entradas que
junto con la tabla global de descriptores GDT, nos va a llevar dentro del núcleo a las
rutinas de manejo de interrupciones que se encuentran en el manejador del
dispositivo. Definida en arch/x86/kernel/traps.c, como gate_desc idt_table[256]. Y
cada entrada de la tabla es una estructura definida en,
arch/x86/include/asm/desc_defs.h
Como:
/* 16byte gate */ 45 struct gate_struct64 { 46 u16 offset_low; 47 u16 segment; 48 unsigned ist : 3, zero0 : 5, type : 5, dpl : 2, p : 1; 49 u16 offset_middle; 50 u32 offset_high; 51 u32 zero1; 52 } attribute((packed));
hard
excepciones
syscall 0x
S D A
S D A
S D A
S D A
S D A
S D A
S D A
255 S D A
B L
IDTR
IDT
S: selector o índice D: desplazamiento A: atributos B: base L: límite
Las primeras entradas de la tabla están ocupadas por las excepciones.
Las interrupciones hardware, comienzan en la entrada 32 de la tabla, ocupan 16
entradas van desde 0x20 y hasta 0x2f.
La interrupción software ocupa la entrada 128, (0x80 en exadecimal).
IDTR es un registro dentro de la CPU que contiene la base y el límite de esta tabla
en memoria.
S con este campo seleccionamos una entrada dentro de la tabla GDT, que nos dará
la base y el límite del núcleo en memoria.
D es el desplazamiento que sumado a la base del núcleo no lleva a la función de
entrada para una determinada interrupción.
1. El decodificador de interrupciones recibe una interrupción (la patilla IRQ a nivel
alto), este interrumpe a la CPU, que automáticamente mediante la entrada que
corresponde a esta interrupción y con la ayuda de la IDT y la GDT nos lleva a
ENTRY (irq_entries_start) dentro de arch/i386/kernel/entry.S, que salta a
common_interrupt.
2. common_interrupt despues de guardar el estado del proceso SAVE_ALL llama
a do_irq pasándole el número de interrupción para ejecutar la función manejadora
ENTRY (irq_entries_start)
common_interrupt(n)
SAVE_ALL
do_IRQ(n) RESTORE_ALL
do_IRQ(n)
action->handler
manejador
handle_irq
IDT + GDT
interrupción (^) CPU
IDTR
GDTR
de la interrupción y posteriormente recupera el estado del proceso interrumpido
RESTART_ALL saltando a ret_from_intr.
3. do_IRQ llama a handle_IRQ.
4. handle_IRQ llama a las funciones necesarias para atender la interrupción action-
>handler dentro del manejador.
5. action->handler se ejecuta la función manejadora de la interrupción dentro del
manejador (driver) del dispositivo que interrumpió.
6. ret_from_intr restaura el estado del proceso interrumpido RESTORE_ALL y
continúa con la ejecución normal.
Flujo proceso a proceso
La función set_intr_gate inicializa la IDT, colocando el desplazamiento que nos lleva
al punto de entrada ENTRY (irq_entries_start) , dentro del fichero
Linux/arch/x86/kernel/entry_32.S.
irq_entries_start nos lleva a common_interrupt pasándole como parámetro el
número de interrupción en el stack cambiado de signo.
ENTRY( irq_entries_start ) 587 RING0_INT_FRAME 588 vector= 589 .rept NR_IRQS 590 ALIGN 591 .if vector 592 CFI_ADJUST_CFA_OFFSET - 593 .endif 594 1: pushl $~(vector) 595 CFI_ADJUST_CFA_OFFSET 4 596 jmp common_interrupt 597 .previous 598 .long 1b 599 .text 600 vector=vector+ 601 .endr 602 END(irq_entries_start)
Guarda el número de la interrupción en el stack, forma de pasar parámetros a una
función, y en negativo para que no exista conflicto con el número de señales
pushl $~(vector)
y salta a la función jmp common_interrupt
453 TRACE_IRQS_IRET
454 restore_nocheck_notrace: 455 RESTORE_REGS # recupera el estado de la máquina 456 addl $4, %esp # skip orig_eax/error_code 457 CFI_ADJUST_CFA_OFFSET -
restore_all:
RESTORE_REGS
do_IRQ , se encuentra en el fichero arch/x86/kernel/irq.c, maneja todas las interrupciones
normales de los dispositivos IRQ's (las interrupciones especiales producidas por tener varias CPU’s
(SMP), tienen un tratamiento especifico. Recibe el número de irq a través del registro eax y
un puntero a una estructura pt_regs donde se han guardado los registros. Llama a la
función handle_irq(irq, desc) , que contiene el manejador de esa interrupción.
199 unsigned int do_IRQ(struct pt_regs *regs) 200 { 201 struct pt_regs old_regs; 202 / high bit used in ret_from_ code */ 203 int overflow;
/* el numero de interrupción se encuentra en los bits más bajos de eax */
204 unsigned vector = ~regs->orig_ax; 205 struct irq_desc *desc; 206 unsigned irq; 207 208 209 old_regs = set_irq_regs(regs); 210 irq_enter(); 211 irq = __get_cpu_var(vector_irq)[vector]; 212 213 overflow = check_stack_overflow(); 214
/* accede a la posición del vector irq_desc para esta interrupción */
215 desc = irq_to_desc(irq); 216 if (unlikely(!desc)) { 217 printk(KERN_EMERG "%s: cannot handle IRQ %d vector %#x cpu %d\n", 218 func, irq, vector, smp_processor_id()); 219 BUG(); 220 } 221 222 if (!execute_on_irq_stack(overflow, desc, irq)) { 223 if (unlikely(overflow)) 224 print_stack_overflow();
/* llamada a la función
Llama a la función de alto nivel manejadora de la interrupción, con el número de irq
y un puntero a la entrada del vector de interrupciones irq_desc , la estructura
irq_desc mediante el campo action apunta al campo irqaction->handler que tiene
grabada la dirección de la función. Este campo ha sido previamente inicializado por
el driver del dispositivo.
225 desc->handle_irq(irq, desc);
228 irq_exit(); 229 set_irq_regs(old_regs); 230 return 1; 231 }
Por último la función que entra en juego es el manejador de la interrupción que es
una función que se encuentra dentro del código driver del dispositivo.
3.5 Procedimientos de Inicialización
Para que los dispositivos puedan utilizar el sistema de interrupciones, el sistema al
arrancar debe inicializar mediante unos procedimientos, que se encuentran en
/arch/x86/kernel/irqinit.c, las estructuras de datos relacionas con las interrupciones.
init_IRQ().
Esta función es llamada por el programa de inicio del sistema init start_kernel () , en
init/main.c. Esta función pone a punto la tabla de descriptores de interrupciones
(IDT). Se encuentra definida en el fichero arch/i386/kernel/i8259.c
Primeramente inicializa el vector de interrupciones hardware irq_desc[0..15].
Seguidamente inicializa la tabla IDT, llamando a la función set_intr_gate().
set_intr_gate
init_irq
IDT
INIT
pre_intr_init_hook
Irq_desq[]
319 BUG_ON((unsigned)n > 0xFF); 320 _set_gate(n, GATE_INTERRUPT, addr, 0, 0, __KERNEL_CS); 321 }
n va apuntando a las distintas entradas de la IDT.
DESCTYPE_INT tipo de descriptor interrupcion
addr es el desplazamiento que le va a llevar a la función irq_entries_start.
KERNEL_CS selector que apunta a la entrada de la GDT donde está la base del
código del núcleo.
llama a la macro _set_gate en Linux/arch/x86/asm/desc.h
static inline void _set_gate(int gate, unsigned type, void addr, 300 unsigned dpl, unsigned ist, unsigned seg) 301 { 302 gate_desc s; 303 pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg); 304 / 305 * does not need to be atomic because it is only done once at 306 * setup time 307 */ 308 write_idt_entry(idt_table, gate, &s); 309 }
static inline void pack_gate(gate_desc *gate, unsigned char type, 66 unsigned long base, unsigned dpl, unsigned flags, 67 unsigned short seg) 68 { 69 gate->a = (seg << 16) | (base & 0xffff); 70 gate->b = (base & 0xffff0000) | 71 (((0x80 | type | (dpl << 5)) & 0xff) << 8); 72 }
static inline void native_write_idt_entry(gate_desc *idt, int entry, 116 const gate_desc gate) 117 { 118 memcpy(&idt[entry], gate, sizeof(gate)); 119 }
request_irq
Una vez que el sistema de interrupciones está inicializado, cuando se vayan
cargando los diferentes manejadores de dispositivos éstos harán uso de las
funciónes request_irq y setup_x86_irq para colocar la dirección de la función
manejadora de interrupción propia y activar la interrupción.
request_irq es llamado por los manejadores, en su inicio, para colocar la dirección
de la función manejadora de interrupción. Definida en kernel/irq/manage.c.
Crea un nuevo nodo en la lista irqaction y llena sus campos con los valores
suministrados para una IRQ.
request_irq – Asigna una línea de interrupción irq: Línea de interrupción a asignar handler: Función que se tiene que ejecutar cuando ocurre la IRQ irqflags: Flags del estado de la interrupción devname: Un nombre para el dispositivo asociado a esa interrupción dev_id: Identificador del dispositivo pasado a la función que trata la interrupción
- Esta función asigna recursos a la interrupción y habilita la
- línea de interrupción y el tratamiento de la IRQ. Una vez
- ejecutada esta función el manejador ya puede ser invocado.
- Ya que la función manejadora puede borrar cualquier
- interrupción, se tiene que inicializar el hardware
- y el manejador de interrupciones en el orden adecuado.
- Dev_id debe ser un identificador único. Normalmente se toma
- como identificador la dirección de la estructura device. Ya que
- el manejador recibe este valor tiene sentido usar este valor.
- Si la interrupción es compartida, el dev_id debe ser no NULL
- esto se require cuando se libera la interrupción.
- Flags:
- IRQF_SHARED Interrupción compartida
- IRQF_DISABLED Desactivar interrupción cuando se está procesando
- IRQF_SAMPLE_RANDOM La interrupción se puede usar para random
- IRQF_TRIGGER_* Especifica si se activa por flancos o por nivel
int request_irq(unsigned int irq, irq_handler_t handler, 670 unsigned long irqflags, const char *devname, void *dev_id) 671 { 672 struct irqaction *action;
irqaction
request_irq
setup_irq
MANEJADOR
función manejadora de la interrupción