sb/intel/common: Refactor _PRT generation to support GSI-based tables

Newer Intel SoCs also support _PRT tables, but they route PCI devices to
more than just PIRQs, and statically specify IRQs instead of using link
devices. Extend/refactor intel_acpi_gen_def_acpi_pirq to support this
additional use case.

Signed-off-by: Tim Wawrzynczak <twawrzynczak@chromium.org>
Change-Id: Ica420a3d12fd1d64c8fe6e4b326fd779b3f10868
Reviewed-on: https://review.coreboot.org/c/coreboot/+/50857
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Angel Pons <th3fanbus@gmail.com>
Reviewed-by: Nico Huber <nico.h@gmx.de>
diff --git a/src/southbridge/intel/bd82x6x/lpc.c b/src/southbridge/intel/bd82x6x/lpc.c
index 4ba8863..43d0e04 100644
--- a/src/southbridge/intel/bd82x6x/lpc.c
+++ b/src/southbridge/intel/bd82x6x/lpc.c
@@ -21,6 +21,7 @@
 #include <southbridge/intel/common/pciehp.h>
 #include <southbridge/intel/common/acpi_pirq_gen.h>
 #include <southbridge/intel/common/pmutil.h>
+#include <southbridge/intel/common/rcba_pirq.h>
 #include <southbridge/intel/common/rtc.h>
 #include <southbridge/intel/common/spi.h>
 #include <types.h>
diff --git a/src/southbridge/intel/common/acpi_pirq_gen.c b/src/southbridge/intel/common/acpi_pirq_gen.c
index 18def9b..852ab85 100644
--- a/src/southbridge/intel/common/acpi_pirq_gen.c
+++ b/src/southbridge/intel/common/acpi_pirq_gen.c
@@ -2,101 +2,78 @@
 
 #include <acpi/acpigen.h>
 #include <acpi/acpigen_pci.h>
-#include <console/console.h>
 #include <device/pci_def.h>
 #include <device/pci_ops.h>
 #include <string.h>
 
 #include "acpi_pirq_gen.h"
 
-enum emit_type {
-	EMIT_APIC,
-	EMIT_PICM,
-};
-
-static int create_pirq_matrix(char matrix[32][4])
+static void gen_apic_route(const struct slot_pin_irq_map *pin_irq_map,
+			   unsigned int map_count)
 {
-	struct device *dev;
-	int num_devs = 0;
+	for (unsigned int i = 0; i < map_count; i++)
+		/*
+		 * The reason for subtracting PCI_INT_A from the pin given is
+		 * that PCI defines pins as 1-4, and _PRT uses 0-3.
+		 */
+		acpigen_write_PRT_GSI_entry(pin_irq_map[i].slot,
+					    pin_irq_map[i].pin - PCI_INT_A,
+					    pin_irq_map[i].apic_gsi);
+}
 
