Una introducción sencilla al subsistema de interrupciones de Linux

Una introducción simple al subsistema de interrupciones de Linux. Si necesita profundizar, salte directamente a los capítulos de referencia importantes .

¿Qué es una interrupción?

Cuando la CPU es activada por alguna señal, la CPU suspende el trabajo actual y pasa a procesar el evento de la señal, lo que simplemente se llama interrupción. Esta señal puede ser una señal de un periférico del sistema o alguna señal dentro del chip. La CPU admite muchas interrupciones. Si cada interrupción está conectada directamente a la CPU, es un poco irreal. Por lo tanto, el método actual es conectar la señal de interrupción a un controlador de interrupciones y luego usar una línea de interrupción para activar la interrupción de la CPU. Después de que la CPU responde a la interrupción, juzga qué señal de interrupción se basa en la información del registro. La lógica de hardware de la interrupción no se presenta aquí. La arquitectura ARM se utiliza como ejemplo para presentar el subsistema de interrupción de Linux.

ARM tiene dos líneas de interrupción activadas por nivel bajo: IRQ y FIQ. Para Linux, no se usan preguntas frecuentes, pero se usa una línea de interrupción de IRQ para procesar solicitudes de interrupción.

IRQ(Interrupt Request):指中断模式;
FIQ(Fast Interrupt Request):指快速中断模式;
IRQ与FIQ是ARM处理器的两种不同编程模式.

IRQ和FIQ的区别:
1. 对FIQ你必须进快处理中断请求,并离开这个模式;
2. IRQ可以被FIQ所中断,但FIQ不能被IRQ所中断,在处理FIQ时必须要关闭中断;
3. FIQ的优先级比IRQ高;
4. FIQ模式下,比IRQ模式多了几个独立的寄存器,这就导致可能FIQ中断处理程序不需要通用寄存器压栈;
5. FIQ的中断向量地址在0x0000001C,而IRQ的在0x00000018(也有的在FFFF001C以及FFFF0018),所以FIQ中断处理程序可以一直往下跑而不用跳转;
6. IRQ和FIQ的响应延迟有区别(?????好像中断都是完成一个总线周期才会响应的?????????);

Para el procesador arm, se proporciona un controlador de interrupción de brazo dedicado, concretamente GIC (Controlador de interrupción genérico). El diseño de hardware de GIC también se divide en dos partes correspondientes:

  • Distribuidor GIC
  • Interfaz de CPU GIC

Distribuidor se traduce como distribuidor, que es responsable de distribuir las interrupciones transmitidas desde las fuentes de interrupción. La interfaz de la CPU es evidente, es la interfaz de configuración de la CPU. En la arquitectura multinúcleo, cada CPU corresponde a una interfaz de CPU, que es responsable de distribuir las interrupciones del distribuidor, la interrupción pasada se pasa a la CPU y, al mismo tiempo, realiza una serie de interacciones con la CPU. Por lo tanto, en GIC, el flujo de una interrupción externa es: fuente de interrupción -> distribuidor GIC -> interfaz de CPU GIC -> CPU.

distribuidor

El distribuidor en GIC es compartido por todas las CPU y controla principalmente la recopilación y distribución de interrupciones. Su interfaz implementada específica es:

  • Controla globalmente si las fuentes de interrupción se pasan a la interfaz de la CPU.
  • Controla si una sola línea de interrupción está habilitada. Si no está habilitada, la interrupción generada en la línea de interrupción correspondiente, naturalmente, no se pasará a la interfaz de la CPU.
  • Establezca la prioridad de cada interrupción. Cuando se produce la concurrencia de fuentes de interrupción, las interrupciones con mayor prioridad se entregan a la interfaz de la CPU.
  • Establezca la CPU de destino a la que se entregan todas las fuentes de interrupción. Puede especificar que ciertas interrupciones solo se entreguen a una interfaz de CPU específica.
  • Establezca el modo de disparo de interrupción, ya sea disparador de nivel o disparador de flanco
  • Configure cada interrupción como Grupo 0 o Grupo 1. El Grupo 0 o el Grupo 1 generalmente se distinguen solo en procesadores que implementan el modo seguro.
  • Pase SGI a un núcleo de CPU específico, ya sea único o múltiple.
  • El software puede configurar y borrar directamente el bit pendiente de interrupciones externas, es decir, el software también puede activar interrupciones externas.
