Une introduction simple au sous-système d'interruption Linux

Une introduction simple au sous-système d'interruption Linux. Si vous avez besoin d'approfondir, passez directement aux chapitres de référence importants .

Qu'est-ce qu'une interruption ?

Lorsque le processeur est déclenché par un signal, le processeur suspend le travail en cours et passe au traitement de l'événement de signal, simplement appelé interruption. Ce signal peut être un signal provenant d'un périphérique du système ou un signal à l'intérieur de la puce. Le CPU prend en charge de nombreuses interruptions. Si chaque interruption est directement connectée au CPU, c'est un peu irréaliste. Par conséquent, l'approche actuelle consiste à connecter le signal d'interruption à un contrôleur d'interruption, puis à utiliser une ligne d'interruption pour déclencher l'interruption du CPU. , une fois que la CPU a répondu à l'interruption, elle détermine de quel signal d'interruption il s'agit en fonction des informations du registre. La logique matérielle des interruptions ne sera pas présentée ici.Ce qui suit utilise l'architecture ARM comme exemple pour présenter le sous-système d'interruption de Linux.

ARM a deux lignes d'interruption déclenchées par un niveau bas : IRQ et FIQ. Pour Linux, la FAQ n'est pas utilisée, mais une ligne d'interruption d'IRQ est utilisée pour traiter les demandes d'interruption.

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的响应延迟有区别(?????好像中断都是完成一个总线周期才会响应的?????????);

Pour le processeur arm, un contrôleur d'interruption arm dédié, à savoir GIC (Generic Interrupt Controller), est fourni. La conception matérielle du GIC est également divisée en deux parties correspondantes :

  • Distributeur de CPG
  • Interface CPU GIC

Distributor se traduit par distributeur, qui est chargé de distribuer les interruptions transmises depuis les sources d'interruption. L'interface CPU va de soi, c'est l'interface de configuration du CPU. Dans l'architecture multicœur, chaque CPU correspond à une interface CPU, qui est responsable de la distribution des interruptions du distributeur. L'interruption passée est transmise au CPU et, en même temps, elle effectue une série d'interactions avec le CPU. Par conséquent, dans GIC, le processus de transfert d'une interruption externe vers le haut est le suivant : source d'interruption -> distributeur GIC -> interface CPU GIC -> CPU.

distributeur

Le distributeur dans GIC est partagé par tous les CPU, et contrôle principalement la collecte et la distribution des interruptions. L'interface spécifique implémentée est :

  • Contrôle globalement si les sources d'interruption sont transmises à l'interface CPU.
  • Contrôle si une seule ligne d'interruption est activée. Si elle n'est pas activée, l'interruption générée sur la ligne d'interruption correspondante ne sera naturellement pas transmise à l'interface CPU.
  • Définissez la priorité de chaque interruption. Lorsque la concurrence des sources d'interruption se produit, les interruptions avec une priorité plus élevée sont transmises à l'interface CPU.
  • Définissez le processeur cible auquel toutes les sources d'interruption sont délivrées. Vous pouvez spécifier que certaines interruptions ne sont délivrées qu'à une interface de processeur spécifique.
  • Définissez le mode de déclenchement d'interruption, qu'il s'agisse d'un déclenchement de niveau ou d'un déclenchement sur front.
  • Configurez chaque interruption en tant que Group0 ou Group1. Group0 ou Group1 n'est généralement distingué que sur les processeurs qui implémentent le mode sécurisé.
  • Transmettez SGI à un cœur de processeur spécifique, simple ou multiple.
  • Le logiciel peut directement définir et effacer le bit en attente des interruptions externes, c'est-à-dire que le logiciel peut également déclencher des interruptions externes.
Interface processeur

Pour chaque cœur de CPU connecté au GIC, il y aura une interface CPU correspondante, qui fournit principalement les interfaces suivantes :

  • Permet de contrôler s'il faut envoyer un signal d'interruption à la broche d'interruption du processeur.
  • accuser réception d'un signal d'interruption
  • Marque l'achèvement d'un gestionnaire d'interruption
  • Définir la priorité des interruptions
  • Déterminez l'interruption la plus prioritaire et transmettez-la au cœur du processeur
  • Définir une stratégie de préemption des interruptions

Lorsqu'une interruption se produit, le distributeur envoie l'interruption à l'interface CPU spécifiée en fonction du paramètre du masque CPU cible. À ce stade, l'interface CPU ne transmet pas directement l'interruption au CPU. D'une part, le numéro d'interruption doit être activé. De plus, cette source d'interruption doit avoir une priorité suffisante. Lorsque le processeur ne traite pas d'interruption, la priorité est naturellement la plus basse. Une fois que le processeur traite l'interruption, la priorité devient la priorité de l'interruption en cours de traitement. Si la priorité est supérieure à la priorité de l'interruption en cours de traitement, alors vous pouvez décider si vous souhaitez laisser l'interruption en cours préempter l'interruption précédente en fonction de la politique de préemption de l'interruption.

