162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2010-2012 Advanced Micro Devices, Inc. 462306a36Sopenharmony_ci * Author: Joerg Roedel <jroedel@suse.de> 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#define pr_fmt(fmt) "AMD-Vi: " fmt 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/refcount.h> 1062306a36Sopenharmony_ci#include <linux/mmu_notifier.h> 1162306a36Sopenharmony_ci#include <linux/amd-iommu.h> 1262306a36Sopenharmony_ci#include <linux/mm_types.h> 1362306a36Sopenharmony_ci#include <linux/profile.h> 1462306a36Sopenharmony_ci#include <linux/module.h> 1562306a36Sopenharmony_ci#include <linux/sched.h> 1662306a36Sopenharmony_ci#include <linux/sched/mm.h> 1762306a36Sopenharmony_ci#include <linux/wait.h> 1862306a36Sopenharmony_ci#include <linux/pci.h> 1962306a36Sopenharmony_ci#include <linux/gfp.h> 2062306a36Sopenharmony_ci#include <linux/cc_platform.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#include "amd_iommu.h" 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 2562306a36Sopenharmony_ciMODULE_AUTHOR("Joerg Roedel <jroedel@suse.de>"); 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#define PRI_QUEUE_SIZE 512 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistruct pri_queue { 3062306a36Sopenharmony_ci atomic_t inflight; 3162306a36Sopenharmony_ci bool finish; 3262306a36Sopenharmony_ci int status; 3362306a36Sopenharmony_ci}; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_cistruct pasid_state { 3662306a36Sopenharmony_ci struct list_head list; /* For global state-list */ 3762306a36Sopenharmony_ci refcount_t count; /* Reference count */ 3862306a36Sopenharmony_ci unsigned mmu_notifier_count; /* Counting nested mmu_notifier 3962306a36Sopenharmony_ci calls */ 4062306a36Sopenharmony_ci struct mm_struct *mm; /* mm_struct for the faults */ 4162306a36Sopenharmony_ci struct mmu_notifier mn; /* mmu_notifier handle */ 4262306a36Sopenharmony_ci struct pri_queue pri[PRI_QUEUE_SIZE]; /* PRI tag states */ 4362306a36Sopenharmony_ci struct device_state *device_state; /* Link to our device_state */ 4462306a36Sopenharmony_ci u32 pasid; /* PASID index */ 4562306a36Sopenharmony_ci bool invalid; /* Used during setup and 4662306a36Sopenharmony_ci teardown of the pasid */ 4762306a36Sopenharmony_ci spinlock_t lock; /* Protect pri_queues and 4862306a36Sopenharmony_ci mmu_notifer_count */ 4962306a36Sopenharmony_ci wait_queue_head_t wq; /* To wait for count == 0 */ 5062306a36Sopenharmony_ci}; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_cistruct device_state { 5362306a36Sopenharmony_ci struct list_head list; 5462306a36Sopenharmony_ci u32 sbdf; 5562306a36Sopenharmony_ci atomic_t count; 5662306a36Sopenharmony_ci struct pci_dev *pdev; 5762306a36Sopenharmony_ci struct pasid_state **states; 5862306a36Sopenharmony_ci struct iommu_domain *domain; 5962306a36Sopenharmony_ci int pasid_levels; 6062306a36Sopenharmony_ci int max_pasids; 6162306a36Sopenharmony_ci amd_iommu_invalid_ppr_cb inv_ppr_cb; 6262306a36Sopenharmony_ci amd_iommu_invalidate_ctx inv_ctx_cb; 6362306a36Sopenharmony_ci spinlock_t lock; 6462306a36Sopenharmony_ci wait_queue_head_t wq; 6562306a36Sopenharmony_ci}; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistruct fault { 6862306a36Sopenharmony_ci struct work_struct work; 6962306a36Sopenharmony_ci struct device_state *dev_state; 7062306a36Sopenharmony_ci struct pasid_state *state; 7162306a36Sopenharmony_ci struct mm_struct *mm; 7262306a36Sopenharmony_ci u64 address; 7362306a36Sopenharmony_ci u32 pasid; 7462306a36Sopenharmony_ci u16 tag; 7562306a36Sopenharmony_ci u16 finish; 7662306a36Sopenharmony_ci u16 flags; 7762306a36Sopenharmony_ci}; 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_cistatic LIST_HEAD(state_list); 8062306a36Sopenharmony_cistatic DEFINE_SPINLOCK(state_lock); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic struct workqueue_struct *iommu_wq; 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_cistatic void free_pasid_states(struct device_state *dev_state); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic struct device_state *__get_device_state(u32 sbdf) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci struct device_state *dev_state; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci list_for_each_entry(dev_state, &state_list, list) { 9162306a36Sopenharmony_ci if (dev_state->sbdf == sbdf) 9262306a36Sopenharmony_ci return dev_state; 9362306a36Sopenharmony_ci } 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci return NULL; 9662306a36Sopenharmony_ci} 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistatic struct device_state *get_device_state(u32 sbdf) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci struct device_state *dev_state; 10162306a36Sopenharmony_ci unsigned long flags; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci spin_lock_irqsave(&state_lock, flags); 10462306a36Sopenharmony_ci dev_state = __get_device_state(sbdf); 10562306a36Sopenharmony_ci if (dev_state != NULL) 10662306a36Sopenharmony_ci atomic_inc(&dev_state->count); 10762306a36Sopenharmony_ci spin_unlock_irqrestore(&state_lock, flags); 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci return dev_state; 11062306a36Sopenharmony_ci} 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_cistatic void free_device_state(struct device_state *dev_state) 11362306a36Sopenharmony_ci{ 11462306a36Sopenharmony_ci struct iommu_group *group; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci /* Get rid of any remaining pasid states */ 11762306a36Sopenharmony_ci free_pasid_states(dev_state); 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci /* 12062306a36Sopenharmony_ci * Wait until the last reference is dropped before freeing 12162306a36Sopenharmony_ci * the device state. 12262306a36Sopenharmony_ci */ 12362306a36Sopenharmony_ci wait_event(dev_state->wq, !atomic_read(&dev_state->count)); 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci /* 12662306a36Sopenharmony_ci * First detach device from domain - No more PRI requests will arrive 12762306a36Sopenharmony_ci * from that device after it is unbound from the IOMMUv2 domain. 12862306a36Sopenharmony_ci */ 12962306a36Sopenharmony_ci group = iommu_group_get(&dev_state->pdev->dev); 13062306a36Sopenharmony_ci if (WARN_ON(!group)) 13162306a36Sopenharmony_ci return; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci iommu_detach_group(dev_state->domain, group); 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_ci iommu_group_put(group); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci /* Everything is down now, free the IOMMUv2 domain */ 13862306a36Sopenharmony_ci iommu_domain_free(dev_state->domain); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci /* Finally get rid of the device-state */ 14162306a36Sopenharmony_ci kfree(dev_state); 14262306a36Sopenharmony_ci} 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_cistatic void put_device_state(struct device_state *dev_state) 14562306a36Sopenharmony_ci{ 14662306a36Sopenharmony_ci if (atomic_dec_and_test(&dev_state->count)) 14762306a36Sopenharmony_ci wake_up(&dev_state->wq); 14862306a36Sopenharmony_ci} 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci/* Must be called under dev_state->lock */ 15162306a36Sopenharmony_cistatic struct pasid_state **__get_pasid_state_ptr(struct device_state *dev_state, 15262306a36Sopenharmony_ci u32 pasid, bool alloc) 15362306a36Sopenharmony_ci{ 15462306a36Sopenharmony_ci struct pasid_state **root, **ptr; 15562306a36Sopenharmony_ci int level, index; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci level = dev_state->pasid_levels; 15862306a36Sopenharmony_ci root = dev_state->states; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci while (true) { 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci index = (pasid >> (9 * level)) & 0x1ff; 16362306a36Sopenharmony_ci ptr = &root[index]; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci if (level == 0) 16662306a36Sopenharmony_ci break; 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ci if (*ptr == NULL) { 16962306a36Sopenharmony_ci if (!alloc) 17062306a36Sopenharmony_ci return NULL; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci *ptr = (void *)get_zeroed_page(GFP_ATOMIC); 17362306a36Sopenharmony_ci if (*ptr == NULL) 17462306a36Sopenharmony_ci return NULL; 17562306a36Sopenharmony_ci } 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci root = (struct pasid_state **)*ptr; 17862306a36Sopenharmony_ci level -= 1; 17962306a36Sopenharmony_ci } 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci return ptr; 18262306a36Sopenharmony_ci} 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_cistatic int set_pasid_state(struct device_state *dev_state, 18562306a36Sopenharmony_ci struct pasid_state *pasid_state, 18662306a36Sopenharmony_ci u32 pasid) 18762306a36Sopenharmony_ci{ 18862306a36Sopenharmony_ci struct pasid_state **ptr; 18962306a36Sopenharmony_ci unsigned long flags; 19062306a36Sopenharmony_ci int ret; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci spin_lock_irqsave(&dev_state->lock, flags); 19362306a36Sopenharmony_ci ptr = __get_pasid_state_ptr(dev_state, pasid, true); 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci ret = -ENOMEM; 19662306a36Sopenharmony_ci if (ptr == NULL) 19762306a36Sopenharmony_ci goto out_unlock; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci ret = -ENOMEM; 20062306a36Sopenharmony_ci if (*ptr != NULL) 20162306a36Sopenharmony_ci goto out_unlock; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci *ptr = pasid_state; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci ret = 0; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ciout_unlock: 20862306a36Sopenharmony_ci spin_unlock_irqrestore(&dev_state->lock, flags); 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci return ret; 21162306a36Sopenharmony_ci} 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_cistatic void clear_pasid_state(struct device_state *dev_state, u32 pasid) 21462306a36Sopenharmony_ci{ 21562306a36Sopenharmony_ci struct pasid_state **ptr; 21662306a36Sopenharmony_ci unsigned long flags; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci spin_lock_irqsave(&dev_state->lock, flags); 21962306a36Sopenharmony_ci ptr = __get_pasid_state_ptr(dev_state, pasid, true); 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_ci if (ptr == NULL) 22262306a36Sopenharmony_ci goto out_unlock; 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci *ptr = NULL; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ciout_unlock: 22762306a36Sopenharmony_ci spin_unlock_irqrestore(&dev_state->lock, flags); 22862306a36Sopenharmony_ci} 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_cistatic struct pasid_state *get_pasid_state(struct device_state *dev_state, 23162306a36Sopenharmony_ci u32 pasid) 23262306a36Sopenharmony_ci{ 23362306a36Sopenharmony_ci struct pasid_state **ptr, *ret = NULL; 23462306a36Sopenharmony_ci unsigned long flags; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci spin_lock_irqsave(&dev_state->lock, flags); 23762306a36Sopenharmony_ci ptr = __get_pasid_state_ptr(dev_state, pasid, false); 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci if (ptr == NULL) 24062306a36Sopenharmony_ci goto out_unlock; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci ret = *ptr; 24362306a36Sopenharmony_ci if (ret) 24462306a36Sopenharmony_ci refcount_inc(&ret->count); 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_ciout_unlock: 24762306a36Sopenharmony_ci spin_unlock_irqrestore(&dev_state->lock, flags); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci return ret; 25062306a36Sopenharmony_ci} 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_cistatic void free_pasid_state(struct pasid_state *pasid_state) 25362306a36Sopenharmony_ci{ 25462306a36Sopenharmony_ci kfree(pasid_state); 25562306a36Sopenharmony_ci} 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_cistatic void put_pasid_state(struct pasid_state *pasid_state) 25862306a36Sopenharmony_ci{ 25962306a36Sopenharmony_ci if (refcount_dec_and_test(&pasid_state->count)) 26062306a36Sopenharmony_ci wake_up(&pasid_state->wq); 26162306a36Sopenharmony_ci} 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_cistatic void put_pasid_state_wait(struct pasid_state *pasid_state) 26462306a36Sopenharmony_ci{ 26562306a36Sopenharmony_ci if (!refcount_dec_and_test(&pasid_state->count)) 26662306a36Sopenharmony_ci wait_event(pasid_state->wq, !refcount_read(&pasid_state->count)); 26762306a36Sopenharmony_ci free_pasid_state(pasid_state); 26862306a36Sopenharmony_ci} 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_cistatic void unbind_pasid(struct pasid_state *pasid_state) 27162306a36Sopenharmony_ci{ 27262306a36Sopenharmony_ci struct iommu_domain *domain; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci domain = pasid_state->device_state->domain; 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci /* 27762306a36Sopenharmony_ci * Mark pasid_state as invalid, no more faults will we added to the 27862306a36Sopenharmony_ci * work queue after this is visible everywhere. 27962306a36Sopenharmony_ci */ 28062306a36Sopenharmony_ci pasid_state->invalid = true; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci /* Make sure this is visible */ 28362306a36Sopenharmony_ci smp_wmb(); 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci /* After this the device/pasid can't access the mm anymore */ 28662306a36Sopenharmony_ci amd_iommu_domain_clear_gcr3(domain, pasid_state->pasid); 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci /* Make sure no more pending faults are in the queue */ 28962306a36Sopenharmony_ci flush_workqueue(iommu_wq); 29062306a36Sopenharmony_ci} 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_cistatic void free_pasid_states_level1(struct pasid_state **tbl) 29362306a36Sopenharmony_ci{ 29462306a36Sopenharmony_ci int i; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci for (i = 0; i < 512; ++i) { 29762306a36Sopenharmony_ci if (tbl[i] == NULL) 29862306a36Sopenharmony_ci continue; 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci free_page((unsigned long)tbl[i]); 30162306a36Sopenharmony_ci } 30262306a36Sopenharmony_ci} 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_cistatic void free_pasid_states_level2(struct pasid_state **tbl) 30562306a36Sopenharmony_ci{ 30662306a36Sopenharmony_ci struct pasid_state **ptr; 30762306a36Sopenharmony_ci int i; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci for (i = 0; i < 512; ++i) { 31062306a36Sopenharmony_ci if (tbl[i] == NULL) 31162306a36Sopenharmony_ci continue; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci ptr = (struct pasid_state **)tbl[i]; 31462306a36Sopenharmony_ci free_pasid_states_level1(ptr); 31562306a36Sopenharmony_ci } 31662306a36Sopenharmony_ci} 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_cistatic void free_pasid_states(struct device_state *dev_state) 31962306a36Sopenharmony_ci{ 32062306a36Sopenharmony_ci struct pasid_state *pasid_state; 32162306a36Sopenharmony_ci int i; 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci for (i = 0; i < dev_state->max_pasids; ++i) { 32462306a36Sopenharmony_ci pasid_state = get_pasid_state(dev_state, i); 32562306a36Sopenharmony_ci if (pasid_state == NULL) 32662306a36Sopenharmony_ci continue; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci put_pasid_state(pasid_state); 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci /* Clear the pasid state so that the pasid can be re-used */ 33162306a36Sopenharmony_ci clear_pasid_state(dev_state, pasid_state->pasid); 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci /* 33462306a36Sopenharmony_ci * This will call the mn_release function and 33562306a36Sopenharmony_ci * unbind the PASID 33662306a36Sopenharmony_ci */ 33762306a36Sopenharmony_ci mmu_notifier_unregister(&pasid_state->mn, pasid_state->mm); 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci put_pasid_state_wait(pasid_state); /* Reference taken in 34062306a36Sopenharmony_ci amd_iommu_bind_pasid */ 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci /* Drop reference taken in amd_iommu_bind_pasid */ 34362306a36Sopenharmony_ci put_device_state(dev_state); 34462306a36Sopenharmony_ci } 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci if (dev_state->pasid_levels == 2) 34762306a36Sopenharmony_ci free_pasid_states_level2(dev_state->states); 34862306a36Sopenharmony_ci else if (dev_state->pasid_levels == 1) 34962306a36Sopenharmony_ci free_pasid_states_level1(dev_state->states); 35062306a36Sopenharmony_ci else 35162306a36Sopenharmony_ci BUG_ON(dev_state->pasid_levels != 0); 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci free_page((unsigned long)dev_state->states); 35462306a36Sopenharmony_ci} 35562306a36Sopenharmony_ci 35662306a36Sopenharmony_cistatic struct pasid_state *mn_to_state(struct mmu_notifier *mn) 35762306a36Sopenharmony_ci{ 35862306a36Sopenharmony_ci return container_of(mn, struct pasid_state, mn); 35962306a36Sopenharmony_ci} 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_cistatic void mn_arch_invalidate_secondary_tlbs(struct mmu_notifier *mn, 36262306a36Sopenharmony_ci struct mm_struct *mm, 36362306a36Sopenharmony_ci unsigned long start, unsigned long end) 36462306a36Sopenharmony_ci{ 36562306a36Sopenharmony_ci struct pasid_state *pasid_state; 36662306a36Sopenharmony_ci struct device_state *dev_state; 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci pasid_state = mn_to_state(mn); 36962306a36Sopenharmony_ci dev_state = pasid_state->device_state; 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci if ((start ^ (end - 1)) < PAGE_SIZE) 37262306a36Sopenharmony_ci amd_iommu_flush_page(dev_state->domain, pasid_state->pasid, 37362306a36Sopenharmony_ci start); 37462306a36Sopenharmony_ci else 37562306a36Sopenharmony_ci amd_iommu_flush_tlb(dev_state->domain, pasid_state->pasid); 37662306a36Sopenharmony_ci} 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_cistatic void mn_release(struct mmu_notifier *mn, struct mm_struct *mm) 37962306a36Sopenharmony_ci{ 38062306a36Sopenharmony_ci struct pasid_state *pasid_state; 38162306a36Sopenharmony_ci struct device_state *dev_state; 38262306a36Sopenharmony_ci bool run_inv_ctx_cb; 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci might_sleep(); 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci pasid_state = mn_to_state(mn); 38762306a36Sopenharmony_ci dev_state = pasid_state->device_state; 38862306a36Sopenharmony_ci run_inv_ctx_cb = !pasid_state->invalid; 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci if (run_inv_ctx_cb && dev_state->inv_ctx_cb) 39162306a36Sopenharmony_ci dev_state->inv_ctx_cb(dev_state->pdev, pasid_state->pasid); 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci unbind_pasid(pasid_state); 39462306a36Sopenharmony_ci} 39562306a36Sopenharmony_ci 39662306a36Sopenharmony_cistatic const struct mmu_notifier_ops iommu_mn = { 39762306a36Sopenharmony_ci .release = mn_release, 39862306a36Sopenharmony_ci .arch_invalidate_secondary_tlbs = mn_arch_invalidate_secondary_tlbs, 39962306a36Sopenharmony_ci}; 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_cistatic void set_pri_tag_status(struct pasid_state *pasid_state, 40262306a36Sopenharmony_ci u16 tag, int status) 40362306a36Sopenharmony_ci{ 40462306a36Sopenharmony_ci unsigned long flags; 40562306a36Sopenharmony_ci 40662306a36Sopenharmony_ci spin_lock_irqsave(&pasid_state->lock, flags); 40762306a36Sopenharmony_ci pasid_state->pri[tag].status = status; 40862306a36Sopenharmony_ci spin_unlock_irqrestore(&pasid_state->lock, flags); 40962306a36Sopenharmony_ci} 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_cistatic void finish_pri_tag(struct device_state *dev_state, 41262306a36Sopenharmony_ci struct pasid_state *pasid_state, 41362306a36Sopenharmony_ci u16 tag) 41462306a36Sopenharmony_ci{ 41562306a36Sopenharmony_ci unsigned long flags; 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_ci spin_lock_irqsave(&pasid_state->lock, flags); 41862306a36Sopenharmony_ci if (atomic_dec_and_test(&pasid_state->pri[tag].inflight) && 41962306a36Sopenharmony_ci pasid_state->pri[tag].finish) { 42062306a36Sopenharmony_ci amd_iommu_complete_ppr(dev_state->pdev, pasid_state->pasid, 42162306a36Sopenharmony_ci pasid_state->pri[tag].status, tag); 42262306a36Sopenharmony_ci pasid_state->pri[tag].finish = false; 42362306a36Sopenharmony_ci pasid_state->pri[tag].status = PPR_SUCCESS; 42462306a36Sopenharmony_ci } 42562306a36Sopenharmony_ci spin_unlock_irqrestore(&pasid_state->lock, flags); 42662306a36Sopenharmony_ci} 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_cistatic void handle_fault_error(struct fault *fault) 42962306a36Sopenharmony_ci{ 43062306a36Sopenharmony_ci int status; 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci if (!fault->dev_state->inv_ppr_cb) { 43362306a36Sopenharmony_ci set_pri_tag_status(fault->state, fault->tag, PPR_INVALID); 43462306a36Sopenharmony_ci return; 43562306a36Sopenharmony_ci } 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci status = fault->dev_state->inv_ppr_cb(fault->dev_state->pdev, 43862306a36Sopenharmony_ci fault->pasid, 43962306a36Sopenharmony_ci fault->address, 44062306a36Sopenharmony_ci fault->flags); 44162306a36Sopenharmony_ci switch (status) { 44262306a36Sopenharmony_ci case AMD_IOMMU_INV_PRI_RSP_SUCCESS: 44362306a36Sopenharmony_ci set_pri_tag_status(fault->state, fault->tag, PPR_SUCCESS); 44462306a36Sopenharmony_ci break; 44562306a36Sopenharmony_ci case AMD_IOMMU_INV_PRI_RSP_INVALID: 44662306a36Sopenharmony_ci set_pri_tag_status(fault->state, fault->tag, PPR_INVALID); 44762306a36Sopenharmony_ci break; 44862306a36Sopenharmony_ci case AMD_IOMMU_INV_PRI_RSP_FAIL: 44962306a36Sopenharmony_ci set_pri_tag_status(fault->state, fault->tag, PPR_FAILURE); 45062306a36Sopenharmony_ci break; 45162306a36Sopenharmony_ci default: 45262306a36Sopenharmony_ci BUG(); 45362306a36Sopenharmony_ci } 45462306a36Sopenharmony_ci} 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_cistatic bool access_error(struct vm_area_struct *vma, struct fault *fault) 45762306a36Sopenharmony_ci{ 45862306a36Sopenharmony_ci unsigned long requested = 0; 45962306a36Sopenharmony_ci 46062306a36Sopenharmony_ci if (fault->flags & PPR_FAULT_EXEC) 46162306a36Sopenharmony_ci requested |= VM_EXEC; 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci if (fault->flags & PPR_FAULT_READ) 46462306a36Sopenharmony_ci requested |= VM_READ; 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_ci if (fault->flags & PPR_FAULT_WRITE) 46762306a36Sopenharmony_ci requested |= VM_WRITE; 46862306a36Sopenharmony_ci 46962306a36Sopenharmony_ci return (requested & ~vma->vm_flags) != 0; 47062306a36Sopenharmony_ci} 47162306a36Sopenharmony_ci 47262306a36Sopenharmony_cistatic void do_fault(struct work_struct *work) 47362306a36Sopenharmony_ci{ 47462306a36Sopenharmony_ci struct fault *fault = container_of(work, struct fault, work); 47562306a36Sopenharmony_ci struct vm_area_struct *vma; 47662306a36Sopenharmony_ci vm_fault_t ret = VM_FAULT_ERROR; 47762306a36Sopenharmony_ci unsigned int flags = 0; 47862306a36Sopenharmony_ci struct mm_struct *mm; 47962306a36Sopenharmony_ci u64 address; 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_ci mm = fault->state->mm; 48262306a36Sopenharmony_ci address = fault->address; 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci if (fault->flags & PPR_FAULT_USER) 48562306a36Sopenharmony_ci flags |= FAULT_FLAG_USER; 48662306a36Sopenharmony_ci if (fault->flags & PPR_FAULT_WRITE) 48762306a36Sopenharmony_ci flags |= FAULT_FLAG_WRITE; 48862306a36Sopenharmony_ci flags |= FAULT_FLAG_REMOTE; 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci mmap_read_lock(mm); 49162306a36Sopenharmony_ci vma = vma_lookup(mm, address); 49262306a36Sopenharmony_ci if (!vma) 49362306a36Sopenharmony_ci /* failed to get a vma in the right range */ 49462306a36Sopenharmony_ci goto out; 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci /* Check if we have the right permissions on the vma */ 49762306a36Sopenharmony_ci if (access_error(vma, fault)) 49862306a36Sopenharmony_ci goto out; 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ci ret = handle_mm_fault(vma, address, flags, NULL); 50162306a36Sopenharmony_ciout: 50262306a36Sopenharmony_ci mmap_read_unlock(mm); 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_ci if (ret & VM_FAULT_ERROR) 50562306a36Sopenharmony_ci /* failed to service fault */ 50662306a36Sopenharmony_ci handle_fault_error(fault); 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci finish_pri_tag(fault->dev_state, fault->state, fault->tag); 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci put_pasid_state(fault->state); 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci kfree(fault); 51362306a36Sopenharmony_ci} 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_cistatic int ppr_notifier(struct notifier_block *nb, unsigned long e, void *data) 51662306a36Sopenharmony_ci{ 51762306a36Sopenharmony_ci struct amd_iommu_fault *iommu_fault; 51862306a36Sopenharmony_ci struct pasid_state *pasid_state; 51962306a36Sopenharmony_ci struct device_state *dev_state; 52062306a36Sopenharmony_ci struct pci_dev *pdev = NULL; 52162306a36Sopenharmony_ci unsigned long flags; 52262306a36Sopenharmony_ci struct fault *fault; 52362306a36Sopenharmony_ci bool finish; 52462306a36Sopenharmony_ci u16 tag, devid, seg_id; 52562306a36Sopenharmony_ci int ret; 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci iommu_fault = data; 52862306a36Sopenharmony_ci tag = iommu_fault->tag & 0x1ff; 52962306a36Sopenharmony_ci finish = (iommu_fault->tag >> 9) & 1; 53062306a36Sopenharmony_ci 53162306a36Sopenharmony_ci seg_id = PCI_SBDF_TO_SEGID(iommu_fault->sbdf); 53262306a36Sopenharmony_ci devid = PCI_SBDF_TO_DEVID(iommu_fault->sbdf); 53362306a36Sopenharmony_ci pdev = pci_get_domain_bus_and_slot(seg_id, PCI_BUS_NUM(devid), 53462306a36Sopenharmony_ci devid & 0xff); 53562306a36Sopenharmony_ci if (!pdev) 53662306a36Sopenharmony_ci return -ENODEV; 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci ret = NOTIFY_DONE; 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci /* In kdump kernel pci dev is not initialized yet -> send INVALID */ 54162306a36Sopenharmony_ci if (amd_iommu_is_attach_deferred(&pdev->dev)) { 54262306a36Sopenharmony_ci amd_iommu_complete_ppr(pdev, iommu_fault->pasid, 54362306a36Sopenharmony_ci PPR_INVALID, tag); 54462306a36Sopenharmony_ci goto out; 54562306a36Sopenharmony_ci } 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_ci dev_state = get_device_state(iommu_fault->sbdf); 54862306a36Sopenharmony_ci if (dev_state == NULL) 54962306a36Sopenharmony_ci goto out; 55062306a36Sopenharmony_ci 55162306a36Sopenharmony_ci pasid_state = get_pasid_state(dev_state, iommu_fault->pasid); 55262306a36Sopenharmony_ci if (pasid_state == NULL || pasid_state->invalid) { 55362306a36Sopenharmony_ci /* We know the device but not the PASID -> send INVALID */ 55462306a36Sopenharmony_ci amd_iommu_complete_ppr(dev_state->pdev, iommu_fault->pasid, 55562306a36Sopenharmony_ci PPR_INVALID, tag); 55662306a36Sopenharmony_ci goto out_drop_state; 55762306a36Sopenharmony_ci } 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci spin_lock_irqsave(&pasid_state->lock, flags); 56062306a36Sopenharmony_ci atomic_inc(&pasid_state->pri[tag].inflight); 56162306a36Sopenharmony_ci if (finish) 56262306a36Sopenharmony_ci pasid_state->pri[tag].finish = true; 56362306a36Sopenharmony_ci spin_unlock_irqrestore(&pasid_state->lock, flags); 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_ci fault = kzalloc(sizeof(*fault), GFP_ATOMIC); 56662306a36Sopenharmony_ci if (fault == NULL) { 56762306a36Sopenharmony_ci /* We are OOM - send success and let the device re-fault */ 56862306a36Sopenharmony_ci finish_pri_tag(dev_state, pasid_state, tag); 56962306a36Sopenharmony_ci goto out_drop_state; 57062306a36Sopenharmony_ci } 57162306a36Sopenharmony_ci 57262306a36Sopenharmony_ci fault->dev_state = dev_state; 57362306a36Sopenharmony_ci fault->address = iommu_fault->address; 57462306a36Sopenharmony_ci fault->state = pasid_state; 57562306a36Sopenharmony_ci fault->tag = tag; 57662306a36Sopenharmony_ci fault->finish = finish; 57762306a36Sopenharmony_ci fault->pasid = iommu_fault->pasid; 57862306a36Sopenharmony_ci fault->flags = iommu_fault->flags; 57962306a36Sopenharmony_ci INIT_WORK(&fault->work, do_fault); 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci queue_work(iommu_wq, &fault->work); 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci ret = NOTIFY_OK; 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ciout_drop_state: 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci if (ret != NOTIFY_OK && pasid_state) 58862306a36Sopenharmony_ci put_pasid_state(pasid_state); 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ci put_device_state(dev_state); 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ciout: 59362306a36Sopenharmony_ci pci_dev_put(pdev); 59462306a36Sopenharmony_ci return ret; 59562306a36Sopenharmony_ci} 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_cistatic struct notifier_block ppr_nb = { 59862306a36Sopenharmony_ci .notifier_call = ppr_notifier, 59962306a36Sopenharmony_ci}; 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ciint amd_iommu_bind_pasid(struct pci_dev *pdev, u32 pasid, 60262306a36Sopenharmony_ci struct task_struct *task) 60362306a36Sopenharmony_ci{ 60462306a36Sopenharmony_ci struct pasid_state *pasid_state; 60562306a36Sopenharmony_ci struct device_state *dev_state; 60662306a36Sopenharmony_ci struct mm_struct *mm; 60762306a36Sopenharmony_ci u32 sbdf; 60862306a36Sopenharmony_ci int ret; 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci might_sleep(); 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci if (!amd_iommu_v2_supported()) 61362306a36Sopenharmony_ci return -ENODEV; 61462306a36Sopenharmony_ci 61562306a36Sopenharmony_ci sbdf = get_pci_sbdf_id(pdev); 61662306a36Sopenharmony_ci dev_state = get_device_state(sbdf); 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_ci if (dev_state == NULL) 61962306a36Sopenharmony_ci return -EINVAL; 62062306a36Sopenharmony_ci 62162306a36Sopenharmony_ci ret = -EINVAL; 62262306a36Sopenharmony_ci if (pasid >= dev_state->max_pasids) 62362306a36Sopenharmony_ci goto out; 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci ret = -ENOMEM; 62662306a36Sopenharmony_ci pasid_state = kzalloc(sizeof(*pasid_state), GFP_KERNEL); 62762306a36Sopenharmony_ci if (pasid_state == NULL) 62862306a36Sopenharmony_ci goto out; 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_ci refcount_set(&pasid_state->count, 1); 63262306a36Sopenharmony_ci init_waitqueue_head(&pasid_state->wq); 63362306a36Sopenharmony_ci spin_lock_init(&pasid_state->lock); 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ci mm = get_task_mm(task); 63662306a36Sopenharmony_ci pasid_state->mm = mm; 63762306a36Sopenharmony_ci pasid_state->device_state = dev_state; 63862306a36Sopenharmony_ci pasid_state->pasid = pasid; 63962306a36Sopenharmony_ci pasid_state->invalid = true; /* Mark as valid only if we are 64062306a36Sopenharmony_ci done with setting up the pasid */ 64162306a36Sopenharmony_ci pasid_state->mn.ops = &iommu_mn; 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_ci if (pasid_state->mm == NULL) 64462306a36Sopenharmony_ci goto out_free; 64562306a36Sopenharmony_ci 64662306a36Sopenharmony_ci ret = mmu_notifier_register(&pasid_state->mn, mm); 64762306a36Sopenharmony_ci if (ret) 64862306a36Sopenharmony_ci goto out_free; 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci ret = set_pasid_state(dev_state, pasid_state, pasid); 65162306a36Sopenharmony_ci if (ret) 65262306a36Sopenharmony_ci goto out_unregister; 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci ret = amd_iommu_domain_set_gcr3(dev_state->domain, pasid, 65562306a36Sopenharmony_ci __pa(pasid_state->mm->pgd)); 65662306a36Sopenharmony_ci if (ret) 65762306a36Sopenharmony_ci goto out_clear_state; 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci /* Now we are ready to handle faults */ 66062306a36Sopenharmony_ci pasid_state->invalid = false; 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_ci /* 66362306a36Sopenharmony_ci * Drop the reference to the mm_struct here. We rely on the 66462306a36Sopenharmony_ci * mmu_notifier release call-back to inform us when the mm 66562306a36Sopenharmony_ci * is going away. 66662306a36Sopenharmony_ci */ 66762306a36Sopenharmony_ci mmput(mm); 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_ci return 0; 67062306a36Sopenharmony_ci 67162306a36Sopenharmony_ciout_clear_state: 67262306a36Sopenharmony_ci clear_pasid_state(dev_state, pasid); 67362306a36Sopenharmony_ci 67462306a36Sopenharmony_ciout_unregister: 67562306a36Sopenharmony_ci mmu_notifier_unregister(&pasid_state->mn, mm); 67662306a36Sopenharmony_ci mmput(mm); 67762306a36Sopenharmony_ci 67862306a36Sopenharmony_ciout_free: 67962306a36Sopenharmony_ci free_pasid_state(pasid_state); 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_ciout: 68262306a36Sopenharmony_ci put_device_state(dev_state); 68362306a36Sopenharmony_ci 68462306a36Sopenharmony_ci return ret; 68562306a36Sopenharmony_ci} 68662306a36Sopenharmony_ciEXPORT_SYMBOL(amd_iommu_bind_pasid); 68762306a36Sopenharmony_ci 68862306a36Sopenharmony_civoid amd_iommu_unbind_pasid(struct pci_dev *pdev, u32 pasid) 68962306a36Sopenharmony_ci{ 69062306a36Sopenharmony_ci struct pasid_state *pasid_state; 69162306a36Sopenharmony_ci struct device_state *dev_state; 69262306a36Sopenharmony_ci u32 sbdf; 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci might_sleep(); 69562306a36Sopenharmony_ci 69662306a36Sopenharmony_ci if (!amd_iommu_v2_supported()) 69762306a36Sopenharmony_ci return; 69862306a36Sopenharmony_ci 69962306a36Sopenharmony_ci sbdf = get_pci_sbdf_id(pdev); 70062306a36Sopenharmony_ci dev_state = get_device_state(sbdf); 70162306a36Sopenharmony_ci if (dev_state == NULL) 70262306a36Sopenharmony_ci return; 70362306a36Sopenharmony_ci 70462306a36Sopenharmony_ci if (pasid >= dev_state->max_pasids) 70562306a36Sopenharmony_ci goto out; 70662306a36Sopenharmony_ci 70762306a36Sopenharmony_ci pasid_state = get_pasid_state(dev_state, pasid); 70862306a36Sopenharmony_ci if (pasid_state == NULL) 70962306a36Sopenharmony_ci goto out; 71062306a36Sopenharmony_ci /* 71162306a36Sopenharmony_ci * Drop reference taken here. We are safe because we still hold 71262306a36Sopenharmony_ci * the reference taken in the amd_iommu_bind_pasid function. 71362306a36Sopenharmony_ci */ 71462306a36Sopenharmony_ci put_pasid_state(pasid_state); 71562306a36Sopenharmony_ci 71662306a36Sopenharmony_ci /* Clear the pasid state so that the pasid can be re-used */ 71762306a36Sopenharmony_ci clear_pasid_state(dev_state, pasid_state->pasid); 71862306a36Sopenharmony_ci 71962306a36Sopenharmony_ci /* 72062306a36Sopenharmony_ci * Call mmu_notifier_unregister to drop our reference 72162306a36Sopenharmony_ci * to pasid_state->mm 72262306a36Sopenharmony_ci */ 72362306a36Sopenharmony_ci mmu_notifier_unregister(&pasid_state->mn, pasid_state->mm); 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_ci put_pasid_state_wait(pasid_state); /* Reference taken in 72662306a36Sopenharmony_ci amd_iommu_bind_pasid */ 72762306a36Sopenharmony_ciout: 72862306a36Sopenharmony_ci /* Drop reference taken in this function */ 72962306a36Sopenharmony_ci put_device_state(dev_state); 73062306a36Sopenharmony_ci 73162306a36Sopenharmony_ci /* Drop reference taken in amd_iommu_bind_pasid */ 73262306a36Sopenharmony_ci put_device_state(dev_state); 73362306a36Sopenharmony_ci} 73462306a36Sopenharmony_ciEXPORT_SYMBOL(amd_iommu_unbind_pasid); 73562306a36Sopenharmony_ci 73662306a36Sopenharmony_ciint amd_iommu_init_device(struct pci_dev *pdev, int pasids) 73762306a36Sopenharmony_ci{ 73862306a36Sopenharmony_ci struct device_state *dev_state; 73962306a36Sopenharmony_ci struct iommu_group *group; 74062306a36Sopenharmony_ci unsigned long flags; 74162306a36Sopenharmony_ci int ret, tmp; 74262306a36Sopenharmony_ci u32 sbdf; 74362306a36Sopenharmony_ci 74462306a36Sopenharmony_ci might_sleep(); 74562306a36Sopenharmony_ci 74662306a36Sopenharmony_ci /* 74762306a36Sopenharmony_ci * When memory encryption is active the device is likely not in a 74862306a36Sopenharmony_ci * direct-mapped domain. Forbid using IOMMUv2 functionality for now. 74962306a36Sopenharmony_ci */ 75062306a36Sopenharmony_ci if (cc_platform_has(CC_ATTR_MEM_ENCRYPT)) 75162306a36Sopenharmony_ci return -ENODEV; 75262306a36Sopenharmony_ci 75362306a36Sopenharmony_ci if (!amd_iommu_v2_supported()) 75462306a36Sopenharmony_ci return -ENODEV; 75562306a36Sopenharmony_ci 75662306a36Sopenharmony_ci if (pasids <= 0 || pasids > (PASID_MASK + 1)) 75762306a36Sopenharmony_ci return -EINVAL; 75862306a36Sopenharmony_ci 75962306a36Sopenharmony_ci sbdf = get_pci_sbdf_id(pdev); 76062306a36Sopenharmony_ci 76162306a36Sopenharmony_ci dev_state = kzalloc(sizeof(*dev_state), GFP_KERNEL); 76262306a36Sopenharmony_ci if (dev_state == NULL) 76362306a36Sopenharmony_ci return -ENOMEM; 76462306a36Sopenharmony_ci 76562306a36Sopenharmony_ci spin_lock_init(&dev_state->lock); 76662306a36Sopenharmony_ci init_waitqueue_head(&dev_state->wq); 76762306a36Sopenharmony_ci dev_state->pdev = pdev; 76862306a36Sopenharmony_ci dev_state->sbdf = sbdf; 76962306a36Sopenharmony_ci 77062306a36Sopenharmony_ci tmp = pasids; 77162306a36Sopenharmony_ci for (dev_state->pasid_levels = 0; (tmp - 1) & ~0x1ff; tmp >>= 9) 77262306a36Sopenharmony_ci dev_state->pasid_levels += 1; 77362306a36Sopenharmony_ci 77462306a36Sopenharmony_ci atomic_set(&dev_state->count, 1); 77562306a36Sopenharmony_ci dev_state->max_pasids = pasids; 77662306a36Sopenharmony_ci 77762306a36Sopenharmony_ci ret = -ENOMEM; 77862306a36Sopenharmony_ci dev_state->states = (void *)get_zeroed_page(GFP_KERNEL); 77962306a36Sopenharmony_ci if (dev_state->states == NULL) 78062306a36Sopenharmony_ci goto out_free_dev_state; 78162306a36Sopenharmony_ci 78262306a36Sopenharmony_ci dev_state->domain = iommu_domain_alloc(&pci_bus_type); 78362306a36Sopenharmony_ci if (dev_state->domain == NULL) 78462306a36Sopenharmony_ci goto out_free_states; 78562306a36Sopenharmony_ci 78662306a36Sopenharmony_ci /* See iommu_is_default_domain() */ 78762306a36Sopenharmony_ci dev_state->domain->type = IOMMU_DOMAIN_IDENTITY; 78862306a36Sopenharmony_ci amd_iommu_domain_direct_map(dev_state->domain); 78962306a36Sopenharmony_ci 79062306a36Sopenharmony_ci ret = amd_iommu_domain_enable_v2(dev_state->domain, pasids); 79162306a36Sopenharmony_ci if (ret) 79262306a36Sopenharmony_ci goto out_free_domain; 79362306a36Sopenharmony_ci 79462306a36Sopenharmony_ci group = iommu_group_get(&pdev->dev); 79562306a36Sopenharmony_ci if (!group) { 79662306a36Sopenharmony_ci ret = -EINVAL; 79762306a36Sopenharmony_ci goto out_free_domain; 79862306a36Sopenharmony_ci } 79962306a36Sopenharmony_ci 80062306a36Sopenharmony_ci ret = iommu_attach_group(dev_state->domain, group); 80162306a36Sopenharmony_ci if (ret != 0) 80262306a36Sopenharmony_ci goto out_drop_group; 80362306a36Sopenharmony_ci 80462306a36Sopenharmony_ci iommu_group_put(group); 80562306a36Sopenharmony_ci 80662306a36Sopenharmony_ci spin_lock_irqsave(&state_lock, flags); 80762306a36Sopenharmony_ci 80862306a36Sopenharmony_ci if (__get_device_state(sbdf) != NULL) { 80962306a36Sopenharmony_ci spin_unlock_irqrestore(&state_lock, flags); 81062306a36Sopenharmony_ci ret = -EBUSY; 81162306a36Sopenharmony_ci goto out_free_domain; 81262306a36Sopenharmony_ci } 81362306a36Sopenharmony_ci 81462306a36Sopenharmony_ci list_add_tail(&dev_state->list, &state_list); 81562306a36Sopenharmony_ci 81662306a36Sopenharmony_ci spin_unlock_irqrestore(&state_lock, flags); 81762306a36Sopenharmony_ci 81862306a36Sopenharmony_ci return 0; 81962306a36Sopenharmony_ci 82062306a36Sopenharmony_ciout_drop_group: 82162306a36Sopenharmony_ci iommu_group_put(group); 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_ciout_free_domain: 82462306a36Sopenharmony_ci iommu_domain_free(dev_state->domain); 82562306a36Sopenharmony_ci 82662306a36Sopenharmony_ciout_free_states: 82762306a36Sopenharmony_ci free_page((unsigned long)dev_state->states); 82862306a36Sopenharmony_ci 82962306a36Sopenharmony_ciout_free_dev_state: 83062306a36Sopenharmony_ci kfree(dev_state); 83162306a36Sopenharmony_ci 83262306a36Sopenharmony_ci return ret; 83362306a36Sopenharmony_ci} 83462306a36Sopenharmony_ciEXPORT_SYMBOL(amd_iommu_init_device); 83562306a36Sopenharmony_ci 83662306a36Sopenharmony_civoid amd_iommu_free_device(struct pci_dev *pdev) 83762306a36Sopenharmony_ci{ 83862306a36Sopenharmony_ci struct device_state *dev_state; 83962306a36Sopenharmony_ci unsigned long flags; 84062306a36Sopenharmony_ci u32 sbdf; 84162306a36Sopenharmony_ci 84262306a36Sopenharmony_ci if (!amd_iommu_v2_supported()) 84362306a36Sopenharmony_ci return; 84462306a36Sopenharmony_ci 84562306a36Sopenharmony_ci sbdf = get_pci_sbdf_id(pdev); 84662306a36Sopenharmony_ci 84762306a36Sopenharmony_ci spin_lock_irqsave(&state_lock, flags); 84862306a36Sopenharmony_ci 84962306a36Sopenharmony_ci dev_state = __get_device_state(sbdf); 85062306a36Sopenharmony_ci if (dev_state == NULL) { 85162306a36Sopenharmony_ci spin_unlock_irqrestore(&state_lock, flags); 85262306a36Sopenharmony_ci return; 85362306a36Sopenharmony_ci } 85462306a36Sopenharmony_ci 85562306a36Sopenharmony_ci list_del(&dev_state->list); 85662306a36Sopenharmony_ci 85762306a36Sopenharmony_ci spin_unlock_irqrestore(&state_lock, flags); 85862306a36Sopenharmony_ci 85962306a36Sopenharmony_ci put_device_state(dev_state); 86062306a36Sopenharmony_ci free_device_state(dev_state); 86162306a36Sopenharmony_ci} 86262306a36Sopenharmony_ciEXPORT_SYMBOL(amd_iommu_free_device); 86362306a36Sopenharmony_ci 86462306a36Sopenharmony_ciint amd_iommu_set_invalid_ppr_cb(struct pci_dev *pdev, 86562306a36Sopenharmony_ci amd_iommu_invalid_ppr_cb cb) 86662306a36Sopenharmony_ci{ 86762306a36Sopenharmony_ci struct device_state *dev_state; 86862306a36Sopenharmony_ci unsigned long flags; 86962306a36Sopenharmony_ci u32 sbdf; 87062306a36Sopenharmony_ci int ret; 87162306a36Sopenharmony_ci 87262306a36Sopenharmony_ci if (!amd_iommu_v2_supported()) 87362306a36Sopenharmony_ci return -ENODEV; 87462306a36Sopenharmony_ci 87562306a36Sopenharmony_ci sbdf = get_pci_sbdf_id(pdev); 87662306a36Sopenharmony_ci 87762306a36Sopenharmony_ci spin_lock_irqsave(&state_lock, flags); 87862306a36Sopenharmony_ci 87962306a36Sopenharmony_ci ret = -EINVAL; 88062306a36Sopenharmony_ci dev_state = __get_device_state(sbdf); 88162306a36Sopenharmony_ci if (dev_state == NULL) 88262306a36Sopenharmony_ci goto out_unlock; 88362306a36Sopenharmony_ci 88462306a36Sopenharmony_ci dev_state->inv_ppr_cb = cb; 88562306a36Sopenharmony_ci 88662306a36Sopenharmony_ci ret = 0; 88762306a36Sopenharmony_ci 88862306a36Sopenharmony_ciout_unlock: 88962306a36Sopenharmony_ci spin_unlock_irqrestore(&state_lock, flags); 89062306a36Sopenharmony_ci 89162306a36Sopenharmony_ci return ret; 89262306a36Sopenharmony_ci} 89362306a36Sopenharmony_ciEXPORT_SYMBOL(amd_iommu_set_invalid_ppr_cb); 89462306a36Sopenharmony_ci 89562306a36Sopenharmony_ciint amd_iommu_set_invalidate_ctx_cb(struct pci_dev *pdev, 89662306a36Sopenharmony_ci amd_iommu_invalidate_ctx cb) 89762306a36Sopenharmony_ci{ 89862306a36Sopenharmony_ci struct device_state *dev_state; 89962306a36Sopenharmony_ci unsigned long flags; 90062306a36Sopenharmony_ci u32 sbdf; 90162306a36Sopenharmony_ci int ret; 90262306a36Sopenharmony_ci 90362306a36Sopenharmony_ci if (!amd_iommu_v2_supported()) 90462306a36Sopenharmony_ci return -ENODEV; 90562306a36Sopenharmony_ci 90662306a36Sopenharmony_ci sbdf = get_pci_sbdf_id(pdev); 90762306a36Sopenharmony_ci 90862306a36Sopenharmony_ci spin_lock_irqsave(&state_lock, flags); 90962306a36Sopenharmony_ci 91062306a36Sopenharmony_ci ret = -EINVAL; 91162306a36Sopenharmony_ci dev_state = __get_device_state(sbdf); 91262306a36Sopenharmony_ci if (dev_state == NULL) 91362306a36Sopenharmony_ci goto out_unlock; 91462306a36Sopenharmony_ci 91562306a36Sopenharmony_ci dev_state->inv_ctx_cb = cb; 91662306a36Sopenharmony_ci 91762306a36Sopenharmony_ci ret = 0; 91862306a36Sopenharmony_ci 91962306a36Sopenharmony_ciout_unlock: 92062306a36Sopenharmony_ci spin_unlock_irqrestore(&state_lock, flags); 92162306a36Sopenharmony_ci 92262306a36Sopenharmony_ci return ret; 92362306a36Sopenharmony_ci} 92462306a36Sopenharmony_ciEXPORT_SYMBOL(amd_iommu_set_invalidate_ctx_cb); 92562306a36Sopenharmony_ci 92662306a36Sopenharmony_cistatic int __init amd_iommu_v2_init(void) 92762306a36Sopenharmony_ci{ 92862306a36Sopenharmony_ci int ret; 92962306a36Sopenharmony_ci 93062306a36Sopenharmony_ci if (!amd_iommu_v2_supported()) { 93162306a36Sopenharmony_ci pr_info("AMD IOMMUv2 functionality not available on this system - This is not a bug.\n"); 93262306a36Sopenharmony_ci /* 93362306a36Sopenharmony_ci * Load anyway to provide the symbols to other modules 93462306a36Sopenharmony_ci * which may use AMD IOMMUv2 optionally. 93562306a36Sopenharmony_ci */ 93662306a36Sopenharmony_ci return 0; 93762306a36Sopenharmony_ci } 93862306a36Sopenharmony_ci 93962306a36Sopenharmony_ci ret = -ENOMEM; 94062306a36Sopenharmony_ci iommu_wq = alloc_workqueue("amd_iommu_v2", WQ_MEM_RECLAIM, 0); 94162306a36Sopenharmony_ci if (iommu_wq == NULL) 94262306a36Sopenharmony_ci goto out; 94362306a36Sopenharmony_ci 94462306a36Sopenharmony_ci amd_iommu_register_ppr_notifier(&ppr_nb); 94562306a36Sopenharmony_ci 94662306a36Sopenharmony_ci pr_info("AMD IOMMUv2 loaded and initialized\n"); 94762306a36Sopenharmony_ci 94862306a36Sopenharmony_ci return 0; 94962306a36Sopenharmony_ci 95062306a36Sopenharmony_ciout: 95162306a36Sopenharmony_ci return ret; 95262306a36Sopenharmony_ci} 95362306a36Sopenharmony_ci 95462306a36Sopenharmony_cistatic void __exit amd_iommu_v2_exit(void) 95562306a36Sopenharmony_ci{ 95662306a36Sopenharmony_ci struct device_state *dev_state, *next; 95762306a36Sopenharmony_ci unsigned long flags; 95862306a36Sopenharmony_ci LIST_HEAD(freelist); 95962306a36Sopenharmony_ci 96062306a36Sopenharmony_ci if (!amd_iommu_v2_supported()) 96162306a36Sopenharmony_ci return; 96262306a36Sopenharmony_ci 96362306a36Sopenharmony_ci amd_iommu_unregister_ppr_notifier(&ppr_nb); 96462306a36Sopenharmony_ci 96562306a36Sopenharmony_ci flush_workqueue(iommu_wq); 96662306a36Sopenharmony_ci 96762306a36Sopenharmony_ci /* 96862306a36Sopenharmony_ci * The loop below might call flush_workqueue(), so call 96962306a36Sopenharmony_ci * destroy_workqueue() after it 97062306a36Sopenharmony_ci */ 97162306a36Sopenharmony_ci spin_lock_irqsave(&state_lock, flags); 97262306a36Sopenharmony_ci 97362306a36Sopenharmony_ci list_for_each_entry_safe(dev_state, next, &state_list, list) { 97462306a36Sopenharmony_ci WARN_ON_ONCE(1); 97562306a36Sopenharmony_ci 97662306a36Sopenharmony_ci put_device_state(dev_state); 97762306a36Sopenharmony_ci list_del(&dev_state->list); 97862306a36Sopenharmony_ci list_add_tail(&dev_state->list, &freelist); 97962306a36Sopenharmony_ci } 98062306a36Sopenharmony_ci 98162306a36Sopenharmony_ci spin_unlock_irqrestore(&state_lock, flags); 98262306a36Sopenharmony_ci 98362306a36Sopenharmony_ci /* 98462306a36Sopenharmony_ci * Since free_device_state waits on the count to be zero, 98562306a36Sopenharmony_ci * we need to free dev_state outside the spinlock. 98662306a36Sopenharmony_ci */ 98762306a36Sopenharmony_ci list_for_each_entry_safe(dev_state, next, &freelist, list) { 98862306a36Sopenharmony_ci list_del(&dev_state->list); 98962306a36Sopenharmony_ci free_device_state(dev_state); 99062306a36Sopenharmony_ci } 99162306a36Sopenharmony_ci 99262306a36Sopenharmony_ci destroy_workqueue(iommu_wq); 99362306a36Sopenharmony_ci} 99462306a36Sopenharmony_ci 99562306a36Sopenharmony_cimodule_init(amd_iommu_v2_init); 99662306a36Sopenharmony_cimodule_exit(amd_iommu_v2_exit); 997