interfaz de CPU

Para cada núcleo de CPU conectado al GIC, habrá una interfaz de CPU correspondiente, que proporciona principalmente las siguientes interfaces:

  • Habilite el control sobre si se envía la señal de interrupción al pin de interrupción de la CPU.
  • aceptar una señal de interrupción
  • Marca la finalización de un controlador de interrupciones.
  • Establecer prioridad de interrupción
  • Determine la interrupción de mayor prioridad y pásela al núcleo de la CPU
  • Definir una estrategia de preferencia de interrupciones

Cuando ocurre una interrupción, el distribuidor envía la interrupción a la interfaz de la CPU especificada de acuerdo con la configuración de la máscara de la CPU de destino. En este momento, la interfaz de la CPU no pasa directamente la interrupción a la CPU. Por un lado, el número de interrupción debe estar habilitado. Además, esta fuente de interrupción debe tener suficiente prioridad. Cuando la CPU no está procesando una interrupción, la prioridad es naturalmente la más baja. Una vez que la CPU está procesando la interrupción, la prioridad se convierte en la prioridad de la interrupción que se está procesando. Si la prioridad es mayor que la prioridad de la interrupción que se está procesando, entonces puede decidir si desea permitir que la interrupción actual se adelante a la interrupción anterior según la política de preferencia de la interrupción.

En Linux, la señal de interrupción FIQ no se utiliza y no se admite la preferencia de interrupción, por lo que la ejecución de las interrupciones es secuencial, o incluso si el gic reenvía una señal de interrupción de mayor prioridad durante la ejecución de la interrupción por parte de la CPU, la CPU no lo hará. procesarse porque Linux enmascara las interrupciones durante el procesamiento de interrupciones.

**Concepto de registro bancario:** Se refiere a que una dirección corresponde a copias de múltiples registros, los resultados de diferentes accesos a la CPU son diferentes y pertenecen a sus propios registros.

**EOI:** Después de procesar la interrupción de la CPU, se debe notificar al GIC el mensaje de procesamiento de la interrupción. Esta notificación de mensaje contiene dos partes: reducir la prioridad de informe de la interfaz de la CPU, desactivar la interrupción procesada y cambiar el estado. de la interrupción. Estos dos procesos se pueden configurar en modos de operación unificada y operación separada, que se configuran mediante el bit de configuración EOI del registro GICC_CTLR. Se llama modo EOI (fin de interrupción) en GIC. Cuando el modo EOI está habilitado, escriba el registro GICC_EOIR Se activará la caída de prioridad de la interrupción y la operación de desactivación debe implementarse escribiendo en el registro GICC_DIR. Cuando el modo EOI está deshabilitado, escribir GICC_EOIR directamente activará la caída de prioridad y la desactivación de la interrupción al mismo tiempo. Por lo tanto, habilitar la EOI requiere dos pasos y deshabilitarla requiere un paso.

flujo de código

En el código, es principalmente la inicialización y configuración de gic, y registra cierta información de configuración de interrupciones y funciones de procesamiento para diferentes números de interrupción. Sigamos el código, el código es kernel Linux4.9, arquitectura arm64.

La siguiente información se puede ver desde el dts en el lado del dispositivo:

        gic: interrupt-controller@03020000 {
    
    
                compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic";
                #interrupt-cells = <3>;
                #address-cells = <0>;
                device_type = "gic";
                interrupt-controller;
                reg = <0x0 0x03021000 0 0x1000>, /* GIC Dist */
                      <0x0 0x03022000 0 0x2000>, /* GIC CPU */
                      <0x0 0x03024000 0 0x2000>, /* GIC VCPU Control */
                      <0x0 0x03026000 0 0x2000>; /* GIC VCPU */
                interrupts = <GIC_PPI 9 0xf04>; /* GIC Maintenence IRQ */
                interrupt-parent = <&gic>;
        };

De hecho, esta configuración es root gic. Al buscar en el kernel, puede saber que drivers/irqchip/irq-gic.ccoincide con esta configuración dts.