-	for (dev = pcidev_on_root(0, 0); dev; dev = dev->sibling) {
-		u8 pci_dev;
-		u8 int_pin;
-
-		pci_dev = PCI_SLOT(dev->path.pci.devfn);
-		int_pin = pci_read_config8(dev, PCI_INTERRUPT_PIN);
-
-		if (int_pin == PCI_INT_NONE || int_pin > PCI_INT_D ||
-				matrix[pci_dev][int_pin - PCI_INT_A]
-				!= PIRQ_NONE)
+static void gen_pic_route(const struct slot_pin_irq_map *pin_irq_map,
+			  unsigned int map_count,
+			  const struct pic_pirq_map *pirq_map)
+{
+	for (unsigned int i = 0; i < map_count; i++) {
+		enum pirq pirq = pin_irq_map[i].pic_pirq;
+		unsigned int pin = pin_irq_map[i].pin - PCI_INT_A;
+		if (pirq == PIRQ_INVALID)
 			continue;
 
-		matrix[pci_dev][int_pin - PCI_INT_A] =
-			intel_common_map_pirq(dev, int_pin);
-		printk(BIOS_SPEW, "ACPI_PIRQ_GEN: %s: pin=%d pirq=%d\n",
-			dev_path(dev), int_pin - PCI_INT_A,
-			matrix[pci_dev][int_pin - PCI_INT_A] - PIRQ_A);
-		num_devs++;
-	}
-	return num_devs;
-}
-
-static void gen_pirq_route(const enum emit_type emit, const char *lpcb_path,
-			char pci_int_mapping[32][4])
-{
-	int pci_dev, int_pin;
-	char buffer[DEVICE_PATH_MAX];
-	char pirq;
-
-	for (pci_dev = 0; pci_dev < 32; pci_dev++) {
-		for (int_pin = 0; int_pin < 4; int_pin++) {
-			pirq = pci_int_mapping[pci_dev][int_pin];
-			if (pirq == PIRQ_NONE)
-				continue;
-
-			if (emit == EMIT_APIC) {
-				const unsigned int gsi = 16 + pirq - PIRQ_A;
-				acpigen_write_PRT_GSI_entry(pci_dev, int_pin, gsi);
-			} else {
-				snprintf(buffer, sizeof(buffer),
-					 "%s.LNK%c",
-					 lpcb_path, 'A' + pirq - PIRQ_A);
-				acpigen_write_PRT_source_entry(pci_dev, int_pin, buffer, 0);
-			}
-		}
+		if (pirq_map->type == PIRQ_GSI)
+			acpigen_write_PRT_GSI_entry(pin_irq_map[i].slot,
+						    pin,
+						    pirq_map->gsi[pirq]);
+		else
+			acpigen_write_PRT_source_entry(pin_irq_map[i].slot,
+						       pin,
+						       pirq_map->source_path[pirq],
+						       0);
 	}
 }
 
-void intel_acpi_gen_def_acpi_pirq(const struct device *dev)
+void intel_write_pci0_PRT(const struct slot_pin_irq_map *pin_irq_map,
+			  unsigned int map_count,
+			  const struct pic_pirq_map *pirq_map)
 {
-	const char *lpcb_path = acpi_device_path(dev);
-	char pci_int_mapping[32][4];
-	int num_devs;
-
-	printk(BIOS_DEBUG, "Generating ACPI PIRQ entries\n");
-
-	if (!lpcb_path) {
-		printk(BIOS_ERR, "ACPI_PIRQ_GEN: Missing LPCB ACPI path\n");
-		return;
-	}
-
-	memset(pci_int_mapping, 0, sizeof(pci_int_mapping));
-	num_devs = create_pirq_matrix(pci_int_mapping);
-
+	/* \_SB.PCI0._PRT */
 	acpigen_write_scope("\\_SB.PCI0");
 	acpigen_write_method("_PRT", 0);
 	acpigen_write_if();
 	acpigen_emit_namestring("PICM");
 	acpigen_emit_byte(RETURN_OP);
-	acpigen_write_package(num_devs);
-	gen_pirq_route(EMIT_APIC, lpcb_path, pci_int_mapping);
+	acpigen_write_package(map_count);
+	gen_apic_route(pin_irq_map, map_count);
 	acpigen_pop_len(); /* package */
 	acpigen_write_else();
 	acpigen_emit_byte(RETURN_OP);
-	acpigen_write_package(num_devs);
-	gen_pirq_route(EMIT_PICM, lpcb_path, pci_int_mapping);
+	acpigen_write_package(map_count);
+	gen_pic_route(pin_irq_map, map_count, pirq_map);
 	acpigen_pop_len(); /* package */
 	acpigen_pop_len(); /* else PICM */
 	acpigen_pop_len(); /* _PRT */
 	acpigen_pop_len(); /* \_SB */
 }
+
+bool is_slot_pin_assigned(const struct slot_pin_irq_map *pin_irq_map,
+			  unsigned int map_count, unsigned int slot,
+			  enum pci_pin pin)
+{
+	for (size_t i = 0; i < map_count; i++) {
+		if (pin_irq_map[i].slot == slot && pin_irq_map[i].pin == pin)
+			return true;
+	}
+
+	return false;
+}
diff --git a/src/southbridge/intel/common/acpi_pirq_gen.h b/src/southbridge/intel/common/acpi_pirq_gen.h
index 3fc6b77..83476d1 100644
--- a/src/southbridge/intel/common/acpi_pirq_gen.h
+++ b/src/southbridge/intel/common/acpi_pirq_gen.h
@@ -3,16 +3,20 @@
 #ifndef INTEL_COMMON_ACPI_PIRQ_GEN_H
 #define INTEL_COMMON_ACPI_PIRQ_GEN_H
 
+#include <device/device.h>
+
+#define MAX_SLOTS	32
+
 enum pci_pin {
 	PCI_INT_NONE = 0,
 	PCI_INT_A,
 	PCI_INT_B,
 	PCI_INT_C,
 	PCI_INT_D,
+	PCI_INT_MAX = PCI_INT_D,
 };
 
 enum pirq {
-	PIRQ_NONE = 0,
 	PIRQ_A,
 	PIRQ_B,
 	PIRQ_C,
@@ -21,10 +25,63 @@
 	PIRQ_F,
 	PIRQ_G,
 	PIRQ_H,
+	PIRQ_COUNT,
+	PIRQ_INVALID = 0xff,
 };
 
-void intel_acpi_gen_def_acpi_pirq(const struct device *dev);
-enum pirq intel_common_map_pirq(const struct device *dev,
-				const enum pci_pin pci_pin);
+/*
+ * This struct represents an assignment of slot/pin -> IRQ. Some chipsets may
+ * want to provide both PIC-mode and APIC-mode IRQs (e.g. selected using PICM
+ * set by the OS), therefore a field for each of a PIRQ for PIC-mode and a
+ * GSI for APIC-mode are provided.
+ *
+ * For APIC mode, only GSIs are supported (`acpi_gsi`).
+ *
+ * For PIC mode, if the pirq_map_type is PIRQ_GSI, then `pic_pirq` is used as an
+ * index into `struct pic_pirq_map.gsi`, or for SOURCE_PATH, `pic_pirq` indexes
+ * into `struct pic_pirq_map.source_path` to pick the path to the LNKx device.
+ *
+ * The reasoning for this structure is related to older vs. newer Intel
+ * platforms; older platforms supported routing of PCI IRQs to a PIRQ
+ * only. Newer platforms support routing IRQs to either a PIRQ or (for some PCI
+ * devices) a non-PIRQ GSI.
+ */
+struct slot_pin_irq_map {
+	unsigned int slot;
+	enum pci_pin pin;
+	/* PIRQ # for PIC mode */
+	unsigned int pic_pirq;
+	/* GSI # for APIC mode */
+	unsigned int apic_gsi;
+};
+
+enum pirq_map_type {
+	PIRQ_GSI,
+	PIRQ_SOURCE_PATH,
+};
+
+/*
+ * A PIRQ can be either be statically assigned a GSI or OSPM can use the Methods
+ * on the ACPI device (source_path) to assign IRQs at runtime.
+ */
+struct pic_pirq_map {
+	enum pirq_map_type type;
+	union {
+		unsigned int gsi[PIRQ_COUNT];
+		char source_path[PIRQ_COUNT][DEVICE_PATH_MAX];
+	};
+};
+
+/*
+ * Generate an ACPI _PRT table by providing PIRQ and/or GSI information for each
+ * slot/pin combination, and optionally providing paths to LNKx devices that can
+ * provide IRQs in PIC mode.
+ */
+void intel_write_pci0_PRT(const struct slot_pin_irq_map *pin_irq_map,
+			  unsigned int map_count,
+			  const struct pic_pirq_map *pirq_map);
+
+bool is_slot_pin_assigned(const struct slot_pin_irq_map *pin_irq_map,
+			  unsigned int map_count, unsigned int slot, unsigned int pin);
 
 #endif
diff --git a/src/southbridge/intel/common/rcba_pirq.c b/src/southbridge/intel/common/rcba_pirq.c
index 42e4edc..1037231 100644
--- a/src/southbridge/intel/common/rcba_pirq.c
+++ b/src/southbridge/intel/common/rcba_pirq.c
@@ -1,8 +1,10 @@
 /* SPDX-License-Identifier: GPL-2.0-only */
 
+#include <acpi/acpi_device.h>
 #include <console/console.h>
 #include <device/device.h>
 #include <device/pci.h>
+#include <stdlib.h>
 #include <southbridge/intel/common/acpi_pirq_gen.h>
 #include <southbridge/intel/common/rcba_pirq.h>
 #include <southbridge/intel/common/rcba.h>
@@ -15,7 +17,7 @@
 	D26IR, D27IR, D28IR, D29IR, D30IR, D31IR,
 };
 
