mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-25 21:24:08 +08:00
97027da6ad
The most important part of these updates is the IOMMU groups code enhancement written by Alex Williamson. It abstracts the problem that a given hardware IOMMU can't isolate any given device from any other device (e.g. 32 bit PCI devices can't usually be isolated). Devices that can't be isolated are grouped together. This code is required for the upcoming VFIO framework. Another IOMMU-API change written by be is the introduction of domain attributes. This makes it easier to handle GART-like IOMMUs with the IOMMU-API because now the start-address and the size of the domain address space can be queried. Besides that there are a few cleanups and fixes for the NVidia Tegra IOMMU drivers and the reworked init-code for the AMD IOMMU. The later is from my patch-set to support interrupt remapping. The rest of this patch-set requires x86 changes which are not mergabe yet. So full support for interrupt remapping with AMD IOMMUs will come in a future merge window. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.11 (GNU/Linux) iQIcBAABAgAGBQJQDV/MAAoJECvwRC2XARrjSDcP+gJbtSHDMyZ71zyfQfAZcxJt rTqLbdZRtIjrjgtKSEDp8u5Bo5TK9dAYoZVuJMOZewFzwI/fSfbRsWp1PU0I88Fr ZzM+/o1N9MLvf1e3kRVOzNzUfku+jTQgUBD4txsbtQzc/IeGHe9qP1Bqzs/xg4Pk SjWu7pLNYxaER10z76nRodNn6zGjsc7GFdOW8cJu2HOAHhisIAR291jSQgd6Rz9r zWqSTsXIEzYt2CtU3G2/tFJ554Mp8v5F80gHo+0Ldw8aNxlD6nGtbqGNt+KI8qTv MUL8KJ0TNms9CZdti1CSlSNp51VgJi2GaWKCaDAkYuuER2IbC/8Yp/p2DIIA0GNp HpziIs+dauZPWfZHc6oU7lJAClGAG4MUx7CysVIOzl7ML/Bf4mjYv0faGf5YQfyE weOR+OPPIWDUwgjzHKMAboA4ijkE/v+EKjOaN/S9rEqFEMKC99fwGkf9wUcpZTne 8lzdI2JrgYNDWMVNYlomeLD4lBAbxb/QsnRUa33igjr0MclvMDkp5HaO631Z1+Zx be2z8Rl1CtMwS4qeaOXoeaoNWHU26+oJRZNtCGi/Fw4aKqYXP1dnE/m0GtqEP9Yi +CU2rKbZn3j0+ZcQjCQop8FREPrZ2/Uaji70b6G7WZ2ApcqBxzBffpbMKOmd6T1D HIzGh0fpdYNDuwn6Txit =MbAC -----END PGP SIGNATURE----- Merge tag 'iommu-updates-v3.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/joro/iommu Pull IOMMU updates from Joerg Roedel: "The most important part of these updates is the IOMMU groups code enhancement written by Alex Williamson. It abstracts the problem that a given hardware IOMMU can't isolate any given device from any other device (e.g. 32 bit PCI devices can't usually be isolated). Devices that can't be isolated are grouped together. This code is required for the upcoming VFIO framework. Another IOMMU-API change written by me is the introduction of domain attributes. This makes it easier to handle GART-like IOMMUs with the IOMMU-API because now the start-address and the size of the domain address space can be queried. Besides that there are a few cleanups and fixes for the NVidia Tegra IOMMU drivers and the reworked init-code for the AMD IOMMU. The latter is from my patch-set to support interrupt remapping. The rest of this patch-set requires x86 changes which are not mergabe yet. So full support for interrupt remapping with AMD IOMMUs will come in a future merge window." * tag 'iommu-updates-v3.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/joro/iommu: (33 commits) iommu/amd: Fix hotplug with iommu=pt iommu/amd: Add missing spin_lock initialization iommu/amd: Convert iommu initialization to state machine iommu/amd: Introduce amd_iommu_init_dma routine iommu/amd: Move unmap_flush message to amd_iommu_init_dma_ops() iommu/amd: Split enable_iommus() routine iommu/amd: Introduce early_amd_iommu_init routine iommu/amd: Move informational prinks out of iommu_enable iommu/amd: Split out PCI related parts of IOMMU initialization iommu/amd: Use acpi_get_table instead of acpi_table_parse iommu/amd: Fix sparse warnings iommu/tegra: Don't call alloc_pdir with as->lock iommu/tegra: smmu: Fix unsleepable memory allocation at alloc_pdir() iommu/tegra: smmu: Remove unnecessary sanity check at alloc_pdir() iommu/exynos: Implement DOMAIN_ATTR_GEOMETRY attribute iommu/tegra: Implement DOMAIN_ATTR_GEOMETRY attribute iommu/msm: Implement DOMAIN_ATTR_GEOMETRY attribute iommu/omap: Implement DOMAIN_ATTR_GEOMETRY attribute iommu/vt-d: Implement DOMAIN_ATTR_GEOMETRY attribute iommu/amd: Implement DOMAIN_ATTR_GEOMETRY attribute ...
1009 lines
23 KiB
C
1009 lines
23 KiB
C
/*
|
|
* Copyright (C) 2010-2012 Advanced Micro Devices, Inc.
|
|
* Author: Joerg Roedel <joerg.roedel@amd.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 as published
|
|
* by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that 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
|
|
*/
|
|
|
|
#include <linux/mmu_notifier.h>
|
|
#include <linux/amd-iommu.h>
|
|
#include <linux/mm_types.h>
|
|
#include <linux/profile.h>
|
|
#include <linux/module.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/iommu.h>
|
|
#include <linux/wait.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/gfp.h>
|
|
|
|
#include "amd_iommu_types.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;
|
|
int status;
|
|
};
|
|
|
|
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 mmu_notifier mn; /* mmu_otifier handle */
|
|
struct pri_queue pri[PRI_QUEUE_SIZE]; /* PRI tag states */
|
|
struct device_state *device_state; /* Link to our device_state */
|
|
int pasid; /* PASID index */
|
|
spinlock_t lock; /* Protect pri_queues */
|
|
wait_queue_head_t wq; /* To wait for count == 0 */
|
|
};
|
|
|
|
struct device_state {
|
|
atomic_t count;
|
|
struct pci_dev *pdev;
|
|
struct pasid_state **states;
|
|
struct iommu_domain *domain;
|
|
int pasid_levels;
|
|
int max_pasids;
|
|
amd_iommu_invalid_ppr_cb inv_ppr_cb;
|
|
amd_iommu_invalidate_ctx inv_ctx_cb;
|
|
spinlock_t lock;
|
|
wait_queue_head_t wq;
|
|
};
|
|
|
|
struct fault {
|
|
struct work_struct work;
|
|
struct device_state *dev_state;
|
|
struct pasid_state *state;
|
|
struct mm_struct *mm;
|
|
u64 address;
|
|
u16 devid;
|
|
u16 pasid;
|
|
u16 tag;
|
|
u16 finish;
|
|
u16 flags;
|
|
};
|
|
|
|
static struct device_state **state_table;
|
|
static spinlock_t state_lock;
|
|
|
|
/* List and lock for all pasid_states */
|
|
static LIST_HEAD(pasid_state_list);
|
|
static DEFINE_SPINLOCK(ps_lock);
|
|
|
|
static struct workqueue_struct *iommu_wq;
|
|
|
|
/*
|
|
* Empty page table - Used between
|
|
* mmu_notifier_invalidate_range_start and
|
|
* mmu_notifier_invalidate_range_end
|
|
*/
|
|
static u64 *empty_page_table;
|
|
|
|
static void free_pasid_states(struct device_state *dev_state);
|
|
static void unbind_pasid(struct device_state *dev_state, int pasid);
|
|
static int task_exit(struct notifier_block *nb, unsigned long e, void *data);
|
|
|
|
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)
|
|
{
|
|
/*
|
|
* First detach device from domain - No more PRI requests will arrive
|
|
* from that device after it is unbound from the IOMMUv2 domain.
|
|
*/
|
|
iommu_detach_device(dev_state->domain, &dev_state->pdev->dev);
|
|
|
|
/* Everything is down now, free the IOMMUv2 domain */
|
|
iommu_domain_free(dev_state->domain);
|
|
|
|
/* Finally get rid of the device-state */
|
|
kfree(dev_state);
|
|
}
|
|
|
|
static void put_device_state(struct device_state *dev_state)
|
|
{
|
|
if (atomic_dec_and_test(&dev_state->count))
|
|
wake_up(&dev_state->wq);
|
|
}
|
|
|
|
static void put_device_state_wait(struct device_state *dev_state)
|
|
{
|
|
DEFINE_WAIT(wait);
|
|
|
|
prepare_to_wait(&dev_state->wq, &wait, TASK_UNINTERRUPTIBLE);
|
|
if (!atomic_dec_and_test(&dev_state->count))
|
|
schedule();
|
|
finish_wait(&dev_state->wq, &wait);
|
|
|
|
free_device_state(dev_state);
|
|
}
|
|
|
|
static struct notifier_block profile_nb = {
|
|
.notifier_call = task_exit,
|
|
};
|
|
|
|
static void link_pasid_state(struct pasid_state *pasid_state)
|
|
{
|
|
spin_lock(&ps_lock);
|
|
list_add_tail(&pasid_state->list, &pasid_state_list);
|
|
spin_unlock(&ps_lock);
|
|
}
|
|
|
|
static void __unlink_pasid_state(struct pasid_state *pasid_state)
|
|
{
|
|
list_del(&pasid_state->list);
|
|
}
|
|
|
|
static void unlink_pasid_state(struct pasid_state *pasid_state)
|
|
{
|
|
spin_lock(&ps_lock);
|
|
__unlink_pasid_state(pasid_state);
|
|
spin_unlock(&ps_lock);
|
|
}
|
|
|
|
/* Must be called under dev_state->lock */
|
|
static struct pasid_state **__get_pasid_state_ptr(struct device_state *dev_state,
|
|
int pasid, bool alloc)
|
|
{
|
|
struct pasid_state **root, **ptr;
|
|
int level, index;
|
|
|
|
level = dev_state->pasid_levels;
|
|
root = dev_state->states;
|
|
|
|
while (true) {
|
|
|
|
index = (pasid >> (9 * level)) & 0x1ff;
|
|
ptr = &root[index];
|
|
|
|
if (level == 0)
|
|
break;
|
|
|
|
if (*ptr == NULL) {
|
|
if (!alloc)
|
|
return NULL;
|
|
|
|
*ptr = (void *)get_zeroed_page(GFP_ATOMIC);
|
|
if (*ptr == NULL)
|
|
return NULL;
|
|
}
|
|
|
|
root = (struct pasid_state **)*ptr;
|
|
level -= 1;
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
static int set_pasid_state(struct device_state *dev_state,
|
|
struct pasid_state *pasid_state,
|
|
int pasid)
|
|
{
|
|
struct pasid_state **ptr;
|
|
unsigned long flags;
|
|
int ret;
|
|
|
|
spin_lock_irqsave(&dev_state->lock, flags);
|
|
ptr = __get_pasid_state_ptr(dev_state, pasid, true);
|
|
|
|
ret = -ENOMEM;
|
|
if (ptr == NULL)
|
|
goto out_unlock;
|
|
|
|
ret = -ENOMEM;
|
|
if (*ptr != NULL)
|
|
goto out_unlock;
|
|
|
|
*ptr = pasid_state;
|
|
|
|
ret = 0;
|
|
|
|
out_unlock:
|
|
spin_unlock_irqrestore(&dev_state->lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void clear_pasid_state(struct device_state *dev_state, int pasid)
|
|
{
|
|
struct pasid_state **ptr;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev_state->lock, flags);
|
|
ptr = __get_pasid_state_ptr(dev_state, pasid, true);
|
|
|
|
if (ptr == NULL)
|
|
goto out_unlock;
|
|
|
|
*ptr = NULL;
|
|
|
|
out_unlock:
|
|
spin_unlock_irqrestore(&dev_state->lock, flags);
|
|
}
|
|
|
|
static struct pasid_state *get_pasid_state(struct device_state *dev_state,
|
|
int pasid)
|
|
{
|
|
struct pasid_state **ptr, *ret = NULL;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev_state->lock, flags);
|
|
ptr = __get_pasid_state_ptr(dev_state, pasid, false);
|
|
|
|
if (ptr == NULL)
|
|
goto out_unlock;
|
|
|
|
ret = *ptr;
|
|
if (ret)
|
|
atomic_inc(&ret->count);
|
|
|
|
out_unlock:
|
|
spin_unlock_irqrestore(&dev_state->lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void free_pasid_state(struct pasid_state *pasid_state)
|
|
{
|
|
kfree(pasid_state);
|
|
}
|
|
|
|
static void put_pasid_state(struct pasid_state *pasid_state)
|
|
{
|
|
if (atomic_dec_and_test(&pasid_state->count)) {
|
|
put_device_state(pasid_state->device_state);
|
|
wake_up(&pasid_state->wq);
|
|
}
|
|
}
|
|
|
|
static void put_pasid_state_wait(struct pasid_state *pasid_state)
|
|
{
|
|
DEFINE_WAIT(wait);
|
|
|
|
prepare_to_wait(&pasid_state->wq, &wait, TASK_UNINTERRUPTIBLE);
|
|
|
|
if (atomic_dec_and_test(&pasid_state->count))
|
|
put_device_state(pasid_state->device_state);
|
|
else
|
|
schedule();
|
|
|
|
finish_wait(&pasid_state->wq, &wait);
|
|
mmput(pasid_state->mm);
|
|
free_pasid_state(pasid_state);
|
|
}
|
|
|
|
static void __unbind_pasid(struct pasid_state *pasid_state)
|
|
{
|
|
struct iommu_domain *domain;
|
|
|
|
domain = pasid_state->device_state->domain;
|
|
|
|
amd_iommu_domain_clear_gcr3(domain, pasid_state->pasid);
|
|
clear_pasid_state(pasid_state->device_state, pasid_state->pasid);
|
|
|
|
/* Make sure no more pending faults are in the queue */
|
|
flush_workqueue(iommu_wq);
|
|
|
|
mmu_notifier_unregister(&pasid_state->mn, pasid_state->mm);
|
|
|
|
put_pasid_state(pasid_state); /* Reference taken in bind() function */
|
|
}
|
|
|
|
static void unbind_pasid(struct device_state *dev_state, int pasid)
|
|
{
|
|
struct pasid_state *pasid_state;
|
|
|
|
pasid_state = get_pasid_state(dev_state, pasid);
|
|
if (pasid_state == NULL)
|
|
return;
|
|
|
|
unlink_pasid_state(pasid_state);
|
|
__unbind_pasid(pasid_state);
|
|
put_pasid_state_wait(pasid_state); /* Reference taken in this function */
|
|
}
|
|
|
|
static void free_pasid_states_level1(struct pasid_state **tbl)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < 512; ++i) {
|
|
if (tbl[i] == NULL)
|
|
continue;
|
|
|
|
free_page((unsigned long)tbl[i]);
|
|
}
|
|
}
|
|
|
|
static void free_pasid_states_level2(struct pasid_state **tbl)
|
|
{
|
|
struct pasid_state **ptr;
|
|
int i;
|
|
|
|
for (i = 0; i < 512; ++i) {
|
|
if (tbl[i] == NULL)
|
|
continue;
|
|
|
|
ptr = (struct pasid_state **)tbl[i];
|
|
free_pasid_states_level1(ptr);
|
|
}
|
|
}
|
|
|
|
static void free_pasid_states(struct device_state *dev_state)
|
|
{
|
|
struct pasid_state *pasid_state;
|
|
int i;
|
|
|
|
for (i = 0; i < dev_state->max_pasids; ++i) {
|
|
pasid_state = get_pasid_state(dev_state, i);
|
|
if (pasid_state == NULL)
|
|
continue;
|
|
|
|
put_pasid_state(pasid_state);
|
|
unbind_pasid(dev_state, i);
|
|
}
|
|
|
|
if (dev_state->pasid_levels == 2)
|
|
free_pasid_states_level2(dev_state->states);
|
|
else if (dev_state->pasid_levels == 1)
|
|
free_pasid_states_level1(dev_state->states);
|
|
else if (dev_state->pasid_levels != 0)
|
|
BUG();
|
|
|
|
free_page((unsigned long)dev_state->states);
|
|
}
|
|
|
|
static struct pasid_state *mn_to_state(struct mmu_notifier *mn)
|
|
{
|
|
return container_of(mn, struct pasid_state, mn);
|
|
}
|
|
|
|
static void __mn_flush_page(struct mmu_notifier *mn,
|
|
unsigned long address)
|
|
{
|
|
struct pasid_state *pasid_state;
|
|
struct device_state *dev_state;
|
|
|
|
pasid_state = mn_to_state(mn);
|
|
dev_state = pasid_state->device_state;
|
|
|
|
amd_iommu_flush_page(dev_state->domain, pasid_state->pasid, address);
|
|
}
|
|
|
|
static int mn_clear_flush_young(struct mmu_notifier *mn,
|
|
struct mm_struct *mm,
|
|
unsigned long address)
|
|
{
|
|
__mn_flush_page(mn, address);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mn_change_pte(struct mmu_notifier *mn,
|
|
struct mm_struct *mm,
|
|
unsigned long address,
|
|
pte_t pte)
|
|
{
|
|
__mn_flush_page(mn, address);
|
|
}
|
|
|
|
static void mn_invalidate_page(struct mmu_notifier *mn,
|
|
struct mm_struct *mm,
|
|
unsigned long address)
|
|
{
|
|
__mn_flush_page(mn, address);
|
|
}
|
|
|
|
static void mn_invalidate_range_start(struct mmu_notifier *mn,
|
|
struct mm_struct *mm,
|
|
unsigned long start, unsigned long end)
|
|
{
|
|
struct pasid_state *pasid_state;
|
|
struct device_state *dev_state;
|
|
|
|
pasid_state = mn_to_state(mn);
|
|
dev_state = pasid_state->device_state;
|
|
|
|
amd_iommu_domain_set_gcr3(dev_state->domain, pasid_state->pasid,
|
|
__pa(empty_page_table));
|
|
}
|
|
|
|
static void mn_invalidate_range_end(struct mmu_notifier *mn,
|
|
struct mm_struct *mm,
|
|
unsigned long start, unsigned long end)
|
|
{
|
|
struct pasid_state *pasid_state;
|
|
struct device_state *dev_state;
|
|
|
|
pasid_state = mn_to_state(mn);
|
|
dev_state = pasid_state->device_state;
|
|
|
|
amd_iommu_domain_set_gcr3(dev_state->domain, pasid_state->pasid,
|
|
__pa(pasid_state->mm->pgd));
|
|
}
|
|
|
|
static struct mmu_notifier_ops iommu_mn = {
|
|
.clear_flush_young = mn_clear_flush_young,
|
|
.change_pte = mn_change_pte,
|
|
.invalidate_page = mn_invalidate_page,
|
|
.invalidate_range_start = mn_invalidate_range_start,
|
|
.invalidate_range_end = mn_invalidate_range_end,
|
|
};
|
|
|
|
static void set_pri_tag_status(struct pasid_state *pasid_state,
|
|
u16 tag, int status)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&pasid_state->lock, flags);
|
|
pasid_state->pri[tag].status = status;
|
|
spin_unlock_irqrestore(&pasid_state->lock, flags);
|
|
}
|
|
|
|
static void finish_pri_tag(struct device_state *dev_state,
|
|
struct pasid_state *pasid_state,
|
|
u16 tag)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&pasid_state->lock, flags);
|
|
if (atomic_dec_and_test(&pasid_state->pri[tag].inflight) &&
|
|
pasid_state->pri[tag].finish) {
|
|
amd_iommu_complete_ppr(dev_state->pdev, pasid_state->pasid,
|
|
pasid_state->pri[tag].status, tag);
|
|
pasid_state->pri[tag].finish = false;
|
|
pasid_state->pri[tag].status = PPR_SUCCESS;
|
|
}
|
|
spin_unlock_irqrestore(&pasid_state->lock, flags);
|
|
}
|
|
|
|
static void do_fault(struct work_struct *work)
|
|
{
|
|
struct fault *fault = container_of(work, struct fault, work);
|
|
int npages, write;
|
|
struct page *page;
|
|
|
|
write = !!(fault->flags & PPR_FAULT_WRITE);
|
|
|
|
npages = get_user_pages(fault->state->task, fault->state->mm,
|
|
fault->address, 1, write, 0, &page, NULL);
|
|
|
|
if (npages == 1) {
|
|
put_page(page);
|
|
} else if (fault->dev_state->inv_ppr_cb) {
|
|
int status;
|
|
|
|
status = fault->dev_state->inv_ppr_cb(fault->dev_state->pdev,
|
|
fault->pasid,
|
|
fault->address,
|
|
fault->flags);
|
|
switch (status) {
|
|
case AMD_IOMMU_INV_PRI_RSP_SUCCESS:
|
|
set_pri_tag_status(fault->state, fault->tag, PPR_SUCCESS);
|
|
break;
|
|
case AMD_IOMMU_INV_PRI_RSP_INVALID:
|
|
set_pri_tag_status(fault->state, fault->tag, PPR_INVALID);
|
|
break;
|
|
case AMD_IOMMU_INV_PRI_RSP_FAIL:
|
|
set_pri_tag_status(fault->state, fault->tag, PPR_FAILURE);
|
|
break;
|
|
default:
|
|
BUG();
|
|
}
|
|
} else {
|
|
set_pri_tag_status(fault->state, fault->tag, PPR_INVALID);
|
|
}
|
|
|
|
finish_pri_tag(fault->dev_state, fault->state, fault->tag);
|
|
|
|
put_pasid_state(fault->state);
|
|
|
|
kfree(fault);
|
|
}
|
|
|
|
static int ppr_notifier(struct notifier_block *nb, unsigned long e, void *data)
|
|
{
|
|
struct amd_iommu_fault *iommu_fault;
|
|
struct pasid_state *pasid_state;
|
|
struct device_state *dev_state;
|
|
unsigned long flags;
|
|
struct fault *fault;
|
|
bool finish;
|
|
u16 tag;
|
|
int ret;
|
|
|
|
iommu_fault = data;
|
|
tag = iommu_fault->tag & 0x1ff;
|
|
finish = (iommu_fault->tag >> 9) & 1;
|
|
|
|
ret = NOTIFY_DONE;
|
|
dev_state = get_device_state(iommu_fault->device_id);
|
|
if (dev_state == NULL)
|
|
goto out;
|
|
|
|
pasid_state = get_pasid_state(dev_state, iommu_fault->pasid);
|
|
if (pasid_state == NULL) {
|
|
/* We know the device but not the PASID -> send INVALID */
|
|
amd_iommu_complete_ppr(dev_state->pdev, iommu_fault->pasid,
|
|
PPR_INVALID, tag);
|
|
goto out_drop_state;
|
|
}
|
|
|
|
spin_lock_irqsave(&pasid_state->lock, flags);
|
|
atomic_inc(&pasid_state->pri[tag].inflight);
|
|
if (finish)
|
|
pasid_state->pri[tag].finish = true;
|
|
spin_unlock_irqrestore(&pasid_state->lock, flags);
|
|
|
|
fault = kzalloc(sizeof(*fault), GFP_ATOMIC);
|
|
if (fault == NULL) {
|
|
/* We are OOM - send success and let the device re-fault */
|
|
finish_pri_tag(dev_state, pasid_state, tag);
|
|
goto out_drop_state;
|
|
}
|
|
|
|
fault->dev_state = dev_state;
|
|
fault->address = iommu_fault->address;
|
|
fault->state = pasid_state;
|
|
fault->tag = tag;
|
|
fault->finish = finish;
|
|
fault->flags = iommu_fault->flags;
|
|
INIT_WORK(&fault->work, do_fault);
|
|
|
|
queue_work(iommu_wq, &fault->work);
|
|
|
|
ret = NOTIFY_OK;
|
|
|
|
out_drop_state:
|
|
put_device_state(dev_state);
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static struct notifier_block ppr_nb = {
|
|
.notifier_call = ppr_notifier,
|
|
};
|
|
|
|
static int task_exit(struct notifier_block *nb, unsigned long e, void *data)
|
|
{
|
|
struct pasid_state *pasid_state;
|
|
struct task_struct *task;
|
|
|
|
task = data;
|
|
|
|
/*
|
|
* Using this notifier is a hack - but there is no other choice
|
|
* at the moment. What I really want is a sleeping notifier that
|
|
* is called when an MM goes down. But such a notifier doesn't
|
|
* exist yet. The notifier needs to sleep because it has to make
|
|
* sure that the device does not use the PASID and the address
|
|
* space anymore before it is destroyed. This includes waiting
|
|
* for pending PRI requests to pass the workqueue. The
|
|
* MMU-Notifiers would be a good fit, but they use RCU and so
|
|
* they are not allowed to sleep. Lets see how we can solve this
|
|
* in a more intelligent way in the future.
|
|
*/
|
|
again:
|
|
spin_lock(&ps_lock);
|
|
list_for_each_entry(pasid_state, &pasid_state_list, list) {
|
|
struct device_state *dev_state;
|
|
int pasid;
|
|
|
|
if (pasid_state->task != task)
|
|
continue;
|
|
|
|
/* Drop Lock and unbind */
|
|
spin_unlock(&ps_lock);
|
|
|
|
dev_state = pasid_state->device_state;
|
|
pasid = pasid_state->pasid;
|
|
|
|
if (pasid_state->device_state->inv_ctx_cb)
|
|
dev_state->inv_ctx_cb(dev_state->pdev, pasid);
|
|
|
|
unbind_pasid(dev_state, pasid);
|
|
|
|
/* Task may be in the list multiple times */
|
|
goto again;
|
|
}
|
|
spin_unlock(&ps_lock);
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
int amd_iommu_bind_pasid(struct pci_dev *pdev, int pasid,
|
|
struct task_struct *task)
|
|
{
|
|
struct pasid_state *pasid_state;
|
|
struct device_state *dev_state;
|
|
u16 devid;
|
|
int ret;
|
|
|
|
might_sleep();
|
|
|
|
if (!amd_iommu_v2_supported())
|
|
return -ENODEV;
|
|
|
|
devid = device_id(pdev);
|
|
dev_state = get_device_state(devid);
|
|
|
|
if (dev_state == NULL)
|
|
return -EINVAL;
|
|
|
|
ret = -EINVAL;
|
|
if (pasid < 0 || pasid >= dev_state->max_pasids)
|
|
goto out;
|
|
|
|
ret = -ENOMEM;
|
|
pasid_state = kzalloc(sizeof(*pasid_state), GFP_KERNEL);
|
|
if (pasid_state == NULL)
|
|
goto out;
|
|
|
|
atomic_set(&pasid_state->count, 1);
|
|
init_waitqueue_head(&pasid_state->wq);
|
|
spin_lock_init(&pasid_state->lock);
|
|
|
|
pasid_state->task = task;
|
|
pasid_state->mm = get_task_mm(task);
|
|
pasid_state->device_state = dev_state;
|
|
pasid_state->pasid = pasid;
|
|
pasid_state->mn.ops = &iommu_mn;
|
|
|
|
if (pasid_state->mm == NULL)
|
|
goto out_free;
|
|
|
|
mmu_notifier_register(&pasid_state->mn, pasid_state->mm);
|
|
|
|
ret = set_pasid_state(dev_state, pasid_state, pasid);
|
|
if (ret)
|
|
goto out_unregister;
|
|
|
|
ret = amd_iommu_domain_set_gcr3(dev_state->domain, pasid,
|
|
__pa(pasid_state->mm->pgd));
|
|
if (ret)
|
|
goto out_clear_state;
|
|
|
|
link_pasid_state(pasid_state);
|
|
|
|
return 0;
|
|
|
|
out_clear_state:
|
|
clear_pasid_state(dev_state, pasid);
|
|
|
|
out_unregister:
|
|
mmu_notifier_unregister(&pasid_state->mn, pasid_state->mm);
|
|
|
|
out_free:
|
|
free_pasid_state(pasid_state);
|
|
|
|
out:
|
|
put_device_state(dev_state);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(amd_iommu_bind_pasid);
|
|
|
|
void amd_iommu_unbind_pasid(struct pci_dev *pdev, int pasid)
|
|
{
|
|
struct device_state *dev_state;
|
|
u16 devid;
|
|
|
|
might_sleep();
|
|
|
|
if (!amd_iommu_v2_supported())
|
|
return;
|
|
|
|
devid = device_id(pdev);
|
|
dev_state = get_device_state(devid);
|
|
if (dev_state == NULL)
|
|
return;
|
|
|
|
if (pasid < 0 || pasid >= dev_state->max_pasids)
|
|
goto out;
|
|
|
|
unbind_pasid(dev_state, pasid);
|
|
|
|
out:
|
|
put_device_state(dev_state);
|
|
}
|
|
EXPORT_SYMBOL(amd_iommu_unbind_pasid);
|
|
|
|
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);
|
|
init_waitqueue_head(&dev_state->wq);
|
|
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);
|
|
|
|
/* Get rid of any remaining pasid states */
|
|
free_pasid_states(dev_state);
|
|
|
|
put_device_state_wait(dev_state);
|
|
}
|
|
EXPORT_SYMBOL(amd_iommu_free_device);
|
|
|
|
int amd_iommu_set_invalid_ppr_cb(struct pci_dev *pdev,
|
|
amd_iommu_invalid_ppr_cb cb)
|
|
{
|
|
struct device_state *dev_state;
|
|
unsigned long flags;
|
|
u16 devid;
|
|
int ret;
|
|
|
|
if (!amd_iommu_v2_supported())
|
|
return -ENODEV;
|
|
|
|
devid = device_id(pdev);
|
|
|
|
spin_lock_irqsave(&state_lock, flags);
|
|
|
|
ret = -EINVAL;
|
|
dev_state = state_table[devid];
|
|
if (dev_state == NULL)
|
|
goto out_unlock;
|
|
|
|
dev_state->inv_ppr_cb = cb;
|
|
|
|
ret = 0;
|
|
|
|
out_unlock:
|
|
spin_unlock_irqrestore(&state_lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(amd_iommu_set_invalid_ppr_cb);
|
|
|
|
int amd_iommu_set_invalidate_ctx_cb(struct pci_dev *pdev,
|
|
amd_iommu_invalidate_ctx cb)
|
|
{
|
|
struct device_state *dev_state;
|
|
unsigned long flags;
|
|
u16 devid;
|
|
int ret;
|
|
|
|
if (!amd_iommu_v2_supported())
|
|
return -ENODEV;
|
|
|
|
devid = device_id(pdev);
|
|
|
|
spin_lock_irqsave(&state_lock, flags);
|
|
|
|
ret = -EINVAL;
|
|
dev_state = state_table[devid];
|
|
if (dev_state == NULL)
|
|
goto out_unlock;
|
|
|
|
dev_state->inv_ctx_cb = cb;
|
|
|
|
ret = 0;
|
|
|
|
out_unlock:
|
|
spin_unlock_irqrestore(&state_lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(amd_iommu_set_invalidate_ctx_cb);
|
|
|
|
static int __init amd_iommu_v2_init(void)
|
|
{
|
|
size_t state_table_size;
|
|
int ret;
|
|
|
|
pr_info("AMD IOMMUv2 driver by Joerg Roedel <joerg.roedel@amd.com>\n");
|
|
|
|
if (!amd_iommu_v2_supported()) {
|
|
pr_info("AMD IOMMUv2 functionality not available on this system\n");
|
|
/*
|
|
* Load anyway to provide the symbols to other modules
|
|
* which may use AMD IOMMUv2 optionally.
|
|
*/
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
|
|
ret = -ENOMEM;
|
|
iommu_wq = create_workqueue("amd_iommu_v2");
|
|
if (iommu_wq == NULL)
|
|
goto out_free;
|
|
|
|
ret = -ENOMEM;
|
|
empty_page_table = (u64 *)get_zeroed_page(GFP_KERNEL);
|
|
if (empty_page_table == NULL)
|
|
goto out_destroy_wq;
|
|
|
|
amd_iommu_register_ppr_notifier(&ppr_nb);
|
|
profile_event_register(PROFILE_TASK_EXIT, &profile_nb);
|
|
|
|
return 0;
|
|
|
|
out_destroy_wq:
|
|
destroy_workqueue(iommu_wq);
|
|
|
|
out_free:
|
|
free_pages((unsigned long)state_table, get_order(state_table_size));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __exit amd_iommu_v2_exit(void)
|
|
{
|
|
struct device_state *dev_state;
|
|
size_t state_table_size;
|
|
int i;
|
|
|
|
if (!amd_iommu_v2_supported())
|
|
return;
|
|
|
|
profile_event_unregister(PROFILE_TASK_EXIT, &profile_nb);
|
|
amd_iommu_unregister_ppr_notifier(&ppr_nb);
|
|
|
|
flush_workqueue(iommu_wq);
|
|
|
|
/*
|
|
* The loop below might call flush_workqueue(), so call
|
|
* destroy_workqueue() after it
|
|
*/
|
|
for (i = 0; i < MAX_DEVICES; ++i) {
|
|
dev_state = get_device_state(i);
|
|
|
|
if (dev_state == NULL)
|
|
continue;
|
|
|
|
WARN_ON_ONCE(1);
|
|
|
|
put_device_state(dev_state);
|
|
amd_iommu_free_device(dev_state->pdev);
|
|
}
|
|
|
|
destroy_workqueue(iommu_wq);
|
|
|
|
state_table_size = MAX_DEVICES * sizeof(struct device_state *);
|
|
free_pages((unsigned long)state_table, get_order(state_table_size));
|
|
|
|
free_page((unsigned long)empty_page_table);
|
|
}
|
|
|
|
module_init(amd_iommu_v2_init);
|
|
module_exit(amd_iommu_v2_exit);
|