Wenn das Gerät eine Interrupt-Nachricht auslöst, wird die CPU über den Interrupt benachrichtigt und führt den Interrupt aus. Zu diesem Zeitpunkt ist es natürlich erforderlich, den auszuführenden Interrupt im Voraus zu registrieren. In diesem Abschnitt wird das Verfahren zum Registrieren dieses Interrupts beschrieben. Der Inhalt ist für Interrupts mit MSI-X vorgesehen. Weitere Informationen zu MSI-X finden Sie im Artikel hier.
Das Verfahren zum Registrieren eines Interrupts ist wie folgt.
Weisen Sie zunächst einen Interrupt-Vektor für das Gerät zu. Registrieren Sie als Nächstes den Handler im Interrupt-Deskriptor, der der zugewiesenen Vektornummer entspricht. Im Folgenden wird jeder Prozess anhand des Quellcodes erläutert.
Architektur: x86 Kernel: Linux 4.16
Der Gerätetreiber verwendet pci_alloc_irq_vectors (), um Interruptvektoren für das Gerät zuzuweisen.
\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)
Der Ablauf zum Sichern des Interruptvektors in MSI-X ist wie folgt.
Die MSI-X-Tabelle wird wie folgt erstellt.
\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));
Zunächst liest pci_read_config_word () den Inhalt des Steuerregisters im PCI-Konfigurationsbereich des Geräts und speichert ihn in der Steuerung. Informationen wie die Größe der MSI-X-Tabelle werden im Steuerregister gespeichert.
Erstellen Sie als Nächstes eine MSI-X-Tabelle in der MMIO-Region mit msix_map_region (). base ist die Startadresse der MSI-X-Tabelle.
Als nächstes sichern Sie den Interrupt-Vektor. Erstellen Sie zunächst so viele Interrupt-Deskriptoren wie die Anzahl der erforderlichen Interrupt-Vektoren.
\kernel\irq\irqdomain.c
virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node, affinity);
irq_domain_alloc_descs () erstellt nr_irqs Interrupt-Deskriptoren. virq ist ein Index, der dem erstellten Interrupt-Deskriptor entspricht, und Sie können in virq nach dem Interrupt-Deskriptor suchen. Die für die Suche verwendeten Funktionen sind wie folgt.
\kernel\irq\irqdesc.c
struct irq_desc *irq_to_desc(unsigned int irq)
{
return radix_tree_lookup(&irq_desc_tree, irq);
}
Die Beziehung zwischen virq und dem Interrupt-Deskriptor wird vom Radix-Baum verwaltet, und der Interrupt-Deskriptor wird von radix_tree_lookup () durchsucht. Beachten Sie, dass virq und Interrupt-Vektornummer unterschiedliche Konzepte sind. virq ist ein Index zum Verwalten von Interrupt-Deskriptoren im Kernel und kann Werte bis zu 8191 annehmen. Andererseits ist die Interruptvektornummer eine Nummer, die Interrupts für jede CPU verwaltet und nur Werte bis zu 255 annehmen kann.
Ordnen Sie nach dem Erstellen des Interrupt-Deskriptors den Interrupt-Vektor zu.
\arch\x86\kernel\apic\vector.c
vector = irq_matrix_alloc(vector_matrix, dest, resvd, &cpu);
vector_matrix enthält Informationen darüber, welche Vektornummer für jede CPU verwendet wird. dest ist eine Maske, die die CPU angibt, um den Vektor zu reservieren. Zu diesem Zeitpunkt können mehrere CPUs gleichzeitig angegeben werden, aber schließlich wird eine CPU ausgewählt und der Vektor gesichert. Die CPU-Nummer des von irq_matrix_alloc () zugewiesenen Vektors wird in der CPU gespeichert. Der Rückgabewert ist Vektor und die gesicherte Vektornummer wird eingegeben.
Speichern Sie nach dem Zuweisen der Vektornummer den Interrupt-Deskriptor für die Kombination der CPU-Nummer und der Vektornummer des Zuordnungsziels in der Tabelle. Dieser Deskriptor ist der zuvor erstellte Interrupt-Deskriptor.
\arch\x86\kernel\apic\vector.c
per_cpu(vector_irq, newcpu)[newvec] = desc;
vector_irq ist die Tabelle, newcpu ist die CPU-Nummer, newvec ist die Vektornummer und desc ist der Interrupt-Deskriptor. Mit vector_irq können Sie nach Interrupt-Deskriptoren anhand der Vektornummer suchen. Gehen Sie bei der Suche wie folgt vor.
\arch\x86\kernel\irq.c
desc = __this_cpu_read(vector_irq[vector]);
Mit \ _ \ _ this_cpu_read () können Sie Tabelleninformationen zur aktuellen CPU abrufen.
Schreiben Sie abschließend in die MSI-X-Tabelle. Verwenden Sie irq_msi_compose_msg (), um den zu schreibenden Inhalt festzulegen.
\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 ist das, was in die MSI-X-Tabelle geschrieben werden soll. cfg-> dest_apicid repräsentiert die lokale APIC-ID (wie die CPU-Nummer) des Interrupt-Ziels, und cfg-> vector repräsentiert die Vektornummer.
Schreiben Sie mit __pci_write_msi_msg () in die MSI-X-Tabelle. Die auszuführende Verarbeitung ist wie folgt.
\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 ist ein Zeiger auf einen Eintrag in der MSI-X-Tabelle. Schreiben Sie den zuvor festgelegten Inhalt mit writel () in die MSI-X-Tabelle.
Registrieren Sie den Interrupt-Handler mit 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)
Geben Sie virq für irq an (beachten Sie, dass es sich nicht um eine Vektornummer handelt), geben Sie den Interrupt-Handler für den Handler an und geben Sie den Thread an, den der Handler in thread_fn startet.
In request_threaded_irq () wird die folgende Verarbeitung ausgeführt.
\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);
Verwenden Sie zunächst irq_to_desc (), um nach Interrupt-Deskriptoren aus irq zu suchen. Setzen Sie als Nächstes den Handler und den Gerätenamen in Aktion. Verwenden Sie abschließend __setup_irq (), um den Handler für den Interrupt-Deskriptor festzulegen.
In diesem Artikel wurde das Verfahren zur Interrupt-Registrierung vorgestellt. Nächster Artikel erläutert den Verarbeitungsfluss, wenn ein Interrupt auftritt.
Recommended Posts