Sous Linux, le signal d'interruption FIQ n'est pas utilisé et la préemption d'interruption n'est pas prise en charge. Par conséquent, l'exécution des interruptions est séquentielle, ou même si le gic renvoie un signal d'interruption de priorité plus élevée pendant l'exécution de l'interruption par le CPU, le CPU ne le fera pas. être traité car Linux masque les interruptions pendant le traitement des interruptions.

**Concept de registre bancaire :** Fait référence au fait qu'une adresse correspond à des copies de plusieurs registres. Les résultats des différents accès CPU sont différents et appartiennent à leurs propres registres.

**EOI :** Une fois l'interruption du processeur traitée, le GIC doit être informé du message de traitement de l'interruption. Cette notification de message contient deux parties : réduire la priorité de rapport de l'interface du processeur, désactiver l'interruption traitée et changer d'état. de l'interruption. Ces deux processus peuvent être configurés en mode de fonctionnement unifié et en modes de fonctionnement séparés, qui sont configurés par le bit de configuration EOI du registre GICC_CTLR. Il est appelé mode EOI (fin d'interruption) dans GIC. Lorsque le mode EOI est activé, écrivez le registre GICC_EOIR. La baisse de priorité de l'interruption sera déclenchée et l'opération de désactivation doit être mise en œuvre en écrivant dans le registre GICC_DIR. Lorsque le mode EOI est désactivé, l'écriture directe de GICC_EOIR déclenchera la baisse de priorité et désactivera l'interruption en même temps. Par conséquent, l’activation de l’EOI nécessite deux étapes et sa désactivation nécessite une étape.

flux de code

Dans le code, il s'agit principalement de l'initialisation et de la configuration de gic, et enregistre certaines informations de configuration d'interruption et fonctions de traitement pour différents numéros d'interruption. Suivons le code. Le code est un noyau linux4.9, une architecture arm64.

Les informations suivantes peuvent être vues à partir des dts côté appareil :

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

En fait, cette configuration est root gic. En cherchant dans le noyau, vous pouvez savoir qu'elle drivers/irqchip/irq-gic.ccorrespond à cette configuration 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);
structures de données importantes

Structures de données importantes : gic_chip_data et 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,
};

Pour les conducteurs quotidiens, nous entrons souvent en contact avec irq_desc, qui décrit une seule information 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

Dans __gic_init_bases, il est principalement traité pour le gic root et le git non root. Pour le gic racine, la broche de sortie d'interruption est directement connectée à la ligne IRQ du CPU. La différence avec le gic secondaire est que le gic racine doit gérer SGI et PPI, mais pas le git secondaire. Dans le même temps, dans un environnement multicœur, l'interface CPU correspondante doit être configurée :

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 fonction gic_init_bases réalise principalement les opérations suivantes :

  • Traitement particulier des CPG sans registre bancaire
  • Établissement d'une relation de mappage entre le domaine irq et irq
  • Configuration initiale du registre gic

Pour la mise en œuvre du GIC sans registres bancaires, certains registres du GIC sont liés au CPU, comme la plupart des registres liés à l'interface du CPU. Naturellement, plusieurs cœurs de CPU ne peuvent pas utiliser le même ensemble de registres. Il est nécessaire d'allouer les registres correspondants pour chaque CPU dans le noyau. Par conséquent, il est nécessaire d'utiliser des structures de données et des variables liées au percpu. La plupart des variables percpu dans les structures de données liées au gic sont liées à cette situation.

Par exemple, pour chaque membre du tableau gic_data du type struct gic_chip_data, utilisez gic->dist_base.percpu_base pour enregistrer l'adresse de base du distributeur dans le pilote GIC sans registres bancaires, sinon utilisez gic->dist_base.common_base.

La plupart des implémentations de GIC prennent en charge les registres bancaires, il s'agit donc d'une implémentation normale de GIC.

