mirror of https://lore.kernel.org/lkml/
 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 v9 7/7] PCI: vmd: Add support for second rootbus under VMD
Date: Mon, 26 Oct 2026 20:55:00 +0000	[thread overview]
Message-ID: <20261026205502.3502574-8-szymon.durawa@linux.intel.com> (raw)
In-Reply-To: <20261026205502.3502574-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 1db974ed760f..079fa51f6d07 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
 };
 
@@ -109,6 +132,12 @@ enum vmd_features {
 	 * referred to as MEMBAR2 or MSI-X BAR.
 	 */
 	VMD_FEAT_USE_BIOS_INFO		= (1 << 6),
+
+	/*
+	 * Starting from Intel Arrow Lake, VMD devices have their VMD root ports
+	 * on the additional BUS1 root bus.
+	 */
+	VMD_FEAT_HAS_BUS1_ROOTBUS	= (1 << 7)
 };
 
 #define VMD_BIOS_PM_QUIRK_LTR	0x1003	/* 3145728 ns */
@@ -116,7 +145,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);
 
@@ -175,6 +205,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)
@@ -451,6 +482,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.
@@ -460,6 +521,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;
@@ -468,16 +530,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;
 }
 
 /*
@@ -574,12 +654,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]);
@@ -587,10 +674,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);
@@ -632,16 +722,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,
@@ -698,7 +825,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;
@@ -716,10 +843,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));
@@ -859,7 +1003,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;
 	}
@@ -926,7 +1070,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;
@@ -940,6 +1084,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;
 }
 
 /*
@@ -970,8 +1170,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;
 
@@ -991,55 +1192,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;
 }
 
@@ -1052,7 +1330,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
@@ -1091,6 +1377,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];
@@ -1104,7 +1400,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
@@ -1164,11 +1462,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;
 	}
 
@@ -1176,8 +1501,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;
 }
 
@@ -1214,6 +1564,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)
@@ -1252,8 +1603,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:
@@ -1276,6 +1633,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);
@@ -1376,4 +1741,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-10-26 18:29 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-26 20:54 [PATCH v9 0/7] VMD add second root bus support Szymon Durawa
2026-10-26 20:54 ` [PATCH v9 1/7] PCI: vmd: Add vmd_bus_enumeration() helper function Szymon Durawa
2026-10-26 20:54 ` [PATCH v9 2/7] PCI: vmd: Add vmd_configure_cfgbar() " Szymon Durawa
2026-10-26 20:54 ` [PATCH v9 3/7] PCI: vmd: Add vmd_configure_membar() and vmd_configure_membar1_membar2() Szymon Durawa
2026-10-26 20:54 ` [PATCH v9 4/7] PCI: vmd: Add vmd_create_bus() Szymon Durawa
2026-10-26 20:54 ` [PATCH v9 5/7] PCI: vmd: Replace hardcoded values with enum and defines Szymon Durawa
2026-10-26 20:54 ` [PATCH v9 6/7] PCI: vmd: Convert bus, busn_start and sysdata to an array Szymon Durawa
2026-10-26 20:55 ` Szymon Durawa [this message]

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=20261026205502.3502574-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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®