int __init
gic_of_init(struct device_node *node, struct device_node *parent)
{
    
    
        struct gic_chip_data *gic;
        int irq, ret;

        if (WARN_ON(!node))
                return -ENODEV;

    	/* 通过这个代码,简单可以知道,gic的信息都是通过gic_data这个
    	 * 全局数组进行保存的,每增加一个gic控制器的时候,gic_cnt将
    	 * 会加1 */
        if (WARN_ON(gic_cnt >= CONFIG_ARM_GIC_MAX_NR))
                return -EINVAL;

        gic = &gic_data[gic_cnt];

    	/* 从dts获取gic dist和gic cpu的寄存器地址信息 */
        ret = gic_of_setup(gic, node);
        if (ret)
                return ret;

        /*
         * Disable split EOI/Deactivate if either HYP is not available
         * or the CPU interface is too small.
         */
    	/* 检查是否使能EOI mode(全局变量supports_deactivate表示),默认为true,一般是false */
        if (gic_cnt == 0 && !gic_check_eoimode(node, &gic->raw_cpu_base))
                static_key_slow_dec(&supports_deactivate); /* 不使能EOI则将supports_deactivate置false */

    	/* gic初始化 */
        ret = __gic_init_bases(gic, -1, &node->fwnode);
        if (ret) {
    
    
                gic_teardown(gic);
                return ret;
        }

        if (!gic_cnt) {
    
    
            	/* root gic,如果配置了gic_dist_physaddr,则设置 */
                gic_init_physaddr(node);
                /* 完成逻辑irq和hwirp的映射,下面介绍 */
                gic_of_setup_kvm_info(node);
        }

    	/* 被级联设备的特殊配置 */
        if (parent) {
    
    
                irq = irq_of_parse_and_map(node, 0);
                gic_cascade_irq(gic_cnt, irq);
        }

        if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
                gicv2m_init(&node->fwnode, gic_data[gic_cnt].domain);

        gic_cnt++;
        return 0;
}

IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
estructuras de datos importantes

Estructuras de datos importantes: gic_chip_data e irq_desc.

struct gic_chip_data {
    
    
        struct irq_chip chip;		/* gic硬件控制器相关信息,使能/关闭某中断 */
        union gic_base dist_base;
        union gic_base cpu_base;
        void __iomem *raw_dist_base;
        void __iomem *raw_cpu_base;
        u32 percpu_offset;
        struct irq_domain *domain;
        unsigned int gic_irqs;
		...
};

static struct irq_chip gic_chip = {
    
    
        .irq_mask               = gic_mask_irq,
        .irq_unmask             = gic_unmask_irq,
        .irq_eoi                = gic_eoi_irq,
        .irq_set_type           = gic_set_type,
        .irq_get_irqchip_state  = gic_irq_get_irqchip_state,
        .irq_set_irqchip_state  = gic_irq_set_irqchip_state,
        .flags                  = IRQCHIP_SET_TYPE_MASKED |
                                  IRQCHIP_SKIP_SET_WAKE |
                                  IRQCHIP_MASK_ON_SUSPEND,
};

Para los conductores diarios, a menudo entramos en contacto con irq_desc, que describe una única información irq:

/**
 * struct irq_desc - interrupt descriptor
 * @irq_common_data:    per irq and chip data passed down to chip functions
 * @kstat_irqs:         irq stats per cpu
 * @handle_irq:         highlevel irq-events handler
 * @preflow_handler:    handler called before the flow handler (currently used by sparc)
 * @action:             the irq action chain
 * @status:             status information
 * @core_internal_state__do_not_mess_with_it: core internal status information
 * @depth:              disable-depth, for nested irq_disable() calls
 * @wake_depth:         enable depth, for multiple irq_set_irq_wake() callers
 * @irq_count:          stats field to detect stalled irqs
 * @last_unhandled:     aging timer for unhandled count
 * @irqs_unhandled:     stats field for spurious unhandled interrupts
 * @threads_handled:    stats field for deferred spurious detection of threaded handlers
 * @threads_handled_last: comparator field for deferred spurious detection of theraded handlers
 * @lock:               locking for SMP
 * @affinity_hint:      hint to user space for preferred irq affinity
 * @affinity_notify:    context for notification of affinity changes
 * @pending_mask:       pending rebalanced interrupts
 * @threads_oneshot:    bitfield to handle shared oneshot threads
 * @threads_active:     number of irqaction threads currently running
 * @wait_for_threads:   wait queue for sync_irq to wait for threaded handlers
 * @nr_actions:         number of installed actions on this descriptor
 * @no_suspend_depth:   number of irqactions on a irq descriptor with
 *                      IRQF_NO_SUSPEND set
 * @force_resume_depth: number of irqactions on a irq descriptor with
 *                      IRQF_FORCE_RESUME set
 * @rcu:                rcu head for delayed free
 * @kobj:               kobject used to represent this struct in sysfs
 * @dir:                /proc/irq/ procfs entry
 * @name:               flow handler name for /proc/interrupts output
 */
