diff --git a/xen/arch/x86/domain.c b/xen/arch/x86/domain.c index feda3ba..2f5a69a 100644 --- a/xen/arch/x86/domain.c +++ b/xen/arch/x86/domain.c @@ -510,8 +510,6 @@ void arch_domain_destroy(struct domain *d) pci_release_devices(d); free_domain_pirqs(d); - if ( !is_idle_domain(d) ) - iommu_domain_destroy(d); paging_final_teardown(d); diff --git a/xen/common/domain.c b/xen/common/domain.c index 32f1d48..a7b0207 100644 --- a/xen/common/domain.c +++ b/xen/common/domain.c @@ -385,6 +385,7 @@ int domain_kill(struct domain *d) v4v_destroy(d); evtchn_destroy(d); gnttab_release_mappings(d); + iommu_domain_destroy(d); /* fallthrough */ case DOMDYING_dying: rc = domain_relinquish_resources(d); diff --git a/xen/drivers/passthrough/pci.c b/xen/drivers/passthrough/pci.c index 723cff0..e90a335 100644 --- a/xen/drivers/passthrough/pci.c +++ b/xen/drivers/passthrough/pci.c @@ -75,6 +75,81 @@ struct pci_dev *pci_get_pdev(int bus, int devfn) return NULL; } +struct pci_dev *pci_get_bridge(u8 secondary) +{ + struct pci_dev *pdev = NULL, *m = NULL; + + spin_lock(&pcidevs_lock); + + list_for_each_entry ( pdev, &alldevs_list, alldevs_list ) + { + u8 type = pci_conf_read8(pdev->bus, + PCI_SLOT(pdev->devfn), + PCI_FUNC(pdev->devfn), + PCI_HEADER_TYPE); + if ( (type & 3) == PCI_HEADER_TYPE_BRIDGE ) + { + u8 sec = pci_conf_read8(pdev->bus, + PCI_SLOT(pdev->devfn), + PCI_FUNC(pdev->devfn), + PCI_SECONDARY_BUS); + if ( sec == secondary ) + { + m = pdev; + goto out; + } + } + } +out: + spin_unlock(&pcidevs_lock); + return m; +} + +struct pci_dev *pci_get_gfx(struct domain *d) +{ + struct pci_dev *pdev = NULL, *m = NULL; + + spin_lock(&pcidevs_lock); + + list_for_each_entry ( pdev, &alldevs_list, alldevs_list ) + { + if ( d == NULL || pdev->domain == d ) + { + u16 class = pci_conf_read16(pdev->bus, + PCI_SLOT(pdev->devfn), + PCI_FUNC(pdev->devfn), + PCI_CLASS_DEVICE); + + if ( class == 0x0300 ) + { + m = pdev; + goto out; + } + } + } +out: + spin_unlock(&pcidevs_lock); + return m; + +} + +struct pci_dev *pci_match_pdev(int (*match)(struct pci_dev *)) +{ + struct pci_dev *pdev = NULL, *m = NULL; + + spin_lock(&pcidevs_lock); + + list_for_each_entry ( pdev, &alldevs_list, alldevs_list ) + if ( match(pdev) ) + { + m = pdev; + goto out; + } +out: + spin_unlock(&pcidevs_lock); + return m; +} + struct pci_dev *pci_get_pdev_by_domain(struct domain *d, int bus, int devfn) { struct pci_dev *pdev = NULL; diff --git a/xen/drivers/passthrough/vtd/Makefile b/xen/drivers/passthrough/vtd/Makefile index 0e6f163..00ba797 100644 --- a/xen/drivers/passthrough/vtd/Makefile +++ b/xen/drivers/passthrough/vtd/Makefile @@ -6,3 +6,4 @@ obj-y += dmar.o obj-y += utils.o obj-y += qinval.o obj-y += intremap.o +obj-y += pci_reset.o diff --git a/xen/drivers/passthrough/vtd/iommu.c b/xen/drivers/passthrough/vtd/iommu.c index 687f3fd..df88dc7 100644 --- a/xen/drivers/passthrough/vtd/iommu.c +++ b/xen/drivers/passthrough/vtd/iommu.c @@ -21,7 +21,6 @@ #include #include -#include #include #include #include @@ -32,11 +31,13 @@ #include #include #include +#include #include #include "iommu.h" #include "dmar.h" #include "extern.h" #include "vtd.h" +#include "pci_reset.h" #define domain_iommu_domid(d) ((d)->arch.hvm_domain.hvm_iommu.iommu_domid) @@ -1503,6 +1504,15 @@ static int reassign_device_ownership( if ( (drhd = acpi_find_matched_drhd_unit(pdev)) == NULL ) return -ENODEV; + + /* Only do the reset here if target isn't dom0. + ** For the reassign to dom0 it's done in the iommu_domain_teardown + ** function because reassign_device_ownership happen too late in + ** the process. + */ + if (target->domain_id != 0) + pci_reset_device(bus, devfn); + pdev_iommu = drhd->iommu; domain_context_unmap(source, bus, devfn); @@ -1534,10 +1544,14 @@ static int reassign_device_ownership( void iommu_domain_teardown(struct domain *d) { struct hvm_iommu *hd = domain_hvm_iommu(d); + struct pci_dev *pdev = NULL; if ( list_empty(&acpi_drhd_units) ) return; + if ((pdev = pci_get_gfx(d))) + pci_reset_device(pdev->bus, pdev->devfn); + spin_lock(&hd->mapping_lock); iommu_free_pagetable(hd->pgd_maddr, agaw_to_level(hd->agaw)); hd->pgd_maddr = 0; diff --git a/xen/drivers/passthrough/vtd/pci_reset.c b/xen/drivers/passthrough/vtd/pci_reset.c new file mode 100644 index 0000000..f0614f9 --- /dev/null +++ b/xen/drivers/passthrough/vtd/pci_reset.c @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2011, Citrix Systems. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Copyright (C) Julian Pidancet + * Copyright (C) Jean Guyader + */ + +#include +#include +#include +#include +#include + +#define INTEL_VID 0x8086 +#define INTEL_DID_GMA 0x2a00 +#define INTEL_DID_CALPELLA 0x0046 +#define INTEL_DID_ARRANDALE 0x0042 +#define INTEL_DID_SNB1 0x0126 +#define INTEL_DID_SNB2 0x0102 + +#define NVIDIA_VID 0x10de +#define ATI_VID 0x1002 + +#define IS_MISCGFX(vid, class) ( ((vid) == NVIDIA_VID) && \ + (class) == 0x300 \ + ) + +#define IS_GMA(vid, did) ( (vid) == INTEL_VID && \ + ((did) & 0xff00) == INTEL_DID_GMA) + +#define IS_IRONLAKE(vid, did) ( (vid) == INTEL_VID && \ + ((did) == INTEL_DID_CALPELLA || \ + (did) == INTEL_DID_SNB1 || \ + (did) == INTEL_DID_SNB2 || \ + (did) == INTEL_DID_ARRANDALE) \ + ) + +#define PCI_BDF_FMT "%02x:%02x.%x" + +#define PCI_COMMAND_VALID (PCI_COMMAND_IO | PCI_COMMAND_MEMORY | \ + PCI_COMMAND_MASTER) + +#define PCI_CONFIG_SIZE 0x100 + +#define ypcmem(a,b,c) memcpy(b,a,c) + +static int reg_len[PCI_CONFIG_SIZE] = { +/*00*/ 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, +/*10*/ 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, +/*20*/ 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/*30*/ 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + +/*40*/ 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, +/*50*/ 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, +/*60*/ 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, +/*70*/ 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, + +/*80*/ 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, +/*90*/ 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, +/*a0*/ 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/*b0*/ 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, + +/*c0*/ 0, 1, 2, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, +/*d0*/ 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, +/*e0*/ 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, +/*f0*/ 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, +}; + +static int reg_compare_len[PCI_CONFIG_SIZE] = { +/*00*/ 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, +/*10*/ 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, +/*20*/ 4, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/*30*/ 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + +/*40*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/*50*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/*60*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/*70*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + +/*80*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/*90*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/*a0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/*b0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + +/*c0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/*d0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/*e0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/*f0*/ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, +}; + +static void +read_config_space (u8 b, u16 devfn, u8 * cfg) +{ + u8 d = PCI_SLOT(devfn), f = PCI_FUNC(devfn); + u32 cfg32; + u16 cfg16; + int i; + + for (i = 0; i < PCI_CONFIG_SIZE; ++i) + { + switch (reg_len[i]) + { + case 1: + cfg[i] = pci_conf_read8(b, d, f, i); + break; + case 2: + cfg16 = pci_conf_read16(b, d, f, i); + memcpy (&cfg[i], &cfg16, sizeof (cfg16)); + break; + case 4: + cfg32 = pci_conf_read32(b, d, f, i); + memcpy (&cfg[i], &cfg32, sizeof (cfg32)); + break; + } + } +} + +static void +write_config_space (u8 b, u16 devfn, u8 * cfg) +{ + u8 d = PCI_SLOT(devfn), f = PCI_FUNC(devfn); + u32 cfg32; + u16 cfg16; + int i; + + + for (i = 0; i < PCI_CONFIG_SIZE; ++i) + { + switch (reg_len[i]) + { + case 1: + pci_conf_write8(b, d, f, i, cfg[i]); + break; + case 2: + ypcmem (&cfg[i], &cfg16, sizeof (cfg16)); + pci_conf_write16(b, d, f, i, cfg16); + break; + case 4: + ypcmem (&cfg[i], &cfg32, sizeof (cfg32)); + pci_conf_write32(b, d, f, i, cfg32); + break; + } + } +} + +static int +compare_config_space (u8 b, u16 devfn, u8 * cfg) +{ + u8 d = PCI_SLOT(devfn), f = PCI_FUNC(devfn); + u32 cfg32; + u16 cfg16; + u8 cfg8; + int ret = 0; + int i; + + for (i = 0; i < PCI_CONFIG_SIZE; ++i) + { + switch (reg_compare_len[i]) + { + case 1: + cfg8 = pci_conf_read8(b, d, f, i); + if (memcmp (&cfg[i], &cfg8, sizeof (cfg8))) + { + printk("compare_config_space failed, wait a bit a restore again " PCI_BDF_FMT " %x\n", + b, d, f, i); + ret++; + } + break; + case 2: + cfg16 = pci_conf_read16(b, d, f, i); + if (memcmp (&cfg[i], &cfg16, sizeof (cfg16))) + { + printk("compare_config_space failed, wait a bit a restore again " PCI_BDF_FMT " %x\n", + b, d, f, i); + ret++; + } + break; + case 4: + cfg32 = pci_conf_read32(b, d, f, i); + if (memcmp (&cfg[i], &cfg32, sizeof (cfg32))) + { + printk("compare_config_space failed, wait a bit a restore again " PCI_BDF_FMT " %x\n", + b, d, f, i); + ret++; + } + break; + } + } + return ret; +} + +static int +restore_config_space (u8 b, u16 devfn, u8 * cfg) +{ + int tries = 3; + + while (tries--) + { + write_config_space(b, devfn, cfg); + if (!compare_config_space(b, devfn, cfg)) + return 0; + cpu_relax(); + mdelay(100); /* Wait 100 ms between restores */ + } + return tries == 0; +} + +static void pci_reset_function(u8 bus, u8 devfn, int force) +{ + s_time_t start_time; + u8 d = PCI_SLOT(devfn), f = PCI_FUNC(devfn); + u8 val; + int af_pos; + u8 af; + + printk("Resetting PCI function %d:%d.%d\n", bus, d, f); + + af_pos = pci_find_cap_offset(bus, d, f, PCI_CAP_ID_AF); + if (af_pos == 0) + { + printk("%s: PCI Advanced Features not found\n", __func__); + if (force) { + printk("%s: trying A4h\n", __func__); + af_pos = 0xa4; + } + else + BUG_ON(1); + } + + if (!force) + { + af = pci_conf_read8(bus, d, f, af_pos + PCI_AF_CAP); + if (!(af & PCI_AF_CAP_FLR)) + { + printk("%s: PCI device cannot FLR\n", __func__); + BUG_ON(1); + } + } + + /* Leave CMD MEMORY set otherwise the platform can crashe during FLR */ + pci_conf_write16(bus, d, f, PCI_COMMAND, 2); + + /* Wait for bus accesses to complete */ + start_time = NOW(); + for ( ; ; ) + { + val = pci_conf_read8(bus, d, f, af_pos + PCI_AF_STATUS); + if ( !(val & PCI_AF_STATUS_TP) ) + break; + + if ( NOW() > start_time + MILLISECS(1000) ) + { + printk("%s: Failed to quiesce bus accesses\n", __func__); + BUG_ON(1); + } + + cpu_relax(); + } + + /* Trigger the FLR */ + pci_conf_write8(bus, d, f, af_pos + PCI_AF_CTRL, PCI_AF_CTRL_FLR); + mdelay(100); +} + +static void force_cmd_register(u8 bus, u8 devfn) +{ + u8 d = PCI_SLOT(devfn), f = PCI_FUNC(devfn); + u8 val; + + val = pci_conf_read16(bus, d, f, PCI_COMMAND); + + if ((val & 0x7) == 0) + { + val |= 0x7; + pci_conf_write16(bus, d, f, PCI_COMMAND, val); + } +} + +static void pci_reset_bus(u8 bus, u8 devfn) +{ + u8 d = PCI_SLOT(devfn), f = PCI_FUNC(devfn); + u8 val; + + val = pci_conf_read8(bus, d, f, PCI_BRIDGE_CONTROL); + pci_conf_write8(bus, d, f, PCI_BRIDGE_CONTROL, val | PCI_BRIDGE_CTL_BUS_RESET); + udelay(20000); + pci_conf_write8(bus, d, f, PCI_BRIDGE_CONTROL, val); + udelay(20000); +} + +static void pci_reset_misc_gfx(u8 bus, u8 devfn) +{ + u8 d = PCI_SLOT(devfn), f = PCI_FUNC(devfn); + u16 vendorid, deviceid, class; + + vendorid = pci_conf_read16(bus, d, f, PCI_VENDOR_ID); + deviceid = pci_conf_read16(bus, d, f, PCI_DEVICE_ID); + class = pci_conf_read16(bus, d, f, PCI_CLASS_DEVICE); + + if (IS_MISCGFX(vendorid, class)) { + struct pci_dev *bridge = pci_get_bridge(bus); + u8 *config; + + if (bridge == NULL) { + printk("%s: Can't find bridge.\n", __func__); + return; + } + + config = xmalloc_array(u8, PCI_CONFIG_SIZE); + read_config_space(bus, devfn, config); + + pci_reset_bus(bridge->bus, bridge->devfn); + + udelay(20000); + + restore_config_space(bus, devfn, config); + xfree(config); + } + +} + +static void pci_reset_intel_gfx(u8 bus, u8 devfn) +{ + u8 d = PCI_SLOT(devfn), f = PCI_FUNC(devfn); + u16 vendorid, deviceid; + u8 val; + u8 *config; + int i; + + vendorid = pci_conf_read16(bus, d, f, PCI_VENDOR_ID); + deviceid = pci_conf_read16(bus, d, f, PCI_DEVICE_ID); + + /* Intel GMA */ + if (IS_GMA(vendorid, deviceid)) { + config = xmalloc_array(u8, PCI_CONFIG_SIZE); + read_config_space(bus, devfn, config); + + /* Perform GPU Media reset */ + pci_conf_write8(bus, d, f, 0xC0, 0x0D); + + for (i = 0; i < 10; i++) + { + mdelay(10); + + val = pci_conf_read8(bus, d, f, 0xC0); + if ( !(val & 0x1) ) + break; + } + + pci_reset_function(bus, devfn, 1); + restore_config_space(bus, devfn, config); + xfree(config); + + force_cmd_register(bus, devfn); + } + + /* Ironlake */ + if (IS_IRONLAKE(vendorid, deviceid)) { + + config = xmalloc_array(u8, PCI_CONFIG_SIZE); + read_config_space(bus, devfn, config); + + pci_reset_function(bus, devfn, 0); + + restore_config_space(bus, devfn, config); + xfree(config); + + force_cmd_register(bus, devfn); + } +} + +void pci_reset_device(u8 bus, u8 devfn) +{ + pci_reset_intel_gfx(bus, devfn); + pci_reset_misc_gfx(bus, devfn); +} diff --git a/xen/drivers/passthrough/vtd/pci_reset.h b/xen/drivers/passthrough/vtd/pci_reset.h new file mode 100644 index 0000000..dfd0872 --- /dev/null +++ b/xen/drivers/passthrough/vtd/pci_reset.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2011, Citrix Systems. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Copyright (C) Julian Pidancet + * Copyright (C) Jean Guyader + */ + + +#ifndef PCI_RESET_H_ +# define PCI_RESET_H_ + +void pci_reset_device(u8 bus, u8 devfn); + +#endif /* PCI_RESET_H_ */ diff --git a/xen/include/xen/pci.h b/xen/include/xen/pci.h index 6827e0d..9ed6f60 100644 --- a/xen/include/xen/pci.h +++ b/xen/include/xen/pci.h @@ -77,6 +77,9 @@ int pci_remove_device(u8 bus, u8 devfn); int pci_add_device_ext(u8 bus, u8 devfn, struct pci_dev_info *info); struct pci_dev *pci_get_pdev(int bus, int devfn); struct pci_dev *pci_get_pdev_by_domain(struct domain *d, int bus, int devfn); +struct pci_dev *pci_match_pdev(int (*match)(struct pci_dev *)); +struct pci_dev *pci_get_bridge(u8 secondary); +struct pci_dev *pci_get_gfx(struct domain *d); uint8_t pci_conf_read8( unsigned int bus, unsigned int dev, unsigned int func, unsigned int reg); diff --git a/xen/include/xen/pci_regs.h b/xen/include/xen/pci_regs.h index 361554c..20b00e8 100644 --- a/xen/include/xen/pci_regs.h +++ b/xen/include/xen/pci_regs.h @@ -210,6 +210,7 @@ #define PCI_CAP_ID_AGP3 0x0E /* AGP Target PCI-PCI bridge */ #define PCI_CAP_ID_EXP 0x10 /* PCI Express */ #define PCI_CAP_ID_MSIX 0x11 /* MSI-X */ +#define PCI_CAP_ID_AF 0x13 /* PCI Advanced Features */ #define PCI_CAP_LIST_NEXT 1 /* Next capability in the list */ #define PCI_CAP_FLAGS 2 /* Capability defined flags (16 bits) */ #define PCI_CAP_SIZEOF 4 @@ -315,6 +316,17 @@ #define PCI_CHSWP_EXT 0x40 /* ENUM# status - extraction */ #define PCI_CHSWP_INS 0x80 /* ENUM# status - insertion */ +/* PCI Advanced Feature registers */ + +#define PCI_AF_LENGTH 2 +#define PCI_AF_CAP 3 +#define PCI_AF_CAP_TP 0x01 +#define PCI_AF_CAP_FLR 0x02 +#define PCI_AF_CTRL 4 +#define PCI_AF_CTRL_FLR 0x01 +#define PCI_AF_STATUS 5 +#define PCI_AF_STATUS_TP 0x01 + /* PCI-X registers */ #define PCI_X_CMD 2 /* Modes & Features */