All of lore.kernel.org
 help / color / mirror / Atom feed
From: Szymon Durawa <szymon.durawa@linux.intel.com>
To: nirmal.patel@linux.intel.com, bhelgaas@google.com,
	robh@kernel.org, mani@kernel.org, kwilczynski@kernel.org,
	lpieralisi@kernel.org, asutosh.pathak@linux.intel.com,
	szymon.durawa@linux.intel.com
Cc: linux-pci@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH v10 7/7] PCI: vmd: Add support for second rootbus under VMD
Date: Fri,  6 Nov 2026 18:42:55 +0000	[thread overview]
Message-ID: <20261106184256.4130-8-szymon.durawa@linux.intel.com> (raw)
In-Reply-To: <20261106184256.4130-1-szymon.durawa@linux.intel.com>

Starting from Intel Arrow Lake, VMD exposes a second root bus (BUS1),
reported via Bus Restriction type 3. BUS0 starts at bus 0xE0 and BUS1
at 0xE1; the subordinate bus ranges for each are provided by firmware
in VMD_BUSRANGE0/1. All three VMD BARs are shared between BUS0 and
BUS1: CFGBAR is split by those bus ranges, and MEMBAR1/MEMBAR2 are
split at the offsets in VMD_MEMBAR1_OFFSET and VMD_MEMBAR2_OFFSET1/2.

Add VMD_FEAT_HAS_BUS1_ROOTBUS for client VMDs and, when restriction
type 3 is reported, create and enumerate a second root bus in its own
emulated PCI domain so that its bus numbers and resources stay
separate from BUS0. A "domain1" sysfs link is added alongside the
existing "domain" link. If the BUS1 domain or root bus cannot be
created, the driver warns and continues with BUS0 only. Restriction
type 3 on a VMD without the feature is now rejected instead of being
treated as type 2.

The root ports on BUS1 have PCI_PRIMARY_BUS hardwired to 0x80. Emulate
reads of that register for Type 1 devices on the BUS1 root bus so it
reports the actual root bus number, and set bus->primary on both root
buses before scanning.

Suggested-by: Nirmal Patel <nirmal.patel@linux.intel.com>
Reviewed-by: Asutosh Pathak <asutosh.pathak@linux.intel.com>
Signed-off-by: Szymon Durawa <szymon.durawa@linux.intel.com>
---
 drivers/pci/controller/vmd.c | 467 +++++++++++++++++++++++++++++++----
 1 file changed, 416 insertions(+), 51 deletions(-)

diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c
index 5733faf972d1..6831d0a5c443 100644
--- a/drivers/pci/controller/vmd.c
+++ b/drivers/pci/controller/vmd.c
@@ -4,6 +4,7 @@
  * Copyright (c) 2015, Intel Corporation.
  */
 
+#include <linux/bitfield.h>
 #include <linux/device.h>
 #include <linux/interrupt.h>
 #include <linux/irq.h>
@@ -30,6 +31,7 @@
 #define VMD_RESTRICT_0_BUS_START 0x0
 #define VMD_RESTRICT_1_BUS_START 0x80
 #define VMD_RESTRICT_2_BUS_START 0xE0
+#define VMD_RESTRICT_3_BUS_START 0xE1
 
 #define PCI_REG_VMCAP		0x40
 #define BUS_RESTRICT_CAP(vmcap)	(vmcap & 0x1)
@@ -48,15 +50,36 @@
 #define BASE_ID_REG_28C1		0x2840
 #define MEMBAR2_OFFSET_28C1		0x30d0
 
+/* Primary Bus Number for VMD devices on root bus 0 */
+#define VMD_PRIMARY_BUS0    0x00
+/* Primary Bus Number for VMD devices on root bus 1 */
+#define VMD_PRIMARY_BUS1    0x80
+
+#define VMD_BUSRANGE0       0xc8
+#define VMD_BUSRANGE1       0xcc
+#define VMD_MEMBAR1_OFFSET  0xd0
+#define VMD_MEMBAR2_OFFSET1 0xd8
+#define VMD_MEMBAR2_OFFSET2 0xdc
+#define VMD_BUS_END(busr) FIELD_GET(GENMASK(15, 8), busr)
+#define VMD_BUS_START(busr) FIELD_GET(GENMASK(7, 0), busr)
+
+/*
+ * Add VMD resources for BUS1, it will share the same MMIO space with
+ * previous VMD resources.
+ */
 enum vmd_resource {
-	VMD_RES_CFGBAR = 0, /* VMD Bus0 Config BAR */
-	VMD_RES_MBAR_1, /* VMD Bus0 Resource MemBAR 1 */
-	VMD_RES_MBAR_2, /* VMD Bus0 Resource MemBAR 2 */
+	VMD_RES_CFGBAR = 0,  /* VMD Bus0 Config BAR */
+	VMD_RES_MBAR_1,      /* VMD Bus0 Resource MemBAR 1 */
+	VMD_RES_MBAR_2,      /* VMD Bus0 Resource MemBAR 2 */
+	VMD_RES_BUS1_CFGBAR, /* VMD Bus1 Config BAR */
+	VMD_RES_BUS1_MBAR_1, /* VMD Bus1 Resource MemBAR 1 */
+	VMD_RES_BUS1_MBAR_2, /* VMD Bus1 Resource MemBAR 2 */
 	VMD_RES_COUNT
 };
 
 enum vmd_rootbus {
 	VMD_BUS_0 = 0,
+	VMD_BUS_1,
 	VMD_BUS_COUNT
 };
 
