diff options
| author | Bjorn Helgaas <bhelgaas@google.com> | 2026-08-21 16:40:47 -0500 |
|---|---|---|
| committer | Bjorn Helgaas <bhelgaas@google.com> | 2026-08-21 16:40:47 -0500 |
| commit | e0aba454f87aeb3d2c86fcc401daa7db03061bdb (patch) | |
| tree | a977a5e8c565d8b9821e4d1fb89deb7c47299a21 /drivers | |
| parent | b130a2caf5d3f65c14e9524c65bcf0d64be298d9 (diff) | |
| parent | 55aa45154fe48b7d8e96bc031ad27d5b0edfcdb8 (diff) | |
| download | linux-e0aba454f87aeb3d2c86fcc401daa7db03061bdb.tar.gz linux-e0aba454f87aeb3d2c86fcc401daa7db03061bdb.zip | |
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
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/pci/controller/vmd.c | 203 |
1 files changed, 168 insertions, 35 deletions
diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c index d4ae250d4bc6..241023ecf677 100644 --- a/drivers/pci/controller/vmd.c +++ b/drivers/pci/controller/vmd.c @@ -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 = 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, ®); if (BUS_RESTRICT_CAP(reg)) { pci_read_config_word(dev, PCI_REG_VMCONFIG, ®); + 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); |