struct irq_desc {
    
    
        struct irq_common_data  irq_common_data; /* 由所有irqchip共享的irq数据 */
        struct irq_data         irq_data; /* 中断相关的数据,逻辑irq,物理irq,irq_domain等 */
        unsigned int __percpu   *kstat_irqs; /* 每个CPU的irq统计 */
        irq_flow_handler_t      handle_irq; /* 高级irq事件处理程序 */

    	/* action 应用中将会是一个链表,我们的irq是支持共享的 */
        struct irqaction        *action;        /* IRQ action list */
        ....
           
        wait_queue_head_t       wait_for_threads; /* 等待队列头,中断同步的时候使用,比如进程在disable或free某个irq,需要等待irq执行完成 */
		...
} ____cacheline_internodealigned_in_smp;
__gic_init_bases

En __gic_init_bases, se procesa principalmente para gic raíz y git no raíz. Para el gic raíz, el pin de salida de interrupción está conectado directamente a la línea IRQ de la CPU. La diferencia con el gic secundario es que el gic raíz necesita procesar SGI y PPI, pero el git secundario no. Al mismo tiempo, en un multi- entorno central, es necesario configurar la interfaz de CPU correspondiente.:

static int __init __gic_init_bases(struct gic_chip_data *gic,
                                   int irq_start,
                                   struct fwnode_handle *handle)
{
    
    
        ...
#ifdef CONFIG_SMP
            	/* 设置PPI/SGI中断处理函数,实际上就是写gic的PPI/SGI中断寄存器,
            	 * 多核通信将会调用到gic_raise_softirq */
                set_smp_cross_call(gic_raise_softirq);
#endif
    			/* 设置CPU热插拔时执行的回调函数 */
                cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,
                                          "AP_IRQ_GIC_STARTING",
                                          gic_starting_cpu, NULL);
    			/* 设置中断发生时higher level回调函数,gic_handle_irq是中断处理程序的汇编代码调用,
                 * 这里实际上就是将gic_handle_irq赋值给平台的handle_arch_irq函数指针 */
                set_handle_irq(gic_handle_irq);
        }

        if (static_key_true(&supports_deactivate) && gic == &gic_data[0]) {
    
    
                name = kasprintf(GFP_KERNEL, "GICv2");
                gic_init_chip(gic, NULL, name, true);
        } else {
    
    
                name = kasprintf(GFP_KERNEL, "GIC-%d", (int)(gic-&gic_data[0]));
            	/* 初始化gic_chip_data中的chip变量,irq CPU亲和设置函数 */
                gic_init_chip(gic, NULL, name, false);
        }

		/* 下面重点解释 */
        ret = gic_init_bases(gic, irq_start, handle);
        if (ret)
                kfree(name);

        return ret;
}

gic_init_bases

La función gic_init_bases completa principalmente las siguientes operaciones:

  • Manejo especial GIC sin registro bancario
  • Establecimiento del dominio irq y la relación de mapeo irq
  • Configuración inicial del registro gic