@@ -126,6 +149,12 @@ enum vmd_features {
 	 * against the child's writes and is not sufficient (measured).
 	 */
 	VMD_FEAT_INTERRUPT_QUIRK	= (1 << 7),
+
+	/*
+	 * Starting from Intel Arrow Lake, VMD devices have their VMD root ports
+	 * on the additional BUS1 root bus.
+	 */
+	VMD_FEAT_HAS_BUS1_ROOTBUS	= (1 << 8),
 };
 
 #define VMD_BIOS_PM_QUIRK_LTR	0x1003	/* 3145728 ns */
@@ -133,7 +162,8 @@ enum vmd_features {
 #define VMD_FEATS_CLIENT	(VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP |	\
 				 VMD_FEAT_HAS_BUS_RESTRICTIONS |	\
 				 VMD_FEAT_OFFSET_FIRST_VECTOR |		\
-				 VMD_FEAT_BIOS_PM_QUIRK)
+				 VMD_FEAT_BIOS_PM_QUIRK |		\
+				 VMD_FEAT_HAS_BUS1_ROOTBUS)
 
 static DEFINE_IDA(vmd_instance_ida);
 
@@ -204,6 +234,7 @@ struct vmd_dev {
 	char			*name;
 	int			instance;
 	unsigned long		features;
+	bool			bus1_rootbus;
 };
 
 static inline struct vmd_dev *vmd_from_bus(struct pci_bus *bus)
@@ -487,6 +518,36 @@ static void __iomem *vmd_cfg_addr(struct vmd_dev *vmd, struct pci_bus *bus,
 	return vmd->cfgbar + offset;
 }
 
+static bool vmd_is_bus1_root_bridge(struct vmd_dev *vmd, struct pci_bus *bus,
+				    unsigned int devfn)
+{
+	void __iomem *hdr_addr;
+	u8 hdr_type;
+
+	lockdep_assert_held(&vmd->cfg_lock);
+
+	if (!vmd->bus1_rootbus)
+		return false;
+
+	/* Limit strictly to BUS1 */
+	if (bus->sysdata != &vmd->sysdata[VMD_BUS_1])
+		return false;
+
+	if (!pci_is_root_bus(bus))
+		return false;
+
+	/*
+	 * Offset 0x18 is PCI_PRIMARY_BUS only for Type 1 bridge headers.
+	 * On Type 0 endpoints, 0x18 is BAR2. Only apply the quirk to bridges.
+	 */
+	hdr_addr = vmd_cfg_addr(vmd, bus, devfn, PCI_HEADER_TYPE, 1);
+	if (!hdr_addr)
+		return false;
+
+	hdr_type = readb(hdr_addr) & PCI_HEADER_TYPE_MASK;
+	return hdr_type == PCI_HEADER_TYPE_BRIDGE;
+}
+
 /*
  * CPU may deadlock if config space is not serialized on some versions of this
  * hardware, so all config space access is done under a spinlock.
@@ -496,6 +557,7 @@ static int vmd_pci_read(struct pci_bus *bus, unsigned int devfn, int reg,
 {
 	struct vmd_dev *vmd = vmd_from_bus(bus);
 	void __iomem *addr = vmd_cfg_addr(vmd, bus, devfn, reg, len);
+	int ret = 0;
 
 	if (!addr)
 		return -EFAULT;
@@ -504,16 +566,34 @@ static int vmd_pci_read(struct pci_bus *bus, unsigned int devfn, int reg,
 	switch (len) {
 	case 1:
 		*value = readb(addr);
-		return 0;
+		break;
 	case 2:
 		*value = readw(addr);
-		return 0;
+		break;
 	case 4:
 		*value = readl(addr);
-		return 0;
+		break;
 	default:
-		return -EINVAL;
+		ret = -EINVAL;
+		break;
 	}
+
+	/*
+	 * Quirk: Hardware PCIe root port bridges on BUS1 have
+	 * PCI_PRIMARY_BUS hardwired to 0x80 (VMD_PRIMARY_BUS1).
+	 *
+	 * Emulate the primary bus number to match the actual allocated
+	 * root bus number. This prevents the PCI core from detecting a
+	 * bus mismatch without needing synthetic bus ranges.
+	 */
+	if (!ret && reg <= PCI_PRIMARY_BUS && reg + len > PCI_PRIMARY_BUS &&
+	    vmd_is_bus1_root_bridge(vmd, bus, devfn)) {
+		u8 *buf = (u8 *)value;
+
+		buf[PCI_PRIMARY_BUS - reg] = bus->number;
+	}
+
+	return ret;
 }
 
 /*
@@ -610,12 +690,19 @@ static resource_size_t vmd_cfgbar_ecam_space(struct vmd_dev *vmd)
 	unsigned int ecam_start;
 
 	cfgbar_buses = resource_size(&vmd->dev->resource[VMD_CFGBAR]) >> 20;
-	ecam_start = vmd_bus_to_ecam(vmd, vmd->resources[VMD_RES_CFGBAR].start);
+
+	if (vmd->bus1_rootbus)
+		/* BUS1 splits the aperture, so reset scans start from BUS0. */
+		ecam_start = vmd_bus_to_ecam(vmd, vmd->busn_start[VMD_BUS_0]);
+	else
+		ecam_start = vmd_bus_to_ecam(vmd, vmd->resources[VMD_RES_CFGBAR].start);
+
 	if (ecam_start >= cfgbar_buses)
 		return 0;
 
 	return cfgbar_buses - ecam_start;
 }
