diff --git a/patch/kernel/linux.patch b/patch/kernel/linux.patch new file mode 100644 index 00000000..09546420 --- /dev/null +++ b/patch/kernel/linux.patch @@ -0,0 +1,639 @@ +PCI: vmd: Add support for the second (PCH) VMD rootbus on Intel Arrow Lake + +On Arrow Lake desktop platforms (Core Ultra 200S), NVMe drives attached +through the PCH sit on a second VMD rootbus hardwired to bus 0x80. The +mainline vmd driver only enumerates the first rootbus, so those drives +are invisible to FOS and imaging fails with "no disks found" (FOGProject +issue #107). + +Intel's fix ("PCI: vmd: Add support for second rootbus under VMD", +Szymon Durawa) is still unmerged upstream as of v5 (2026-06-25). Ubuntu +ships it as SAUCE patches in linux-oem-6.11 (Launchpad bug #2085853). +This is those four patches combined and adapted to vanilla 6.12.35: + + 0c9ff92b2efc UBUNTU: SAUCE: PCI: vmd: Clean up vmd_enable_domain function + d20355bdd463 UBUNTU: SAUCE: PCI: vmd: Add VMD PCH rootbus support + e7b7195e3706 UBUNTU: SAUCE: PCI: vmd: Add WA for VMD PCH rootbus support + 040d304c75b9 UBUNTU: SAUCE: PCI: vmd: Fix use-after-free bug in resource + name assignment + +Adaptation: dropped the pci_lock_rescan_remove()/pci_unlock_rescan_remove() +calls in vmd_enable_domain, which exist only in Ubuntu's 6.11 tree, and +Ubuntu's aspm_os_control field which mainline lacks. Everything else is +verbatim. Remove this patch once the upstream series lands in the stable +kernel FOS builds from. + +--- a/drivers/pci/controller/vmd.c ++++ b/drivers/pci/controller/vmd.c +@@ -33,9 +33,34 @@ + #define PCI_REG_VMLOCK 0x70 + #define MB2_SHADOW_EN(vmlock) (vmlock & 0x2) + ++#define VMD_PRIMARY_PCH_BUS 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) ((busr >> 8) & 0xff) ++#define VMD_BUS_START(busr) (busr & 0x00ff) ++ + #define MB2_SHADOW_OFFSET 0x2000 + #define MB2_SHADOW_SIZE 16 + ++enum vmd_resource { ++ VMD_RES_CFGBAR = 0, ++ VMD_RES_MBAR_1, /*VMD Resource MemBAR 1 */ ++ VMD_RES_MBAR_2, /*VMD Resource MemBAR 2 */ ++ VMD_RES_PCH_CFGBAR, ++ VMD_RES_PCH_MBAR_1, /*VMD Resource PCH MemBAR 1 */ ++ VMD_RES_PCH_MBAR_2, /*VMD Resource PCH MemBAR 2 */ ++ VMD_RES_COUNT ++}; ++ ++enum vmd_rootbus { ++ VMD_BUS_0 = 0, ++ VMD_BUS_1, ++ VMD_BUS_COUNT ++}; ++ + enum vmd_features { + /* + * Device may contain registers which hint the physical location of the +@@ -76,6 +101,12 @@ + * proper power management of the SoC. + */ + VMD_FEAT_BIOS_PM_QUIRK = (1 << 5), ++ ++ /* ++ * Starting from Intel Arrow Lake, VMD devices have their VMD rootports ++ * connected to CPU IOC and PCH rootbuses. ++ */ ++ VMD_FEAT_HAS_PCH_ROOTBUS = (1 << 6) + }; + + #define VMD_BIOS_PM_QUIRK_LTR 0x1003 /* 3145728 ns */ +@@ -83,7 +114,8 @@ + #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_PCH_ROOTBUS) + + static DEFINE_IDA(vmd_instance_ida); + +@@ -134,10 +166,10 @@ + struct vmd_irq_list *irqs; + + struct pci_sysdata sysdata; +- struct resource resources[3]; ++ struct resource resources[VMD_RES_COUNT]; + struct irq_domain *irq_domain; +- struct pci_bus *bus; +- u8 busn_start; ++ struct pci_bus *bus[VMD_BUS_COUNT]; ++ u8 busn_start[VMD_BUS_COUNT]; + u8 first_vec; + char *name; + int instance; +@@ -366,11 +398,30 @@ + } + } + ++static inline u8 vmd_has_pch_rootbus(struct vmd_dev *vmd) ++{ ++ return vmd->busn_start[VMD_BUS_1] != 0; ++} ++ + 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 char bus_number; ++ unsigned int busnr_ecam; ++ u32 offset; ++ ++ /* ++ * VMD WA: for PCH rootbus, bus number is set to VMD_PRIMARY_PCH_BUS ++ * (see comment in vmd_create_pch_bus()) but original value is 0xE1 ++ * which is stored in vmd->busn_start[VMD_BUS_1]. ++ */ ++ if (vmd_has_pch_rootbus(vmd) && bus->number == VMD_PRIMARY_PCH_BUS) ++ bus_number = vmd->busn_start[VMD_BUS_1]; ++ else ++ bus_number = bus->number; ++ ++ busnr_ecam = bus_number - vmd->busn_start[VMD_BUS_0]; ++ offset = PCIE_ECAM_OFFSET(busnr_ecam, devfn, reg); + + if (offset + len >= resource_size(&vmd->dev->resource[VMD_CFGBAR])) + return NULL; +@@ -507,10 +558,15 @@ + + static void vmd_domain_reset(struct vmd_dev *vmd) + { +- u16 bus, max_buses = resource_size(&vmd->resources[0]); ++ u16 bus, max_buses = resource_size(&vmd->resources[VMD_RES_CFGBAR]); + u8 dev, functions, fn, hdr_type; + char __iomem *base; + ++ if (vmd_has_pch_rootbus(vmd)) { ++ max_buses += resource_size(&vmd->resources[VMD_RES_PCH_CFGBAR]); ++ max_buses += 2; ++ } ++ + for (bus = 0; bus < max_buses; bus++) { + for (dev = 0; dev < 32; dev++) { + base = vmd->cfgbar + PCIE_ECAM_OFFSET(bus, +@@ -555,8 +611,8 @@ + + static void vmd_attach_resources(struct vmd_dev *vmd) + { +- vmd->dev->resource[VMD_MEMBAR1].child = &vmd->resources[1]; +- vmd->dev->resource[VMD_MEMBAR2].child = &vmd->resources[2]; ++ vmd->dev->resource[VMD_MEMBAR1].child = &vmd->resources[VMD_RES_MBAR_1]; ++ vmd->dev->resource[VMD_MEMBAR2].child = &vmd->resources[VMD_RES_MBAR_2]; + } + + static void vmd_detach_resources(struct vmd_dev *vmd) +@@ -635,7 +691,7 @@ + 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; +@@ -646,13 +702,25 @@ + + switch (BUS_RESTRICT_CFG(reg)) { + case 0: +- vmd->busn_start = 0; ++ vmd->busn_start[VMD_BUS_0] = 0; + break; + case 1: +- vmd->busn_start = 128; ++ vmd->busn_start[VMD_BUS_0] = 128; + break; + case 2: +- vmd->busn_start = 224; ++ vmd->busn_start[VMD_BUS_0] = 224; ++ break; ++ case 3: ++ if (!(features & VMD_FEAT_HAS_PCH_ROOTBUS)) { ++ pci_err(dev, "VMD Bus Restriction detected type %d, but PCH Rootbus is not supported, aborting.\n", ++ BUS_RESTRICT_CFG(reg)); ++ return -ENODEV; ++ } ++ ++ /* IOC start bus */ ++ vmd->busn_start[VMD_BUS_0] = 224; ++ /* PCH start bus */ ++ vmd->busn_start[VMD_BUS_1] = 225; + break; + default: + pci_err(dev, "Unknown Bus Offset Setting (%d)\n", +@@ -774,17 +842,252 @@ + return 0; + } + +-static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features) ++static void vmd_configure_cfgbar(struct vmd_dev *vmd) + { +- struct pci_sysdata *sd = &vmd->sysdata; +- struct resource *res; ++ struct resource *res = &vmd->dev->resource[VMD_CFGBAR]; ++ ++ vmd->resources[VMD_RES_CFGBAR] = (struct resource){ ++ .name = "VMD CFGBAR", ++ .start = vmd->busn_start[VMD_BUS_0], ++ .end = vmd->busn_start[VMD_BUS_0] + ++ (resource_size(res) >> 20) - 1, ++ .flags = IORESOURCE_BUS | IORESOURCE_PCI_FIXED, ++ }; ++ ++ if (vmd_has_pch_rootbus(vmd)) { ++ u16 ioc_range = 0; ++ u16 pch_range = 0; ++ ++ pci_read_config_word(vmd->dev, VMD_BUSRANGE0, &ioc_range); ++ pci_read_config_word(vmd->dev, VMD_BUSRANGE1, &pch_range); ++ ++ /* ++ * Resize CPU IOC CFGBAR range to make space for PCH owned ++ * devices by adjusting range end with value stored in ++ * VMD_BUSRANGE0 register. ++ */ ++ vmd->resources[VMD_RES_CFGBAR].start = VMD_BUS_START(ioc_range); ++ vmd->resources[VMD_RES_CFGBAR].end = VMD_BUS_END(ioc_range); ++ ++ vmd->resources[VMD_RES_PCH_CFGBAR] = (struct resource){ ++ .name = "VMD CFGBAR PCH", ++ .start = VMD_BUS_START(pch_range), ++ .end = VMD_BUS_END(pch_range), ++ .flags = IORESOURCE_BUS | IORESOURCE_PCI_FIXED, ++ .parent = &vmd->resources[VMD_RES_CFGBAR], ++ }; ++ } ++} ++ ++/** ++ * vmd_configure_membar - Configure VMD MemBAR register, which points ++ * to MMIO address assigned by the OS or BIOS. ++ * @vmd: the VMD device ++ * @resource_number: resource buffer number to be filled in ++ * @membar_number: number of the MemBAR ++ * @start_offset: 4K aligned offset applied to start of VMD’s MEMBAR MMIO space ++ * @end_offset: 4K aligned offset applied to end of VMD’s MEMBAR MMIO space ++ * @parent: resource assigned as a parent, may be NULL ++ * ++ * Function fills resource buffer inside the VMD structure. ++ */ ++static void vmd_configure_membar(struct vmd_dev *vmd, ++ enum vmd_resource resource_number, ++ u8 membar_number, resource_size_t start_offset, ++ resource_size_t end_offset, ++ struct resource *parent) ++{ ++ struct resource *res_parent; + u32 upper_bits; + unsigned long flags; ++ const char *name; ++ ++ struct resource *res = &vmd->dev->resource[membar_number]; ++ ++ upper_bits = upper_32_bits(res->end); ++ flags = res->flags & ~IORESOURCE_SIZEALIGN; ++ if (!upper_bits) ++ flags &= ~IORESOURCE_MEM_64; ++ ++ // Use static strings instead of dynamic formatting ++ switch (resource_number) { ++ case VMD_RES_MBAR_1: ++ name = "VMD MEMBAR1"; ++ break; ++ case VMD_RES_MBAR_2: ++ name = "VMD MEMBAR2"; ++ break; ++ case VMD_RES_PCH_MBAR_1: ++ name = "VMD MEMBAR1 PCH"; ++ break; ++ case VMD_RES_PCH_MBAR_2: ++ name = "VMD MEMBAR2 PCH"; ++ break; ++ default: ++ name = "VMD MEMBAR"; ++ break; ++ } ++ ++ res_parent = parent; ++ if (!res_parent) ++ res_parent = res; ++ ++ vmd->resources[resource_number] = (struct resource){ ++ .name = name, ++ .start = res->start + start_offset, ++ .end = res->end - end_offset, ++ .flags = flags, ++ .parent = res_parent, ++ }; ++} ++ ++static void vmd_configure_membar1_membar2(struct vmd_dev *vmd, ++ resource_size_t mbar2_ofs) ++{ ++ if (vmd_has_pch_rootbus(vmd)) { ++ u32 pch_mbar1_ofs = 0; ++ u64 pch_mbar2_ofs = 0; ++ u32 reg; ++ ++ pci_read_config_dword(vmd->dev, VMD_MEMBAR1_OFFSET, ++ &pch_mbar1_ofs); ++ ++ pci_read_config_dword(vmd->dev, VMD_MEMBAR2_OFFSET1, ®); ++ pch_mbar2_ofs = reg; ++ ++ pci_read_config_dword(vmd->dev, VMD_MEMBAR2_OFFSET2, ®); ++ pch_mbar2_ofs |= (u64)reg << 32; ++ ++ /* ++ * Resize CPU IOC MEMBAR1 and MEMBAR2 ranges to make space ++ * for PCH owned devices by adjusting range end with values ++ * stored in VMD_MEMBAR1_OFFSET and VMD_MEMBAR2_OFFSET registers ++ */ ++ vmd_configure_membar(vmd, VMD_RES_MBAR_1, VMD_MEMBAR1, 0, ++ pch_mbar1_ofs, NULL); ++ vmd_configure_membar(vmd, VMD_RES_MBAR_2, VMD_MEMBAR2, ++ mbar2_ofs, pch_mbar2_ofs - mbar2_ofs, ++ NULL); ++ ++ vmd_configure_membar(vmd, VMD_RES_PCH_MBAR_1, VMD_MEMBAR1, ++ pch_mbar1_ofs, 0, ++ &vmd->resources[VMD_RES_MBAR_1]); ++ vmd_configure_membar(vmd, VMD_RES_PCH_MBAR_2, VMD_MEMBAR2, ++ mbar2_ofs + pch_mbar2_ofs, 0, ++ &vmd->resources[VMD_RES_MBAR_2]); ++ } else { ++ vmd_configure_membar(vmd, VMD_RES_MBAR_1, VMD_MEMBAR1, 0, 0, ++ NULL); ++ vmd_configure_membar(vmd, VMD_RES_MBAR_2, VMD_MEMBAR2, ++ mbar2_ofs, 0, NULL); ++ } ++} ++ ++static void vmd_bus_enumeration(struct pci_bus *bus, unsigned long features) ++{ ++ struct pci_bus *child; ++ struct pci_dev *dev; ++ int ret; ++ ++ vmd_acpi_begin(); ++ ++ pci_scan_child_bus(bus); ++ ++ if (bus->primary == 0) ++ vmd_domain_reset(vmd_from_bus(bus)); ++ ++ /* ++ * When Intel VMD is enabled, the OS does not discover the Root Ports ++ * owned by Intel VMD within the MMCFG space. pci_reset_bus() applies ++ * a reset to the parent of the PCI device supplied as argument. This ++ * is why we pass a child device, so the reset can be triggered at ++ * the Intel bridge level and propagated to all the children in the ++ * hierarchy. ++ */ ++ list_for_each_entry(child, &bus->children, node) { ++ if (!list_empty(&child->devices)) { ++ dev = list_first_entry(&child->devices, struct pci_dev, ++ bus_list); ++ ret = pci_reset_bus(dev); ++ if (ret) ++ pci_warn(dev, "can't reset device: %d\n", ret); ++ ++ break; ++ } ++ } ++ ++ pci_assign_unassigned_bus_resources(bus); ++ ++ pci_walk_bus(bus, vmd_pm_enable_quirk, &features); ++ ++ /* ++ * VMD root buses are virtual and don't return true on pci_is_pcie() ++ * and will fail pcie_bus_configure_settings() early. It can instead be ++ * run on each of the real root ports. ++ */ ++ list_for_each_entry(child, &bus->children, node) ++ pcie_bus_configure_settings(child); ++ ++ pci_bus_add_devices(bus); ++ ++ vmd_acpi_end(); ++} ++ ++static int vmd_create_pch_bus(struct vmd_dev *vmd, struct pci_sysdata *sd, ++ resource_size_t *offset) ++{ ++ LIST_HEAD(resources_pch); ++ ++ pci_add_resource(&resources_pch, &vmd->resources[VMD_RES_PCH_CFGBAR]); ++ pci_add_resource_offset(&resources_pch, ++ &vmd->resources[VMD_RES_PCH_MBAR_1], offset[0]); ++ pci_add_resource_offset(&resources_pch, ++ &vmd->resources[VMD_RES_PCH_MBAR_2], offset[1]); ++ ++ vmd->bus[VMD_BUS_1] = pci_create_root_bus(&vmd->dev->dev, ++ vmd->busn_start[VMD_BUS_1], ++ &vmd_ops, sd, &resources_pch); ++ ++ if (!vmd->bus[VMD_BUS_1]) { ++ pci_free_resource_list(&resources_pch); ++ pci_stop_root_bus(vmd->bus[VMD_BUS_1]); ++ pci_remove_root_bus(vmd->bus[VMD_BUS_1]); ++ return -ENODEV; ++ } ++ ++ /* ++ * primary bus is not set by pci_create_root_bus(), it is updated here ++ */ ++ vmd->bus[VMD_BUS_1]->primary = VMD_PRIMARY_PCH_BUS; ++ ++ /* This is a workaround for pci_scan_bridge_extend() code. ++ * It assigns setup as broken when primary != bus->number and ++ * for PCH rootbus primary is not "hard-wired to 0". ++ * To avoid this, vmd->bus[VMD_BUS_1]->number and ++ * vmd->bus[VMD_BUS_1]->primary are updated to the same value. ++ */ ++ vmd->bus[VMD_BUS_1]->number = VMD_PRIMARY_PCH_BUS; ++ ++ vmd_copy_host_bridge_flags( ++ pci_find_host_bridge(vmd->dev->bus), ++ to_pci_host_bridge(vmd->bus[VMD_BUS_1]->bridge)); ++ ++ if (vmd->irq_domain) ++ dev_set_msi_domain(&vmd->bus[VMD_BUS_1]->dev, ++ vmd->irq_domain); ++ else ++ dev_set_msi_domain(&vmd->bus[VMD_BUS_1]->dev, ++ dev_get_msi_domain(&vmd->dev->dev)); ++ ++ return 0; ++} ++ ++static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features) ++{ ++ struct pci_sysdata *sd = &vmd->sysdata; + LIST_HEAD(resources); + resource_size_t offset[2] = {0}; + resource_size_t membar2_offset = 0x2000; +- struct pci_bus *child; +- struct pci_dev *dev; + int ret; + + /* +@@ -809,18 +1112,12 @@ + * 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; + } + +- res = &vmd->dev->resource[VMD_CFGBAR]; +- vmd->resources[0] = (struct resource) { +- .name = "VMD CFGBAR", +- .start = vmd->busn_start, +- .end = vmd->busn_start + (resource_size(res) >> 20) - 1, +- .flags = IORESOURCE_BUS | IORESOURCE_PCI_FIXED, +- }; ++ vmd_configure_cfgbar(vmd); + + /* + * If the window is below 4GB, clear IORESOURCE_MEM_64 so we can +@@ -834,36 +1131,12 @@ + * + * The only way we could use a 64-bit non-prefetchable MEMBAR is + * if its address is <4GB so that we can convert it to a 32-bit +- * resource. To be visible to the host OS, all VMD endpoints must ++ * resource. To be visible to the host OS, all VMD endpoints must + * be initially configured by platform BIOS, which includes setting +- * up these resources. We can assume the device is configured ++ * up these resources. We can assume the device is configured + * according to the platform needs. + */ +- res = &vmd->dev->resource[VMD_MEMBAR1]; +- upper_bits = upper_32_bits(res->end); +- flags = res->flags & ~IORESOURCE_SIZEALIGN; +- if (!upper_bits) +- flags &= ~IORESOURCE_MEM_64; +- vmd->resources[1] = (struct resource) { +- .name = "VMD MEMBAR1", +- .start = res->start, +- .end = res->end, +- .flags = flags, +- .parent = res, +- }; +- +- res = &vmd->dev->resource[VMD_MEMBAR2]; +- upper_bits = upper_32_bits(res->end); +- flags = res->flags & ~IORESOURCE_SIZEALIGN; +- if (!upper_bits) +- flags &= ~IORESOURCE_MEM_64; +- vmd->resources[2] = (struct resource) { +- .name = "VMD MEMBAR2", +- .start = res->start + membar2_offset, +- .end = res->end, +- .flags = flags, +- .parent = res, +- }; ++ vmd_configure_membar1_membar2(vmd, membar2_offset); + + sd->vmd_dev = vmd->dev; + sd->domain = vmd_find_free_domain(); +@@ -899,70 +1172,49 @@ + vmd_set_msi_remapping(vmd, false); + } + +- pci_add_resource(&resources, &vmd->resources[0]); +- pci_add_resource_offset(&resources, &vmd->resources[1], offset[0]); +- pci_add_resource_offset(&resources, &vmd->resources[2], offset[1]); +- +- vmd->bus = pci_create_root_bus(&vmd->dev->dev, vmd->busn_start, +- &vmd_ops, sd, &resources); +- if (!vmd->bus) { ++ pci_add_resource(&resources, &vmd->resources[VMD_RES_CFGBAR]); ++ pci_add_resource_offset(&resources, &vmd->resources[VMD_RES_MBAR_1], ++ offset[0]); ++ pci_add_resource_offset(&resources, &vmd->resources[VMD_RES_MBAR_2], ++ 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]) { + pci_free_resource_list(&resources); + vmd_remove_irq_domain(vmd); + return -ENODEV; + } + +- vmd_copy_host_bridge_flags(pci_find_host_bridge(vmd->dev->bus), +- to_pci_host_bridge(vmd->bus->bridge)); ++ 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); + if (vmd->irq_domain) +- dev_set_msi_domain(&vmd->bus->dev, vmd->irq_domain); ++ dev_set_msi_domain(&vmd->bus[VMD_BUS_0]->dev, ++ vmd->irq_domain); + else +- dev_set_msi_domain(&vmd->bus->dev, ++ dev_set_msi_domain(&vmd->bus[VMD_BUS_0]->dev, + dev_get_msi_domain(&vmd->dev->dev)); + +- WARN(sysfs_create_link(&vmd->dev->dev.kobj, &vmd->bus->dev.kobj, +- "domain"), "Can't create symlink to domain\n"); +- +- vmd_acpi_begin(); +- +- pci_scan_child_bus(vmd->bus); +- vmd_domain_reset(vmd); +- +- /* When Intel VMD is enabled, the OS does not discover the Root Ports +- * owned by Intel VMD within the MMCFG space. pci_reset_bus() applies +- * a reset to the parent of the PCI device supplied as argument. This +- * is why we pass a child device, so the reset can be triggered at +- * the Intel bridge level and propagated to all the children in the +- * hierarchy. +- */ +- list_for_each_entry(child, &vmd->bus->children, node) { +- if (!list_empty(&child->devices)) { +- dev = list_first_entry(&child->devices, +- struct pci_dev, bus_list); +- ret = pci_reset_bus(dev); +- if (ret) +- pci_warn(dev, "can't reset device: %d\n", ret); +- +- break; ++ WARN(sysfs_create_link(&vmd->dev->dev.kobj, ++ &vmd->bus[VMD_BUS_0]->dev.kobj, "domain"), ++ "Can't create symlink to domain\n"); ++ ++ vmd_bus_enumeration(vmd->bus[VMD_BUS_0], features); ++ ++ if (vmd_has_pch_rootbus(vmd)) { ++ ret = vmd_create_pch_bus(vmd, sd, offset); ++ if (ret) { ++ pci_err(vmd->dev, "Can't create PCH bus: %d\n", ret); ++ return ret; + } +- } +- +- pci_assign_unassigned_bus_resources(vmd->bus); +- +- pci_walk_bus(vmd->bus, vmd_pm_enable_quirk, &features); + +- /* +- * VMD root buses are virtual and don't return true on pci_is_pcie() +- * and will fail pcie_bus_configure_settings() early. It can instead be +- * run on each of the real root ports. +- */ +- list_for_each_entry(child, &vmd->bus->children, node) +- pcie_bus_configure_settings(child); +- +- pci_bus_add_devices(vmd->bus); ++ vmd_bus_enumeration(vmd->bus[VMD_BUS_1], features); ++ } + +- vmd_acpi_end(); + return 0; + } + +@@ -1056,9 +1308,13 @@ + { + struct vmd_dev *vmd = pci_get_drvdata(dev); + +- pci_stop_root_bus(vmd->bus); ++ pci_stop_root_bus(vmd->bus[VMD_BUS_0]); + sysfs_remove_link(&vmd->dev->dev.kobj, "domain"); +- pci_remove_root_bus(vmd->bus); ++ pci_remove_root_bus(vmd->bus[VMD_BUS_0]); ++ if (vmd_has_pch_rootbus(vmd)) { ++ pci_stop_root_bus(vmd->bus[VMD_BUS_1]); ++ pci_remove_root_bus(vmd->bus[VMD_BUS_1]); ++ } + vmd_cleanup_srcu(vmd); + vmd_detach_resources(vmd); + vmd_remove_irq_domain(vmd); +@@ -1148,4 +1404,4 @@ + MODULE_AUTHOR("Intel Corporation"); + MODULE_DESCRIPTION("Volume Management Device driver"); + MODULE_LICENSE("GPL v2"); +-MODULE_VERSION("0.6"); ++MODULE_VERSION("0.7");