Para la implementación de GIC sin registros bancarios, algunos registros en GIC están relacionados con la CPU, como la mayoría de los registros relacionados con la interfaz de la CPU. Naturalmente, varios núcleos de CPU no pueden usar el mismo conjunto de registros. Necesitan asignar registros correspondientes a cada CPU en el kernel.espacio de almacenamiento, por lo que es necesario utilizar estructuras de datos y variables relacionadas con percpu. La mayoría de las variables de percpu en estructuras de datos relacionadas con gic están relacionadas con esta situación.

Por ejemplo, para cada miembro de la matriz gic_data de tipo struct gic_chip_data, el controlador GIC sin registro bancario usa gic->dist_base.percpu_base para registrar la dirección base del distribuidor; de lo contrario, se usa gic->dist_base.common_base.

La mayoría de las implementaciones de GIC admiten registros bancarios, por lo que es una implementación de GIC normal.

La inicialización se completa principalmente mediante las funciones gic_dist_init y gic_cpu_init. Estas dos funciones están dirigidas respectivamente al distribuidor de GIC y a las funciones de inicialización relacionadas con la CPU. Los elementos de configuración correspondientes de estas dos funciones son:

  • Habilite globalmente la entrega de interrupciones desde el distribuidor a la interfaz de la CPU
  • Establezca la máscara de CPU de destino pasada para cada interrupción, generalmente una CPU específica
  • Establezca el modo de activación predeterminado para cada interrupción, el valor predeterminado es el activador de bajo nivel (la plataforma específica depende de la implementación del controlador GIC)
  • Establecer prioridad para cada interrupción
  • La inicialización restablece todas las líneas de interrupción
  • Registre la máscara de CPU correspondiente para cada interfaz de CPU, por supuesto, esta máscara solo corresponde a una CPU.
  • Establecer el umbral de la máscara de interrupción de la interfaz de la CPU
  • Algunos trabajos de inicialización de otra interfaz de CPU.

Una vez completada la configuración, GIC comienza a funcionar.

dominio irq y hwirq

El dominio de interrupción es responsable del mapeo de hwirq e irq lógico en gic. hwirq es hardware irq, la identificación de irq en el hardware de git. Consulte la introducción de gic de la plataforma correspondiente:

  • 0 ~ 15 corresponde a la interrupción SGI, la interrupción es percpu
  • 16 ~ 31 corresponde a la interrupción PPI, la interrupción es percpu
  • 32 ~ 1020 corresponde a interrupciones SPI, es decir, interrupciones compartidas, que son compartidas por todas las CPU, a qué CPU se distribuirán estas interrupciones depende de la configuración.

irq logico

Cuando una fuente de interrupción genera una interrupción, solo necesito poder encontrar el recurso de interrupción correspondiente a la interrupción en función del mapeo del número de interrupción. Los recursos de interrupción aquí incluyen la función de devolución de llamada de interrupción/los parámetros correspondientes a la ejecución de la interrupción, etc. .

Pero el problema es que para gic en cascada, los hwirqs correspondientes a diferentes interrupciones pueden ser los mismos, por lo que se necesita una capa de mapeo para los números de interrupción en el hardware, es decir, se mantiene una tabla de mapeo de irq lógico global y única en El software Cada ID de un GIC tiene una irq lógica única correspondiente, y luego el recurso de interrupción correspondiente se puede hacer coincidir a través de la irq lógica única. Entonces se completa una tabla de mapeo completa: gic hwirq -> irq lógico -> recurso de interrupción correspondiente.

gic_init_bases

static int gic_init_bases(struct gic_chip_data *gic, int irq_start,
                          struct fwnode_handle *handle)
{
    
    
		...
        /*
         * Find out how many interrupts are supported.
         * The GIC only supports up to 1020 interrupt sources.
         */
        /* 获取gic支持的irq数量 */
        gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
        gic_irqs = (gic_irqs + 1) * 32;
        if (gic_irqs > 1020)
                gic_irqs = 1020;
        gic->gic_irqs = gic_irqs;