+
 static void vmd_domain_reset(struct vmd_dev *vmd)
 {
 	u16 bus, max_buses = resource_size(&vmd->resources[VMD_RES_CFGBAR]);
@@ -623,10 +710,13 @@ static void vmd_domain_reset(struct vmd_dev *vmd)
 	unsigned int ecam_bus;
 	char __iomem *base;
 
+	if (vmd->bus1_rootbus)
+		max_buses = resource_size(&vmd->dev->resource[VMD_CFGBAR]) >> 20;
+
 	max_buses = min_t(u16, max_buses, vmd_cfgbar_ecam_space(vmd));
 	for (bus = 0; bus < max_buses; bus++) {
-		ecam_bus = vmd_bus_to_ecam(vmd, vmd->resources[VMD_RES_CFGBAR].start +
-					   bus);
+		/* Calculate the ECAM bus nr starting from the first bus of VMD_BUS_0 */
+		ecam_bus = vmd_bus_to_ecam(vmd, vmd->busn_start[VMD_BUS_0] + bus);
 		for (dev = 0; dev < 32; dev++) {
 			base = vmd->cfgbar + PCIE_ECAM_OFFSET(ecam_bus,
 						PCI_DEVFN(dev, 0), 0);
@@ -668,16 +758,53 @@ static void vmd_domain_reset(struct vmd_dev *vmd)
 	}
 }
 
-static void vmd_attach_resources(struct vmd_dev *vmd)
+static int vmd_attach_resources(struct vmd_dev *vmd)
 {
-	vmd->dev->resource[VMD_MEMBAR1].child = &vmd->resources[VMD_RES_MBAR_1];
-	vmd->dev->resource[VMD_MEMBAR2].child = &vmd->resources[VMD_RES_MBAR_2];
+	int ret;
+
+	ret = request_resource(&vmd->dev->resource[VMD_MEMBAR1],
+			       &vmd->resources[VMD_RES_MBAR_1]);
+	if (ret)
+		return ret;
+
+	ret = request_resource(&vmd->dev->resource[VMD_MEMBAR2],
+			       &vmd->resources[VMD_RES_MBAR_2]);
+	if (ret) {
+		release_resource(&vmd->resources[VMD_RES_MBAR_1]);
+		return ret;
+	}
+
+	if (vmd->bus1_rootbus && vmd->sysdata[VMD_BUS_1].domain >= 0) {
+		ret = request_resource(&vmd->dev->resource[VMD_MEMBAR1],
+				       &vmd->resources[VMD_RES_BUS1_MBAR_1]);
+		if (ret) {
+			release_resource(&vmd->resources[VMD_RES_MBAR_1]);
+			release_resource(&vmd->resources[VMD_RES_MBAR_2]);
+			return ret;
+		}
+
+		ret = request_resource(&vmd->dev->resource[VMD_MEMBAR2],
+				       &vmd->resources[VMD_RES_BUS1_MBAR_2]);
+		if (ret) {
+			release_resource(&vmd->resources[VMD_RES_MBAR_1]);
+			release_resource(&vmd->resources[VMD_RES_MBAR_2]);
+			release_resource(&vmd->resources[VMD_RES_BUS1_MBAR_1]);
+			return ret;
+		}
+	}
+
+	return 0;
 }
 
 static void vmd_detach_resources(struct vmd_dev *vmd)
 {
-	vmd->dev->resource[VMD_MEMBAR1].child = NULL;
-	vmd->dev->resource[VMD_MEMBAR2].child = NULL;
+	if (vmd->bus1_rootbus && vmd->sysdata[VMD_BUS_1].domain >= 0) {
+		release_resource(&vmd->resources[VMD_RES_BUS1_MBAR_2]);
+		release_resource(&vmd->resources[VMD_RES_BUS1_MBAR_1]);
+	}
+
+	release_resource(&vmd->resources[VMD_RES_MBAR_2]);
+	release_resource(&vmd->resources[VMD_RES_MBAR_1]);
 }
 
 static int vmd_get_phys_offsets(struct vmd_dev *vmd, bool native_hint,
@@ -734,7 +861,7 @@ static int vmd_get_phys_offsets(struct vmd_dev *vmd, bool native_hint,
 	return 0;
 }
 
-static int vmd_get_bus_number_start(struct vmd_dev *vmd)
+static int vmd_get_bus_number_start(struct vmd_dev *vmd, unsigned long features)
 {
 	struct pci_dev *dev = vmd->dev;
 	u16 reg;
@@ -752,10 +879,27 @@ static int vmd_get_bus_number_start(struct vmd_dev *vmd)
 		case 1:
 			vmd->busn_start[VMD_BUS_0] = VMD_RESTRICT_1_BUS_START;
 			break;
-		case 3:
 		case 2:
 			vmd->busn_start[VMD_BUS_0] = VMD_RESTRICT_2_BUS_START;
 			break;
+		case 3:
+			if (!(features & VMD_FEAT_HAS_BUS1_ROOTBUS)) {
+				pci_err(dev, "VMD Bus Restriction detected type %d, but BUS1 root bus is not supported, aborting.\n",
+					BUS_RESTRICT_CFG(reg));
+				return -ENODEV;
+			}
+
+			/*
+			 * Per Intel VMD specification (e.g., Arrow Lake-S):
+			 * - VMD (on root bus 0) secondary bus start number: 0xE0
+			 *	(devices span 0xE2..0xF0 via VMD_BUSRANGE0).
+			 * - VMD (on root bus 1) secondary bus start number: 0xE1
+			 *	(devices span 0xF1..0xFF via VMD_BUSRANGE1).
+			 */
+			vmd->busn_start[VMD_BUS_0] = VMD_RESTRICT_2_BUS_START;
+			vmd->busn_start[VMD_BUS_1] = VMD_RESTRICT_3_BUS_START;
+			vmd->bus1_rootbus = true;
+			break;
 		default:
 			pci_err(dev, "Unknown Bus Offset Setting (%d)\n",
 				BUS_RESTRICT_CFG(reg));
@@ -908,7 +1052,7 @@ static int vmd_prepare_offsets_and_bus(struct vmd_dev *vmd,
 	 * limits the bus range to between 0-127, 128-255, or 224-255.
 	 */
 	if (features & VMD_FEAT_HAS_BUS_RESTRICTIONS) {
-		ret = vmd_get_bus_number_start(vmd);
+		ret = vmd_get_bus_number_start(vmd, features);
 		if (ret)
 			return ret;
 	}
@@ -975,7 +1119,7 @@ static int vmd_pm_enable_quirk(struct pci_dev *pdev, void *userdata)
 	return 0;
 }
 
-static void vmd_configure_cfgbar(struct vmd_dev *vmd)
+static int vmd_configure_cfgbar(struct vmd_dev *vmd)
 {
 	struct resource *res = &vmd->dev->resource[VMD_CFGBAR];
 	resource_size_t busn_end;
@@ -989,6 +1133,62 @@ static void vmd_configure_cfgbar(struct vmd_dev *vmd)
 		.end   = busn_end,
 		.flags = IORESOURCE_BUS | IORESOURCE_PCI_FIXED,
 	};
+
+	if (vmd->bus1_rootbus) {
+		int ret;
+		u16 bus0_range = 0;
+		u16 bus1_range = 0;
+		u8 bus0_start, bus0_end;
+		u8 bus1_start, bus1_end;
+
+		ret = pci_read_config_word(vmd->dev, VMD_BUSRANGE0, &bus0_range);
+		if (ret) {
+			pci_err(vmd->dev, "Failed to read VMD_BUSRANGE0: %d\n", ret);
+			return -EIO;
+		}
+
+		ret = pci_read_config_word(vmd->dev, VMD_BUSRANGE1, &bus1_range);
+		if (ret) {
+			pci_err(vmd->dev, "Failed to read VMD_BUSRANGE1: %d\n", ret);
+			return -EIO;
+		}
+
+		bus0_start = VMD_BUS_START(bus0_range);
+		bus0_end = VMD_BUS_END(bus0_range);
+		bus1_start = VMD_BUS_START(bus1_range);
+		bus1_end = VMD_BUS_END(bus1_range);
+
+		if (bus0_start > bus0_end || bus1_start > bus1_end) {
+			pci_err(vmd->dev,
+				"Invalid bus range(s): BUS0 [%02x-%02x], BUS1 [%02x-%02x]\n",
+				bus0_start, bus0_end, bus1_start, bus1_end);
+			return -EINVAL;
+		}
+
+		if (!(bus0_end < bus1_start || bus1_end < bus0_start)) {
+			pci_err(vmd->dev,
+				"Overlapping bus ranges: BUS0 [%02x-%02x], BUS1 [%02x-%02x]\n",
+				bus0_start, bus0_end, bus1_start, bus1_end);
+			return -EINVAL;
+		}
+
+		/*
+		 * Resize BUS0 CFGBAR range to make space for BUS1
+		 * owned devices by adjusting range end with value stored in
+		 * VMD_BUSRANGE0 register.
+		 */
+		vmd->resources[VMD_RES_CFGBAR].start = bus0_start;
+		vmd->resources[VMD_RES_CFGBAR].end = bus0_end;
+
+		vmd->resources[VMD_RES_BUS1_CFGBAR] = (struct resource){
+			.name = "VMD CFGBAR BUS1",
+			.start = bus1_start,
+			.end = bus1_end,
+			.flags = IORESOURCE_BUS | IORESOURCE_PCI_FIXED,
+		};
+	}
+
+	return 0;
 }
 
 /*
@@ -1019,8 +1219,9 @@ static int vmd_configure_membar(struct vmd_dev *vmd, u8 resource_number,
 	if (!upper_bits)
 		flags &= ~IORESOURCE_MEM_64;
 
-	name = devm_kasprintf(&vmd->dev->dev, GFP_KERNEL, "VMD MEMBAR%d",
-			      resource_number);
+	name = devm_kasprintf(&vmd->dev->dev, GFP_KERNEL, "VMD MEMBAR%d %s",
+			      membar_number / 2,
+			      resource_number > VMD_RES_MBAR_2 ? "BUS1" : "");
 	if (!name)
 		return -ENOMEM;
 
@@ -1040,55 +1241,132 @@ static int vmd_configure_membar1_membar2(struct vmd_dev *vmd,
 {
 	int ret;
 
-	ret = vmd_configure_membar(vmd, VMD_RES_MBAR_1, VMD_MEMBAR1, 0, 0);
-	if (ret)
-		return ret;
+	if (vmd->bus1_rootbus) {
+		u32 reg = 0;
+		u32 bus1_mbar1_ofs = 0;
+		u64 bus1_mbar2_ofs = 0;
+		resource_size_t mbar1_sz, mbar2_sz;
 
-	ret = vmd_configure_membar(vmd, VMD_RES_MBAR_2, VMD_MEMBAR2, mbar2_ofs, 0);
-	if (ret) {
-		devm_kfree(&vmd->dev->dev, (void *)vmd->resources[VMD_RES_MBAR_1].name);
-		memset(&vmd->resources[VMD_RES_MBAR_1], 0,
-		       sizeof(vmd->resources[VMD_RES_MBAR_1]));
-		return ret;
+		mbar1_sz = resource_size(&vmd->dev->resource[VMD_MEMBAR1]);
+		mbar2_sz = resource_size(&vmd->dev->resource[VMD_MEMBAR2]);
+
+		ret = pci_read_config_dword(vmd->dev, VMD_MEMBAR1_OFFSET,
+					    &bus1_mbar1_ofs);
+		if (ret)
+			return -EIO;
+
+		ret = pci_read_config_dword(vmd->dev, VMD_MEMBAR2_OFFSET1, &reg);
+		if (ret)
+			return -EIO;
+		bus1_mbar2_ofs = reg;
+
+		ret = pci_read_config_dword(vmd->dev, VMD_MEMBAR2_OFFSET2, &reg);
+		if (ret)
+			return -EIO;
+		bus1_mbar2_ofs |= (u64)reg << 32;
+
+		if (!bus1_mbar1_ofs || bus1_mbar1_ofs >= mbar1_sz) {
+			pci_err(vmd->dev, "Invalid MEMBAR1 offset %#llx (BAR size %#llx)\n",
+				(unsigned long long)bus1_mbar1_ofs,
+				(unsigned long long)mbar1_sz);
+			return -EINVAL;
+		}
+
+		if (!bus1_mbar2_ofs || mbar2_ofs >= mbar2_sz ||
+		    bus1_mbar2_ofs <= mbar2_ofs || bus1_mbar2_ofs >= mbar2_sz) {
+			pci_err(vmd->dev,
+				"Invalid MEMBAR2 split %#llx (shadow offset %#llx, BAR size %#llx)\n",
+				(unsigned long long)bus1_mbar2_ofs,
+				(unsigned long long)mbar2_ofs,
+				(unsigned long long)mbar2_sz);
+			return -EINVAL;
+		}
+
+		/*
+		 * Resize BUS MEMBAR1 and MEMBAR2 ranges to make space
+		 * for BUS1 owned devices by adjusting range end with values
+		 * stored in VMD_MEMBAR1_OFFSET and VMD_MEMBAR2_OFFSET registers
+		 */
+		ret = vmd_configure_membar(vmd, VMD_RES_MBAR_1, VMD_MEMBAR1, 0,
+					   mbar1_sz - bus1_mbar1_ofs);
+		if (ret)
+			return ret;
+
+		ret = vmd_configure_membar(vmd, VMD_RES_MBAR_2, VMD_MEMBAR2,
+					   mbar2_ofs, mbar2_sz - bus1_mbar2_ofs);
+		if (ret)
+			return ret;
+
+		ret = vmd_configure_membar(vmd, VMD_RES_BUS1_MBAR_1, VMD_MEMBAR1,
+					   bus1_mbar1_ofs, 0);
+		if (ret)
+			return ret;
+
+		ret = vmd_configure_membar(vmd, VMD_RES_BUS1_MBAR_2, VMD_MEMBAR2,
+					   bus1_mbar2_ofs, 0);
+		if (ret)
+			return ret;
+	} else {
+		ret = vmd_configure_membar(vmd, VMD_RES_MBAR_1, VMD_MEMBAR1, 0, 0);
+		if (ret)
+			return ret;
+
+		ret = vmd_configure_membar(vmd, VMD_RES_MBAR_2, VMD_MEMBAR2,
+					   mbar2_ofs, 0);
+		if (ret)
+			return ret;
 	}
 
 	return 0;
 }
 
