iommu/amd: Implement device aquisition code for IOMMUv2
authorJoerg Roedel <joerg.roedel@amd.com>
Wed, 23 Nov 2011 16:30:39 +0000 (17:30 +0100)
committerJoerg Roedel <joerg.roedel@amd.com>
Mon, 12 Dec 2011 14:32:51 +0000 (15:32 +0100)
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>
drivers/iommu/amd_iommu_v2.c
include/linux/amd-iommu.h

index a19e07d..bfceed2 100644 (file)
  * 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);
index 4152c30..e8c7a2e 100644 (file)
 #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; }