diff options
| author | Joerg Roedel <joerg.roedel@amd.com> | 2011-11-23 17:30:39 +0100 | 
|---|---|---|
| committer | Joerg Roedel <joerg.roedel@amd.com> | 2011-12-12 15:32:51 +0100 | 
| commit | ed96f228ba9725edf69385bffdc19ee5bb0ec641 (patch) | |
| tree | 56fc5167fd66b4b7012ac0307d264223a30940e4 | |
| parent | e3c495c74bc008a5c3671bf3411bc7f5382fa58f (diff) | |
iommu/amd: Implement device aquisition code for IOMMUv2
This patch adds the amd_iommu_init_device() and
amd_iommu_free_device() functions which make a device and
the IOMMU ready for IOMMUv2 usage.
Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>
| -rw-r--r-- | drivers/iommu/amd_iommu_v2.c | 210 | ||||
| -rw-r--r-- | include/linux/amd-iommu.h | 23 | 
2 files changed, 232 insertions, 1 deletions
| diff --git a/drivers/iommu/amd_iommu_v2.c b/drivers/iommu/amd_iommu_v2.c index a19e07d11d12..bfceed25c186 100644 --- a/drivers/iommu/amd_iommu_v2.c +++ b/drivers/iommu/amd_iommu_v2.c @@ -16,20 +16,230 @@   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA   */ +#include <linux/amd-iommu.h> +#include <linux/mm_types.h>  #include <linux/module.h> +#include <linux/iommu.h> +#include <linux/pci.h> +#include <linux/gfp.h> + +#include "amd_iommu_proto.h"  MODULE_LICENSE("GPL v2");  MODULE_AUTHOR("Joerg Roedel <joerg.roedel@amd.com>"); +#define MAX_DEVICES		0x10000 +#define PRI_QUEUE_SIZE		512 + +struct pri_queue { +	atomic_t inflight; +	bool finish; +}; + +struct pasid_state { +	struct list_head list;			/* For global state-list */ +	atomic_t count;				/* Reference count */ +	struct task_struct *task;		/* Task bound to this PASID */ +	struct mm_struct *mm;			/* mm_struct for the faults */ +	struct pri_queue pri[PRI_QUEUE_SIZE];	/* PRI tag states */ +	struct device_state *device_state;	/* Link to our device_state */ +	int pasid;				/* PASID index */ +}; + +struct device_state { +	atomic_t count; +	struct pci_dev *pdev; +	struct pasid_state **states; +	struct iommu_domain *domain; +	int pasid_levels; +	int max_pasids; +	spinlock_t lock; +}; + +struct device_state **state_table; +static spinlock_t state_lock; + +/* List and lock for all pasid_states */ +static LIST_HEAD(pasid_state_list); + +static u16 device_id(struct pci_dev *pdev) +{ +	u16 devid; + +	devid = pdev->bus->number; +	devid = (devid << 8) | pdev->devfn; + +	return devid; +} + +static struct device_state *get_device_state(u16 devid) +{ +	struct device_state *dev_state; +	unsigned long flags; + +	spin_lock_irqsave(&state_lock, flags); +	dev_state = state_table[devid]; +	if (dev_state != NULL) +		atomic_inc(&dev_state->count); +	spin_unlock_irqrestore(&state_lock, flags); + +	return dev_state; +} + +static void free_device_state(struct device_state *dev_state) +{ +	iommu_detach_device(dev_state->domain, &dev_state->pdev->dev); +	iommu_domain_free(dev_state->domain); +	kfree(dev_state); +} + +static void put_device_state(struct device_state *dev_state) +{ +	if (atomic_dec_and_test(&dev_state->count)) +		free_device_state(dev_state); +} + +int amd_iommu_init_device(struct pci_dev *pdev, int pasids) +{ +	struct device_state *dev_state; +	unsigned long flags; +	int ret, tmp; +	u16 devid; + +	might_sleep(); + +	if (!amd_iommu_v2_supported()) +		return -ENODEV; + +	if (pasids <= 0 || pasids > (PASID_MASK + 1)) +		return -EINVAL; + +	devid = device_id(pdev); + +	dev_state = kzalloc(sizeof(*dev_state), GFP_KERNEL); +	if (dev_state == NULL) +		return -ENOMEM; + +	spin_lock_init(&dev_state->lock); +	dev_state->pdev = pdev; + +	tmp = pasids; +	for (dev_state->pasid_levels = 0; (tmp - 1) & ~0x1ff; tmp >>= 9) +		dev_state->pasid_levels += 1; + +	atomic_set(&dev_state->count, 1); +	dev_state->max_pasids = pasids; + +	ret = -ENOMEM; +	dev_state->states = (void *)get_zeroed_page(GFP_KERNEL); +	if (dev_state->states == NULL) +		goto out_free_dev_state; + +	dev_state->domain = iommu_domain_alloc(&pci_bus_type); +	if (dev_state->domain == NULL) +		goto out_free_states; + +	amd_iommu_domain_direct_map(dev_state->domain); + +	ret = amd_iommu_domain_enable_v2(dev_state->domain, pasids); +	if (ret) +		goto out_free_domain; + +	ret = iommu_attach_device(dev_state->domain, &pdev->dev); +	if (ret != 0) +		goto out_free_domain; + +	spin_lock_irqsave(&state_lock, flags); + +	if (state_table[devid] != NULL) { +		spin_unlock_irqrestore(&state_lock, flags); +		ret = -EBUSY; +		goto out_free_domain; +	} + +	state_table[devid] = dev_state; + +	spin_unlock_irqrestore(&state_lock, flags); + +	return 0; + +out_free_domain: +	iommu_domain_free(dev_state->domain); + +out_free_states: +	free_page((unsigned long)dev_state->states); + +out_free_dev_state: +	kfree(dev_state); + +	return ret; +} +EXPORT_SYMBOL(amd_iommu_init_device); + +void amd_iommu_free_device(struct pci_dev *pdev) +{ +	struct device_state *dev_state; +	unsigned long flags; +	u16 devid; + +	if (!amd_iommu_v2_supported()) +		return; + +	devid = device_id(pdev); + +	spin_lock_irqsave(&state_lock, flags); + +	dev_state = state_table[devid]; +	if (dev_state == NULL) { +		spin_unlock_irqrestore(&state_lock, flags); +		return; +	} + +	state_table[devid] = NULL; + +	spin_unlock_irqrestore(&state_lock, flags); + +	put_device_state(dev_state); +} +EXPORT_SYMBOL(amd_iommu_free_device); +  static int __init amd_iommu_v2_init(void)  { +	size_t state_table_size; +  	pr_info("AMD IOMMUv2 driver by Joerg Roedel <joerg.roedel@amd.com>"); +	spin_lock_init(&state_lock); + +	state_table_size = MAX_DEVICES * sizeof(struct device_state *); +	state_table = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, +					       get_order(state_table_size)); +	if (state_table == NULL) +		return -ENOMEM; +  	return 0;  }  static void __exit amd_iommu_v2_exit(void)  { +	struct device_state *dev_state; +	size_t state_table_size; +	int i; + +	for (i = 0; i < MAX_DEVICES; ++i) { +		dev_state = get_device_state(i); + +		if (dev_state == NULL) +			continue; + +		WARN_ON_ONCE(1); + +		amd_iommu_free_device(dev_state->pdev); +		put_device_state(dev_state); +	} + +	state_table_size = MAX_DEVICES * sizeof(struct device_state *); +	free_pages((unsigned long)state_table, get_order(state_table_size));  }  module_init(amd_iommu_v2_init); diff --git a/include/linux/amd-iommu.h b/include/linux/amd-iommu.h index 4152c3073db4..e8c7a2ec86b3 100644 --- a/include/linux/amd-iommu.h +++ b/include/linux/amd-iommu.h @@ -20,10 +20,12 @@  #ifndef _ASM_X86_AMD_IOMMU_H  #define _ASM_X86_AMD_IOMMU_H -#include <linux/irqreturn.h> +#include <linux/types.h>  #ifdef CONFIG_AMD_IOMMU +struct pci_dev; +  extern int amd_iommu_detect(void); @@ -33,6 +35,7 @@ extern int amd_iommu_detect(void);   * @pdev: The PCI device the workaround is necessary for   * @erratum: The erratum workaround to enable   * + * The function needs to be called before amd_iommu_init_device().   * Possible values for the erratum number are for now:   * - AMD_PRI_DEV_ERRATUM_ENABLE_RESET - Reset PRI capability when PRI   *					is enabled @@ -44,6 +47,24 @@ extern int amd_iommu_detect(void);  extern void amd_iommu_enable_device_erratum(struct pci_dev *pdev, u32 erratum); +/** + * amd_iommu_init_device() - Init device for use with IOMMUv2 driver + * @pdev: The PCI device to initialize + * @pasids: Number of PASIDs to support for this device + * + * This function does all setup for the device pdev so that it can be + * used with IOMMUv2. + * Returns 0 on success or negative value on error. + */ +extern int amd_iommu_init_device(struct pci_dev *pdev, int pasids); + +/** + * amd_iommu_free_device() - Free all IOMMUv2 related device resources + *			     and disable IOMMUv2 usage for this device + * @pdev: The PCI device to disable IOMMUv2 usage for' + */ +extern void amd_iommu_free_device(struct pci_dev *pdev); +  #else  static inline int amd_iommu_detect(void) { return -ENODEV; } | 