-static int vmd_create_bus(struct vmd_dev *vmd, struct pci_sysdata *sd,
-			  resource_size_t *offset, bool vmd_in_guest)
+static int vmd_create_bus(struct vmd_dev *vmd, enum vmd_rootbus bus_number,
+			  struct pci_sysdata *sd, resource_size_t *offset,
+			  bool vmd_in_guest, u8 primary)
 {
+	u8 cfgbar = bus_number * 3;
+	u8 membar1 = cfgbar + 1;
+	u8 membar2 = cfgbar + 2;
+	struct pci_bus *vmd_bus;
 	LIST_HEAD(resources);
 
-	pci_add_resource(&resources, &vmd->resources[VMD_RES_CFGBAR]);
-	pci_add_resource_offset(&resources, &vmd->resources[VMD_RES_MBAR_1],
+	pci_add_resource(&resources, &vmd->resources[cfgbar]);
+	pci_add_resource_offset(&resources, &vmd->resources[membar1],
 				offset[0]);
-	pci_add_resource_offset(&resources, &vmd->resources[VMD_RES_MBAR_2],
+	pci_add_resource_offset(&resources, &vmd->resources[membar2],
 				offset[1]);
 
-	vmd->bus[VMD_BUS_0] = pci_create_root_bus(&vmd->dev->dev,
-						  vmd->busn_start[VMD_BUS_0],
-						  &vmd_ops, sd, &resources);
-	if (!vmd->bus[VMD_BUS_0]) {
+	vmd_bus = pci_create_root_bus(&vmd->dev->dev,
+				      vmd->busn_start[bus_number], &vmd_ops, sd,
+				      &resources);
+
+	if (!vmd_bus) {
 		pci_free_resource_list(&resources);
 		return -ENODEV;
 	}
 
+	/*
+	 * pci_create_root_bus() does not initialise bus->primary.
+	 * Set it here before any scanning so bridge traversal logic
+	 * sees the correct upstream bus number from the start.
+	 */
+	vmd_bus->primary = primary;
+
 	/*
 	 * Don't copy _OSC control flags from root bridge if running in a VM, as
 	 * they don't reflect the physical root bridge capabilities.
 	 */
 	if (!vmd_in_guest)
 		vmd_copy_host_bridge_flags(pci_find_host_bridge(vmd->dev->bus),
-					   to_pci_host_bridge(vmd->bus[VMD_BUS_0]->bridge));
-
-	vmd_attach_resources(vmd);
+					   to_pci_host_bridge(vmd_bus->bridge));
 	if (vmd->irq_domain)
-		dev_set_msi_domain(&vmd->bus[VMD_BUS_0]->dev, vmd->irq_domain);
+		dev_set_msi_domain(&vmd_bus->dev, vmd->irq_domain);
 	else
-		dev_set_msi_domain(&vmd->bus[VMD_BUS_0]->dev,
+		dev_set_msi_domain(&vmd_bus->dev,
 				   dev_get_msi_domain(&vmd->dev->dev));
 
+	vmd->bus[bus_number] = vmd_bus;
+
 	return 0;
 }
 
@@ -1101,7 +1379,15 @@ static void vmd_bus_enumeration(struct pci_bus *bus, unsigned long features)
 	vmd_acpi_begin();
 
 	pci_scan_child_bus(bus);
-	vmd_domain_reset(vmd_from_bus(bus));
+
+	/*
+	 * vmd_domain_reset() walks the full VMD CFGBAR aperture, so a single
+	 * invocation from BUS0 resets bridge windows for the whole VMD domain,
+	 * including BUS1. Running it again during BUS1 enumeration would
+	 * re-clobber windows already assigned for BUS0.
+	 */
+	if (bus->primary == VMD_PRIMARY_BUS0)
+		vmd_domain_reset(vmd_from_bus(bus));
 
 	/*
 	 * When Intel VMD is enabled, the OS does not discover the Root Ports
@@ -1140,6 +1426,16 @@ static void vmd_bus_enumeration(struct pci_bus *bus, unsigned long features)
 	vmd_acpi_end();
 }
 
+/* Drop BUS1 configuration and continue with BUS0 only. */
+static void vmd_disable_bus1(struct vmd_dev *vmd)
+{
+	vmd->resources[VMD_RES_BUS1_CFGBAR] = (struct resource){};
+	vmd->resources[VMD_RES_BUS1_MBAR_1] = (struct resource){};
+	vmd->resources[VMD_RES_BUS1_MBAR_2] = (struct resource){};
+	vmd->sysdata[VMD_BUS_1].domain = PCI_DOMAIN_NR_NOT_SET;
+	vmd->bus[VMD_BUS_1] = NULL;
+}
+
 static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features)
 {
 	struct pci_sysdata *sd = &vmd->sysdata[VMD_BUS_0];
@@ -1153,7 +1449,9 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features)
 	if (ret)
 		return ret;
 
-	vmd_configure_cfgbar(vmd);
+	ret = vmd_configure_cfgbar(vmd);
+	if (ret)
+		return ret;
 
 	/*
 	 * If the window is below 4GB, clear IORESOURCE_MEM_64 so we can
@@ -1213,11 +1511,38 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features)
 
 	sd->node = pcibus_to_node(vmd->dev->bus);
 
-	ret = vmd_create_bus(vmd, sd, offset, vmd_in_guest);
+	if (vmd->bus1_rootbus) {
+		vmd->sysdata[VMD_BUS_1].vmd_dev = vmd->dev;
+		vmd->sysdata[VMD_BUS_1].node = pcibus_to_node(vmd->dev->bus);
+		vmd->sysdata[VMD_BUS_1].domain = pci_bus_find_emul_domain_nr(0, 0x10000, INT_MAX);
+		if (vmd->sysdata[VMD_BUS_1].domain < 0) {
+			pci_warn(vmd->dev,
+				 "Can't allocate BUS1 domain: %d, continuing with BUS0 only\n",
+				 vmd->sysdata[VMD_BUS_1].domain);
+			vmd_disable_bus1(vmd);
+		}
+	}
+
+	ret = vmd_attach_resources(vmd);
+	if (ret) {
+		pci_bus_release_emul_domain_nr(sd->domain);
+		if (vmd->bus1_rootbus && vmd->sysdata[VMD_BUS_1].domain >= 0)
+			pci_bus_release_emul_domain_nr(vmd->sysdata[VMD_BUS_1].domain);
+
+		vmd_remove_irq_domain(vmd);
+		return ret;
+	}
+
+	ret = vmd_create_bus(vmd, VMD_BUS_0, sd, offset, vmd_in_guest,
+			     VMD_PRIMARY_BUS0);
 	if (ret) {
 		pci_err(vmd->dev, "Can't create bus: %d\n", ret);
 		pci_bus_release_emul_domain_nr(sd->domain);
+		if (vmd->bus1_rootbus && vmd->sysdata[VMD_BUS_1].domain >= 0)
+			pci_bus_release_emul_domain_nr(vmd->sysdata[VMD_BUS_1].domain);
+
 		vmd_remove_irq_domain(vmd);
+		vmd_detach_resources(vmd);
 		return ret;
 	}
 
@@ -1225,8 +1550,33 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features)
 			       &vmd->bus[VMD_BUS_0]->dev.kobj, "domain"),
 	     "Can't create symlink to domain\n");
 
+	if (vmd->bus1_rootbus && vmd->sysdata[VMD_BUS_1].domain >= 0) {
+		ret = vmd_create_bus(vmd, VMD_BUS_1, &vmd->sysdata[VMD_BUS_1], offset,
+				     vmd_in_guest, VMD_PRIMARY_BUS1);
+		if (ret) {
+			pci_warn(vmd->dev,
+				 "Can't create BUS1: %d, continuing with BUS0 only\n",
+				 ret);
+
+			release_resource(&vmd->resources[VMD_RES_BUS1_MBAR_2]);
+			release_resource(&vmd->resources[VMD_RES_BUS1_MBAR_1]);
+			pci_bus_release_emul_domain_nr(vmd->sysdata[VMD_BUS_1].domain);
+			vmd_disable_bus1(vmd);
+		}
+
+		if (vmd->bus1_rootbus && vmd->sysdata[VMD_BUS_1].domain >= 0) {
+			WARN(sysfs_create_link(&vmd->dev->dev.kobj,
+					       &vmd->bus[VMD_BUS_1]->dev.kobj,
+					       "domain1"),
+			     "Can't create symlink to domain1\n");
+		}
+	}
+
 	vmd_bus_enumeration(vmd->bus[VMD_BUS_0], features);
 
+	if (vmd->bus1_rootbus && vmd->sysdata[VMD_BUS_1].domain >= 0)
+		vmd_bus_enumeration(vmd->bus[VMD_BUS_1], features);
+
 	return 0;
 }
 
@@ -1263,6 +1613,7 @@ static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id)
 
 	vmd->dev = dev;
 	vmd->sysdata[VMD_BUS_0].domain = PCI_DOMAIN_NR_NOT_SET;
+	vmd->sysdata[VMD_BUS_1].domain = PCI_DOMAIN_NR_NOT_SET;
 	vmd->features = features;
 	vmd->instance = ida_alloc(&vmd_instance_ida, GFP_KERNEL);
 	if (vmd->instance < 0)
@@ -1301,8 +1652,14 @@ static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id)
 	if (err)
 		goto out_release_instance;
 
-	dev_info(&vmd->dev->dev, "Bound to PCI domain %04x\n",
-		 vmd->sysdata[VMD_BUS_0].domain);
+	if (vmd->bus1_rootbus && vmd->sysdata[VMD_BUS_1].domain >= 0)
+		dev_info(&vmd->dev->dev, "Bound to PCI domains %04x and %04x\n",
+			 vmd->sysdata[VMD_BUS_0].domain,
+			 vmd->sysdata[VMD_BUS_1].domain);
+	else
+		dev_info(&vmd->dev->dev, "Bound to PCI domain %04x\n",
+			 vmd->sysdata[VMD_BUS_0].domain);
+
 	return 0;
 
  out_release_instance:
@@ -1325,6 +1682,14 @@ static void vmd_remove(struct pci_dev *dev)
 	pci_stop_root_bus(vmd->bus[VMD_BUS_0]);
 	sysfs_remove_link(&vmd->dev->dev.kobj, "domain");
 	pci_remove_root_bus(vmd->bus[VMD_BUS_0]);
+
+	if (vmd->bus1_rootbus && vmd->bus[VMD_BUS_1]) {
+		pci_stop_root_bus(vmd->bus[VMD_BUS_1]);
+		sysfs_remove_link(&vmd->dev->dev.kobj, "domain1");
+		pci_remove_root_bus(vmd->bus[VMD_BUS_1]);
+		pci_bus_release_emul_domain_nr(vmd->sysdata[VMD_BUS_1].domain);
+	}
+
 	vmd_cleanup_srcu(vmd);
 	vmd_detach_resources(vmd);
 	vmd_remove_irq_domain(vmd);
@@ -1425,4 +1790,4 @@ module_pci_driver(vmd_drv);
 MODULE_AUTHOR("Intel Corporation");
 MODULE_DESCRIPTION("Volume Management Device driver");
 MODULE_LICENSE("GPL v2");
-MODULE_VERSION("0.6");
+MODULE_VERSION("0.7");
-- 
2.43.0


  parent reply	other threads:[~2026-11-06 16:16 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-06 18:42 [PATCH v10 0/7] VMD add second root bus support Szymon Durawa
2026-11-06 18:42 ` [PATCH v10 1/7] PCI: vmd: Add vmd_bus_enumeration() helper function Szymon Durawa
2026-11-06 16:26   ` sashiko-bot
2026-11-06 18:42 ` [PATCH v10 2/7] PCI: vmd: Add vmd_configure_cfgbar() " Szymon Durawa
2026-11-06 16:33   ` sashiko-bot
2026-11-06 18:42 ` [PATCH v10 3/7] PCI: vmd: Add vmd_configure_membar() and vmd_configure_membar1_membar2() Szymon Durawa
2026-11-06 16:44   ` sashiko-bot
2026-11-06 18:42 ` [PATCH v10 4/7] PCI: vmd: Add vmd_create_bus() Szymon Durawa
2026-11-06 16:49   ` sashiko-bot
2026-11-06 18:42 ` [PATCH v10 5/7] PCI: vmd: Replace hardcoded values with enum and defines Szymon Durawa
2026-11-06 16:51   ` sashiko-bot
2026-11-06 18:42 ` [PATCH v10 6/7] PCI: vmd: Convert bus, busn_start and sysdata to an array Szymon Durawa
2026-11-06 16:57   ` sashiko-bot
2026-11-06 18:42 ` Szymon Durawa [this message]
2026-11-06 17:23   ` [PATCH v10 7/7] PCI: vmd: Add support for second rootbus under VMD sashiko-bot
2026-11-07 17:06 ` [PATCH v10 0/7] VMD add second root bus support Manivannan Sadhasivam
2026-11-11  0:32 ` Vladimir Nedoshivin

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261106184256.4130-8-szymon.durawa@linux.intel.com \
    --to=szymon.durawa@linux.intel.com \
    --cc=asutosh.pathak@linux.intel.com \
    --cc=bhelgaas@google.com \
    --cc=kwilczynski@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pci@vger.kernel.org \
    --cc=lpieralisi@kernel.org \
    --cc=mani@kernel.org \
    --cc=nirmal.patel@linux.intel.com \
    --cc=robh@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.