====== Controlador de Interrupciones Genérico ====== Este artículo asume que el lector se encuentra familiarizado con los [[https://developer.arm.com/documentation/den0013/d/Exception-Handling/Types-of-exception|tipos de excepción]], [[https://developer.arm.com/documentation/den0013/d/Interrupt-Handling/External-interrupt-requests/Simplistic-interrupt-handling?lang=en | el manejo de una interrupción]] y [[https://developer.arm.com/documentation/den0013/d/ARM-Processor-Modes-and-Registers/Registers?lang=en| los registros de cada modo]] de un núcleo ARM, versión 7 para el caso que nos compete. Antes de comenzar vale la pena recordar dos conceptos elementales: * Un controlador de interrupción es un módulo (IC, ASIC, FPGA, etc) que se encuentran entre las fuentes de interrupción (periféricos) y **los núcleos** ARM, el cual decide cómo gestionar (priorizar, habilitar, direccionar) las interrupciones. * El núcleo ARM acepta solo **dos señales** de entrada no programadas que manejan interrupciones de los sistemas externos: **nIRQ** y nFIQ. Como se debería saber el sistema ingresa al modo IRQ si se recibe nIRQ (activación por nivel bajo 0), o en modo FIQ si se recibe nFIQ (activación por nivel bajo 0). Si bien dependiendo de las licencias ARM, es decir del fabricante de SoC, se decide el mecanismo para direccionar las señales de interrupción al núcleo, en este artículo se tratará el método estandarizado y más frecuentemente empleado, el [[https://developer.arm.com/documentation/198123/0302/What-is-a-Generic-Interrupt-Controller- | Global Interrupt Controller]]. Es importante aclarar que no se pretende brindar una explicación detallada del **//GIC//**, ya que para ello se encuentra disponible la [[https://developer.arm.com/documentation/ihi0069/h/?lang=en | especificación]] correspondiente, sino su aplicación para el caso de uso de la Cátedra. ===== Arquitectura ===== En la versión 2 de la especificación (ARM IHI 0048B ID061411) se disponía de un diagrama que puede ser de mucha utilidad para comprender los [[https://developer.arm.com/documentation/198123/0302/Arm-GIC-fundamentals | bloques constitutivos del GIC]] y su interconexión con el núcleo ARM. {{ :arm_gic_partitioning.png?800 |GIC logical partitioning}} Si bien el diagrama presenta la arquitectura para múltiples núcleos (processors), en nuestro caso de uso (inicialización mono procesador) solo basta con reducir el esquema a un solo procesador. Según la fuente, hay tres tipos principales de interrupciones definidas. ** Interrupciones de periféricos compartidos (Shared Peripheral Interrupts):** Identificador de interrupción del 32 al 1019. Son las fuentes de interrupción generadas por los diferentes periféricos del sistema que pueden ser direccionadas (re/compartirse) a cualquiera (uno o más) de los núcleos según la configuración implementada. A modo de ejemplo se podría mencionar el caso de dos temporizadores independientes (TIMER0 y TIMER1) conectados a las líneas SPI y configurándose en el //Distribuitor// para direccionarse a dos de los núcleos disponibles. En la interrupción del TIMER0, la señal se direcciona al primer procesador disponible (cualquiera de los dos configurados). Si el TIMER1 requiere su atención, cuando aún no finalizó la correspondiente al TIMER0, el //Distribuitor// la direcciona al restante núcleo configurado para su manejo. **Interrupciones de periféricos privados (Private Peripheral Interrupts):** Identificador de interrupción del 16 al 31. Son las fuentes de interrupción generadas por los diferentes periféricos del sistema que debe ser direccionadas a un núcleo específico. Un ejemplo típico de este tipo de interrupción se presenta en el caso los sistemas asimétricos como ser un Cortex A y Cortex M, en donde una interrupción generada por un Watchdog correspondiente al núcleo A solo debe direccionarse al mismo. **Interrupciones generadas por software (Software Generated Interrupts):** Identificador de interrupción del 0 al 15. ARM reserva este rango específicamente para la comunicación entre procesadores. Una SGI puede ser direccionada a uno o más procesadores a través del //Distributor//. Las Interrupciones 0 a 31 son almacenadas por el //Distribuitor// para cada //CPU Interface//, es decir, cada procesador las ve de manera diferente y se identifican por el CPUID. Por ejemplo la PPI 20 podría estar pendiente en CPU0 pero no en CPU1. Mientras que en el caso de la SPI, será la misma en todos los CPU. Independientemente de todo lo mencionado, cada núcleo recibe una señal a través de líneas nIRQ o nFIQ para interrumpir la ejecución del programa. A continuación describiremos sintéticamente el objetivo de cada submódulo ==== Distributor: ==== Es responsable de gestionar las interrupciones en todo el sistema, decide las prioridades entre ellas, el mecanismo de direccionamiento de las mismas así como también permite la habilitación de cada una en forma independiente. Solo existe una instancia de este componente y esta orientado a presentar las interfaces de interconexión a los la periféricos. ==== CPU Interface ==== Existe una instancia por núcleo de CPU disponible e interconecta la interfaz provista por el Distributor con el núcleo correspondiente. Cada interfase permite: * Ítem de lista desordenadaÍtem de lista desordenadaDes/habilitar la señal de IRQ al procesador * Dar acuse de recibo de la interrupción * Indicar que el procesamiento de la interrupción se ha finalizado * Establecer la mascara de prioridades de interrupción * Definir la [[https://en.wikipedia.org/wiki/Preemption_(computing) | preemption policy]] del procesador * Determinar la interrupción de mas alta prioridad ===== Interfaz de programación ===== [[https://developer.arm.com/documentation/ihi0048/a/Programmers-Model/About-the-programmers-model | El modelo de programación]] así como [[https://developer.arm.com/documentation/ihi0048/a/Programmers-Model/About-the-programmers-model/Distributor-register-map?lang=en | los registros del Distributor]] y la [[https://developer.arm.com/documentation/ihi0048/a/Programmers-Model/About-the-programmers-model/CPU-interface-register-map | CPU Interface ]] se encuentra ampliamente descriptos en [[https://developer.arm.com/documentation/ihi0069/h/?lang=en | la especificación de referencia]], por lo cual en este apartado se centrará en los pasos mínimos de configuración necesarios para des/habilitar una SPI. A fin de brindar una referencia genérica agnóstica de la implementación SoC específica, se debe tener presente que el GIC es parte del denominado [[https://developer.arm.com/documentation/ddi0344/k/glossary | Advanced Peripheral Bus]] y por ende la dirección base a partir de la cual es posible acceder a los registros asociados al //Distributor// y la //CPU Interface// es la denominada //Peripheral Base//, or lo que es posible especificar GIC Distributor base address DISTBASE = PERIPHBASE + 0x1000 CPU Interface base address CPUBASE = PERIPHBASE + 0x0000. El valor especifico de la dirección base se brinda en la documentación especifica de cada fabricante ([[https://developer.arm.com/documentation/dui0417/d/programmer-s-reference/generic-interrupt-controller--gic/generic-interrupt-controller-registers|PB-A8]], [[https://docs.amd.com/r/en-US/ug585-zynq-7000-SoC-TRM/CPU-Private-Bus-Registers|Zynq-7000]]) #include /*GIC Register Definitions*/ #if defined(__PB_A8__) #define GIC0_CPU_BASE 0x1E000000 #define GIC0_DISTRIBUTOR_BASE 0x1E001000 #elif defined(i386) #define GIC0_CPU_BASE 0xF8F00100 #define GIC0_DISTRIBUTOR_BASE 0xF8F01000 #endif #define ICCICR *((uint32_t *) GIC0_CPU_BASE + 0x00) //CPU Interface Control Register #define ICCPMR *((uint32_t *) GIC0_CPU_BASE + 0x04) //Interrupt Priority Mask Register #define ICCBPR *((uint32_t *) GIC0_CPU_BASE + 0x08) //Binary Point Register #define ICCIAR *((uint32_t *) GIC0_CPU_BASE + 0x0C) //Interrupt Acknowledge #define ICCEOIR *((uint32_t *) GIC0_CPU_BASE + 0x10) //End of Interrupt Register #define ICDDCR *((uint32_t *) GIC0_DISTRIBUTOR_BASE + 0x000) //Distributor Control Register #define ICDICTR *((uint32_t *) GIC0_DISTRIBUTOR_BASE + 0x004) //Interrupt Controller Type Register #define ICDISER(n) *(((uint32_t *) GIC0_DISTRIBUTOR_BASE + 0x100) + n) //Interrupt n Set-Enable Registers #define ICDICER(n) *(((uint32_t *) GIC0_DISTRIBUTOR_BASE + 0x180) + n) //Interrupt Clear-Enable Registers #define ICDIPR(n) *(((uint32_t *) GIC0_DISTRIBUTOR_BASE + 0x400) + n) //Interrupt Priority Registers #define ICDIPTR(n) *(((uint32_t *) GIC0_DISTRIBUTOR_BASE + 0x800) + n) //Interrupt Processor Targets Registers #define ICDICFR(n) *(((uint32_t *) GIC0_DISTRIBUTOR_BASE + 0xC00) + n) //Interrupt Configuration Registers Teniendo presente las direcciones de acceso, se puede establecer una secuencia de inicialización básica compuesta de los siguientes pasos * Deshabilitar el //Distributor// y la //CPU Interface// void gic_init(void){ ICDDCR = 0); * Configurar el número máximo de interrupciones que serán gestionadas por el GIC mediante el ICDICTR. Siendo el número máximo de interrupciones 32(N+1) donde N es el valor a cargar en el registro. ICDICTR = 0x3; * Habilitar el número de interrupción asociada al periférico que se desee atender. ICDISER representa la dirección base y su rango máximo es 0x7C (127) por lo que si se quiere habilitar la IRQ36 se debe establecer en 1 el bit 4 del ICDISER(1), ya que este registro opera como ICDISERn = 0x100 + (4*M), donde M = N/32 (division entera). ICDISER(1) = 0x4; * Configurar el procesador al cual la interrupción debe ser direccionada. El conjunto de registros ICDIPTRn debe establecer la parte baja de cada byte en 0x1 para sistemas de un único núcleo for i=0; i++; i<24 {ICDIPTR(i) = 0x1111;} {{ :td3:under_construction.jpg?100 |}} * Configurar si la detección de la interrupción es por nivel o flanco. Solo es aplicable a PPI {{ :td3:under_construction.jpg?100 |}} * Configurar la prioridad. {{ :td3:under_construction.jpg?100 |}} * Habilitar el //Distributor// y la //CPU Interface// ICDDCR = 1); } ===== Gestión de las interrupciones en el Core ===== En los párrafos precedentes se brindó una referencia básica sobre la operación y configuración del //GIC//, es decir sobre el sistema de gestión de interrupciones externo al/los núcleos, a continuación el artículo se centrará sobre la contra-parte situada en el propio núcleo. En esta instancia es bien sabido que cada [[https://developer.arm.com/documentation/den0013/d/ARM-Processor-Modes-and-Registers/Registers | modo de operación]] debe contar con su propia pila, a fin de preservar el contexto de ejecución, lo cual debe realizarse en la etapa [[https://sge.frba.utn.edu.ar/wiki/td3/lib/exe/detail.php?id=inicializacion_de_procesador_cortex-a8&media=initialization_soc.png | stack_configure]] de la secuencia de inicialización del procesador stack_setup_irq: cps #0xd2 @Change Processor Status a IRQ ldr r0, #CONFIG_PILA_LIMITE_DIRECCION sub sp, r0, #(CONFIG_PILA_IRQ_LARGO + CONFIG_PILA_FIQ_LARGO) cpsie if, #0x13 @Change Processor Status a SVC con IRQ habilitada mov pc, lr @retorna Una vez que el //GIC// a señalizado al núcleo respecto a una interrupción pendiente, este debe realizar una [[https://developer.arm.com/documentation/den0013/d/Interrupt-Handling/External-interrupt-requests/Simplistic-interrupt-handling | secuencia de pasos mínima (descripta en el manual)]] para gestionar la interrupción, la cual se ejemplifica mediante el siguiente código irq_handler: sub lr, lr, #4 @retornar a la instruccion en ejecucion al momento de la interrupcion stmfd sp!, {r0-r12, lr} @resguarda contexto en pila de IRQ mrs r11, spsr @reguarda Saved Processor Status Register push {r11} @bl handler_dispatch @Gestor generico para identificar y derivar al controlador especifico bl timer0_handler @Nuestro caso de uso (simple) solo presenta una IRQ @bl data_synchronization_barrier @requerido en SoC multi core (dsb, dmb segun systema) pop {r11} msr spsr, r11 @reguarda Saved Processor Status Register ldmfd sp!, {r0-r12, pc}^ @restaura contexto y retorna ===== Caso de uso PB-A8 ===== A los fines de la placa empleada, para la resolución de la guía de trabajos prácticos del primer cuatrimestre, el [[https://developer.arm.com/documentation/dui0417/d/programmer-s-reference/generic-interrupt-controller--gic?lang=en | GIC a configurar para el uso de IRQ es el 0]] el cual tiene asociado el [[https://developer.arm.com/documentation/dui0417/d/programmer-s-reference/generic-interrupt-controller--gic/interrupt-signals?lang=en | ID#36 para el Timer0]] En forma complementaría al código, en C, presentado anteriormente para la configuración del GIC, a continuación se brinda a modo de referencia básica el equivalente en ensamblador .global GIC_CFG GIC_CFG: push {r0-r1, lr} mov r0, #0 ldr r1, =0x1E001000 @ICDDCR str r0, [r1] @Deshabilita CPU Interface y Distributor mov r0, #36 @IRQ ID 36 para Timer0 mov r1, #1 @Sistema mono procesador bl _IRQ_CFG @Llamada a rutina con argumentos r0, r1 ldr r1, =0x1E000004 @Interrupt Priority Mask Register (ICCPMR) ldr r0, [r1] @Carga el valor actual orr r0, r0, #0xF bic r0, r0, #1 @BIt Clear -> r0 = r0 and 0xFFFF FFFE str r0, [r1] @Habilita todas las prioridades mov r0, #1 ldr r1, =0x1E000000 @ICCICR str r0, [r1] @Habilita señalizar las interrupciones mov r0, #1 ldr r1, =0x1E001000 @ICDDCR str r0, [r1] @Habilita CPU Interface y Distributor pop {r0-r1, pc} @ r0 = Interrupt ID, N @ r1 = target CPU @ Asume que los valores por defecto de los registros no configurados @ son correctos _IRQ_CFG: push {r2-r5, lr} mov r3, #1 @32(N+1) maximo, solo necesito hastala 36 ldr r2, =0x1E001004 @ICDICTR ldr [r2], r3 /*Interrupt Set-Enable Registers (ICDISERn) * reg_offset = (integer_div(N / 32) * 4 * value = 1 << (N mod 32) */ lsr r4, r0, #3 @ Logical Shift Right = INT(N/(2³)) = reg_offset bic r4, r4, #3 @ BIt Clear -> r4 and 0b1111 1100 ldr r2, =0x1E000100 @ ICDISER registro 0 add r4, r2, r4 @ r4=ICDISER sobre el que se debe operar and r2, r0, #0x1F @N mod 32 mov r3, #1 lsl r2, r3, r2 @Logical Shift Left = 1 << (N mod 32) = r2 ldr r5, [r4] @Carga actual valor de ICDISERn orr r5, r5, r2 @Modifica segun r2 str r5, [r4] @Carga nuevo valor a ICDISERn /* Interrupt Processor Targets Register (ICDIPTRn) * reg_offset = integer_div(N / 4) * 4 * index = N mod 4 */ bic r4, r0, #3 @ r4=r0 and 0b1111 1100=reg_offset ldr r2, =0x1E000800 @ ICDIPTR registro 0 add r4, r2, r4 @ r4= ICDIPTR sobre el que se debe operar and r2, r0, #0x3 @N mod 4 add r4, r2, r4 @Byte asociado al CPU en ICDIPTR strb r1, [R4] @Solo modifica el byte asociado al CPU pop {r2-r5, pc} @alternativa a mov pc, lr ===== Caso de uso Beagle Bone Black===== {{ :td3:nueva_seccion.jpg?400 |}} --- //[[cnigri@frba.utn.edu.ar|ChristiaN]] 2023/06/22 20:29//