[LINUX] Interrupt registration procedure as seen in the source code

Interrupt registration procedure

When the device raises an interrupt message, the cpu is notified of the interrupt and executes the interrupt. At this time, of course, it is necessary to register the interrupt to be executed in advance. This section describes the procedure for registering this interrupt. The content is intended for interrupts using MSI-X. For more information on MSI-X, please refer to the article here.

The interrupt registration procedure is as follows.

  1. Securing interrupt vector
  2. Registering an interrupt handler

First, allocate an interrupt vector to allocate for the device. Next, register the handler in the interrupt descriptor corresponding to the allocated vector number. In the following, each process will be explained while looking at the source code.

Target

Architecture: x86 Kernel: Linux 4.16

1. Securing interrupt vector

The device driver uses pci_alloc_irq_vectors () to allocate interrupt vectors for the device.

\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)

The flow of securing interrupt vectors in MSI-X is as follows.

  1. Create MSI-X table
  2. Securing interrupt vector
  3. Write to MSI-X table

Creating an MSI-X table

The MSI-X table is created as follows.

\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));

First, pci_read_config_word () reads the contents of the control register in the PCI configuration space of the device and stores it in control. Information such as the size of the MSI-X table is stored in the control register.

Next, create an MSI-X table in the MMIO region with msix_map_region (). base is the start address of the MSI-X table.

Securing interrupt vector

Next, secure the interrupt vector. First, create interrupt descriptors for the number of required interrupt vectors.

\kernel\irq\irqdomain.c


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

irq_domain_alloc_descs () creates nr_irqs interrupt descriptors. virq is an index corresponding to the created interrupt descriptor, and you can search for interrupt descriptors from virq. The functions used for the search are as follows.

\kernel\irq\irqdesc.c


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

The relationship between virq and interrupt descriptor is managed by radix tree, and interrupt descriptor is searched by radix_tree_lookup (). Note that virq and interrupt vector numbers are different concepts. virq is an index for managing interrupt descriptors inside the kernel, and can take values up to 8191. On the other hand, the interrupt vector number is a number that manages interrupts for each CPU, and can only take values up to 255.

After creating the interrupt descriptor, allocate the interrupt vector.

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


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

vector_matrix has information on which vector number is used for each cpu. dest is a mask that specifies the cpu to reserve the vector. At this time, multiple cpu can be specified at the same time, but finally one cpu is selected and the vector is secured. The cpu number of the vector allocated by irq_matrix_alloc () is stored in cpu. The return value is vector, and the secured vector number is entered.

After allocating the vector number, save the interrupt descriptor for the combination of the allocated cpu number and vector number in the table. This descriptor is the interrupt descriptor created earlier.

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


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

vector_irq is the table, newcpu is the cpu number, newvec is the vector number, and desc is the interrupt descriptor. You can search for interrupt descriptors by vector number with vector_irq. When searching, do as follows.

\arch\x86\kernel\irq.c


desc = __this_cpu_read(vector_irq[vector]);

You can get table information about the current cpu with \ _ \ _ this_cpu_read ().

Write to MSI-X table

Finally, write to the MSI-X table. Use irq_msi_compose_msg () to set the content to be written.

\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 is what to write to the MSI-X table. cfg-> dest_apicid represents the local APIC id (like the cpu number) of the interrupt destination, and cfg-> vector represents the vector number.

Write to the MSI-X table with __pci_write_msi_msg (). The processing to be performed is as follows.

\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 is a pointer to an entry in the MSI-X table. Write the contents set earlier to the MSI-X table with writel ().

2. Registering an interrupt handler

Register the interrupt handler with 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)

Specify virq for irq (note that it is not a vector number), specify the interrupt handler for handler, and specify the thread that the handler starts in thread_fn.

In request_threaded_irq (), the following processing is performed.

\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);

First, use irq_to_desc () to search for interrupt descriptors from irq. Next, set the handler and device name in action. Finally, use __setup_irq () to set the handler for the interrupt descriptor.

Summary

This article introduced the interrupt registration procedure. Next article will explain the flow of processing when an interrupt occurs.

Recommended Posts

Interrupt registration procedure as seen in the source code
Interrupt processing flow seen in source code
View the implementation source code in iPython
[Python] Read the Flask source code
Get the EDINET code list in Python
Syntax highlighting source code in PowerPoint / Keynote
[Python] Read the source code of Bottle Part 1
Class inheritance practice in python as seen in sklearn
pip install Specify the github repository as the source
Factfulness of the new coronavirus seen in Splunk
The transition of baseball as seen from the data