Merge branch 'pci/controller/vmd'

- Support device ID 0x28C1 and assume that BIOS has already enumerated the
  hierarchy below VMD and stored bus range info for OS to use (Nirmal
  Patel)

- Add support for VMCONFIG BUS_RESTRICT_CFG=3, which makes it possible to
  enumerate downstream devices on Intel Arrow Lake-HX systems and probably
  others (Ali Alaei)

- Observe _OSC negotiation for VMD hierarchy only when running on bare
  metal, not when running in a VM (Nirmal Patel)

- Add Nova Lake (NVL) and Dunlow (DNL) Device IDs (Szymon Durawa)

* pci/controller/vmd:
  PCI: vmd: Add Nova Lake (NVL) and Dunlow (DNL) Device IDs
  PCI: vmd: Only copy root bridge _OSC control flags in bare metal OS
  PCI: vmd: Handle BUS_RESTRICT_CFG value 3 for Arrow Lake-HX
  PCI: vmd: Add feature to scan BIOS-enumerated devices
This commit is contained in:
Bjorn Helgaas
2026-08-21 16:40:47 -05:00
2 changed files with 169 additions and 35 deletions

View File

@@ -37,6 +37,12 @@
#define MB2_SHADOW_OFFSET 0x2000
#define MB2_SHADOW_SIZE 16
/* DMR BAR4 register offsets */
#define SHADOW_MEMBAR1_28C1 0x2818 /* MEMBAR1 physical address */
#define SHADOW_MEMBAR2_28C1 0x2820 /* MEMBAR2 physical address */
#define BASE_ID_REG_28C1 0x2840
#define MEMBAR2_OFFSET_28C1 0x30d0
enum vmd_features {
/*
* Device may contain registers which hint the physical location of the
@@ -77,6 +83,15 @@ enum vmd_features {
* proper power management of the SoC.
*/
VMD_FEAT_BIOS_PM_QUIRK = (1 << 5),
/*
* Newer VMD with device ID 0x28C1 has unique settings compared to its
* predecessor where BIOS enumerates the entire VMD device tree and
* stores respective configurations including bus start range and
* shadow registers in VMD MMIO space in VMD BAR4/BAR5, otherwise
* referred to as MEMBAR2 or MSI-X BAR.
*/
VMD_FEAT_USE_BIOS_INFO = (1 << 6),
};
#define VMD_BIOS_PM_QUIRK_LTR 0x1003 /* 3145728 ns */
@@ -142,6 +157,7 @@ struct vmd_dev {
u8 first_vec;
char *name;
int instance;
unsigned long features;
};
static inline struct vmd_dev *vmd_from_bus(struct pci_bus *bus)
@@ -366,6 +382,9 @@ static void vmd_set_msi_remapping(struct vmd_dev *vmd, bool enable)
{
u16 reg;
if (!!(vmd->features & VMD_FEAT_USE_BIOS_INFO))
return;
pci_read_config_word(vmd->dev, PCI_REG_VMCONFIG, &reg);
reg = enable ? (reg & ~VMCONFIG_MSI_REMAP) :
(reg | VMCONFIG_MSI_REMAP);
@@ -389,11 +408,22 @@ static void vmd_remove_irq_domain(struct vmd_dev *vmd)
}
}
static unsigned int vmd_bus_to_ecam(struct vmd_dev *vmd, unsigned int busnr)
{
if (!!(vmd->features & VMD_FEAT_USE_BIOS_INFO))
return busnr;
return busnr - vmd->busn_start;
}
static void __iomem *vmd_cfg_addr(struct vmd_dev *vmd, struct pci_bus *bus,
unsigned int devfn, int reg, int len)
{
unsigned int busnr_ecam = bus->number - vmd->busn_start;
u32 offset = PCIE_ECAM_OFFSET(busnr_ecam, devfn, reg);
unsigned int busnr_ecam;
u32 offset;
busnr_ecam = vmd_bus_to_ecam(vmd, bus->number);
offset = PCIE_ECAM_OFFSET(busnr_ecam, devfn, reg);
if (offset + len >= resource_size(&vmd->dev->resource[VMD_CFGBAR]))
return NULL;
@@ -518,22 +548,37 @@ static inline void vmd_acpi_begin(void) { }
static inline void vmd_acpi_end(void) { }
#endif /* CONFIG_ACPI */
static resource_size_t vmd_cfgbar_ecam_space(struct vmd_dev *vmd)
{
resource_size_t cfgbar_buses;
unsigned int ecam_start;
cfgbar_buses = resource_size(&vmd->dev->resource[VMD_CFGBAR]) >> 20;
ecam_start = vmd_bus_to_ecam(vmd, vmd->resources[0].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[0]);
u8 dev, functions, fn, hdr_type;
unsigned int ecam_bus;
char __iomem *base;
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[0].start + bus);
for (dev = 0; dev < 32; dev++) {
base = vmd->cfgbar + PCIE_ECAM_OFFSET(bus,
base = vmd->cfgbar + PCIE_ECAM_OFFSET(ecam_bus,
PCI_DEVFN(dev, 0), 0);
hdr_type = readb(base + PCI_HEADER_TYPE);
functions = (hdr_type & PCI_HEADER_TYPE_MFD) ? 8 : 1;
for (fn = 0; fn < functions; fn++) {
base = vmd->cfgbar + PCIE_ECAM_OFFSET(bus,
base = vmd->cfgbar + PCIE_ECAM_OFFSET(ecam_bus,
PCI_DEVFN(dev, fn), 0);
hdr_type = readb(base + PCI_HEADER_TYPE) &
@@ -640,6 +685,8 @@ static int vmd_get_bus_number_start(struct vmd_dev *vmd)
pci_read_config_word(dev, PCI_REG_VMCAP, &reg);
if (BUS_RESTRICT_CAP(reg)) {
pci_read_config_word(dev, PCI_REG_VMCONFIG, &reg);
if (PCI_POSSIBLE_ERROR(reg))
return -ENODEV;
switch (BUS_RESTRICT_CFG(reg)) {
case 0:
@@ -648,6 +695,7 @@ static int vmd_get_bus_number_start(struct vmd_dev *vmd)
case 1:
vmd->busn_start = 128;
break;
case 3:
case 2:
vmd->busn_start = 224;
break;
@@ -661,6 +709,46 @@ static int vmd_get_bus_number_start(struct vmd_dev *vmd)
return 0;
}
static int vmd_get_bus_info_from_bar4(struct vmd_dev *vmd,
resource_size_t *offset1,
resource_size_t *offset2)
{
u64 phys1, phys2, bar4_2840;
void __iomem *bar4;
u32 base_id;
u8 base_bus;
bar4 = pci_ioremap_bar(vmd->dev, 4);
if (!bar4)
return -ENOMEM;
/* Read shadow registers for MEMBAR1 and MEMBAR2 physical addresses */
phys1 = readq(bar4 + SHADOW_MEMBAR1_28C1);
phys2 = readq(bar4 + SHADOW_MEMBAR2_28C1);
/*
* Read and set bus start number from Base ID register. 24-bit Base ID
* register is part of 64-bit shadowed reqid hide range register and
* holds segment, bus, device and function.
*/
bar4_2840 = readq(bar4 + BASE_ID_REG_28C1);
base_id = bar4_2840 & 0xFFFFFF;
base_bus = base_id >> 8;
vmd->busn_start = base_bus;
/* Calculate offsets like vmd_get_phys_offsets() does */
if (phys1)
*offset1 = vmd->dev->resource[VMD_MEMBAR1].start -
(phys1 & PCI_BASE_ADDRESS_MEM_MASK);
if (phys2)
*offset2 = vmd->dev->resource[VMD_MEMBAR2].start -
(phys2 & PCI_BASE_ADDRESS_MEM_MASK);
pci_iounmap(vmd->dev, bar4);
return 0;
}
static irqreturn_t vmd_irq(int irq, void *data)
{
struct vmd_irq_list *irqs = data;
@@ -711,6 +799,52 @@ static int vmd_alloc_irqs(struct vmd_dev *vmd)
return 0;
}
static int vmd_prepare_offsets_and_bus(struct vmd_dev *vmd,
unsigned long features,
resource_size_t *membar2_offset,
resource_size_t *offset1,
resource_size_t *offset2)
{
int ret;
/*
* Shadow registers may exist in certain VMD device IDs which allow
* guests to correctly assign host physical addresses to the root ports
* and child devices. These registers will either return the host value
* or 0, depending on an enable bit in the VMD device.
*
* For certain VMD devices (i.e. 0x28C1), BIOS places device info
* in BAR4 shadow registers to determine the base bus number and memory
* offsets.
*/
if (features & VMD_FEAT_USE_BIOS_INFO) {
*membar2_offset = MEMBAR2_OFFSET_28C1;
ret = vmd_get_bus_info_from_bar4(vmd, offset1, offset2);
if (ret)
return ret;
} else if (features & VMD_FEAT_HAS_MEMBAR_SHADOW) {
*membar2_offset = MB2_SHADOW_OFFSET + MB2_SHADOW_SIZE;
ret = vmd_get_phys_offsets(vmd, true, offset1, offset2);
if (ret)
return ret;
} else if (features & VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP) {
ret = vmd_get_phys_offsets(vmd, false, offset1, offset2);
if (ret)
return ret;
}
/*
* Certain VMD devices may have a root port configuration option which
* 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);
if (ret)
return ret;
}
return 0;
}
/*
* Since VMD is an aperture to regular PCIe root ports, only allow it to
* control features that the OS is allowed to control on the physical PCI bus.
@@ -780,42 +914,25 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features)
LIST_HEAD(resources);
resource_size_t offset[2] = {0};
resource_size_t membar2_offset = 0x2000;
resource_size_t busn_end;
struct pci_bus *child;
struct pci_dev *dev;
bool vmd_in_guest;
int ret;
/*
* Shadow registers may exist in certain VMD device ids which allow
* guests to correctly assign host physical addresses to the root ports
* and child devices. These registers will either return the host value
* or 0, depending on an enable bit in the VMD device.
*/
if (features & VMD_FEAT_HAS_MEMBAR_SHADOW) {
membar2_offset = MB2_SHADOW_OFFSET + MB2_SHADOW_SIZE;
ret = vmd_get_phys_offsets(vmd, true, &offset[0], &offset[1]);
if (ret)
return ret;
} else if (features & VMD_FEAT_HAS_MEMBAR_SHADOW_VSCAP) {
ret = vmd_get_phys_offsets(vmd, false, &offset[0], &offset[1]);
if (ret)
return ret;
}
/*
* Certain VMD devices may have a root port configuration option which
* 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);
if (ret)
return ret;
}
ret = vmd_prepare_offsets_and_bus(vmd, features, &membar2_offset,
&offset[0], &offset[1]);
if (ret)
return ret;
/* Do not let resource[0] end go out of bounds */
res = &vmd->dev->resource[VMD_CFGBAR];
busn_end = vmd->busn_start + (resource_size(res) >> 20) - 1;
busn_end = min_t(resource_size_t, busn_end, 0xff);
vmd->resources[0] = (struct resource) {
.name = "VMD CFGBAR",
.start = vmd->busn_start,
.end = vmd->busn_start + (resource_size(res) >> 20) - 1,
.end = busn_end,
.flags = IORESOURCE_BUS | IORESOURCE_PCI_FIXED,
};
@@ -862,14 +979,16 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features)
.parent = res,
};
/* Non-zero offset means guest/direct assign view. */
vmd_in_guest = offset[0] || offset[1];
/*
* Currently MSI remapping must be enabled in guest passthrough mode
* due to some missing interrupt remapping plumbing. This is probably
* acceptable because the guest is usually CPU-limited and MSI
* remapping doesn't become a performance bottleneck.
*/
if (!(features & VMD_FEAT_CAN_BYPASS_MSI_REMAP) ||
offset[0] || offset[1]) {
if (!(features & VMD_FEAT_CAN_BYPASS_MSI_REMAP) || vmd_in_guest) {
ret = vmd_alloc_irqs(vmd);
if (ret)
return ret;
@@ -910,8 +1029,13 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features)
return -ENODEV;
}
vmd_copy_host_bridge_flags(pci_find_host_bridge(vmd->dev->bus),
to_pci_host_bridge(vmd->bus->bridge));
/*
* 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->bridge));
vmd_attach_resources(vmd);
if (vmd->irq_domain)
@@ -998,6 +1122,7 @@ static int vmd_probe(struct pci_dev *dev, const struct pci_device_id *id)
vmd->dev = dev;
vmd->sysdata.domain = PCI_DOMAIN_NR_NOT_SET;
vmd->features = features;
vmd->instance = ida_alloc(&vmd_instance_ida, GFP_KERNEL);
if (vmd->instance < 0)
return vmd->instance;
@@ -1114,6 +1239,10 @@ static const struct pci_device_id vmd_ids[] = {
.driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW |
VMD_FEAT_HAS_BUS_RESTRICTIONS |
VMD_FEAT_CAN_BYPASS_MSI_REMAP,},
{PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_VMD_28C1),
.driver_data = VMD_FEAT_HAS_MEMBAR_SHADOW |
VMD_FEAT_CAN_BYPASS_MSI_REMAP |
VMD_FEAT_USE_BIOS_INFO,},
{PCI_VDEVICE(INTEL, 0x467f),
.driver_data = VMD_FEATS_CLIENT,},
{PCI_VDEVICE(INTEL, 0x4c3d),
@@ -1132,6 +1261,10 @@ static const struct pci_device_id vmd_ids[] = {
.driver_data = VMD_FEATS_CLIENT,},
{PCI_VDEVICE(INTEL, 0xb07f),
.driver_data = VMD_FEATS_CLIENT,},
{PCI_VDEVICE(INTEL, 0xd70b),
.driver_data = VMD_FEATS_CLIENT,},
{PCI_VDEVICE(INTEL, 0xd73b),
.driver_data = VMD_FEATS_CLIENT,},
{0,}
};
MODULE_DEVICE_TABLE(pci, vmd_ids);

View File

@@ -2892,6 +2892,7 @@
#define PCI_DEVICE_ID_INTEL_HDA_ICH8 0x284b
#define PCI_DEVICE_ID_INTEL_ICH8_6 0x2850
#define PCI_DEVICE_ID_INTEL_VMD_28C0 0x28c0
#define PCI_DEVICE_ID_INTEL_VMD_28C1 0x28c1
#define PCI_DEVICE_ID_INTEL_ICH9_0 0x2910
#define PCI_DEVICE_ID_INTEL_ICH9_2 0x2912
#define PCI_DEVICE_ID_INTEL_ICH9_3 0x2913