soc/intel/common: Add new IRQ module
The Intel FSP provides a default set of IO-APIC IRQs for PCI devices, if
the DevIntConfigPtr UPD is not filled in. However, the FSP has a list of
rules that the input IRQ table must conform to:
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
6) PCI Express RPs must be assigned in a special way (FIXED_INT_PIN)
7) Some functions require a unique IRQ number
8) PCI functions must avoid sharing an IRQ with a GPIO pad which routes
its IRQ through IO-APIC.
Since the FSP has no visibility into the actual GPIOs used on the board
when GpioOverride is selected, IRQ conflicts can occur between PCI
devices and GPIOs. This patch gives SoC code the ability to generate a
table of PCI IRQs that will meet the BWG/FSP rules and also not conflict
with GPIO IRQs.
BUG=b:130217151, b:171580862, b:176858827
TEST=Boot with patch series on volteer, verify IO-APIC IRQs in
`/proc/interrupts` match what is expected. No `GSI INT` or
`could not derive routing` messages seen in `dmesg` output.
Verified TPM, touchpad, touchscreen IRQs all function as expected.
Signed-off-by: Tim Wawrzynczak <twawrzynczak@chromium.org>
Change-Id: I0c22a08ce589fa80d0bb1e637422304a3af2045c
Reviewed-on: https://review.coreboot.org/c/coreboot/+/49408
Reviewed-by: Furquan Shaikh <furquan@google.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
diff --git a/src/soc/intel/common/block/irq/Kconfig b/src/soc/intel/common/block/irq/Kconfig
new file mode 100644
index 0000000..d4d9ab1
--- /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 0000000..71b0691
--- /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 0000000..0a62bb5
--- /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;
+}