        if (handle) {
    
               /* DT/ACPI */
            	/* 创建一个domain,并添加到irq_domain_list */
                gic->domain = irq_domain_create_linear(handle, gic_irqs,
                                                       &gic_irq_domain_hierarchy_ops,
                                                       gic);
        } else {
    
                    /* Legacy support */
                /*
                 * For primary GICs, skip over SGIs.
                 * For secondary GICs, skip over PPIs, too.
                 */
                if (gic == &gic_data[0] && (irq_start & 31) > 0) {
    
    
                        hwirq_base = 16;
                        if (irq_start != -1)
                                irq_start = (irq_start & ~31) + 16;
                } else {
    
    
                        hwirq_base = 32;
                }

                gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */

                irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,
                                           numa_node_id());
                if (irq_base < 0) {
    
    
                        WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",
                             irq_start);
                        irq_base = irq_start;
                }

                gic->domain = irq_domain_add_legacy(NULL, gic_irqs, irq_base,
                                        hwirq_base, &gic_irq_domain_ops, gic);
        }
        ...
}

gic_of_setup_kvm_info

A través del gic_init_bases anterior, podemos ver que solo se ha completado la creación del dominio irq, pero el mapeo de hwirq y el irq lógico aún no se ha completado. De hecho, gic_of_setup_kvm_info completa el establecimiento del mapeo correspondiente.

gic_of_setup_kvm_info llama a irq_of_parse_and_map—>irq_create_fwspec_mapping, y la lógica implementada es la siguiente:

  • Encuentre el dominio irq correspondiente llamando a ops->match pasado a la función irq_domain_create_linear a través de irq_find_matching_fwspec;
  • irq_domain_translate obtiene información de descripción del hardware relacionada con irq;
  • irq_domain_alloc_irqs se llama a irq domain alloc (gic_irq_domain_alloc) a través de irq_domain_alloc_irqs_recursive para completar el mapeo;
  • Solicite irq_data y otros recursos;

De hecho, antes del mapeo, la información del hardware se obtiene a través de gic_irq_domain_translate para comprender el punto de partida de hwirq. Generalmente, en el controlador gic, las primeras 16 señales SGI se ignorarán. Si el campo de interrupciones está configurado en dts, su información del campo 0 Significa lo que debe omitirse. Generalmente lo configuramos como GIC_PPI (en realidad 1), por lo que la señal PPI (+16) se ignorará, por lo que nuestro hwirq ignorará las 32 señales anteriores. *El análisis de irq dts también se realiza en of_irq_parse_one. *El mapeo consiste en obtener varios recursos irq de acuerdo con el irq lógico y completar la asignación correspondiente. El irq lógico está determinado por hwirq, desde el comienzo de hwirq para encontrar un irq lógico inactivo y devolverlo (BITMAP).

static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
                                irq_hw_number_t hw)
{
    
    
        struct gic_chip_data *gic = d->host_data;

        if (hw < 32) {
    
    
                irq_set_percpu_devid(irq);
                irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
                                    handle_percpu_devid_irq, NULL, NULL);
                irq_set_status_flags(irq, IRQ_NOAUTOEN);
        } else {
    
    
            	/* 完成映射和赋值irq_desc的handle_irq,指向handle_fasteoi_irq */
                irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
                                    handle_fasteoi_irq, NULL, NULL);
                irq_set_probe(irq);
        }
        return 0;
}

núcleo_inicial

Al rastrear el código, encontramos que las funciones early_irq_init e init_IRQ se llaman durante start_kernel. En early_irq_init, la estructura irq_desc se creará y se agregará a la lista vinculada del árbol rojo-negro global estático irq_desc_tree. Luego, en init_IRQ, se llamará a la función irqchip_init. En esta función, of_irq_init() activará el gic_of_init (llamada al puntero de función desc->irq_init_cb) mencionado anteriormente.

Volviendo a gic_of_init, si no es un gic raíz, se tratará de manera especial. En gic_cascade_irq, se configurará la función del controlador de interrupciones de un determinado irq y se verificará la configuración de interrupción correspondiente al gic de segundo nivel.

Lo anterior es solo una inicialización gic, pero no está muy claro cómo se maneja la interrupción real. Continuaremos viendo cómo el controlador solicita una interrupción y cómo la CPU maneja la interrupción cuando llega.

solicitud_irq