-enum pirq intel_common_map_pirq(const struct device *dev, const enum pci_pin pci_pin)
+static enum pirq map_pirq(const struct device *dev, const enum pci_pin pci_pin)
 {
 	u8 slot = PCI_SLOT(dev->path.pci.devfn);
 	u8 shift = 4 * (pci_pin - PCI_INT_A);
@@ -25,18 +27,66 @@
 	if (pci_pin < PCI_INT_A || pci_pin > PCI_INT_D) {
 		printk(BIOS_ERR, "ACPI_PIRQ_GEN: Slot %d PCI pin %d out of bounds\n",
 			slot, pci_pin);
-		return PIRQ_NONE;
+		return PIRQ_INVALID;
 	}
 
 	/* Slot 24 should not exist and has no D24IR but better be safe here */
 	if (slot < MIN_SLOT || slot > MAX_SLOT || slot == 24) {
 		/* non-PCH devices use 1:1 mapping. */
-		return (enum pirq)pci_pin;
+		return (enum pirq)(pci_pin - PCI_INT_A);
 	}
 
 	reg = pirq_dir_route_reg[slot - MIN_SLOT];
 
 	pirq = (RCBA16(reg) >> shift) & 0x7;
 
-	return (enum pirq)(PIRQ_A + pirq);
+	return (enum pirq)pirq;
+}
+
+void intel_acpi_gen_def_acpi_pirq(const struct device *lpc)
+{
+	struct slot_pin_irq_map *pin_irq_map;
+	const char *lpcb_path = acpi_device_path(lpc);
+	struct pic_pirq_map pirq_map = {0};
+	unsigned int map_count = 0;
+	int i;
+
+	if (!lpcb_path) {
+		printk(BIOS_ERR, "ACPI_PIRQ_GEN: Missing LPCB ACPI path\n");
+		return;
+	}
+
+	printk(BIOS_DEBUG, "Generating ACPI PIRQ entries\n");
+
+	pin_irq_map = calloc(sizeof(struct slot_pin_irq_map), MAX_SLOTS * PCI_INT_MAX);
+	pirq_map.type = PIRQ_SOURCE_PATH;
+	for (i = 0; i < PIRQ_COUNT; i++)
+		snprintf(pirq_map.source_path[i], sizeof(pirq_map.source_path[i]),
+			 "%s.LNK%c", lpcb_path, 'A' + i);
+
+	for (struct device *dev = pcidev_on_root(0, 0); dev; dev = dev->sibling) {
+		const u8 pci_dev = PCI_SLOT(dev->path.pci.devfn);
+		const u8 int_pin = pci_read_config8(dev, PCI_INTERRUPT_PIN);
+
+		if (int_pin < PCI_INT_A || int_pin > PCI_INT_D)
+			continue;
+
+		if (is_slot_pin_assigned(pin_irq_map, map_count, pci_dev, int_pin))
+			continue;
+
+		enum pirq pirq = map_pirq(dev, int_pin);
+		pin_irq_map[map_count].slot = pci_dev;
+		pin_irq_map[map_count].pin = (enum pci_pin)int_pin;
+		pin_irq_map[map_count].pic_pirq = pirq;
+		/* PIRQs are mapped to GSIs starting at 16 */
+		pin_irq_map[map_count].apic_gsi = 16 + (unsigned int)pirq;
+		printk(BIOS_SPEW, "ACPI_PIRQ_GEN: %s: pin=%d pirq=%d\n",
+		       dev_path(dev), int_pin - PCI_INT_A,
+		       pin_irq_map[map_count].pic_pirq);
+		map_count++;
+	}
+
+	intel_write_pci0_PRT(pin_irq_map, map_count, &pirq_map);
+
+	free(pin_irq_map);
 }
