diff options
Diffstat (limited to 'drivers/iommu/amd_iommu.c')
| -rw-r--r-- | drivers/iommu/amd_iommu.c | 234 | 
1 files changed, 79 insertions, 155 deletions
diff --git a/drivers/iommu/amd_iommu.c b/drivers/iommu/amd_iommu.c index 4aec6a29e316..ecb0109a5360 100644 --- a/drivers/iommu/amd_iommu.c +++ b/drivers/iommu/amd_iommu.c @@ -46,7 +46,6 @@  #include "amd_iommu_proto.h"  #include "amd_iommu_types.h"  #include "irq_remapping.h" -#include "pci.h"  #define CMD_SET_TYPE(cmd, t) ((cmd)->data[1] |= ((t) << 28)) @@ -81,7 +80,7 @@ LIST_HEAD(hpet_map);   */  static struct protection_domain *pt_domain; -static struct iommu_ops amd_iommu_ops; +static const struct iommu_ops amd_iommu_ops;  static ATOMIC_NOTIFIER_HEAD(ppr_notifier);  int amd_iommu_max_glx_val = -1; @@ -133,9 +132,6 @@ static void free_dev_data(struct iommu_dev_data *dev_data)  	list_del(&dev_data->dev_data_list);  	spin_unlock_irqrestore(&dev_data_list_lock, flags); -	if (dev_data->group) -		iommu_group_put(dev_data->group); -  	kfree(dev_data);  } @@ -264,167 +260,79 @@ static bool check_device(struct device *dev)  	return true;  } -static struct pci_bus *find_hosted_bus(struct pci_bus *bus) -{ -	while (!bus->self) { -		if (!pci_is_root_bus(bus)) -			bus = bus->parent; -		else -			return ERR_PTR(-ENODEV); -	} - -	return bus; -} - -#define REQ_ACS_FLAGS	(PCI_ACS_SV | PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF) - -static struct pci_dev *get_isolation_root(struct pci_dev *pdev) -{ -	struct pci_dev *dma_pdev = pdev; - -	/* Account for quirked devices */ -	swap_pci_ref(&dma_pdev, pci_get_dma_source(dma_pdev)); - -	/* -	 * If it's a multifunction device that does not support our -	 * required ACS flags, add to the same group as lowest numbered -	 * function that also does not suport the required ACS flags. -	 */ -	if (dma_pdev->multifunction && -	    !pci_acs_enabled(dma_pdev, REQ_ACS_FLAGS)) { -		u8 i, slot = PCI_SLOT(dma_pdev->devfn); - -		for (i = 0; i < 8; i++) { -			struct pci_dev *tmp; - -			tmp = pci_get_slot(dma_pdev->bus, PCI_DEVFN(slot, i)); -			if (!tmp) -				continue; - -			if (!pci_acs_enabled(tmp, REQ_ACS_FLAGS)) { -				swap_pci_ref(&dma_pdev, tmp); -				break; -			} -			pci_dev_put(tmp); -		} -	} - -	/* -	 * Devices on the root bus go through the iommu.  If that's not us, -	 * find the next upstream device and test ACS up to the root bus. -	 * Finding the next device may require skipping virtual buses. -	 */ -	while (!pci_is_root_bus(dma_pdev->bus)) { -		struct pci_bus *bus = find_hosted_bus(dma_pdev->bus); -		if (IS_ERR(bus)) -			break; - -		if (pci_acs_path_enabled(bus->self, NULL, REQ_ACS_FLAGS)) -			break; - -		swap_pci_ref(&dma_pdev, pci_dev_get(bus->self)); -	} - -	return dma_pdev; -} - -static int use_pdev_iommu_group(struct pci_dev *pdev, struct device *dev) +static int init_iommu_group(struct device *dev)  { -	struct iommu_group *group = iommu_group_get(&pdev->dev); -	int ret; +	struct iommu_group *group; -	if (!group) { -		group = iommu_group_alloc(); -		if (IS_ERR(group)) -			return PTR_ERR(group); +	group = iommu_group_get_for_dev(dev); -		WARN_ON(&pdev->dev != dev); -	} +	if (IS_ERR(group)) +		return PTR_ERR(group); -	ret = iommu_group_add_device(group, dev);  	iommu_group_put(group); -	return ret; +	return 0;  } -static int use_dev_data_iommu_group(struct iommu_dev_data *dev_data, -				    struct device *dev) +static int __last_alias(struct pci_dev *pdev, u16 alias, void *data)  { -	if (!dev_data->group) { -		struct iommu_group *group = iommu_group_alloc(); -		if (IS_ERR(group)) -			return PTR_ERR(group); - -		dev_data->group = group; -	} - -	return iommu_group_add_device(dev_data->group, dev); +	*(u16 *)data = alias; +	return 0;  } -static int init_iommu_group(struct device *dev) +static u16 get_alias(struct device *dev)  { -	struct iommu_dev_data *dev_data; -	struct iommu_group *group; -	struct pci_dev *dma_pdev; -	int ret; - -	group = iommu_group_get(dev); -	if (group) { -		iommu_group_put(group); -		return 0; -	} - -	dev_data = find_dev_data(get_device_id(dev)); -	if (!dev_data) -		return -ENOMEM; +	struct pci_dev *pdev = to_pci_dev(dev); +	u16 devid, ivrs_alias, pci_alias; -	if (dev_data->alias_data) { -		u16 alias; -		struct pci_bus *bus; +	devid = get_device_id(dev); +	ivrs_alias = amd_iommu_alias_table[devid]; +	pci_for_each_dma_alias(pdev, __last_alias, &pci_alias); -		if (dev_data->alias_data->group) -			goto use_group; +	if (ivrs_alias == pci_alias) +		return ivrs_alias; -		/* -		 * If the alias device exists, it's effectively just a first -		 * level quirk for finding the DMA source. -		 */ -		alias = amd_iommu_alias_table[dev_data->devid]; -		dma_pdev = pci_get_bus_and_slot(alias >> 8, alias & 0xff); -		if (dma_pdev) { -			dma_pdev = get_isolation_root(dma_pdev); -			goto use_pdev; +	/* +	 * DMA alias showdown +	 * +	 * The IVRS is fairly reliable in telling us about aliases, but it +	 * can't know about every screwy device.  If we don't have an IVRS +	 * reported alias, use the PCI reported alias.  In that case we may +	 * still need to initialize the rlookup and dev_table entries if the +	 * alias is to a non-existent device. +	 */ +	if (ivrs_alias == devid) { +		if (!amd_iommu_rlookup_table[pci_alias]) { +			amd_iommu_rlookup_table[pci_alias] = +				amd_iommu_rlookup_table[devid]; +			memcpy(amd_iommu_dev_table[pci_alias].data, +			       amd_iommu_dev_table[devid].data, +			       sizeof(amd_iommu_dev_table[pci_alias].data));  		} -		/* -		 * If the alias is virtual, try to find a parent device -		 * and test whether the IOMMU group is actualy rooted above -		 * the alias.  Be careful to also test the parent device if -		 * we think the alias is the root of the group. -		 */ -		bus = pci_find_bus(0, alias >> 8); -		if (!bus) -			goto use_group; - -		bus = find_hosted_bus(bus); -		if (IS_ERR(bus) || !bus->self) -			goto use_group; +		return pci_alias; +	} -		dma_pdev = get_isolation_root(pci_dev_get(bus->self)); -		if (dma_pdev != bus->self || (dma_pdev->multifunction && -		    !pci_acs_enabled(dma_pdev, REQ_ACS_FLAGS))) -			goto use_pdev; +	pr_info("AMD-Vi: Using IVRS reported alias %02x:%02x.%d " +		"for device %s[%04x:%04x], kernel reported alias " +		"%02x:%02x.%d\n", PCI_BUS_NUM(ivrs_alias), PCI_SLOT(ivrs_alias), +		PCI_FUNC(ivrs_alias), dev_name(dev), pdev->vendor, pdev->device, +		PCI_BUS_NUM(pci_alias), PCI_SLOT(pci_alias), +		PCI_FUNC(pci_alias)); -		pci_dev_put(dma_pdev); -		goto use_group; +	/* +	 * If we don't have a PCI DMA alias and the IVRS alias is on the same +	 * bus, then the IVRS table may know about a quirk that we don't. +	 */ +	if (pci_alias == devid && +	    PCI_BUS_NUM(ivrs_alias) == pdev->bus->number) { +		pdev->dev_flags |= PCI_DEV_FLAGS_DMA_ALIAS_DEVFN; +		pdev->dma_alias_devfn = ivrs_alias & 0xff; +		pr_info("AMD-Vi: Added PCI DMA alias %02x.%d for %s\n", +			PCI_SLOT(ivrs_alias), PCI_FUNC(ivrs_alias), +			dev_name(dev));  	} -	dma_pdev = get_isolation_root(pci_dev_get(to_pci_dev(dev))); -use_pdev: -	ret = use_pdev_iommu_group(dma_pdev, dev); -	pci_dev_put(dma_pdev); -	return ret; -use_group: -	return use_dev_data_iommu_group(dev_data->alias_data, dev); +	return ivrs_alias;  }  static int iommu_init_device(struct device *dev) @@ -441,7 +349,8 @@ static int iommu_init_device(struct device *dev)  	if (!dev_data)  		return -ENOMEM; -	alias = amd_iommu_alias_table[dev_data->devid]; +	alias = get_alias(dev); +  	if (alias != dev_data->devid) {  		struct iommu_dev_data *alias_data; @@ -470,6 +379,9 @@ static int iommu_init_device(struct device *dev)  	dev->archdata.iommu = dev_data; +	iommu_device_link(amd_iommu_rlookup_table[dev_data->devid]->iommu_dev, +			  dev); +  	return 0;  } @@ -489,12 +401,22 @@ static void iommu_ignore_device(struct device *dev)  static void iommu_uninit_device(struct device *dev)  { +	struct iommu_dev_data *dev_data = search_dev_data(get_device_id(dev)); + +	if (!dev_data) +		return; + +	iommu_device_unlink(amd_iommu_rlookup_table[dev_data->devid]->iommu_dev, +			    dev); +  	iommu_group_remove_device(dev); +	/* Unlink from alias, it may change if another device is re-plugged */ +	dev_data->alias_data = NULL; +  	/* -	 * Nothing to do here - we keep dev_data around for unplugged devices -	 * and reuse it when the device is re-plugged - not doing so would -	 * introduce a ton of races. +	 * We keep dev_data around for unplugged devices and reuse it when the +	 * device is re-plugged - not doing so would introduce a ton of races.  	 */  } @@ -3227,14 +3149,16 @@ free_domains:  static void cleanup_domain(struct protection_domain *domain)  { -	struct iommu_dev_data *dev_data, *next; +	struct iommu_dev_data *entry;  	unsigned long flags;  	write_lock_irqsave(&amd_iommu_devtable_lock, flags); -	list_for_each_entry_safe(dev_data, next, &domain->dev_list, list) { -		__detach_device(dev_data); -		atomic_set(&dev_data->bind, 0); +	while (!list_empty(&domain->dev_list)) { +		entry = list_first_entry(&domain->dev_list, +					 struct iommu_dev_data, list); +		__detach_device(entry); +		atomic_set(&entry->bind, 0);  	}  	write_unlock_irqrestore(&amd_iommu_devtable_lock, flags); @@ -3473,7 +3397,7 @@ static int amd_iommu_domain_has_cap(struct iommu_domain *domain,  	return 0;  } -static struct iommu_ops amd_iommu_ops = { +static const struct iommu_ops amd_iommu_ops = {  	.domain_init = amd_iommu_domain_init,  	.domain_destroy = amd_iommu_domain_destroy,  	.attach_dev = amd_iommu_attach_device,  |