Attention is currently required from: Martin Roth, Patrick Rudolph. Tim Wawrzynczak has uploaded this change for review. ( https://review.coreboot.org/c/coreboot/+/50895 )
Change subject: soc/intel/common: Add new IRQ module ......................................................................
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 FSPs rules and also not conflict with GPIO IRQs.
BUG=b:171580862 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: I3f22a0e2e4ef0cedcecd6bd6650f904994a8a620 --- A src/soc/intel/common/block/include/intelblocks/irq.h A src/soc/intel/common/block/irq/Kconfig A src/soc/intel/common/block/irq/Makefile.inc A src/soc/intel/common/block/irq/irq.c 4 files changed, 431 insertions(+), 0 deletions(-)
git pull ssh://review.coreboot.org:29418/coreboot refs/changes/95/50895/1
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 0000000..8548f1e --- /dev/null +++ b/src/soc/intel/common/block/include/intelblocks/irq.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef SOC_INTEL_COMMON_IRQ_H +#define SOC_INTEL_COMMON_IRQ_H + +#include <device/device.h> +#include <device/pci_def.h> +#include <device/pci.h> +#include <southbridge/intel/common/acpi_pirq_gen.h> +#include <types.h> + +#define MAX_SLOTS 32 +#define MAX_FNS 8 + +enum irq_constraint_type { + UNKNOWN, + PIRQ, /* Can use a shared PIRQx (16-23) */ + FIXED_INT_PIN, /* Requires a specific INTx# pin */ + UNIQUE_IRQ, /* Requires its own IRQ (> 23) */ +}; + +struct irq_constraint { + pci_devfn_t devfn; + enum irq_constraint_type type; + enum pci_pin pci_pin; +}; + +struct soc_irq_constraints { + struct irq_constraint constraints[MAX_SLOTS][MAX_FNS]; +}; + +struct pci_irq_entry { + pci_devfn_t devfn; + enum pci_pin pin; + unsigned int irq; +}; + +const struct pci_irq_entry *assign_pci_irqs(size_t *num); +const struct soc_irq_constraints *soc_irq_constraints(void); + +#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 0000000..309d19c --- /dev/null +++ b/src/soc/intel/common/block/irq/Kconfig @@ -0,0 +1,8 @@ +config SOC_INTEL_COMMON_BLOCK_IRQ + bool + select SOC_INTEL_COMMON_BLOCK_GPIO + help + Intel common block support for assigning PCI IRQs dynamically, + so they can be passed to the FSP via UPD. The SoC must provide + a list of IRQ programming constraints; this module will not + use any IRQs that are used by GPIOs that are 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..0c4dfe4 --- /dev/null +++ b/src/soc/intel/common/block/irq/irq.c @@ -0,0 +1,381 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include <console/console.h> +#include <device/device.h> +#include <device/pci.h> +#include <intelblocks/gpio.h> +#include <intelblocks/irq.h> +#include <intelblocks/lpc_lib.h> +#include <southbridge/intel/common/acpi_pirq_gen.h> +#include <string.h> +#include <types.h> + +#define MIN_IRQ 16 +#define MAX_SHARED_IRQ 23 +#define TOTAL_SHARED_IRQ (MAX_SHARED_IRQ - MIN_IRQ + 1) +#define MAX_IRQ 119 +#define TOTAL_IRQ (MAX_IRQ - MIN_IRQ + 1) +#define MAX_IRQ_ENTRIES 120 + +#define EMPTY_FN 0xff +#define MAX_PINS 4 + +#define IDX2PIN(i) (enum pci_pin)((i) + PCI_INT_A) +#define PIN2IDX(p) (size_t)((p) - PCI_INT_A) +#define INVALID_IRQ -1 + +#define MAX_ENTRIES 64 + +static struct pci_irq_entry entries[MAX_ENTRIES]; +static size_t entry_count; + +/* + * 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 + * 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. + */ + +static bool add_entry(pci_devfn_t devfn, enum pci_pin pin, unsigned int irq) +{ + if (entry_count >= MAX_ENTRIES) + return false; + + entries[entry_count].devfn = devfn; + entries[entry_count].pin = pin; + entries[entry_count].irq = irq; + ++entry_count; + return true; +} + +static bool is_valid_pin(enum pci_pin pin) +{ + return pin >= PCI_INT_A && pin <= PCI_INT_D; +} + +static int get_pci_irq_pin(pci_devfn_t devfn) +{ + const uint8_t pin = pci_s_read_config8(devfn, PCI_INTERRUPT_PIN); + if (!is_valid_pin(pin)) + return PCI_INT_NONE; + + return pin; +} + +static int find_free_unique_irq(void) +{ + static unsigned int next_irq = MAX_SHARED_IRQ + 1; + + while (next_irq < MAX_IRQ && gpio_routes_ioapic_irq(next_irq)) + ++next_irq; + + if (next_irq > MAX_IRQ) + return INVALID_IRQ; + + return next_irq++; +} + +static enum pci_pin find_free_pin(enum pci_pin fn_pin_map[MAX_FNS]) +{ + size_t i; + bool used[MAX_PINS] = {false}; + + for (i = 0; i < MAX_FNS; i++) { + enum pci_pin pin = fn_pin_map[i]; + if (is_valid_pin(pin)) + used[PIN2IDX(pin)] = true; + } + + for (i = 0; i < MAX_PINS; i++) + if (!used[i]) + return (enum pci_pin)IDX2PIN(i); + + return PCI_INT_NONE; +} + +static enum pci_pin find_shareable_pin(enum pci_pin fn_pin_map[MAX_FNS], + uint8_t pin_irq_map[MAX_PINS]) +{ + unsigned int share_count[MAX_PINS] = {0}; + enum pci_pin shared = PCI_INT_NONE; + int min_shared = MAX_FNS; + size_t i; + + for (i = 0; i < MAX_FNS; i++) { + enum pci_pin pin = fn_pin_map[i]; + /* + * <= MAX_SHARED_IRQ to also catch unmapped pins, which will get + * assigned a PIRQ eventually. + */ + if (is_valid_pin(pin) && pin_irq_map[PIN2IDX(pin)] <= MAX_SHARED_IRQ) + share_count[i]++; + } + + for (i = 0; i < MAX_PINS; i++) { + if (pin_irq_map[i] <= MAX_SHARED_IRQ && share_count[i] < min_shared) { + min_shared = share_count[i]; + shared = IDX2PIN(i); + } + } + + return shared; +} + +static int find_global_least_used_pirq(const uint8_t pin_irq_map[MAX_PINS]) +{ + unsigned int share_count[TOTAL_SHARED_IRQ] = {0}; + unsigned int least_shared; + enum pci_pin pin; + int least_index; + size_t i; + + for (i = 0; i < entry_count; i++) { + int irq = entries[i].irq; + if (irq >= MIN_IRQ && irq <= MAX_SHARED_IRQ) + share_count[irq - MIN_IRQ]++; + } + + for (pin = PCI_INT_A; pin <= PCI_INT_D; pin++) { + int irq = pin_irq_map[PIN2IDX(pin)]; + if (irq >= MIN_IRQ && irq <= MAX_SHARED_IRQ) + share_count[irq - MIN_IRQ]++; + } + + least_index = -1; + least_shared = 255; + + for (i = 0; i < TOTAL_SHARED_IRQ; i++) { + if (share_count[i] < least_shared) { + least_shared = share_count[i]; + least_index = i; + } + } + + if (least_index >= 0) + return least_index + MIN_IRQ; + + return INVALID_IRQ; +} + +static int mark_empty_fns(const struct irq_constraint *constraints, + enum pci_pin fn_pin_map[MAX_FNS]) +{ + int empty_count = 0; + size_t i; + + memset(fn_pin_map, EMPTY_FN, MAX_FNS * sizeof(enum pci_pin)); + + for (i = 0; i < MAX_FNS; i++) { + if (constraints[i].type == UNKNOWN) + ++empty_count; + else + fn_pin_map[i] = PCI_INT_NONE; + } + + return empty_count; +} + +static bool fill_fixed_pins(const struct irq_constraint *constraints, + enum pci_pin fn_pin_map[MAX_FNS]) +{ + size_t i; + + for (i = 0; i < MAX_FNS; i++) { + if (constraints[i].type == FIXED_INT_PIN) { + if (!is_valid_pin(constraints[i].pci_pin)) { + return false; + } + + fn_pin_map[i] = constraints[i].pci_pin; + } else if (constraints[i].type != UNKNOWN) { + enum pci_pin pin = get_pci_irq_pin(constraints[i].devfn); + if (pin != PCI_INT_NONE) + fn_pin_map[i] = pin; + } + } + + return true; +} + +static bool assign_unique_irqs(const struct irq_constraint *constraints, + enum pci_pin fn_pin_map[MAX_FNS], + uint8_t pin_irq_map[MAX_PINS]) +{ + size_t i; + + for (i = 0; i < MAX_FNS; i++) { + if (constraints[i].type == UNIQUE_IRQ) { + enum pci_pin pin; + if (!is_valid_pin(fn_pin_map[i])) { + pin = find_free_pin(fn_pin_map); + if (pin == PCI_INT_NONE) { + printk(BIOS_ERR, "ERROR: %s: No free pins left for UNIQUE IRQ\n", __func__); + return false; + } + + fn_pin_map[i] = pin; + } else { + pin = fn_pin_map[i]; + } + + int irq = find_free_unique_irq(); + if (irq == INVALID_IRQ) { + printk(BIOS_ERR, "ERROR: No free unique IRQs?\n"); + return false; + } + + pin_irq_map[PIN2IDX(pin)] = irq; + } + } + + return true; +} + +static bool assign_pins(const struct irq_constraint *constraints, + enum pci_pin fn_pin_map[MAX_FNS], + uint8_t pin_irq_map[MAX_PINS]) +{ + size_t i; + + for (i = 0; i < MAX_FNS; i++) { + if (fn_pin_map[i] == PCI_INT_NONE) { + enum pci_pin pin = find_free_pin(fn_pin_map); + if (pin == PCI_INT_NONE) + pin = find_shareable_pin(fn_pin_map, pin_irq_map); + + if (pin == PCI_INT_NONE) { + printk(BIOS_ERR, "ERROR: fn %lu no free or shared pins?\n", i); + return false; + } + + fn_pin_map[i] = pin; + } + } + + return true; +} + +static bool is_pin_used(const enum pci_pin fn_pin_map[MAX_FNS], enum pci_pin pin) +{ + size_t i; + + for (i = 0; i < MAX_FNS; i++) + if (fn_pin_map[i] == pin) + return true; + + return false; +} + +static bool assign_pirqs(enum pci_pin fn_pin_map[MAX_FNS], uint8_t pin_irq_map[MAX_PINS]) +{ + enum pci_pin pin; + + for (pin = PCI_INT_A; pin <= PCI_INT_D; pin++) { + if (is_pin_used(fn_pin_map, pin) && !pin_irq_map[PIN2IDX(pin)]) { + int irq = find_global_least_used_pirq(pin_irq_map); + if (irq == INVALID_IRQ) { + printk(BIOS_ERR, "ERROR: %s no unused pirqs?\n", __func__); + return false; + } + + pin_irq_map[PIN2IDX(pin)] = irq; + } + } + + return true; +} + +static bool assign_slot(const struct irq_constraint *constraints) +{ + const unsigned int slot = PCI_SLOT(constraints->devfn); + enum pci_pin fn_pin_map[MAX_FNS] = {PCI_INT_NONE}; + uint8_t pin_irq_map[MAX_PINS] = {0}; + size_t i; + + if (mark_empty_fns(constraints, fn_pin_map) == MAX_FNS) + return true; + + if (!fill_fixed_pins(constraints, fn_pin_map) || + !assign_unique_irqs(constraints, fn_pin_map, pin_irq_map) || + !assign_pins(constraints, fn_pin_map, pin_irq_map) || + !assign_pirqs(fn_pin_map, pin_irq_map)) + return false; + + for (i = 0; i < MAX_FNS; i++) { + enum pci_pin pin = fn_pin_map[i]; + if (is_valid_pin(pin) && pin_irq_map[PIN2IDX(pin)]) + add_entry(PCI_DEVFN(slot, i), pin, pin_irq_map[PIN2IDX(pin)]); + } + + return true; +} + +const struct pci_irq_entry *assign_pci_irqs(size_t *num) +{ + const struct soc_irq_constraints *list; + size_t i; + + list = soc_irq_constraints(); + + for (i = 0; i < MAX_SLOTS; i++) { + if (!assign_slot(list->constraints[i])) { + *num = 0; + return NULL; + } + } + + for (i = 0; i < entry_count; i++) + printk(BIOS_INFO, "PCI %2X.%X, %s, using IRQ #%d\n", + PCI_SLOT(entries[i].devfn), PCI_FUNC(entries[i].devfn), + pin_to_str(entries[i].pin), entries[i].irq); + + *num = entry_count; + return entries; +} + +int intel_common_map_pic(enum pirq pirq) +{ + size_t num; + const uint8_t *pirq_routing_table = lpc_get_pch_pirq_routing(&num); + if (pirq >= PIRQ_A && pirq <= PIRQ_H && pirq <= num) + return pirq_routing_table[pirq - PIRQ_A]; + + return PIRQ_NONE; +} + +int intel_create_pirq_matrix(char matrix[32][4]) +{ + size_t i; + int count = 0; + + for (i = 0; i < entry_count; i++) { + unsigned int slot = PCI_SLOT(entries[i].devfn); + unsigned int pin_idx = PIN2IDX(entries[i].pin); + + if (!matrix[slot][pin_idx]) { + matrix[slot][pin_idx] = entries[i].irq + PIRQ_A - 16; + ++count; + } + } + + return count; +}