diff --git a/src/southbridge/intel/common/rcba_pirq.h b/src/southbridge/intel/common/rcba_pirq.h
index 34d864c..eb6d0b8 100644
--- a/src/southbridge/intel/common/rcba_pirq.h
+++ b/src/southbridge/intel/common/rcba_pirq.h
@@ -3,6 +3,8 @@
 #ifndef SOUTHBRIDGE_INTEL_COMMON_RCBA_PIRQ_H
 #define SOUTHBRIDGE_INTEL_COMMON_RCBA_PIRQ_H
 
+#include <device/device.h>
+
 /*
  * The DnnIR registers use common RCBA offsets across these chipsets:
  * bd82x6x, i82801, i89xx, ibexpeak, lynxpoint
@@ -23,4 +25,7 @@
 #define D20IR		0x3160	/* 16bit */
 #define D19IR		0x3168	/* 16bit */
 
+/* Generate an ACPI _PRT table for chipsets that use PIRQs exclusively */
+void intel_acpi_gen_def_acpi_pirq(const struct device *dev);
+
 #endif /* SOUTHBRIDGE_INTEL_COMMON_RCBA_PIRQ_H */
diff --git a/src/southbridge/intel/i82801gx/lpc.c b/src/southbridge/intel/i82801gx/lpc.c
index 08019ac..9c3b0e9 100644
--- a/src/southbridge/intel/i82801gx/lpc.c
+++ b/src/southbridge/intel/i82801gx/lpc.c
@@ -16,6 +16,7 @@
 #include <acpi/acpigen.h>
 #include <arch/smp/mpspec.h>
 #include <southbridge/intel/common/acpi_pirq_gen.h>
