diff --git a/drivers/pci/controller/vmd.c b/drivers/pci/controller/vmd.c old mode 100644 new mode 100755 index a726de0af011f9..7b8c127329b407 --- a/drivers/pci/controller/vmd.c +++ b/drivers/pci/controller/vmd.c @@ -23,6 +23,11 @@ #define VMD_MEMBAR1 2 #define VMD_MEMBAR2 4 +#define VMD_RESTRICT_0_BUS_START 0 +#define VMD_RESTRICT_1_BUS_START 128 +#define VMD_RESTRICT_2_BUS_START 224 +#define VMD_RESTRICT_3_BUS_START 225 + #define PCI_REG_VMCAP 0x40 #define BUS_RESTRICT_CAP(vmcap) (vmcap & 0x1) #define PCI_REG_VMCONFIG 0x44 @@ -34,6 +39,36 @@ #define MB2_SHADOW_OFFSET 0x2000 #define MB2_SHADOW_SIZE 16 +#define VMD_PRIMARY_BUS0 0x00 +#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) ((busr >> 8) & 0xff) +#define VMD_BUS_START(busr) (busr & 0x00ff) + +/* + * 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_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 +}; + enum vmd_features { /* * Device may contain registers which hint the physical location of the @@ -74,6 +109,12 @@ enum vmd_features { * 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 additional BUS1 rootport. + */ + VMD_FEAT_HAS_BUS1_ROOTBUS = (1 << 6) }; #define VMD_BIOS_PM_QUIRK_LTR 0x1003 /* 3145728 ns */ @@ -81,7 +122,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); @@ -132,13 +174,14 @@ struct vmd_dev { 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; + bool bus1_rootbus; }; static inline struct vmd_dev *vmd_from_bus(struct pci_bus *bus) @@ -378,8 +421,22 @@ static void vmd_remove_irq_domain(struct vmd_dev *vmd) 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 workaraund: for BUS1, bus->number is set to VMD_PRIMARY_BUS1 + * (see comment under vmd_create_bus() for BUS1) but original value + * is 225 which is stored in vmd->busn_start[VMD_BUS_1]. + */ + if (vmd->bus1_rootbus && bus->number == VMD_PRIMARY_BUS1) + 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; @@ -516,11 +573,16 @@ static inline void vmd_acpi_end(void) { } static void vmd_domain_reset(struct vmd_dev *vmd) { - u16 bus, max_buses = resource_size(&vmd->resources[0]); + u16 bus, bus_cnt = resource_size(&vmd->resources[VMD_RES_CFGBAR]); u8 dev, functions, fn, hdr_type; char __iomem *base; - for (bus = 0; bus < max_buses; bus++) { + if (vmd->bus1_rootbus) { + bus_cnt += resource_size(&vmd->resources[VMD_RES_BUS1_CFGBAR]); + bus_cnt += 2; + } + + for (bus = 0; bus < bus_cnt; bus++) { for (dev = 0; dev < 32; dev++) { base = vmd->cfgbar + PCIE_ECAM_OFFSET(bus, PCI_DEVFN(dev, 0), 0); @@ -564,14 +626,26 @@ static void vmd_domain_reset(struct vmd_dev *vmd) 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]; + + if (vmd->bus1_rootbus) { + vmd->resources[VMD_RES_MBAR_1].sibling = + &vmd->resources[VMD_RES_BUS1_MBAR_1]; + vmd->resources[VMD_RES_MBAR_2].sibling = + &vmd->resources[VMD_RES_BUS1_MBAR_2]; + } } 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->resources[VMD_RES_MBAR_1].sibling = NULL; + vmd->resources[VMD_RES_MBAR_2].sibling = NULL; + } } /* @@ -644,7 +718,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; @@ -655,13 +729,26 @@ static int vmd_get_bus_number_start(struct vmd_dev *vmd) switch (BUS_RESTRICT_CFG(reg)) { case 0: - vmd->busn_start = 0; + vmd->busn_start[VMD_BUS_0] = VMD_RESTRICT_0_BUS_START; break; case 1: - vmd->busn_start = 128; + vmd->busn_start[VMD_BUS_0] = VMD_RESTRICT_1_BUS_START; break; case 2: - vmd->busn_start = 224; + 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 Rootbus is not supported, aborting.\n", + BUS_RESTRICT_CFG(reg)); + return -ENODEV; + } + + /* BUS0 start number */ + vmd->busn_start[VMD_BUS_0] = VMD_RESTRICT_2_BUS_START; + /* BUS1 start number */ + 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", @@ -778,19 +865,217 @@ static int vmd_pm_enable_quirk(struct pci_dev *pdev, void *userdata) return 0; } -static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features) +static void vmd_configure_cfgbar(struct vmd_dev *vmd) +{ + 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->bus1_rootbus) { + u16 bus0_range = 0; + u16 bus1_range = 0; + + pci_read_config_word(vmd->dev, VMD_BUSRANGE0, &bus0_range); + pci_read_config_word(vmd->dev, VMD_BUSRANGE1, &bus1_range); + + /* + * 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 = VMD_BUS_START(bus0_range); + vmd->resources[VMD_RES_CFGBAR].end = VMD_BUS_END(bus0_range); + + vmd->resources[VMD_RES_BUS1_CFGBAR] = (struct resource){ + .name = "VMD CFGBAR BUS1", + .start = VMD_BUS_START(bus1_range), + .end = VMD_BUS_END(bus1_range), + .flags = IORESOURCE_BUS | IORESOURCE_PCI_FIXED, + .parent = res, + }; + } +} + +/* + * 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 + * + * 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 pci_sysdata *sd = &vmd->sysdata; - struct resource *res; u32 upper_bits; unsigned long flags; + + 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; + + vmd->resources[resource_number] = (struct resource){ + .name = kasprintf( + GFP_KERNEL, "VMD MEMBAR%d %s", membar_number / 2, + resource_number > VMD_RES_MBAR_2 ? "BUS1" : ""), + .start = res->start + start_offset, + .end = res->end - end_offset, + .flags = flags, + .parent = res, + }; +} + +static void vmd_configure_membar1_membar2(struct vmd_dev *vmd, + resource_size_t mbar2_ofs) +{ + if (vmd->bus1_rootbus) { + u32 bus1_mbar1_ofs = 0; + u64 bus1_mbar2_ofs = 0; + u32 reg; + + pci_read_config_dword(vmd->dev, VMD_MEMBAR1_OFFSET, + &bus1_mbar1_ofs); + + pci_read_config_dword(vmd->dev, VMD_MEMBAR2_OFFSET1, ®); + bus1_mbar2_ofs = reg; + + pci_read_config_dword(vmd->dev, VMD_MEMBAR2_OFFSET2, ®); + bus1_mbar2_ofs |= (u64)reg << 32; + + /* + * 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 + */ + vmd_configure_membar(vmd, VMD_RES_MBAR_1, VMD_MEMBAR1, 0, + bus1_mbar1_ofs); + vmd_configure_membar(vmd, VMD_RES_MBAR_2, VMD_MEMBAR2, + mbar2_ofs, bus1_mbar2_ofs - mbar2_ofs); + + vmd_configure_membar(vmd, VMD_RES_BUS1_MBAR_1, VMD_MEMBAR1, + bus1_mbar1_ofs, 0); + vmd_configure_membar(vmd, VMD_RES_BUS1_MBAR_2, VMD_MEMBAR2, + mbar2_ofs + bus1_mbar2_ofs, 0); + } else { + vmd_configure_membar(vmd, VMD_RES_MBAR_1, VMD_MEMBAR1, 0, 0); + vmd_configure_membar(vmd, VMD_RES_MBAR_2, VMD_MEMBAR2, + mbar2_ofs, 0); + } +} + +static int vmd_create_bus(struct vmd_dev *vmd, enum vmd_rootbus bus_number, + struct pci_sysdata *sd, resource_size_t *offset) +{ + u8 cfgbar = bus_number * 3; + u8 membar1 = cfgbar + 1; + u8 membar2 = cfgbar + 2; + struct pci_bus *vmd_bus; LIST_HEAD(resources); - resource_size_t offset[2] = {0}; - resource_size_t membar2_offset = 0x2000; + + pci_add_resource(&resources, &vmd->resources[cfgbar]); + pci_add_resource_offset(&resources, &vmd->resources[membar1], + offset[0]); + pci_add_resource_offset(&resources, &vmd->resources[membar2], + offset[1]); + + 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); + + if (bus_number == VMD_PRIMARY_BUS0) + 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_attach_resources(vmd); + if (vmd->irq_domain) + dev_set_msi_domain(&vmd_bus->dev, vmd->irq_domain); + else + dev_set_msi_domain(&vmd_bus->dev, + dev_get_msi_domain(&vmd->dev->dev)); + + vmd->bus[bus_number] = vmd_bus; + + return 0; +} + +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 == VMD_PRIMARY_BUS0) + 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_enable_domain(struct vmd_dev *vmd, unsigned long features) +{ + struct pci_sysdata *sd = &vmd->sysdata; + resource_size_t offset[2] = {0}; + resource_size_t membar2_offset = 0x2000; + int ret; + /* * Shadow registers may exist in certain VMD device ids which allow * guests to correctly assign host physical addresses to the root ports @@ -813,18 +1098,12 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features) * 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 @@ -838,36 +1117,12 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features) * * 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(); @@ -903,70 +1158,50 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features) 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]); + ret = vmd_create_bus(vmd, VMD_BUS_0, sd, offset); - vmd->bus = pci_create_root_bus(&vmd->dev->dev, vmd->busn_start, - &vmd_ops, sd, &resources); - if (!vmd->bus) { - pci_free_resource_list(&resources); - vmd_remove_irq_domain(vmd); - return -ENODEV; + if (ret) { + pci_err(vmd->dev, "Can't create bus: %d\n", ret); + return ret; } - vmd_copy_host_bridge_flags(pci_find_host_bridge(vmd->dev->bus), - to_pci_host_bridge(vmd->bus->bridge)); + WARN(sysfs_create_link(&vmd->dev->dev.kobj, + &vmd->bus[VMD_BUS_0]->dev.kobj, "domain"), + "Can't create symlink to domain\n"); - vmd_attach_resources(vmd); - if (vmd->irq_domain) - dev_set_msi_domain(&vmd->bus->dev, vmd->irq_domain); - else - dev_set_msi_domain(&vmd->bus->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); + vmd_bus_enumeration(vmd->bus[VMD_BUS_0], features); - /* 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; + if (vmd->bus1_rootbus) { + ret = vmd_create_bus(vmd, VMD_BUS_1, sd, offset); + if (ret) { + pci_err(vmd->dev, "Can't create BUS1: %d\n", ret); + return ret; } - } - pci_assign_unassigned_bus_resources(vmd->bus); + /* + * Primary bus is not set by pci_create_root_bus(), it is + * updated here + */ + vmd->bus[VMD_BUS_1]->primary = VMD_PRIMARY_BUS1; - pci_walk_bus(vmd->bus, vmd_pm_enable_quirk, &features); + /* + * This is a workaround for pci_scan_bridge_extend() code. + * It assigns setup as broken, because rootbus number for BUS1 + * is hardwired to a fixed non-zero value. + * To avoid this, vmd->bus[VMD_BUS_1]->number and + * vmd->bus[VMD_BUS_1]->primary are updated to the same value, + * which bypasses setup validation condition. + */ + vmd->bus[VMD_BUS_1]->number = VMD_PRIMARY_BUS1; - /* - * 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); + WARN(sysfs_create_link(&vmd->dev->dev.kobj, + &vmd->bus[VMD_BUS_1]->dev.kobj, + "domain1"), + "Can't create symlink to domain1\n"); - pci_bus_add_devices(vmd->bus); + vmd_bus_enumeration(vmd->bus[VMD_BUS_1], features); + } - vmd_acpi_end(); return 0; } @@ -1042,9 +1277,22 @@ static void vmd_remove(struct pci_dev *dev) { 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]); + + /* CFGBARs are static, do not require releasing memory */ + kfree(vmd->resources[VMD_RES_MBAR_1].name); + kfree(vmd->resources[VMD_RES_MBAR_2].name); + + if (vmd->bus1_rootbus) { + 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]); + kfree(vmd->resources[VMD_RES_BUS1_MBAR_1].name); + kfree(vmd->resources[VMD_RES_BUS1_MBAR_2].name); + } + vmd_cleanup_srcu(vmd); vmd_detach_resources(vmd); vmd_remove_irq_domain(vmd); @@ -1130,4 +1378,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");