diff options
-rw-r--r-- | src/soc/intel/common/block/include/intelblocks/irq.h | 47 | ||||
-rw-r--r-- | src/soc/intel/common/block/irq/Kconfig | 9 | ||||
-rw-r--r-- | src/soc/intel/common/block/irq/Makefile.inc | 1 | ||||
-rw-r--r-- | src/soc/intel/common/block/irq/irq.c | 345 |
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; +} |