+#include <southbridge/intel/common/rcba_pirq.h>
 #include <southbridge/intel/common/hpet.h>
 #include <southbridge/intel/common/pmbase.h>
 #include <southbridge/intel/common/spi.h>
diff --git a/src/southbridge/intel/i82801ix/lpc.c b/src/southbridge/intel/i82801ix/lpc.c
index 667d81d..32d4efc 100644
--- a/src/southbridge/intel/i82801ix/lpc.c
+++ b/src/southbridge/intel/i82801ix/lpc.c
@@ -19,6 +19,7 @@
 #include <southbridge/intel/common/pciehp.h>
 #include <southbridge/intel/common/pmutil.h>
 #include <southbridge/intel/common/acpi_pirq_gen.h>
+#include <southbridge/intel/common/rcba_pirq.h>
 
 #define NMI_OFF	0
 
diff --git a/src/southbridge/intel/i82801jx/lpc.c b/src/southbridge/intel/i82801jx/lpc.c
index f70636d..55b4746 100644
--- a/src/southbridge/intel/i82801jx/lpc.c
+++ b/src/southbridge/intel/i82801jx/lpc.c
@@ -20,6 +20,7 @@
 #include <southbridge/intel/common/pciehp.h>
 #include <southbridge/intel/common/pmutil.h>
 #include <southbridge/intel/common/acpi_pirq_gen.h>
+#include <southbridge/intel/common/rcba_pirq.h>
 
 #define NMI_OFF	0
 
diff --git a/src/southbridge/intel/ibexpeak/lpc.c b/src/southbridge/intel/ibexpeak/lpc.c
index ae3233c..6fe44c6 100644
--- a/src/southbridge/intel/ibexpeak/lpc.c
+++ b/src/southbridge/intel/ibexpeak/lpc.c
@@ -21,6 +21,7 @@
 #include <southbridge/intel/common/pciehp.h>
 #include <southbridge/intel/common/acpi_pirq_gen.h>
 #include <southbridge/intel/common/spi.h>
+#include <southbridge/intel/common/rcba_pirq.h>
 
 #define NMI_OFF	0
 
diff --git a/src/southbridge/intel/lynxpoint/lpc.c b/src/southbridge/intel/lynxpoint/lpc.c
index 71e4d74..8d9b451 100644
--- a/src/southbridge/intel/lynxpoint/lpc.c
+++ b/src/southbridge/intel/lynxpoint/lpc.c
@@ -17,6 +17,7 @@
 #include "pch.h"
 #include <acpi/acpigen.h>
 #include <southbridge/intel/common/acpi_pirq_gen.h>
+#include <southbridge/intel/common/rcba_pirq.h>
 #include <southbridge/intel/common/rtc.h>
 #include <southbridge/intel/common/spi.h>
 #include <types.h>