L'initialisation se fait principalement par les fonctions gic_dist_init et gic_cpu_init. Ces deux fonctions concernent respectivement le distributeur GIC et les fonctions d'initialisation liées au CPU. Les éléments de paramétrage correspondants de ces deux fonctions sont :

  • Activer globalement le transfert des interruptions du distributeur vers l'interface CPU
  • Définissez le masque CPU cible transmis pour chaque interruption, généralement un CPU spécifique
  • Définissez le mode de déclenchement par défaut pour chaque interruption, la valeur par défaut est le déclenchement de bas niveau (la plate-forme spécifique dépend de l'implémentation du pilote GIC)
  • Définir la priorité pour chaque interruption
  • L'initialisation réinitialise toutes les lignes d'interruption
  • Enregistrez le masque CPU correspondant pour chaque interface CPU. Bien entendu, ce masque ne correspond qu'à un seul CPU.
  • Définir le seuil du masque d'interruption de l'interface CPU
  • Quelques travaux d'initialisation d'autres interfaces CPU

Une fois la configuration terminée, GIC commence à fonctionner.

domaine irq et hwirq

Le domaine d'interruption est responsable du mappage de hwirq et de l'irq logique dans gic. hwirq est l'irq matériel, l'identifiant irq sur le matériel git, vérifiez l'introduction gic de la plateforme correspondante :

  • 0 ~ 15 correspond à l'interruption SGI, qui est percpu
  • 16 ~ 31 correspond à l'interruption PPI, qui est percpu
  • 32 ~ 1020 correspondent aux interruptions SPI, c'est-à-dire aux interruptions partagées, qui sont partagées par tous les processeurs, et à quel processeur ces interruptions seront distribuées dépend de la configuration

logique

Lorsqu'une source d'interruption génère une interruption, il me suffit de pouvoir trouver la ressource d'interruption correspondant à l'interruption en fonction du mappage du numéro d'interruption. La ressource d'interruption comprend ici la fonction/les paramètres de rappel d'interruption correspondant à l'exécution de l'interruption, etc. .

Mais le problème est que pour le gic en cascade, le hwirq correspondant aux différentes interruptions peut être le même. Il est donc nécessaire de faire une couche de mappage du numéro d'interruption sur le matériel, c'est-à-dire de maintenir une logique globale et unique. Table de mappage irq sur le logiciel. Chaque ID d'un GIC a un irq logique unique correspondant, puis la ressource d'interruption correspondante peut être mise en correspondance via l'irq logique unique. Ainsi, une table de mappage complète est complétée : gic hwirq -> logical irq -> ressource d'interruption correspondante.

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

Grâce aux gic_init_bases ci-dessus, on peut voir que la création du domaine irq est seulement terminée, mais le mappage entre hwirq et l'irq logique n'est pas terminé. En fait, gic_of_setup_kvm_info termine l'établissement du mappage correspondant.

gic_of_setup_kvm_info implémente la logique en appelant irq_of_parse_and_map—>irq_create_fwspec_mapping comme suit :

  • Recherchez le domaine irq correspondant en appelant ops->match transmis à la fonction irq_domain_create_linear via irq_find_matching_fwspec ;
  • irq_domain_translate obtient les informations de description du matériel liées à l'irq ;
  • irq_domain_alloc_irqs est appelé pour l'allocation de domaine irq (gic_irq_domain_alloc) via irq_domain_alloc_irqs_recursive pour terminer le mappage ;
  • Postulez pour irq_data et d’autres ressources ;

En fait, avant le mappage, les informations matérielles sont obtenues via gic_irq_domain_translate pour comprendre le point de départ de hwirq. Généralement, dans le pilote gic, les 16 premiers signaux SGI seront ignorés. Si le champ des interruptions est configuré en dts, son 0ème champ d'information Cela signifie ce qui doit être ignoré. Nous le configurons généralement comme GIC_PPI (en fait 1), donc le signal PPI (+16) sera ignoré, donc notre hwirq ignorera les 32 signaux précédents. *L'analyse des dts irq est également effectuée dans of_irq_parse_one. *Le mappage consiste à obtenir diverses ressources irq basées sur l'irq logique et à terminer la mission correspondante. L'irq logique est déterminé par le hwirq. Recherchez un irq logique inactif depuis le début du hwirq et revenez (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;
}

start_kernel

En traçant le code, nous avons constaté que les fonctions early_irq_init et init_IRQ sont appelées pendant start_kernel. Dans early_irq_init, la structure irq_desc sera créée et ajoutée à la liste chaînée arborescente globale rouge-noire statique irq_desc_tree. Ensuite dans init_IRQ, la fonction irqchip_init sera appelée. Dans cette fonction, of_irq_init() déclenche l'appel au gic_of_init mentionné ci-dessus (appel du pointeur de fonction desc->irq_init_cb), etc.

De retour à gic_of_init, s'il ne s'agit pas d'un gic root, il sera traité spécialement. Dans gic_cascade_irq, la fonction de gestionnaire d'interruption d'un certain irq sera définie et la configuration d'interruption correspondant au gic de deuxième niveau sera vérifiée.

Ce qui précède n'est qu'une initialisation gic, mais la manière dont l'interruption réelle est gérée n'est pas très claire. Continuons à voir comment le pilote demande une interruption et comment le CPU gère l'interruption lorsqu'elle survient.

requête_irq

Dans les pilotes quotidiens du noyau, les irq utilisés par les périphériques sont configurés en dts. Une fois que le pilote a obtenu l'irq via dts, il le demande ensuite via la fonction request_irq() et atteint finalement request_threaded_irq. request_threaded_irq complète essentiellement la logique suivante :

  • Via irq_to_desc, recherchez irq_desc dans la liste chaînée statique globale irq_desc_tree ;
  • Créer une irqaction et terminer l'initialisation des fonctions de traitement des interruptions et d'autres informations ;
  • Initialisation complète de l'irq via la fonction __setup_irq ;

Logique __setup_irq :

  • Vérifiez si l'interruption doit créer un thread d'interruption.Si nécessaire, le thread du noyau correspondant sera créé, donc parfois ps peut créer un irq/%d-%sthread du noyau avec un certain nom, qui est créé ici ;
  • S'il s'agit d'une interruption partagée et que d'autres périphériques l'ont déjà demandée, ajoutez irqaction à irq_desc ;
  • Si c'est la première fois que vous postulez, appelez la fonction irq_request_resources de irq_chip pour demander des ressources, définissez le mode de déclenchement (niveau/bord), l'affinité du CPU, activez les interruptions, etc.

FAQ

Pourquoi avons-nous besoin de -32 pour remplir les dts lorsque nous obtenons le numéro d'interruption du manuel de la puce lors de l'écriture d'un pilote de périphérique ?

Comme introduit ci-dessus dans la sous-section gic_of_setup_kvm_info , le pilote gic ignorera les 16 premiers signaux SGI. Dans le même temps, le nœud gic: interruption-controller dans dts est configuré avec le paramètre 0 des interruptions. Le paramètre 0 signifie que le domaine irq ignorera SGI puis Skip interrompus[0] * 16 interruptions, et le paramètre d'interruption 0 dans dts est généralement configuré comme GIC_PPI. GIC_PPI est défini comme 1 sur la plate-forme ARM, qui ignore les 16 interruptions SGI et 16 PPI précédentes, alors remplissez hwirq en dts Quand, -32 est requis.

Qu'arrive-t-il au logiciel après l'interruption ?

Lorsque le signal d'interruption externe arrive, après avoir traversé le distributeur gic, l'interruption est activée et le CPU n'est pas en traitement d'interruption. Si le CPU répond à l'interruption, il entrera en traitement d'interruption. Comment ARM Linux gère-t-il les interruptions ?

Entrer dans le CPU après une interruption passera à la table des vecteurs d'interruption (pour une introduction à la table des vecteurs d'interruption, reportez-vous au lien à la fin de l'article). La table des vecteurs d'interruption utilisée dépend de l'état actuel du processeur et du pointeur de pile. statut.

La table des vecteurs d'interruption arm64 est définie dans les vecteurs de arch/arm64/kernel/entry.S et passe à la fonction de traitement correspondante via 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 n'active pas l'interruption fiq, et le champ invalide est un vecteur d'exception non implémenté, donc lorsqu'une interruption irq se produit, elle passera de la table des vecteurs d'interruption à 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

Passez de l'assembly à handle_arch_irq. handle_arch_irq est un pointeur de fonction. Lorsque vous appelez la fonction __gic_init_bases, pointez-la vers la fonction 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);
}

