@@ -35,6 +35,19 @@
#define MB2_SHADOW_OFFSET 0x2000
#define MB2_SHADOW_SIZE 16
+enum legacy_da_mode {
+ VMD_DA_NONE,
+ VMD_DA_HOST,
+ VMD_DA_GUEST,
+};
+
+static int legacy_da_mode;
+static char legacy_da_mode_str[sizeof("guest")];
+module_param_string(legacy_da_mode, legacy_da_mode_str,
+ sizeof(legacy_da_mode_str), 0);
+MODULE_PARM_DESC(legacy_da_mode,
+ "use legacy host-provided addressing hints in Root Ports to assist guest passthrough (off, host, guest)");
+
enum vmd_features {
/*
* Device may contain registers which hint the physical location of the
@@ -97,6 +110,12 @@ struct vmd_irq_list {
unsigned int count;
};
+struct root_port_addr {
+ int port;
+ u64 membase;
+ u64 pref_membase;
+};
+
struct vmd_dev {
struct pci_dev *dev;
@@ -112,6 +131,7 @@ struct vmd_dev {
struct pci_bus *bus;
u8 busn_start;
u8 first_vec;
+ struct root_port_addr rp_addr;
};
static inline struct vmd_dev *vmd_from_bus(struct pci_bus *bus)
@@ -483,6 +503,97 @@ static int vmd_find_free_domain(void)
return domain + 1;
}
+#define VMD_RP_BASE(vmd, port) ((vmd)->cfgbar + (port) * 8 * 4096)
+static void vmd_save_root_port_info(struct vmd_dev *vmd)
+{
+ resource_size_t physical = 0;
+ char __iomem *addr;
+ int port;
+
+ if (upper_32_bits(pci_resource_start(vmd->dev, VMD_MEMBAR1)))
+ return;
+
+ for (port = 0; port < 4; port++) {
+ u32 membase;
+
+ addr = VMD_RP_BASE(vmd, port) + PCI_MEMORY_BASE;
+ membase = readl(addr);
+
+ /* Break on first found root port */
+ if ((membase != 0xffffffff) && (membase != 0) &&
+ (membase != 0x0000fff0))
+ break;
+ }
+
+ if (port >= 4)
+ return;
+
+ vmd->rp_addr.port = port;
+
+ /* Only save the first root port index in host mode */
+ if (legacy_da_mode == VMD_DA_HOST)
+ return;
+
+ addr = VMD_RP_BASE(vmd, port) + PCI_MEMORY_BASE;
+ physical = ((u64)readw(addr) & 0xfff0) << 16;
+ vmd->rp_addr.membase = physical;
+
+ addr = VMD_RP_BASE(vmd, port) + PCI_PREF_BASE_UPPER32;
+ physical = ((u64)readl(addr)) << 32;
+ vmd->rp_addr.pref_membase = physical;
+
+ addr = VMD_RP_BASE(vmd, port) + PCI_PREF_MEMORY_BASE;
+ physical |= ((u64)readw(addr) & 0xfff0) << 16;
+ vmd->rp_addr.pref_membase |= physical;
+
+ writel(0, VMD_RP_BASE(vmd, port) + PCI_MEMORY_BASE);
+ writel(0, VMD_RP_BASE(vmd, port) + PCI_PREF_BASE_UPPER32);
+ writel(0, VMD_RP_BASE(vmd, port) + PCI_PREF_MEMORY_BASE);
+ writel(0, VMD_RP_BASE(vmd, port) + PCI_PREF_MEMORY_LIMIT);
+}
+
+static void vmd_restore_root_port_info(struct vmd_dev *vmd)
+{
+ resource_size_t phyaddr;
+ char __iomem *addr;
+ u32 val;
+ int port;
+
+ port = vmd->rp_addr.port;
+ if (legacy_da_mode == VMD_DA_HOST) {
+ /* Write the MEMBAR information to prepare the guest */
+ phyaddr = pci_resource_start(vmd->dev, VMD_MEMBAR1);
+ if (upper_32_bits(phyaddr))
+ return;
+
+ addr = VMD_RP_BASE(vmd, port) + PCI_MEMORY_BASE;
+ val = (phyaddr >> 16) & 0xfff0;
+ writew(val, addr);
+
+ phyaddr = pci_resource_start(vmd->dev, VMD_MEMBAR2);
+ addr = VMD_RP_BASE(vmd, port) + PCI_PREF_BASE_UPPER32;
+ val = phyaddr >> 32;
+ writel(val, addr);
+
+ addr = VMD_RP_BASE(vmd, port) + PCI_PREF_MEMORY_BASE;
+ val = (phyaddr >> 16) & 0xfff0;
+ writew(val, addr);
+ } else if (legacy_da_mode == VMD_DA_GUEST) {
+ /* Restore information provided by Host */
+ addr = VMD_RP_BASE(vmd, port) + PCI_MEMORY_BASE;
+ val = (vmd->rp_addr.membase >> 16) & 0xfff0;
+ writew(val, addr);
+
+ addr = VMD_RP_BASE(vmd, port) + PCI_PREF_BASE_UPPER32;
+ val = vmd->rp_addr.pref_membase >> 32;
+ writel(val, addr);
+
+ addr = VMD_RP_BASE(vmd, port) + PCI_PREF_MEMORY_BASE;
+ val = (vmd->rp_addr.pref_membase >> 16) & 0xfff0;
+ writew(val, addr);
+ }
+}
+
static void vmd_phys_to_offset(struct vmd_dev *vmd, u64 phys1, u64 phys2,
resource_size_t *offset1,
resource_size_t *offset2)
@@ -500,7 +611,19 @@ static int vmd_get_phys_offsets(struct vmd_dev *vmd, unsigned long features,
struct pci_dev *dev = vmd->dev;
u64 phys1, phys2;
- if (features & VMD_FEAT_HAS_MEMBAR_SHADOW) {
+ if (!strncmp(legacy_da_mode_str, "host", 4))
+ legacy_da_mode = VMD_DA_HOST;
+ else if (!strncmp(legacy_da_mode_str, "guest", 5))
+ legacy_da_mode = VMD_DA_GUEST;
+
+ if (legacy_da_mode != VMD_DA_NONE) {
+ vmd_save_root_port_info(vmd);
+ if (legacy_da_mode == VMD_DA_GUEST) {
+ vmd_phys_to_offset(vmd, vmd->rp_addr.membase,
+ vmd->rp_addr.pref_membase,
+ offset1, offset2);
+ }
+ } else if (features & VMD_FEAT_HAS_MEMBAR_SHADOW) {
u32 vmlock;
int ret;
@@ -732,6 +855,7 @@ static int vmd_enable_domain(struct vmd_dev *vmd, unsigned long features)
if (!vmd->bus) {
pci_free_resource_list(&resources);
vmd_remove_irq_domain(vmd);
+ vmd_restore_root_port_info(vmd);
return -ENODEV;
}
@@ -821,6 +945,7 @@ static void vmd_remove(struct pci_dev *dev)
vmd_cleanup_srcu(vmd);
vmd_detach_resources(vmd);
vmd_remove_irq_domain(vmd);
+ vmd_restore_root_port_info(vmd);
}
#ifdef CONFIG_PM_SLEEP