En los controladores diarios del kernel, los irqs utilizados por los periféricos se configuran en dts. Después de que el controlador obtiene el irq a través de dts, lo solicita a través de la función request_irq() y finalmente llega a request_threaded_irq. request_threaded_irq esencialmente completa la siguiente lógica:

  • A través de irq_to_desc, busque irq_desc en la lista enlazada estática global irq_desc_tree;
  • Crear una acción irq y completar la inicialización de las funciones de procesamiento de interrupciones y otra información;
  • Complete la inicialización de irq mediante la función __setup_irq;

Lógica __setup_irq:

  • Compruebe si la interrupción necesita crear un hilo de interrupción. Si es necesario, se creará el hilo del núcleo correspondiente, por lo que a veces ps puede crear un irq/%d-%shilo del núcleo con algún nombre, que se crea aquí;
  • Si es una interrupción compartida y otros dispositivos la han solicitado antes, agregue irqaction a irq_desc;
  • Si es la primera vez que realiza la solicitud, llame a la función irq_request_resources de irq_chip para solicitar recursos, configurar el modo de activación (nivel/borde), la afinidad de la CPU, habilitar interrupciones, etc.;

Preguntas más frecuentes

¿Por qué necesitamos -32 para completar el dts cuando obtenemos el número de interrupción del manual del chip al escribir un controlador periférico?

Como se introdujo anteriormente en la subsección gic_of_setup_kvm_info , el controlador gic ignorará las primeras 16 señales SGI. Al mismo tiempo, el gic: nodo controlador de interrupciones en dts está configurado con el parámetro 0 de interrupciones. El parámetro 0 significa que el dominio irq se saltará SGI y luego Saltar interrupciones [0] * 16 interrupciones, y el parámetro de interrupciones 0 en dts generalmente se configura como GIC_PPI. GIC_PPI se define como 1 en la plataforma ARM, que omite las 16 interrupciones SGI y 16 PPI anteriores, así que complete hwirq en dts Cuando, se requiere -32.

¿Qué sucede con el software después de que se produce la interrupción?

Cuando llega la señal de interrupción externa, después de pasar por el distribuidor gic, la interrupción se habilita, la CPU no está en el procesamiento de la interrupción, la CPU responde a la interrupción y luego ingresa al procesamiento de la interrupción. ¿Cómo maneja ARM Linux las interrupciones?

Al ingresar a la CPU después de una interrupción, se saltará a la tabla de vectores de interrupción (para obtener una introducción a la tabla de vectores de interrupción, consulte el enlace al final del artículo), qué tabla de vectores de interrupción usar depende del estado actual de la CPU y de la pila. estado del puntero.

La tabla de vectores de interrupción arm64 se define en los vectores de arch/arm64/kernel/entry.S y salta a la función de procesamiento correspondiente a través de kernel_ventry.

/*
 * Exception vectors.
 */
        .pushsection ".entry.text", "ax"

        .align  11
ENTRY(vectors)
        kernel_ventry   1, sync_invalid                 // Synchronous EL1t
        kernel_ventry   1, irq_invalid                  // IRQ EL1t
        kernel_ventry   1, fiq_invalid                  // FIQ EL1t
        kernel_ventry   1, error_invalid                // Error EL1t

        kernel_ventry   1, sync                         // Synchronous EL1h
        kernel_ventry   1, irq                          // IRQ EL1h
        kernel_ventry   1, fiq_invalid                  // FIQ EL1h
        kernel_ventry   1, error_invalid                // Error EL1h

        kernel_ventry   0, sync                         // Synchronous 64-bit EL0
        kernel_ventry   0, irq                          // IRQ 64-bit EL0
        kernel_ventry   0, fiq_invalid                  // FIQ 64-bit EL0
        kernel_ventry   0, error_invalid                // Error 64-bit EL0

#ifdef CONFIG_COMPAT
        kernel_ventry   0, sync_compat, 32              // Synchronous 32-bit EL0
        kernel_ventry   0, irq_compat, 32               // IRQ 32-bit EL0
        kernel_ventry   0, fiq_invalid_compat, 32       // FIQ 32-bit EL0
        kernel_ventry   0, error_invalid_compat, 32     // Error 32-bit EL0
