diff options
| -rw-r--r-- | drivers/pci/intel-iommu.c | 82 | 
1 files changed, 77 insertions, 5 deletions
diff --git a/drivers/pci/intel-iommu.c b/drivers/pci/intel-iommu.c index 855dd7ca47f..b1e97e68250 100644 --- a/drivers/pci/intel-iommu.c +++ b/drivers/pci/intel-iommu.c @@ -48,6 +48,7 @@  #define IS_GFX_DEVICE(pdev) ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY)  #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) +#define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e)  #define IOAPIC_RANGE_START	(0xfee00000)  #define IOAPIC_RANGE_END	(0xfeefffff) @@ -94,6 +95,7 @@ static inline unsigned long virt_to_dma_pfn(void *p)  /* global iommu list, set NULL for ignored DMAR units */  static struct intel_iommu **g_iommus; +static void __init check_tylersburg_isoch(void);  static int rwbf_quirk;  /* @@ -1934,6 +1936,9 @@ error:  }  static int iommu_identity_mapping; +#define IDENTMAP_ALL		1 +#define IDENTMAP_GFX		2 +#define IDENTMAP_AZALIA		4  static int iommu_domain_identity_map(struct dmar_domain *domain,  				     unsigned long long start, @@ -2151,8 +2156,14 @@ static int domain_add_dev_info(struct dmar_domain *domain,  static int iommu_should_identity_map(struct pci_dev *pdev, int startup)  { -	if (iommu_identity_mapping == 2) -		return IS_GFX_DEVICE(pdev); +	if ((iommu_identity_mapping & IDENTMAP_AZALIA) && IS_AZALIA(pdev)) +		return 1; + +	if ((iommu_identity_mapping & IDENTMAP_GFX) && IS_GFX_DEVICE(pdev)) +		return 1; + +	if (!(iommu_identity_mapping & IDENTMAP_ALL)) +		return 0;  	/*  	 * We want to start off with all devices in the 1:1 domain, and @@ -2332,11 +2343,14 @@ int __init init_dmars(void)  	}  	if (iommu_pass_through) -		iommu_identity_mapping = 1; +		iommu_identity_mapping |= IDENTMAP_ALL; +  #ifdef CONFIG_DMAR_BROKEN_GFX_WA -	else -		iommu_identity_mapping = 2; +	iommu_identity_mapping |= IDENTMAP_GFX;  #endif + +	check_tylersburg_isoch(); +  	/*  	 * If pass through is not set or not enabled, setup context entries for  	 * identity mappings for rmrr, gfx, and isa and may fall back to static @@ -3670,3 +3684,61 @@ static void __devinit quirk_iommu_rwbf(struct pci_dev *dev)  }  DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_rwbf); + +/* On Tylersburg chipsets, some BIOSes have been known to enable the +   ISOCH DMAR unit for the Azalia sound device, but not give it any +   TLB entries, which causes it to deadlock. Check for that.  We do +   this in a function called from init_dmars(), instead of in a PCI +   quirk, because we don't want to print the obnoxious "BIOS broken" +   message if VT-d is actually disabled. +*/ +static void __init check_tylersburg_isoch(void) +{ +	struct pci_dev *pdev; +	uint32_t vtisochctrl; + +	/* If there's no Azalia in the system anyway, forget it. */ +	pdev = pci_get_device(PCI_VENDOR_ID_INTEL, 0x3a3e, NULL); +	if (!pdev) +		return; +	pci_dev_put(pdev); + +	/* System Management Registers. Might be hidden, in which case +	   we can't do the sanity check. But that's OK, because the +	   known-broken BIOSes _don't_ actually hide it, so far. */ +	pdev = pci_get_device(PCI_VENDOR_ID_INTEL, 0x342e, NULL); +	if (!pdev) +		return; + +	if (pci_read_config_dword(pdev, 0x188, &vtisochctrl)) { +		pci_dev_put(pdev); +		return; +	} + +	pci_dev_put(pdev); + +	/* If Azalia DMA is routed to the non-isoch DMAR unit, fine. */ +	if (vtisochctrl & 1) +		return; + +	/* Drop all bits other than the number of TLB entries */ +	vtisochctrl &= 0x1c; + +	/* If we have the recommended number of TLB entries (16), fine. */ +	if (vtisochctrl == 0x10) +		return; + +	/* Zero TLB entries? You get to ride the short bus to school. */ +	if (!vtisochctrl) { +		WARN(1, "Your BIOS is broken; DMA routed to ISOCH DMAR unit but no TLB space.\n" +		     "BIOS vendor: %s; Ver: %s; Product Version: %s\n", +		     dmi_get_system_info(DMI_BIOS_VENDOR), +		     dmi_get_system_info(DMI_BIOS_VERSION), +		     dmi_get_system_info(DMI_PRODUCT_VERSION)); +		iommu_identity_mapping |= IDENTMAP_AZALIA; +		return; +	} +	 +	printk(KERN_WARNING "DMAR: Recommended TLB entries for ISOCH unit is 16; your BIOS set %d\n", +	       vtisochctrl); +}  |