[LINUX] Comment enregistrer une interruption comme vu dans le code source

Procédure d'enregistrement d'interruption

Lorsque l'appareil émet un message d'interruption, le processeur est averti de l'interruption et exécute l'interruption. A ce moment, bien entendu, il est nécessaire d'enregistrer à l'avance l'interruption à exécuter. Cette section décrit la procédure d'enregistrement de cette interruption. Le contenu est destiné aux interruptions utilisant MSI-X. Pour plus d'informations sur MSI-X, veuillez consulter l'article ici.

La procédure d'enregistrement d'une interruption est la suivante.

  1. Sécurisation du vecteur d'interruption
  2. Enregistrement d'un gestionnaire d'interruption

Tout d'abord, allouez un vecteur d'interruption à allouer au périphérique. Ensuite, enregistrez le gestionnaire dans le descripteur d'interruption correspondant au numéro de vecteur alloué. Ci-dessous, chaque processus est expliqué en regardant le code source.

Cible

Architecture: x86 Noyau: Linux 4.16

1. Sécurisation du vecteur d'interruption

Le pilote de périphérique utilise pci_alloc_irq_vectors () pour allouer des vecteurs d'interruption pour le périphérique.

\include\linux\pci.h


static inline int
pci_alloc_irq_vectors(struct pci_dev *dev, unsigned int min_vecs,
		      unsigned int max_vecs, unsigned int flags)

Le déroulement de la sécurisation du vecteur d'interruption dans MSI-X est le suivant.

  1. Créez une table MSI-X
  2. Sécurisation du vecteur d'interruption
  3. Écrire dans la table MSI-X

Créer une table MSI-X

La table MSI-X est créée comme suit.

\drivers\pci\msi.c


pci_read_config_word(dev, dev->msix_cap + PCI_MSIX_FLAGS, &control);

base = msix_map_region(dev, msix_table_size(control));

Tout d'abord, pci_read_config_word () lit le contenu du registre de contrôle dans l'espace de configuration PCI du périphérique et le stocke sous contrôle. Des informations telles que la taille de la table MSI-X sont stockées dans le registre de contrôle.

Ensuite, créez une table MSI-X dans la région MMIO avec msix_map_region (). base est l'adresse de début de la table MSI-X.

Sécuriser le vecteur d'interruption

Ensuite, sécurisez le vecteur d'interruption. Commencez par créer autant de descripteurs d'interruption que le nombre de vecteurs d'interruption requis.

\kernel\irq\irqdomain.c


virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node, affinity);

irq_domain_alloc_descs () crée des descripteurs d'interruption nr_irqs. virq est un index correspondant au descripteur d'interruption créé, et vous pouvez rechercher le descripteur d'interruption depuis virq. Les fonctions utilisées pour la recherche sont les suivantes.

\kernel\irq\irqdesc.c


struct irq_desc *irq_to_desc(unsigned int irq)
{
	return radix_tree_lookup(&irq_desc_tree, irq);
}

La relation entre virq et le descripteur d'interruption est gérée par l'arbre de base et le descripteur d'interruption est recherché par radix_tree_lookup (). Notez que virq et nombre de vecteurs d'interruption sont des concepts différents. virq est un index de gestion des descripteurs d'interruption à l'intérieur du noyau, et peut prendre des valeurs jusqu'à 8191. D'autre part, le numéro de vecteur d'interruption est un nombre qui gère les interruptions pour chaque CPU et ne peut prendre que des valeurs jusqu'à 255.

Après avoir créé le descripteur d'interruption, attribuez le vecteur d'interruption.

\arch\x86\kernel\apic\vector.c


vector = irq_matrix_alloc(vector_matrix, dest, resvd, &cpu);

vector_matrix a des informations sur le numéro de vecteur utilisé pour chaque processeur. dest est un masque qui spécifie le processeur pour réserver le vecteur. À ce stade, plusieurs processeurs peuvent être spécifiés en même temps, mais finalement un processeur est sélectionné et le vecteur est sécurisé. Le numéro cpu du vecteur alloué par irq_matrix_alloc () est stocké dans cpu. La valeur de retour est vector et le numéro de vecteur sécurisé est entré.

Après avoir attribué le numéro de vecteur, enregistrez le descripteur d'interruption pour la combinaison du numéro de processeur et du numéro de vecteur de la destination d'allocation dans la table. Ce descripteur est le descripteur d'interruption créé précédemment.