#else
        kernel_ventry   0, sync_invalid, 32             // Synchronous 32-bit EL0
        kernel_ventry   0, irq_invalid, 32              // IRQ 32-bit EL0
        kernel_ventry   0, fiq_invalid, 32              // FIQ 32-bit EL0
        kernel_ventry   0, error_invalid, 32            // Error 32-bit EL0
#endif
END(vectors)

Linux no habilita las interrupciones fiq y el campo no válido es un vector de excepción no implementado, por lo que cuando ocurre una interrupción irq, saltará de la tabla de vectores de interrupción a el1_irq:

el1_irq:
        kernel_entry 1	//保存堆栈类的操作
        enable_dbg
#ifdef CONFIG_TRACE_IRQFLAGS
        bl      trace_hardirqs_off
#endif

        irq_handler	//中断处理函数,下面

#ifdef CONFIG_PREEMPT
        ldr     w24, [tsk, #TSK_TI_PREEMPT]     // get preempt count
        cbnz    w24, 1f                         // preempt count != 0
        ldr     x0, [tsk, #TSK_TI_FLAGS]        // get flags
        tbz     x0, #TIF_NEED_RESCHED, 1f       // needs rescheduling?
        bl      el1_preempt
1:
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
        bl      trace_hardirqs_on
#endif
        kernel_exit 1
ENDPROC(el1_irq)

/*
 * Interrupt handling.
 */
        .macro  irq_handler
        ldr_l   x1, handle_arch_irq
        mov     x0, sp
        irq_stack_entry
        blr     x1		//跳转到上面赋值到x1的handle_arch_irq函数
        irq_stack_exit
        .endm

Saltó del ensamblado a handle_arch_irq, handle_arch_irq es un puntero de función, en la función __gic_init_bases, apunta a la función gic_handle_irq.

static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
    
    
        u32 irqstat, irqnr;
        struct gic_chip_data *gic = &gic_data[0];
        void __iomem *cpu_base = gic_data_cpu_base(gic);

        do {
    
    
            	/* 获取中断号 */
                irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
                irqnr = irqstat & GICC_IAR_INT_ID_MASK;

                if (likely(irqnr > 15 && irqnr < 1020)) {
    
    
                        if (static_key_true(&supports_deactivate))
                                writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
                    	/* 处理中断 */
                        handle_domain_irq(gic->domain, irqnr, regs);
                        continue;
                }
            	/* 中断号小于16则是SGI,软中断,CPU间的异常通信 */
                if (irqnr < 16) {
    
    
                        writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
                        if (static_key_true(&supports_deactivate))
                                writel_relaxed(irqstat, cpu_base + GIC_CPU_DEACTIVATE);
#ifdef CONFIG_SMP
                        /*
                         * Ensure any shared data written by the CPU sending
                         * the IPI is read after we've read the ACK register
                         * on the GIC.
                         *
                         * Pairs with the write barrier in gic_raise_softirq
                         */
                        smp_rmb();
                        handle_IPI(irqnr, regs);
#endif
                        continue;
                }
                break;
        } while (1);
}

En handle_domain_irq, el irq lógico se encuentra en función de hwirq, seguido de generic_handle_irq—>generic_handle_irq_desc—>desc->handle_irq(desc).

¿Y a qué función apunta desc->handle_irq? Volviendo a la función ops->alloc del dominio irq, al realizar hwirq y el mapeo lógico de irq, apunte desc->handle_irq a la función handle_fasteoi_irq, y si hwirq es menor que 32, apunte a la función handle_percpu_devid_irq.

La dirección del flujo de handle_fasteoi_irq es: handle_fasteoi_irq—>handle_irq_event—>handle_irq_event_percpu—>__handle_irq_event_percpu, aquí, cada controlador se procesará uno por uno desde desc->action. action->handler es la función de procesamiento pasada durante request_irq anterior. Hasta ahora, el procesamiento de interrupción básico está completo.

Referencia importante

Introducción al subsistema de interrupción de Linux-arm-gic

Subsistema de interrupción de Linux: análisis del código fuente del controlador GIC

Interpretación de la tabla de vectores de excepción arm64 del Kernel Linux 5.14: interpretación del procesamiento de interrupciones

Supongo que te gusta

Origin blog.csdn.net/weixin_41944449/article/details/127122521
Recomendado
Clasificación