Dans handle_domain_irq, l'irq logique est trouvé en fonction de hwirq, suivi de generic_handle_irq—>generic_handle_irq_desc—>desc->handle_irq(desc).

Et vers quelle fonction desc->handle_irq pointe-t-il ? Revenez à la fonction ops->alloc du domaine irq. Lors du mappage de hwirq et de l'irq logique, pointez desc->handle_irq vers la fonction handle_fasteoi_irq, et si hwirq est inférieur à 32, pointez vers la fonction handle_percpu_devid_irq.

Le sens de flux de handle_fasteoi_irq est : handle_fasteoi_irq—>handle_irq_event—>handle_irq_event_percpu—>__handle_irq_event_percpu. Ici, chaque gestionnaire sera traité un par un à partir de desc->action. action->handler est la fonction de traitement transmise lors de request_irq ci-dessus. À ce stade, le traitement de base des interruptions est terminé.

Référence importante

Introduction au sous-système d'interruption Linux-arm-gic

Sous-système d'interruption Linux - Analyse du code source du pilote GIC

Interprétation de la table des vecteurs d'exceptions arm64 du noyau Linux 5.14 - interprétation du traitement des interruptions

Je suppose que tu aimes

Origine blog.csdn.net/weixin_41944449/article/details/127122521
conseillé
Classement