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, ®);
+ if (ret)
+ return -EIO;
+ bus1_mbar2_ofs = reg;
+
+ ret = pci_read_config_dword(vmd->dev, VMD_MEMBAR2_OFFSET2, ®);
+ 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
prev 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®