summaryrefslogtreecommitdiff
path: root/src/soc
diff options
context:
space:
mode:
Diffstat (limited to 'src/soc')
-rw-r--r--src/soc/intel/common/block/include/intelblocks/irq.h47
-rw-r--r--src/soc/intel/common/block/irq/Kconfig9
-rw-r--r--src/soc/intel/common/block/irq/Makefile.inc1
-rw-r--r--src/soc/intel/common/block/irq/irq.c345
4 files changed, 402 insertions, 0 deletions
diff --git a/src/soc/intel/common/block/include/intelblocks/irq.h b/src/soc/intel/common/block/include/intelblocks/irq.h
new file mode 100644
index 0000000000..1c267052c8
--- /dev/null
+++ b/src/soc/intel/common/block/include/intelblocks/irq.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef SOC_INTEL_COMMON_IRQ_H
+#define SOC_INTEL_COMMON_IRQ_H
+
+#include <southbridge/intel/common/acpi_pirq_gen.h>
+#include <types.h>
+
+#define MAX_FNS 8
+
+#define ANY_PIRQ(x) [PCI_FUNC(x)] = { .fixed_int_pin = PCI_INT_NONE,\
+ .fixed_pirq = PIRQ_INVALID, \
+ .irq_route = IRQ_PIRQ, }
+#define DIRECT_IRQ(x) [PCI_FUNC(x)] = { .fixed_int_pin = PCI_INT_NONE,\
+ .fixed_pirq = PIRQ_INVALID, \
+ .irq_route = IRQ_DIRECT,}
+#define FIXED_INT_ANY_PIRQ(x, pin) [PCI_FUNC(x)] = { .fixed_int_pin = pin, \
+ .fixed_pirq = PIRQ_INVALID, \
+ .irq_route = IRQ_PIRQ,}
+#define FIXED_INT_PIRQ(x, pin, pirq) [PCI_FUNC(x)] = { .fixed_int_pin = pin, \
+ .fixed_pirq = pirq, \
+ .irq_route = IRQ_PIRQ,}
+
+struct slot_irq_constraints {
+ unsigned int slot;
+ struct {
+ enum pci_pin fixed_int_pin;
+ enum pirq fixed_pirq;
+ enum {
+ IRQ_NONE = 0, /* Empty function */
+ IRQ_PIRQ = 1, /* PIRQ routing, i.e. IRQs 16 - 23 */
+ IRQ_DIRECT = 2, /* No PIRQ routing, i.e., IRQs > 23 */
+ } irq_route;
+ } fns[MAX_FNS];
+};
+
+struct pci_irq_entry {
+ unsigned int devfn;
+ enum pci_pin pin;
+ unsigned int irq;
+ struct pci_irq_entry *next;
+};
+
+const struct pci_irq_entry *assign_pci_irqs(const struct slot_irq_constraints *constraints,
+ size_t num_slots);
+
+#endif /* SOC_INTEL_COMMON_IRQ_H */
diff --git a/src/soc/intel/common/block/irq/Kconfig b/src/soc/intel/common/block/irq/Kconfig
new file mode 100644
index 0000000000..d4d9ab1b03
--- /dev/null
+++ b/src/soc/intel/common/block/irq/Kconfig
@@ -0,0 +1,9 @@
+config SOC_INTEL_COMMON_BLOCK_IRQ
+ bool
+ select SOC_INTEL_COMMON_BLOCK_GPIO
+ help
+ Intel common block support for assigning PCI IRQs dynamically. This
+ allows coreboot to control the IRQ assignments. They are passed to the
+ FSP via UPD, and also exposed to the OS in ACPI tables. The SoC must
+ provide a list of IRQ programming constraints; this module will avoid
+ IRQs that are used by GPIOs routed to IOAPIC.
diff --git a/src/soc/intel/common/block/irq/Makefile.inc b/src/soc/intel/common/block/irq/Makefile.inc
new file mode 100644
index 0000000000..71b069105c
--- /dev/null
+++ b/src/soc/intel/common/block/irq/Makefile.inc
@@ -0,0 +1 @@
+ramstage-$(CONFIG_SOC_INTEL_COMMON_BLOCK_IRQ) += irq.c
diff --git a/src/soc/intel/common/block/irq/irq.c b/src/soc/intel/common/block/irq/irq.c
new file mode 100644
index 0000000000..0a62bb58c9
--- /dev/null
+++ b/src/soc/intel/common/block/irq/irq.c
@@ -0,0 +1,345 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <assert.h>
+#include <console/console.h>
+#include <device/pci.h>
+#include <device/pci_def.h>
+#include <intelblocks/gpio.h>
+#include <intelblocks/irq.h>
+#include <southbridge/intel/common/acpi_pirq_gen.h>
+#include <stdlib.h>
+#include <string.h>
+#include <types.h>
+
+#define MIN_SHARED_IRQ 16
+#define MAX_SHARED_IRQ 23
+#define TOTAL_SHARED_IRQ (MAX_SHARED_IRQ - MIN_SHARED_IRQ + 1)
+#define MAX_IRQS 120
+
+#define IDX2PIN(i) (enum pci_pin)((i) + PCI_INT_A)
+#define PIN2IDX(p) (size_t)((p) - PCI_INT_A)
+#define INVALID_IRQ -1
+
+struct pin_info {
+ enum pin_state {
+ FREE_PIN,
+ SHARED_IRQ_PIN,
+ UNIQUE_IRQ_PIN,
+ } pin_state;
+ unsigned int usage_count;
+ unsigned int irq;
+};
+
+static unsigned int irq_share_count[TOTAL_SHARED_IRQ];
+
+/*
+ * Assign PCI IRQs & pins according to controller rules.
+ *
+ * This information is provided to the FSP in order for it to do the
+ * programming; this is required because the FSP is also responsible for
+ * enabling some PCI devices so they will show up on their respective PCI
+ * buses. The FSP & PCH BIOS Specification contain rules for how certain IPs
+ * require their interrupt pin and interrupt line to be programmed.
+ *
+ * IOAPIC IRQs are used for PCI devices & GPIOs. The GPIO IRQs are fixed in
+ * hardware (the IRQ field is RO), and often start at 24, which means
+ * conflicts with PCI devices (if using the default FSP configuration) are very
+ * possible.
+ *
+ * These are the rules:
+ * 1) One entry per slot/function
+ * 2) Functions using PIRQs must use IOxAPIC IRQs 16-23
+ * 3) Single-function devices must use INTA
+ * 4) Each slot must have consistent INTx<->PIRQy mappings
+ * 5) Some functions have special interrupt pin requirements (FIXED_INT_ANY_PIRQ)
+ * 6) PCI Express RPs must be assigned in a special way (FIXED_INT_PIRQ)
+ * 7) Some functions require a unique IRQ number (mostly LPSS devices, DIRECT_IRQ)
+ * 8) PCI functions must avoid sharing an IRQ with a GPIO pad which routes its
+ * IRQ through IO-APIC.
+ */
+
+static int find_free_unique_irq(void)
+{
+ static unsigned int next_irq = MAX_SHARED_IRQ + 1;
+
+ while (next_irq < MAX_IRQS && gpio_routes_ioapic_irq(next_irq))
+ ++next_irq;
+
+ if (next_irq == MAX_IRQS)
+ return INVALID_IRQ;
+
+ return next_irq++;
+}
+
+static enum pci_pin find_free_pin(const struct pin_info pin_info[PCI_INT_MAX])
+{
+ for (size_t pin_idx = 0; pin_idx < PCI_INT_MAX; pin_idx++) {
+ if (pin_info[pin_idx].pin_state == FREE_PIN)
+ return IDX2PIN(pin_idx);
+ }
+
+ return PCI_INT_NONE;
+}
+
+static enum pci_pin find_shareable_pin(const struct pin_info pin_info[PCI_INT_MAX])
+{
+ unsigned int least_shared = 255;
+ int least_index = -1;
+
+ for (size_t pin_idx = 0; pin_idx < PCI_INT_MAX; pin_idx++) {
+ if (pin_info[pin_idx].pin_state == SHARED_IRQ_PIN &&
+ pin_info[pin_idx].usage_count < least_shared) {
+ least_shared = pin_info[pin_idx].usage_count;
+ least_index = pin_idx;
+ }
+ }
+
+ if (least_index < 0)
+ return PCI_INT_NONE;
+
+ return IDX2PIN(least_index);
+}
+
+static enum pirq find_global_least_used_pirq(void)
+{
+ unsigned int least_shared = 255;
+ int least_index = -1;
+
+ for (size_t i = 0; i < TOTAL_SHARED_IRQ; i++) {
+ if (irq_share_count[i] < least_shared) {
+ least_shared = irq_share_count[i];
+ least_index = i;
+ }
+ }
+
+ if (least_index >= 0)
+ return (enum pirq)least_index + PIRQ_A;
+
+ return PIRQ_INVALID;
+}
+
+
+static int pirq_to_irq(enum pirq pirq)
+{
+ return pirq_idx(pirq) + MIN_SHARED_IRQ;
+}
+
+static bool assign_pirq(struct pin_info pin_info[PCI_INT_MAX], enum pci_pin pin, enum pirq pirq)
+{
+ if (pirq < PIRQ_A || pirq > PIRQ_H) {
+ printk(BIOS_ERR, "ERROR: Invalid pirq constraint %u\n", pirq);
+ return false;
+ }
+
+ const int irq = pirq_to_irq(pirq);
+ pin_info[PIN2IDX(pin)].irq = irq;
+ irq_share_count[pirq_idx(pirq)]++;
+ return true;
+}
+
+static bool assign_pin(enum pci_pin pin, unsigned int fn, enum pin_state state,
+ struct pin_info *pin_info,
+ enum pci_pin fn_pin_map[MAX_FNS])
+{
+ if (pin < PCI_INT_A || pin > PCI_INT_D) {
+ printk(BIOS_ERR, "ERROR: Invalid pin constraint %u\n", pin);
+ return false;
+ }
+
+ const size_t pin_idx = PIN2IDX(pin);
+ pin_info[pin_idx].pin_state = state;
+ pin_info[pin_idx].usage_count++;
+ fn_pin_map[fn] = pin;
+
+ return true;
+}
+
+static bool assign_fixed_pins(const struct slot_irq_constraints *constraints,
+ struct pin_info *pin_info, enum pci_pin fn_pin_map[MAX_FNS])
+{
+ for (size_t i = 0; i < MAX_FNS; i++) {
+ const enum pci_pin fixed_int_pin = constraints->fns[i].fixed_int_pin;
+ if (fixed_int_pin == PCI_INT_NONE)
+ continue;
+
+ if (!assign_pin(fixed_int_pin, i, SHARED_IRQ_PIN, pin_info, fn_pin_map))
+ return false;
+ }
+
+ return true;
+}
+
+static bool assign_fixed_pirqs(const struct slot_irq_constraints *constraints,
+ struct pin_info *pin_info, enum pci_pin fn_pin_map[MAX_FNS])
+{
+ for (size_t i = 0; i < MAX_FNS; i++) {
+ const enum pirq fixed_pirq = constraints->fns[i].fixed_pirq;
+ if (fixed_pirq == PIRQ_INVALID)
+ continue;
+
+ /* A constraint with a fixed pirq is assumed to also have a
+ fixed pin */
+ const enum pci_pin pin = fn_pin_map[i];
+ if (pin == PCI_INT_NONE) {
+ printk(BIOS_ERR, "ERROR: Slot %u, pirq %u, no pin for function %lu\n",
+ constraints->slot, fixed_pirq, i);
+ return false;
+ }
+
+ if (!assign_pirq(pin_info, pin, fixed_pirq))
+ return false;
+ }
+
+ return true;
+}
+
+static bool assign_direct_irqs(const struct slot_irq_constraints *constraints,
+ struct pin_info *pin_info, enum pci_pin fn_pin_map[MAX_FNS])
+{
+ for (size_t i = 0; i < MAX_FNS; i++) {
+ if (constraints->fns[i].irq_route != IRQ_DIRECT)
+ continue;
+
+ enum pci_pin pin = find_free_pin(pin_info);
+ if (pin == PCI_INT_NONE)
+ return false;
+
+ if (!assign_pin(pin, i, UNIQUE_IRQ_PIN, pin_info, fn_pin_map))
+ return false;
+
+ const int irq = find_free_unique_irq();
+ if (irq == INVALID_IRQ) {
+ printk(BIOS_ERR, "ERROR: No free unique IRQs found\n");
+ return false;
+ }
+
+ const size_t pin_idx = PIN2IDX(pin);
+ pin_info[pin_idx].irq = irq;
+ }
+
+ return true;
+}
+
+static bool assign_shareable_pins(const struct slot_irq_constraints *constraints,
+ struct pin_info *pin_info, enum pci_pin fn_pin_map[MAX_FNS])
+{
+ for (size_t i = 0; i < MAX_FNS; i++) {
+ if (constraints->fns[i].irq_route != IRQ_PIRQ)
+ continue;
+
+ if (fn_pin_map[i] == PCI_INT_NONE) {
+ enum pci_pin pin = find_free_pin(pin_info);
+ if (pin == PCI_INT_NONE) {
+ pin = find_shareable_pin(pin_info);
+
+ if (pin == PCI_INT_NONE) {
+ printk(BIOS_ERR, "ERROR: No shareable pins found\n");
+ return false;
+ }
+ }
+
+ if (!assign_pin(pin, i, SHARED_IRQ_PIN, pin_info, fn_pin_map))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool assign_pirqs(struct pin_info pin_info[PCI_INT_MAX])
+{
+ for (size_t pin_idx = 0; pin_idx < PCI_INT_MAX; pin_idx++) {
+ if (pin_info[pin_idx].pin_state != SHARED_IRQ_PIN || pin_info[pin_idx].irq != 0)
+ continue;
+
+ enum pirq pirq = find_global_least_used_pirq();
+ if (pirq == PIRQ_INVALID)
+ return false;
+
+ if (!assign_pirq(pin_info, IDX2PIN(pin_idx), pirq))
+ return false;
+ }
+
+ return true;
+}
+
+static void add_entry(struct pci_irq_entry **head, pci_devfn_t devfn, enum pci_pin pin,
+ unsigned int irq)
+{
+ struct pci_irq_entry *entry = malloc(sizeof(*entry));
+ struct pci_irq_entry **tmp = head;
+
+ entry->devfn = devfn;
+ entry->pin = pin;
+ entry->irq = irq;
+ entry->next = NULL;
+
+ while (*tmp)
+ tmp = &(*tmp)->next;
+
+ *tmp = entry;
+}
+
+static void add_slot_entries(struct pci_irq_entry **head, unsigned int slot,
+ struct pin_info pin_info[PCI_INT_MAX],
+ const enum pci_pin fn_pin_map[MAX_FNS])
+{
+ for (size_t fn = 0; fn < MAX_FNS; fn++) {
+ if (fn_pin_map[fn] == PCI_INT_NONE)
+ continue;
+
+ const size_t pin_idx = PIN2IDX(fn_pin_map[fn]);
+ add_entry(head, PCI_DEVFN(slot, fn), fn_pin_map[fn], pin_info[pin_idx].irq);
+ }
+}
+
+static bool assign_slot(struct pci_irq_entry **head,
+ const struct slot_irq_constraints *constraints)
+{
+ struct pin_info pin_info[PCI_INT_MAX] = {0};
+ enum pci_pin fn_pin_map[MAX_FNS] = {0};
+
+ /* The order in which pins are assigned is important in that strict constraints must
+ * be resolved first. This means fixed_int_pin -> fixed_pirq -> direct route ->
+ * shared pins -> shared pirqs
+ */
+ if (!assign_fixed_pins(constraints, pin_info, fn_pin_map))
+ return false;
+
+ if (!assign_fixed_pirqs(constraints, pin_info, fn_pin_map))
+ return false;
+
+ if (!assign_direct_irqs(constraints, pin_info, fn_pin_map))
+ return false;
+
+ if (!assign_shareable_pins(constraints, pin_info, fn_pin_map))
+ return false;
+
+ if (!assign_pirqs(pin_info))
+ return false;
+
+ add_slot_entries(head, constraints->slot, pin_info, fn_pin_map);
+ return true;
+}
+
+const struct pci_irq_entry *assign_pci_irqs(const struct slot_irq_constraints *constraints,
+ size_t num_slots)
+{
+ struct pci_irq_entry *entries = NULL;
+
+ for (size_t i = 0; i < num_slots; i++) {
+ if (!assign_slot(&entries, &constraints[i]))
+ return NULL;
+ }
+
+ const struct pci_irq_entry *entry = entries;
+ while (entry) {
+ printk(BIOS_INFO, "PCI %2X.%X, %s, using IRQ #%d\n",
+ PCI_SLOT(entry->devfn), PCI_FUNC(entry->devfn),
+ pin_to_str(entry->pin), entry->irq);
+
+ entry = entry->next;
+ }
+
+ return entries;
+}