soc/amd/common/data_fabric/domain: read IO decode windows from registers

Before add_io_regions only reported one fixed IO range to the resource
allocator that covered the whole IO range from 0x0000 to 0xffff. Instead
read the data fabric IO space decode base and limit address register
pairs to get the actual IO port decoding from the data fabric registers.
This will also help with adding support for multiple PCI root domains to
the common data fabric domain code so that Genoa can use it. In that
case each PCI root domain will only decode a part of the whole IO port
range.

Beware that the data fabric IO base and limit fields can contain values
that correspond to IO port addresses far outside of the addressable IO
port range. In case of Picasso, the IO limit read from the only enabled
DF IO range register would be 0x1ffffff after converting the raw data to
an IO port address. To not give the resource allocator wrong constraints
make sure that the IO limit we report will be at maximum 0xffff.

TEST=On Mandolin (Picasso) and Birman (Phoenix) the full range of IO
port addresses still gets reported as a domain IO resource producer like
before the patch:

  DOMAIN: 0000 io: base: 0 size: 0 align: 0 gran: 0 limit: ffff done

Signed-off-by: Felix Held <felix-coreboot@felixheld.de>
Change-Id: I087d96f7bdaae0d7b53089f6abaf0500a4b064e9
Reviewed-on: https://review.coreboot.org/c/coreboot/+/76960
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Martin L Roth <gaumless@gmail.com>
diff --git a/src/soc/amd/common/block/data_fabric/domain.c b/src/soc/amd/common/block/data_fabric/domain.c
index cfd4e0f..7328fc6 100644
--- a/src/soc/amd/common/block/data_fabric/domain.c
+++ b/src/soc/amd/common/block/data_fabric/domain.c
@@ -152,10 +152,40 @@
 /* Tell the resource allocator about the usable I/O space */
 static void add_data_fabric_io_regions(struct device *domain, unsigned int *idx)
 {
-	/* TODO: Systems with more than one PCI root need to read the data fabric registers to
-	   see which IO ranges get decoded to which PCI root. */
+	union df_io_base base_reg;
+	union df_io_limit limit_reg;
+	resource_t io_base;
+	resource_t io_limit;
 
-	report_data_fabric_io(domain, (*idx)++, 0, 0xffff);
+	for (unsigned int i = 0; i < DF_IO_REG_COUNT; i++) {
+		base_reg.raw = data_fabric_broadcast_read32(DF_IO_BASE(i));
+
+		/* Relevant IO regions need to have both reads and writes enabled */
+		if (!base_reg.we || !base_reg.re)
+			continue;
+
+		limit_reg.raw = data_fabric_broadcast_read32(DF_IO_LIMIT(i));
+
+		/* TODO: Systems with more than one PCI root need to check to which PCI root
+		   the IO range gets decoded to. */
+
+		io_base = base_reg.io_base << DF_IO_ADDR_SHIFT;
+		io_limit = ((limit_reg.io_limit + 1) << DF_IO_ADDR_SHIFT) - 1;
+
+		/* Beware that the lower 25 bits of io_base and io_limit can be non-zero
+		   despite there only being 16 bits worth of IO port address space. */
+		if (io_base > 0xffff) {
+			printk(BIOS_WARNING, "DF IO base register %d value outside of valid "
+					     "IO port address range.\n", i);
+			continue;
+		}
+		/* If only the IO limit is outside of the valid 16 bit IO port range, report
+		   the limit as 0xffff, so that the resource allcator won't put IO BARs outside
+		   of the 16 bit IO port address range. */
+		io_limit = MIN(io_limit, 0xffff);
+
+		report_data_fabric_io(domain, (*idx)++, io_base, io_limit);
+	}
 }
 
 void amd_pci_domain_read_resources(struct device *domain)