\arch\x86\kernel\apic\vector.c


per_cpu(vector_irq, newcpu)[newvec] = desc;

vector_irq est la table, newcpu est le numéro du processeur, newvec est le numéro du vecteur et desc est le descripteur d'interruption. Vous pouvez rechercher des descripteurs d'interruption par numéro de vecteur avec vector_irq. Lors de la recherche, procédez comme suit.

\arch\x86\kernel\irq.c


desc = __this_cpu_read(vector_irq[vector]);

Vous pouvez obtenir des informations de table sur le processeur actuel avec \ _ \ _ this_cpu_read ().

Écrire dans la table MSI-X

Enfin, écrivez dans la table MSI-X. Utilisez irq_msi_compose_msg () pour définir le contenu à écrire.

\arch\x86\kernel\apic\msi.c


static void irq_msi_compose_msg(struct irq_data *data, struct msi_msg *msg)
{
	struct irq_cfg *cfg = irqd_cfg(data);

	msg->address_hi = MSI_ADDR_BASE_HI;

	if (x2apic_enabled())
		msg->address_hi |= MSI_ADDR_EXT_DEST_ID(cfg->dest_apicid);

	msg->address_lo =
		MSI_ADDR_BASE_LO |
		((apic->irq_dest_mode == 0) ?
			MSI_ADDR_DEST_MODE_PHYSICAL :
			MSI_ADDR_DEST_MODE_LOGICAL) |
		MSI_ADDR_REDIRECTION_CPU |
		MSI_ADDR_DEST_ID(cfg->dest_apicid);

	msg->data =
		MSI_DATA_TRIGGER_EDGE |
		MSI_DATA_LEVEL_ASSERT |
		MSI_DATA_DELIVERY_FIXED |
		MSI_DATA_VECTOR(cfg->vector);
}

msg-> address_hi, msg-> address_lo, msg-> data est ce que vous écrivez dans la table MSI-X. cfg-> dest_apicid représente l'identifiant APIC local (comme le numéro du processeur) de la destination d'interruption, et cfg-> vector représente le numéro du vecteur.

Écrivez dans la table MSI-X avec __pci_write_msi_msg (). Le traitement à effectuer est le suivant.

\drivers\pci\msi.c


void __iomem *base = pci_msix_desc_addr(entry);

writel(msg->address_lo, base + PCI_MSIX_ENTRY_LOWER_ADDR);
writel(msg->address_hi, base + PCI_MSIX_ENTRY_UPPER_ADDR);
writel(msg->data, base + PCI_MSIX_ENTRY_DATA);

base est un pointeur vers une entrée dans la table MSI-X. Écrivez le contenu défini précédemment dans la table MSI-X avec writel ().

2. Enregistrement d'un gestionnaire d'interruption

Enregistrez le gestionnaire d'interruption avec request_threaded_irq ().

\kernel\irq\manage.c


int request_threaded_irq(unsigned int irq, irq_handler_t handler,
			 irq_handler_t thread_fn, unsigned long irqflags,
			 const char *devname, void *dev_id)

Spécifiez virq pour irq (notez qu'il ne s'agit pas d'un nombre vectoriel), spécifiez le gestionnaire d'interruption du gestionnaire et spécifiez le thread que le gestionnaire démarre dans thread_fn.

Dans request_threaded_irq (), le traitement suivant est effectué.

\kernel\irq\manage.c


desc = irq_to_desc(irq);

action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;

retval = __setup_irq(irq, desc, action);

Tout d'abord, utilisez irq_to_desc () pour rechercher des descripteurs d'interruption d'irq. Ensuite, définissez le gestionnaire et le nom de l'appareil en action. Enfin, utilisez __setup_irq () pour définir le gestionnaire du descripteur d'interruption.

Résumé

Cet article a présenté la procédure d'enregistrement d'interruption. Article suivant expliquera le déroulement du traitement lorsqu'une interruption se produit.

Recommended Posts

Comment enregistrer une interruption comme vu dans le code source
Flux de traitement d'interruption vu dans le code source
Afficher le code source de l'implémentation dans iPython
[Python] Lire le code source de Flask
Obtenir la liste de codes EDINET en Python
La syntaxe met en évidence le code source dans PowerPoint / Keynote
[Python] Lire le code source de Bottle Part 1
Pratique de l'héritage de classe en python comme vu dans sklearn
pip install Spécifiez le référentiel github comme source
Transition du baseball vue à partir des données