mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
Merge branches 'fixes', 'arm/smmu/updates', 'intel/vt-d', 'amd/amd-vi' and 'core' into next
This commit is contained in:
@@ -2675,6 +2675,15 @@ Kernel parameters
|
||||
1 - Bypass the IOMMU for DMA.
|
||||
unset - Use value of CONFIG_IOMMU_DEFAULT_PASSTHROUGH.
|
||||
|
||||
iommu.debug_pagealloc=
|
||||
[KNL,EARLY] When CONFIG_IOMMU_DEBUG_PAGEALLOC is set, this
|
||||
parameter enables the feature at boot time. By default, it
|
||||
is disabled and the system behaves the same way as a kernel
|
||||
built without CONFIG_IOMMU_DEBUG_PAGEALLOC.
|
||||
Format: { "0" | "1" }
|
||||
0 - Sanitizer disabled.
|
||||
1 - Sanitizer enabled, expect runtime overhead.
|
||||
|
||||
io7= [HW] IO7 for Marvel-based Alpha systems
|
||||
See comment before marvel_specify_io7 in
|
||||
arch/alpha/kernel/core_marvel.c.
|
||||
|
||||
@@ -13251,6 +13251,7 @@ F: drivers/iommu/
|
||||
F: include/linux/iommu.h
|
||||
F: include/linux/iova.h
|
||||
F: include/linux/of_iommu.h
|
||||
F: rust/kernel/iommu/
|
||||
|
||||
IOMMUFD
|
||||
M: Jason Gunthorpe <jgg@nvidia.com>
|
||||
|
||||
@@ -384,6 +384,25 @@ config SPRD_IOMMU
|
||||
|
||||
Say Y here if you want to use the multimedia devices listed above.
|
||||
|
||||
config IOMMU_DEBUG_PAGEALLOC
|
||||
bool "Debug IOMMU mappings against page allocations"
|
||||
depends on DEBUG_PAGEALLOC && IOMMU_API && PAGE_EXTENSION
|
||||
help
|
||||
This enables a consistency check between the kernel page allocator and
|
||||
the IOMMU subsystem. It verifies that pages being allocated or freed
|
||||
are not currently mapped in any IOMMU domain.
|
||||
|
||||
This helps detect DMA use-after-free bugs where a driver frees a page
|
||||
but forgets to unmap it from the IOMMU, potentially allowing a device
|
||||
to overwrite memory that the kernel has repurposed.
|
||||
|
||||
These checks are best-effort and may not detect all problems.
|
||||
|
||||
Due to performance overhead, this feature is disabled by default.
|
||||
You must enable "iommu.debug_pagealloc" from the kernel command
|
||||
line to activate the runtime checks.
|
||||
|
||||
If unsure, say N.
|
||||
endif # IOMMU_SUPPORT
|
||||
|
||||
source "drivers/iommu/generic_pt/Kconfig"
|
||||
|
||||
@@ -36,3 +36,4 @@ obj-$(CONFIG_IOMMU_SVA) += iommu-sva.o
|
||||
obj-$(CONFIG_IOMMU_IOPF) += io-pgfault.o
|
||||
obj-$(CONFIG_SPRD_IOMMU) += sprd-iommu.o
|
||||
obj-$(CONFIG_APPLE_DART) += apple-dart.o
|
||||
obj-$(CONFIG_IOMMU_DEBUG_PAGEALLOC) += iommu-debug-pagealloc.o
|
||||
|
||||
@@ -30,6 +30,16 @@ config AMD_IOMMU
|
||||
your BIOS for an option to enable it or if you have an IVRS ACPI
|
||||
table.
|
||||
|
||||
config AMD_IOMMU_IOMMUFD
|
||||
bool "Enable IOMMUFD features for AMD IOMMU (EXPERIMENTAL)"
|
||||
depends on IOMMUFD
|
||||
depends on AMD_IOMMU
|
||||
help
|
||||
Support for IOMMUFD features intended to support virtual machines
|
||||
with accelerated virtual IOMMUs.
|
||||
|
||||
Say Y here if you are doing development and testing on this feature.
|
||||
|
||||
config AMD_IOMMU_DEBUGFS
|
||||
bool "Enable AMD IOMMU internals in DebugFS"
|
||||
depends on AMD_IOMMU && IOMMU_DEBUGFS
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-y += iommu.o init.o quirks.o ppr.o pasid.o
|
||||
obj-$(CONFIG_AMD_IOMMU_IOMMUFD) += iommufd.o nested.o
|
||||
obj-$(CONFIG_AMD_IOMMU_DEBUGFS) += debugfs.o
|
||||
|
||||
@@ -190,4 +190,37 @@ void amd_iommu_domain_set_pgtable(struct protection_domain *domain,
|
||||
struct dev_table_entry *get_dev_table(struct amd_iommu *iommu);
|
||||
struct iommu_dev_data *search_dev_data(struct amd_iommu *iommu, u16 devid);
|
||||
|
||||
void amd_iommu_set_dte_v1(struct iommu_dev_data *dev_data,
|
||||
struct protection_domain *domain, u16 domid,
|
||||
struct pt_iommu_amdv1_hw_info *pt_info,
|
||||
struct dev_table_entry *new);
|
||||
void amd_iommu_update_dte(struct amd_iommu *iommu,
|
||||
struct iommu_dev_data *dev_data,
|
||||
struct dev_table_entry *new);
|
||||
|
||||
static inline void
|
||||
amd_iommu_make_clear_dte(struct iommu_dev_data *dev_data, struct dev_table_entry *new)
|
||||
{
|
||||
struct dev_table_entry *initial_dte;
|
||||
struct amd_iommu *iommu = get_amd_iommu_from_dev(dev_data->dev);
|
||||
|
||||
/* All existing DTE must have V bit set */
|
||||
new->data128[0] = DTE_FLAG_V;
|
||||
new->data128[1] = 0;
|
||||
|
||||
/*
|
||||
* Restore cached persistent DTE bits, which can be set by information
|
||||
* in IVRS table. See set_dev_entry_from_acpi().
|
||||
*/
|
||||
initial_dte = amd_iommu_get_ivhd_dte_flags(iommu->pci_seg->id, dev_data->devid);
|
||||
if (initial_dte) {
|
||||
new->data128[0] |= initial_dte->data128[0];
|
||||
new->data128[1] |= initial_dte->data128[1];
|
||||
}
|
||||
}
|
||||
|
||||
/* NESTED */
|
||||
struct iommu_domain *
|
||||
amd_iommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags,
|
||||
const struct iommu_user_data *user_data);
|
||||
#endif /* AMD_IOMMU_H */
|
||||
|
||||
@@ -17,9 +17,12 @@
|
||||
#include <linux/list.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/iommufd.h>
|
||||
#include <linux/irqreturn.h>
|
||||
#include <linux/generic_pt/iommu.h>
|
||||
|
||||
#include <uapi/linux/iommufd.h>
|
||||
|
||||
/*
|
||||
* Maximum number of IOMMUs supported
|
||||
*/
|
||||
@@ -108,6 +111,7 @@
|
||||
|
||||
/* Extended Feature 2 Bits */
|
||||
#define FEATURE_SEVSNPIO_SUP BIT_ULL(1)
|
||||
#define FEATURE_GCR3TRPMODE BIT_ULL(3)
|
||||
#define FEATURE_SNPAVICSUP GENMASK_ULL(7, 5)
|
||||
#define FEATURE_SNPAVICSUP_GAM(x) \
|
||||
(FIELD_GET(FEATURE_SNPAVICSUP, x) == 0x1)
|
||||
@@ -186,6 +190,7 @@
|
||||
#define CONTROL_EPH_EN 45
|
||||
#define CONTROL_XT_EN 50
|
||||
#define CONTROL_INTCAPXT_EN 51
|
||||
#define CONTROL_GCR3TRPMODE 58
|
||||
#define CONTROL_IRTCACHEDIS 59
|
||||
#define CONTROL_SNPAVIC_EN 61
|
||||
|
||||
@@ -350,6 +355,9 @@
|
||||
#define DTE_FLAG_V BIT_ULL(0)
|
||||
#define DTE_FLAG_TV BIT_ULL(1)
|
||||
#define DTE_FLAG_HAD (3ULL << 7)
|
||||
#define DTE_MODE_MASK GENMASK_ULL(11, 9)
|
||||
#define DTE_HOST_TRP GENMASK_ULL(51, 12)
|
||||
#define DTE_FLAG_PPR BIT_ULL(52)
|
||||
#define DTE_FLAG_GIOV BIT_ULL(54)
|
||||
#define DTE_FLAG_GV BIT_ULL(55)
|
||||
#define DTE_GLX GENMASK_ULL(57, 56)
|
||||
@@ -358,7 +366,7 @@
|
||||
|
||||
#define DTE_FLAG_IOTLB BIT_ULL(32)
|
||||
#define DTE_FLAG_MASK (0x3ffULL << 32)
|
||||
#define DEV_DOMID_MASK 0xffffULL
|
||||
#define DTE_DOMID_MASK GENMASK_ULL(15, 0)
|
||||
|
||||
#define DTE_GCR3_14_12 GENMASK_ULL(60, 58)
|
||||
#define DTE_GCR3_30_15 GENMASK_ULL(31, 16)
|
||||
@@ -493,6 +501,38 @@ struct pdom_iommu_info {
|
||||
u32 refcnt; /* Count of attached dev/pasid per domain/IOMMU */
|
||||
};
|
||||
|
||||
struct amd_iommu_viommu {
|
||||
struct iommufd_viommu core;
|
||||
struct protection_domain *parent; /* nest parent domain for this viommu */
|
||||
struct list_head pdom_list; /* For protection_domain->viommu_list */
|
||||
|
||||
/*
|
||||
* Per-vIOMMU guest domain ID to host domain ID mapping.
|
||||
* Indexed by guest domain ID.
|
||||
*/
|
||||
struct xarray gdomid_array;
|
||||
};
|
||||
|
||||
/*
|
||||
* Contains guest domain ID mapping info,
|
||||
* which is stored in the struct xarray gdomid_array.
|
||||
*/
|
||||
struct guest_domain_mapping_info {
|
||||
refcount_t users;
|
||||
u32 hdom_id; /* Host domain ID */
|
||||
};
|
||||
|
||||
/*
|
||||
* Nested domain is specifically used for nested translation
|
||||
*/
|
||||
struct nested_domain {
|
||||
struct iommu_domain domain; /* generic domain handle used by iommu core code */
|
||||
u16 gdom_id; /* domain ID from gDTE */
|
||||
struct guest_domain_mapping_info *gdom_info;
|
||||
struct iommu_hwpt_amd_guest gdte; /* Guest vIOMMU DTE */
|
||||
struct amd_iommu_viommu *viommu; /* AMD hw-viommu this nested domain belong to */
|
||||
};
|
||||
|
||||
/*
|
||||
* This structure contains generic data for IOMMU protection domains
|
||||
* independent of their use.
|
||||
@@ -513,6 +553,12 @@ struct protection_domain {
|
||||
|
||||
struct mmu_notifier mn; /* mmu notifier for the SVA domain */
|
||||
struct list_head dev_data_list; /* List of pdom_dev_data */
|
||||
|
||||
/*
|
||||
* Store reference to list of vIOMMUs, which use this protection domain.
|
||||
* This will be used to look up host domain ID when flushing this domain.
|
||||
*/
|
||||
struct list_head viommu_list;
|
||||
};
|
||||
PT_IOMMU_CHECK_DOMAIN(struct protection_domain, iommu, domain);
|
||||
PT_IOMMU_CHECK_DOMAIN(struct protection_domain, amdv1.iommu, domain);
|
||||
@@ -706,7 +752,7 @@ struct amd_iommu {
|
||||
|
||||
u32 flags;
|
||||
volatile u64 *cmd_sem;
|
||||
atomic64_t cmd_sem_val;
|
||||
u64 cmd_sem_val;
|
||||
/*
|
||||
* Track physical address to directly use it in build_completion_wait()
|
||||
* and avoid adding any special checks and handling for kdump.
|
||||
|
||||
@@ -1122,6 +1122,14 @@ static void iommu_enable_gt(struct amd_iommu *iommu)
|
||||
return;
|
||||
|
||||
iommu_feature_enable(iommu, CONTROL_GT_EN);
|
||||
|
||||
/*
|
||||
* This feature needs to be enabled prior to a call
|
||||
* to iommu_snp_enable(). Since this function is called
|
||||
* in early_enable_iommu(), it is safe to enable here.
|
||||
*/
|
||||
if (check_feature2(FEATURE_GCR3TRPMODE))
|
||||
iommu_feature_enable(iommu, CONTROL_GCR3TRPMODE);
|
||||
}
|
||||
|
||||
/* sets a specific bit in the device table entry. */
|
||||
@@ -1179,7 +1187,7 @@ static bool __reuse_device_table(struct amd_iommu *iommu)
|
||||
for (devid = 0; devid <= pci_seg->last_bdf; devid++) {
|
||||
old_dev_tbl_entry = &pci_seg->old_dev_tbl_cpy[devid];
|
||||
dte_v = FIELD_GET(DTE_FLAG_V, old_dev_tbl_entry->data[0]);
|
||||
dom_id = FIELD_GET(DEV_DOMID_MASK, old_dev_tbl_entry->data[1]);
|
||||
dom_id = FIELD_GET(DTE_DOMID_MASK, old_dev_tbl_entry->data[1]);
|
||||
|
||||
if (!dte_v || !dom_id)
|
||||
continue;
|
||||
@@ -1877,7 +1885,7 @@ static int __init init_iommu_one(struct amd_iommu *iommu, struct ivhd_header *h,
|
||||
iommu->pci_seg = pci_seg;
|
||||
|
||||
raw_spin_lock_init(&iommu->lock);
|
||||
atomic64_set(&iommu->cmd_sem_val, 0);
|
||||
iommu->cmd_sem_val = 0;
|
||||
|
||||
/* Add IOMMU to internal data structures */
|
||||
list_add_tail(&iommu->list, &amd_iommu_list);
|
||||
|
||||
+192
-119
@@ -43,6 +43,7 @@
|
||||
#include <linux/generic_pt/iommu.h>
|
||||
|
||||
#include "amd_iommu.h"
|
||||
#include "iommufd.h"
|
||||
#include "../irq_remapping.h"
|
||||
#include "../iommu-pages.h"
|
||||
|
||||
@@ -75,6 +76,8 @@ static void set_dte_entry(struct amd_iommu *iommu,
|
||||
struct iommu_dev_data *dev_data,
|
||||
phys_addr_t top_paddr, unsigned int top_level);
|
||||
|
||||
static int device_flush_dte(struct iommu_dev_data *dev_data);
|
||||
|
||||
static void amd_iommu_change_top(struct pt_iommu *iommu_table,
|
||||
phys_addr_t top_paddr, unsigned int top_level);
|
||||
|
||||
@@ -85,6 +88,10 @@ static bool amd_iommu_enforce_cache_coherency(struct iommu_domain *domain);
|
||||
static int amd_iommu_set_dirty_tracking(struct iommu_domain *domain,
|
||||
bool enable);
|
||||
|
||||
static void clone_aliases(struct amd_iommu *iommu, struct device *dev);
|
||||
|
||||
static int iommu_completion_wait(struct amd_iommu *iommu);
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* Helper functions
|
||||
@@ -202,6 +209,16 @@ static void update_dte256(struct amd_iommu *iommu, struct iommu_dev_data *dev_da
|
||||
spin_unlock_irqrestore(&dev_data->dte_lock, flags);
|
||||
}
|
||||
|
||||
void amd_iommu_update_dte(struct amd_iommu *iommu,
|
||||
struct iommu_dev_data *dev_data,
|
||||
struct dev_table_entry *new)
|
||||
{
|
||||
update_dte256(iommu, dev_data, new);
|
||||
clone_aliases(iommu, dev_data->dev);
|
||||
device_flush_dte(dev_data);
|
||||
iommu_completion_wait(iommu);
|
||||
}
|
||||
|
||||
static void get_dte256(struct amd_iommu *iommu, struct iommu_dev_data *dev_data,
|
||||
struct dev_table_entry *dte)
|
||||
{
|
||||
@@ -1185,7 +1202,12 @@ static int wait_on_sem(struct amd_iommu *iommu, u64 data)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
while (*iommu->cmd_sem != data && i < LOOP_TIMEOUT) {
|
||||
/*
|
||||
* cmd_sem holds a monotonically non-decreasing completion sequence
|
||||
* number.
|
||||
*/
|
||||
while ((__s64)(READ_ONCE(*iommu->cmd_sem) - data) < 0 &&
|
||||
i < LOOP_TIMEOUT) {
|
||||
udelay(1);
|
||||
i += 1;
|
||||
}
|
||||
@@ -1417,6 +1439,12 @@ static int iommu_queue_command(struct amd_iommu *iommu, struct iommu_cmd *cmd)
|
||||
return iommu_queue_command_sync(iommu, cmd, true);
|
||||
}
|
||||
|
||||
static u64 get_cmdsem_val(struct amd_iommu *iommu)
|
||||
{
|
||||
lockdep_assert_held(&iommu->lock);
|
||||
return ++iommu->cmd_sem_val;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function queues a completion wait command into the command
|
||||
* buffer of an IOMMU
|
||||
@@ -1431,20 +1459,19 @@ static int iommu_completion_wait(struct amd_iommu *iommu)
|
||||
if (!iommu->need_sync)
|
||||
return 0;
|
||||
|
||||
data = atomic64_inc_return(&iommu->cmd_sem_val);
|
||||
build_completion_wait(&cmd, iommu, data);
|
||||
|
||||
raw_spin_lock_irqsave(&iommu->lock, flags);
|
||||
|
||||
data = get_cmdsem_val(iommu);
|
||||
build_completion_wait(&cmd, iommu, data);
|
||||
|
||||
ret = __iommu_queue_command_sync(iommu, &cmd, false);
|
||||
raw_spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
return ret;
|
||||
|
||||
ret = wait_on_sem(iommu, data);
|
||||
|
||||
out_unlock:
|
||||
raw_spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1522,6 +1549,32 @@ static void amd_iommu_flush_tlb_domid(struct amd_iommu *iommu, u32 dom_id)
|
||||
iommu_completion_wait(iommu);
|
||||
}
|
||||
|
||||
static int iommu_flush_pages_v1_hdom_ids(struct protection_domain *pdom, u64 address, size_t size)
|
||||
{
|
||||
int ret = 0;
|
||||
struct amd_iommu_viommu *aviommu;
|
||||
|
||||
list_for_each_entry(aviommu, &pdom->viommu_list, pdom_list) {
|
||||
unsigned long i;
|
||||
struct guest_domain_mapping_info *gdom_info;
|
||||
struct amd_iommu *iommu = container_of(aviommu->core.iommu_dev,
|
||||
struct amd_iommu, iommu);
|
||||
|
||||
xa_lock(&aviommu->gdomid_array);
|
||||
xa_for_each(&aviommu->gdomid_array, i, gdom_info) {
|
||||
struct iommu_cmd cmd;
|
||||
|
||||
pr_debug("%s: iommu=%#x, hdom_id=%#x\n", __func__,
|
||||
iommu->devid, gdom_info->hdom_id);
|
||||
build_inv_iommu_pages(&cmd, address, size, gdom_info->hdom_id,
|
||||
IOMMU_NO_PASID, false);
|
||||
ret |= iommu_queue_command(iommu, &cmd);
|
||||
}
|
||||
xa_unlock(&aviommu->gdomid_array);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void amd_iommu_flush_all(struct amd_iommu *iommu)
|
||||
{
|
||||
struct iommu_cmd cmd;
|
||||
@@ -1670,6 +1723,17 @@ static int domain_flush_pages_v1(struct protection_domain *pdom,
|
||||
ret |= iommu_queue_command(pdom_iommu_info->iommu, &cmd);
|
||||
}
|
||||
|
||||
/*
|
||||
* A domain w/ v1 table can be a nest parent, which can have
|
||||
* multiple nested domains. Each nested domain has 1:1 mapping
|
||||
* between gDomID and hDomID. Therefore, flush every hDomID
|
||||
* associated to this nest parent domain.
|
||||
*
|
||||
* See drivers/iommu/amd/nested.c: amd_iommu_alloc_domain_nested()
|
||||
*/
|
||||
if (!list_empty(&pdom->viommu_list))
|
||||
ret |= iommu_flush_pages_v1_hdom_ids(pdom, address, size);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -2010,127 +2074,112 @@ int amd_iommu_clear_gcr3(struct iommu_dev_data *dev_data, ioasid_t pasid)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void make_clear_dte(struct iommu_dev_data *dev_data, struct dev_table_entry *ptr,
|
||||
struct dev_table_entry *new)
|
||||
{
|
||||
/* All existing DTE must have V bit set */
|
||||
new->data128[0] = DTE_FLAG_V;
|
||||
new->data128[1] = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note:
|
||||
* The old value for GCR3 table and GPT have been cleared from caller.
|
||||
*/
|
||||
static void set_dte_gcr3_table(struct amd_iommu *iommu,
|
||||
struct iommu_dev_data *dev_data,
|
||||
struct dev_table_entry *target)
|
||||
static void set_dte_gcr3_table(struct iommu_dev_data *dev_data,
|
||||
struct dev_table_entry *new)
|
||||
{
|
||||
struct gcr3_tbl_info *gcr3_info = &dev_data->gcr3_info;
|
||||
u64 gcr3;
|
||||
u64 gcr3 = iommu_virt_to_phys(gcr3_info->gcr3_tbl);
|
||||
|
||||
if (!gcr3_info->gcr3_tbl)
|
||||
return;
|
||||
new->data[0] |= DTE_FLAG_TV |
|
||||
(dev_data->ppr ? DTE_FLAG_PPR : 0) |
|
||||
(pdom_is_v2_pgtbl_mode(dev_data->domain) ? DTE_FLAG_GIOV : 0) |
|
||||
DTE_FLAG_GV |
|
||||
FIELD_PREP(DTE_GLX, gcr3_info->glx) |
|
||||
FIELD_PREP(DTE_GCR3_14_12, gcr3 >> 12) |
|
||||
DTE_FLAG_IR | DTE_FLAG_IW;
|
||||
|
||||
pr_debug("%s: devid=%#x, glx=%#x, gcr3_tbl=%#llx\n",
|
||||
__func__, dev_data->devid, gcr3_info->glx,
|
||||
(unsigned long long)gcr3_info->gcr3_tbl);
|
||||
|
||||
gcr3 = iommu_virt_to_phys(gcr3_info->gcr3_tbl);
|
||||
|
||||
target->data[0] |= DTE_FLAG_GV |
|
||||
FIELD_PREP(DTE_GLX, gcr3_info->glx) |
|
||||
FIELD_PREP(DTE_GCR3_14_12, gcr3 >> 12);
|
||||
if (pdom_is_v2_pgtbl_mode(dev_data->domain))
|
||||
target->data[0] |= DTE_FLAG_GIOV;
|
||||
|
||||
target->data[1] |= FIELD_PREP(DTE_GCR3_30_15, gcr3 >> 15) |
|
||||
FIELD_PREP(DTE_GCR3_51_31, gcr3 >> 31);
|
||||
new->data[1] |= FIELD_PREP(DTE_DOMID_MASK, dev_data->gcr3_info.domid) |
|
||||
FIELD_PREP(DTE_GCR3_30_15, gcr3 >> 15) |
|
||||
(dev_data->ats_enabled ? DTE_FLAG_IOTLB : 0) |
|
||||
FIELD_PREP(DTE_GCR3_51_31, gcr3 >> 31);
|
||||
|
||||
/* Guest page table can only support 4 and 5 levels */
|
||||
if (amd_iommu_gpt_level == PAGE_MODE_5_LEVEL)
|
||||
target->data[2] |= FIELD_PREP(DTE_GPT_LEVEL_MASK, GUEST_PGTABLE_5_LEVEL);
|
||||
new->data[2] |= FIELD_PREP(DTE_GPT_LEVEL_MASK, GUEST_PGTABLE_5_LEVEL);
|
||||
else
|
||||
target->data[2] |= FIELD_PREP(DTE_GPT_LEVEL_MASK, GUEST_PGTABLE_4_LEVEL);
|
||||
new->data[2] |= FIELD_PREP(DTE_GPT_LEVEL_MASK, GUEST_PGTABLE_4_LEVEL);
|
||||
}
|
||||
|
||||
void amd_iommu_set_dte_v1(struct iommu_dev_data *dev_data,
|
||||
struct protection_domain *domain, u16 domid,
|
||||
struct pt_iommu_amdv1_hw_info *pt_info,
|
||||
struct dev_table_entry *new)
|
||||
{
|
||||
u64 host_pt_root = __sme_set(pt_info->host_pt_root);
|
||||
|
||||
/* Note Dirty tracking is used for v1 table only for now */
|
||||
new->data[0] |= DTE_FLAG_TV |
|
||||
FIELD_PREP(DTE_MODE_MASK, pt_info->mode) |
|
||||
(domain->dirty_tracking ? DTE_FLAG_HAD : 0) |
|
||||
FIELD_PREP(DTE_HOST_TRP, host_pt_root >> 12) |
|
||||
DTE_FLAG_IR | DTE_FLAG_IW;
|
||||
|
||||
new->data[1] |= FIELD_PREP(DTE_DOMID_MASK, domid) |
|
||||
(dev_data->ats_enabled ? DTE_FLAG_IOTLB : 0);
|
||||
}
|
||||
|
||||
static void set_dte_v1(struct iommu_dev_data *dev_data,
|
||||
struct protection_domain *domain, u16 domid,
|
||||
phys_addr_t top_paddr, unsigned int top_level,
|
||||
struct dev_table_entry *new)
|
||||
{
|
||||
struct pt_iommu_amdv1_hw_info pt_info;
|
||||
|
||||
/*
|
||||
* When updating the IO pagetable, the new top and level
|
||||
* are provided as parameters. For other operations i.e.
|
||||
* device attach, retrieve the current pagetable info
|
||||
* via the IOMMU PT API.
|
||||
*/
|
||||
if (top_paddr) {
|
||||
pt_info.host_pt_root = top_paddr;
|
||||
pt_info.mode = top_level + 1;
|
||||
} else {
|
||||
WARN_ON(top_paddr || top_level);
|
||||
pt_iommu_amdv1_hw_info(&domain->amdv1, &pt_info);
|
||||
}
|
||||
|
||||
amd_iommu_set_dte_v1(dev_data, domain, domid, &pt_info, new);
|
||||
}
|
||||
|
||||
static void set_dte_passthrough(struct iommu_dev_data *dev_data,
|
||||
struct protection_domain *domain,
|
||||
struct dev_table_entry *new)
|
||||
{
|
||||
new->data[0] |= DTE_FLAG_TV | DTE_FLAG_IR | DTE_FLAG_IW;
|
||||
|
||||
new->data[1] |= FIELD_PREP(DTE_DOMID_MASK, domain->id) |
|
||||
(dev_data->ats_enabled) ? DTE_FLAG_IOTLB : 0;
|
||||
}
|
||||
|
||||
static void set_dte_entry(struct amd_iommu *iommu,
|
||||
struct iommu_dev_data *dev_data,
|
||||
phys_addr_t top_paddr, unsigned int top_level)
|
||||
{
|
||||
u16 domid;
|
||||
u32 old_domid;
|
||||
struct dev_table_entry *initial_dte;
|
||||
struct dev_table_entry new = {};
|
||||
struct protection_domain *domain = dev_data->domain;
|
||||
struct gcr3_tbl_info *gcr3_info = &dev_data->gcr3_info;
|
||||
struct dev_table_entry *dte = &get_dev_table(iommu)[dev_data->devid];
|
||||
struct pt_iommu_amdv1_hw_info pt_info;
|
||||
|
||||
make_clear_dte(dev_data, dte, &new);
|
||||
amd_iommu_make_clear_dte(dev_data, &new);
|
||||
|
||||
if (gcr3_info && gcr3_info->gcr3_tbl)
|
||||
domid = dev_data->gcr3_info.domid;
|
||||
else {
|
||||
domid = domain->id;
|
||||
old_domid = READ_ONCE(dte->data[1]) & DTE_DOMID_MASK;
|
||||
if (gcr3_info->gcr3_tbl)
|
||||
set_dte_gcr3_table(dev_data, &new);
|
||||
else if (domain->domain.type == IOMMU_DOMAIN_IDENTITY)
|
||||
set_dte_passthrough(dev_data, domain, &new);
|
||||
else if ((domain->domain.type & __IOMMU_DOMAIN_PAGING) &&
|
||||
domain->pd_mode == PD_MODE_V1)
|
||||
set_dte_v1(dev_data, domain, domain->id, top_paddr, top_level, &new);
|
||||
else
|
||||
WARN_ON(true);
|
||||
|
||||
if (domain->domain.type & __IOMMU_DOMAIN_PAGING) {
|
||||
/*
|
||||
* When updating the IO pagetable, the new top and level
|
||||
* are provided as parameters. For other operations i.e.
|
||||
* device attach, retrieve the current pagetable info
|
||||
* via the IOMMU PT API.
|
||||
*/
|
||||
if (top_paddr) {
|
||||
pt_info.host_pt_root = top_paddr;
|
||||
pt_info.mode = top_level + 1;
|
||||
} else {
|
||||
WARN_ON(top_paddr || top_level);
|
||||
pt_iommu_amdv1_hw_info(&domain->amdv1,
|
||||
&pt_info);
|
||||
}
|
||||
|
||||
new.data[0] |= __sme_set(pt_info.host_pt_root) |
|
||||
(pt_info.mode & DEV_ENTRY_MODE_MASK)
|
||||
<< DEV_ENTRY_MODE_SHIFT;
|
||||
}
|
||||
}
|
||||
|
||||
new.data[0] |= DTE_FLAG_IR | DTE_FLAG_IW;
|
||||
|
||||
/*
|
||||
* When SNP is enabled, we can only support TV=1 with non-zero domain ID.
|
||||
* This is prevented by the SNP-enable and IOMMU_DOMAIN_IDENTITY check in
|
||||
* do_iommu_domain_alloc().
|
||||
*/
|
||||
WARN_ON(amd_iommu_snp_en && (domid == 0));
|
||||
new.data[0] |= DTE_FLAG_TV;
|
||||
|
||||
if (dev_data->ppr)
|
||||
new.data[0] |= 1ULL << DEV_ENTRY_PPR;
|
||||
|
||||
if (domain->dirty_tracking)
|
||||
new.data[0] |= DTE_FLAG_HAD;
|
||||
|
||||
if (dev_data->ats_enabled)
|
||||
new.data[1] |= DTE_FLAG_IOTLB;
|
||||
|
||||
old_domid = READ_ONCE(dte->data[1]) & DEV_DOMID_MASK;
|
||||
new.data[1] |= domid;
|
||||
|
||||
/*
|
||||
* Restore cached persistent DTE bits, which can be set by information
|
||||
* in IVRS table. See set_dev_entry_from_acpi().
|
||||
*/
|
||||
initial_dte = amd_iommu_get_ivhd_dte_flags(iommu->pci_seg->id, dev_data->devid);
|
||||
if (initial_dte) {
|
||||
new.data128[0] |= initial_dte->data128[0];
|
||||
new.data128[1] |= initial_dte->data128[1];
|
||||
}
|
||||
|
||||
set_dte_gcr3_table(iommu, dev_data, &new);
|
||||
|
||||
update_dte256(iommu, dev_data, &new);
|
||||
amd_iommu_update_dte(iommu, dev_data, &new);
|
||||
|
||||
/*
|
||||
* A kdump kernel might be replacing a domain ID that was copied from
|
||||
@@ -2148,10 +2197,9 @@ static void set_dte_entry(struct amd_iommu *iommu,
|
||||
static void clear_dte_entry(struct amd_iommu *iommu, struct iommu_dev_data *dev_data)
|
||||
{
|
||||
struct dev_table_entry new = {};
|
||||
struct dev_table_entry *dte = &get_dev_table(iommu)[dev_data->devid];
|
||||
|
||||
make_clear_dte(dev_data, dte, &new);
|
||||
update_dte256(iommu, dev_data, &new);
|
||||
amd_iommu_make_clear_dte(dev_data, &new);
|
||||
amd_iommu_update_dte(iommu, dev_data, &new);
|
||||
}
|
||||
|
||||
/* Update and flush DTE for the given device */
|
||||
@@ -2163,10 +2211,6 @@ static void dev_update_dte(struct iommu_dev_data *dev_data, bool set)
|
||||
set_dte_entry(iommu, dev_data, 0, 0);
|
||||
else
|
||||
clear_dte_entry(iommu, dev_data);
|
||||
|
||||
clone_aliases(iommu, dev_data->dev);
|
||||
device_flush_dte(dev_data);
|
||||
iommu_completion_wait(iommu);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2499,6 +2543,7 @@ static void protection_domain_init(struct protection_domain *domain)
|
||||
spin_lock_init(&domain->lock);
|
||||
INIT_LIST_HEAD(&domain->dev_list);
|
||||
INIT_LIST_HEAD(&domain->dev_data_list);
|
||||
INIT_LIST_HEAD(&domain->viommu_list);
|
||||
xa_init(&domain->iommu_array);
|
||||
}
|
||||
|
||||
@@ -2760,6 +2805,14 @@ static struct iommu_domain *amd_iommu_domain_alloc_paging_v2(struct device *dev,
|
||||
return &domain->domain;
|
||||
}
|
||||
|
||||
static inline bool is_nest_parent_supported(u32 flags)
|
||||
{
|
||||
/* Only allow nest parent when these features are supported */
|
||||
return check_feature(FEATURE_GT) &&
|
||||
check_feature(FEATURE_GIOSUP) &&
|
||||
check_feature2(FEATURE_GCR3TRPMODE);
|
||||
}
|
||||
|
||||
static struct iommu_domain *
|
||||
amd_iommu_domain_alloc_paging_flags(struct device *dev, u32 flags,
|
||||
const struct iommu_user_data *user_data)
|
||||
@@ -2767,16 +2820,28 @@ amd_iommu_domain_alloc_paging_flags(struct device *dev, u32 flags,
|
||||
{
|
||||
struct amd_iommu *iommu = get_amd_iommu_from_dev(dev);
|
||||
const u32 supported_flags = IOMMU_HWPT_ALLOC_DIRTY_TRACKING |
|
||||
IOMMU_HWPT_ALLOC_PASID;
|
||||
IOMMU_HWPT_ALLOC_PASID |
|
||||
IOMMU_HWPT_ALLOC_NEST_PARENT;
|
||||
|
||||
if ((flags & ~supported_flags) || user_data)
|
||||
return ERR_PTR(-EOPNOTSUPP);
|
||||
|
||||
switch (flags & supported_flags) {
|
||||
case IOMMU_HWPT_ALLOC_DIRTY_TRACKING:
|
||||
/* Allocate domain with v1 page table for dirty tracking */
|
||||
if (!amd_iommu_hd_support(iommu))
|
||||
case IOMMU_HWPT_ALLOC_NEST_PARENT:
|
||||
case IOMMU_HWPT_ALLOC_DIRTY_TRACKING | IOMMU_HWPT_ALLOC_NEST_PARENT:
|
||||
/*
|
||||
* Allocate domain with v1 page table for dirty tracking
|
||||
* and/or Nest parent.
|
||||
*/
|
||||
if ((flags & IOMMU_HWPT_ALLOC_DIRTY_TRACKING) &&
|
||||
!amd_iommu_hd_support(iommu))
|
||||
break;
|
||||
|
||||
if ((flags & IOMMU_HWPT_ALLOC_NEST_PARENT) &&
|
||||
!is_nest_parent_supported(flags))
|
||||
break;
|
||||
|
||||
return amd_iommu_domain_alloc_paging_v1(dev, flags);
|
||||
case IOMMU_HWPT_ALLOC_PASID:
|
||||
/* Allocate domain with v2 page table if IOMMU supports PASID. */
|
||||
@@ -3078,6 +3143,7 @@ static bool amd_iommu_enforce_cache_coherency(struct iommu_domain *domain)
|
||||
|
||||
const struct iommu_ops amd_iommu_ops = {
|
||||
.capable = amd_iommu_capable,
|
||||
.hw_info = amd_iommufd_hw_info,
|
||||
.blocked_domain = &blocked_domain,
|
||||
.release_domain = &blocked_domain,
|
||||
.identity_domain = &identity_domain.domain,
|
||||
@@ -3090,6 +3156,8 @@ const struct iommu_ops amd_iommu_ops = {
|
||||
.is_attach_deferred = amd_iommu_is_attach_deferred,
|
||||
.def_domain_type = amd_iommu_def_domain_type,
|
||||
.page_response = amd_iommu_page_response,
|
||||
.get_viommu_size = amd_iommufd_get_viommu_size,
|
||||
.viommu_init = amd_iommufd_viommu_init,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_IRQ_REMAP
|
||||
@@ -3114,18 +3182,23 @@ static void iommu_flush_irt_and_complete(struct amd_iommu *iommu, u16 devid)
|
||||
return;
|
||||
|
||||
build_inv_irt(&cmd, devid);
|
||||
data = atomic64_inc_return(&iommu->cmd_sem_val);
|
||||
build_completion_wait(&cmd2, iommu, data);
|
||||
|
||||
raw_spin_lock_irqsave(&iommu->lock, flags);
|
||||
data = get_cmdsem_val(iommu);
|
||||
build_completion_wait(&cmd2, iommu, data);
|
||||
|
||||
ret = __iommu_queue_command_sync(iommu, &cmd, true);
|
||||
if (ret)
|
||||
goto out;
|
||||
goto out_err;
|
||||
ret = __iommu_queue_command_sync(iommu, &cmd2, false);
|
||||
if (ret)
|
||||
goto out;
|
||||
goto out_err;
|
||||
raw_spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
|
||||
wait_on_sem(iommu, data);
|
||||
out:
|
||||
return;
|
||||
|
||||
out_err:
|
||||
raw_spin_unlock_irqrestore(&iommu->lock, flags);
|
||||
}
|
||||
|
||||
@@ -3239,7 +3312,7 @@ static struct irq_remap_table *alloc_irq_table(struct amd_iommu *iommu,
|
||||
struct irq_remap_table *new_table = NULL;
|
||||
struct amd_iommu_pci_seg *pci_seg;
|
||||
unsigned long flags;
|
||||
int nid = iommu && iommu->dev ? dev_to_node(&iommu->dev->dev) : NUMA_NO_NODE;
|
||||
int nid = iommu->dev ? dev_to_node(&iommu->dev->dev) : NUMA_NO_NODE;
|
||||
u16 alias;
|
||||
|
||||
spin_lock_irqsave(&iommu_table_lock, flags);
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2025 Advanced Micro Devices, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/iommu.h>
|
||||
|
||||
#include "iommufd.h"
|
||||
#include "amd_iommu.h"
|
||||
#include "amd_iommu_types.h"
|
||||
|
||||
static const struct iommufd_viommu_ops amd_viommu_ops;
|
||||
|
||||
void *amd_iommufd_hw_info(struct device *dev, u32 *length, enum iommu_hw_info_type *type)
|
||||
{
|
||||
struct iommu_hw_info_amd *hwinfo;
|
||||
|
||||
if (*type != IOMMU_HW_INFO_TYPE_DEFAULT &&
|
||||
*type != IOMMU_HW_INFO_TYPE_AMD)
|
||||
return ERR_PTR(-EOPNOTSUPP);
|
||||
|
||||
hwinfo = kzalloc(sizeof(*hwinfo), GFP_KERNEL);
|
||||
if (!hwinfo)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
*length = sizeof(*hwinfo);
|
||||
*type = IOMMU_HW_INFO_TYPE_AMD;
|
||||
|
||||
hwinfo->efr = amd_iommu_efr;
|
||||
hwinfo->efr2 = amd_iommu_efr2;
|
||||
|
||||
return hwinfo;
|
||||
}
|
||||
|
||||
size_t amd_iommufd_get_viommu_size(struct device *dev, enum iommu_viommu_type viommu_type)
|
||||
{
|
||||
return VIOMMU_STRUCT_SIZE(struct amd_iommu_viommu, core);
|
||||
}
|
||||
|
||||
int amd_iommufd_viommu_init(struct iommufd_viommu *viommu, struct iommu_domain *parent,
|
||||
const struct iommu_user_data *user_data)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct protection_domain *pdom = to_pdomain(parent);
|
||||
struct amd_iommu_viommu *aviommu = container_of(viommu, struct amd_iommu_viommu, core);
|
||||
|
||||
xa_init_flags(&aviommu->gdomid_array, XA_FLAGS_ALLOC1);
|
||||
aviommu->parent = pdom;
|
||||
|
||||
viommu->ops = &amd_viommu_ops;
|
||||
|
||||
spin_lock_irqsave(&pdom->lock, flags);
|
||||
list_add(&aviommu->pdom_list, &pdom->viommu_list);
|
||||
spin_unlock_irqrestore(&pdom->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void amd_iommufd_viommu_destroy(struct iommufd_viommu *viommu)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct amd_iommu_viommu *aviommu = container_of(viommu, struct amd_iommu_viommu, core);
|
||||
struct protection_domain *pdom = aviommu->parent;
|
||||
|
||||
spin_lock_irqsave(&pdom->lock, flags);
|
||||
list_del(&aviommu->pdom_list);
|
||||
spin_unlock_irqrestore(&pdom->lock, flags);
|
||||
xa_destroy(&aviommu->gdomid_array);
|
||||
}
|
||||
|
||||
/*
|
||||
* See include/linux/iommufd.h
|
||||
* struct iommufd_viommu_ops - vIOMMU specific operations
|
||||
*/
|
||||
static const struct iommufd_viommu_ops amd_viommu_ops = {
|
||||
.destroy = amd_iommufd_viommu_destroy,
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2025 Advanced Micro Devices, Inc.
|
||||
*/
|
||||
|
||||
#ifndef AMD_IOMMUFD_H
|
||||
#define AMD_IOMMUFD_H
|
||||
|
||||
#if IS_ENABLED(CONFIG_AMD_IOMMU_IOMMUFD)
|
||||
void *amd_iommufd_hw_info(struct device *dev, u32 *length, enum iommu_hw_info_type *type);
|
||||
size_t amd_iommufd_get_viommu_size(struct device *dev, enum iommu_viommu_type viommu_type);
|
||||
int amd_iommufd_viommu_init(struct iommufd_viommu *viommu, struct iommu_domain *parent,
|
||||
const struct iommu_user_data *user_data);
|
||||
#else
|
||||
#define amd_iommufd_hw_info NULL
|
||||
#define amd_iommufd_viommu_init NULL
|
||||
#define amd_iommufd_get_viommu_size NULL
|
||||
#endif /* CONFIG_AMD_IOMMU_IOMMUFD */
|
||||
|
||||
#endif /* AMD_IOMMUFD_H */
|
||||
@@ -0,0 +1,294 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (C) 2025 Advanced Micro Devices, Inc.
|
||||
*/
|
||||
|
||||
#define dev_fmt(fmt) "AMD-Vi: " fmt
|
||||
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/refcount.h>
|
||||
#include <uapi/linux/iommufd.h>
|
||||
|
||||
#include "amd_iommu.h"
|
||||
|
||||
static const struct iommu_domain_ops nested_domain_ops;
|
||||
|
||||
static inline struct nested_domain *to_ndomain(struct iommu_domain *dom)
|
||||
{
|
||||
return container_of(dom, struct nested_domain, domain);
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate guest DTE to make sure that configuration for host (v1)
|
||||
* and guest (v2) page tables are valid when allocating nested domain.
|
||||
*/
|
||||
static int validate_gdte_nested(struct iommu_hwpt_amd_guest *gdte)
|
||||
{
|
||||
u32 gpt_level = FIELD_GET(DTE_GPT_LEVEL_MASK, gdte->dte[2]);
|
||||
|
||||
/* Must be zero: Mode, Host-TPR */
|
||||
if (FIELD_GET(DTE_MODE_MASK, gdte->dte[0]) != 0 ||
|
||||
FIELD_GET(DTE_HOST_TRP, gdte->dte[0]) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* GCR3 TRP must be non-zero if V, GV is set */
|
||||
if (FIELD_GET(DTE_FLAG_V, gdte->dte[0]) == 1 &&
|
||||
FIELD_GET(DTE_FLAG_GV, gdte->dte[0]) == 1 &&
|
||||
FIELD_GET(DTE_GCR3_14_12, gdte->dte[0]) == 0 &&
|
||||
FIELD_GET(DTE_GCR3_30_15, gdte->dte[1]) == 0 &&
|
||||
FIELD_GET(DTE_GCR3_51_31, gdte->dte[1]) == 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* Valid Guest Paging Mode values are 0 and 1 */
|
||||
if (gpt_level != GUEST_PGTABLE_4_LEVEL &&
|
||||
gpt_level != GUEST_PGTABLE_5_LEVEL)
|
||||
return -EINVAL;
|
||||
|
||||
/* GLX = 3 is reserved */
|
||||
if (FIELD_GET(DTE_GLX, gdte->dte[0]) == 3)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* We need to check host capability before setting
|
||||
* the Guest Paging Mode
|
||||
*/
|
||||
if (gpt_level == GUEST_PGTABLE_5_LEVEL &&
|
||||
amd_iommu_gpt_level < PAGE_MODE_5_LEVEL)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *gdom_info_load_or_alloc_locked(struct xarray *xa, unsigned long index)
|
||||
{
|
||||
struct guest_domain_mapping_info *elm, *res;
|
||||
|
||||
elm = xa_load(xa, index);
|
||||
if (elm)
|
||||
return elm;
|
||||
|
||||
xa_unlock(xa);
|
||||
elm = kzalloc(sizeof(struct guest_domain_mapping_info), GFP_KERNEL);
|
||||
xa_lock(xa);
|
||||
if (!elm)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
res = __xa_cmpxchg(xa, index, NULL, elm, GFP_KERNEL);
|
||||
if (xa_is_err(res))
|
||||
res = ERR_PTR(xa_err(res));
|
||||
|
||||
if (res) {
|
||||
kfree(elm);
|
||||
return res;
|
||||
}
|
||||
|
||||
refcount_set(&elm->users, 0);
|
||||
return elm;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is assigned to struct iommufd_viommu_ops.alloc_domain_nested()
|
||||
* during the call to struct iommu_ops.viommu_init().
|
||||
*/
|
||||
struct iommu_domain *
|
||||
amd_iommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags,
|
||||
const struct iommu_user_data *user_data)
|
||||
{
|
||||
int ret;
|
||||
struct nested_domain *ndom;
|
||||
struct guest_domain_mapping_info *gdom_info;
|
||||
struct amd_iommu_viommu *aviommu = container_of(viommu, struct amd_iommu_viommu, core);
|
||||
|
||||
if (user_data->type != IOMMU_HWPT_DATA_AMD_GUEST)
|
||||
return ERR_PTR(-EOPNOTSUPP);
|
||||
|
||||
ndom = kzalloc(sizeof(*ndom), GFP_KERNEL);
|
||||
if (!ndom)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ret = iommu_copy_struct_from_user(&ndom->gdte, user_data,
|
||||
IOMMU_HWPT_DATA_AMD_GUEST,
|
||||
dte);
|
||||
if (ret)
|
||||
goto out_err;
|
||||
|
||||
ret = validate_gdte_nested(&ndom->gdte);
|
||||
if (ret)
|
||||
goto out_err;
|
||||
|
||||
ndom->gdom_id = FIELD_GET(DTE_DOMID_MASK, ndom->gdte.dte[1]);
|
||||
ndom->domain.ops = &nested_domain_ops;
|
||||
ndom->domain.type = IOMMU_DOMAIN_NESTED;
|
||||
ndom->viommu = aviommu;
|
||||
|
||||
/*
|
||||
* Normally, when a guest has multiple pass-through devices,
|
||||
* the IOMMU driver setup DTEs with the same stage-2 table and
|
||||
* use the same host domain ID (hDomId). In case of nested translation,
|
||||
* if the guest setup different stage-1 tables with same PASID,
|
||||
* IOMMU would use the same TLB tag. This will results in TLB
|
||||
* aliasing issue.
|
||||
*
|
||||
* The guest is assigning gDomIDs based on its own algorithm for managing
|
||||
* cache tags of (DomID, PASID). Within a single viommu, the nest parent domain
|
||||
* (w/ S2 table) is used by all DTEs. But we need to consistently map the gDomID
|
||||
* to a single hDomID. This is done using an xarray in the vIOMMU to
|
||||
* keep track of the gDomID mapping. When the S2 is changed, the INVALIDATE_IOMMU_PAGES
|
||||
* command must be issued for each hDomID in the xarray.
|
||||
*/
|
||||
xa_lock(&aviommu->gdomid_array);
|
||||
|
||||
gdom_info = gdom_info_load_or_alloc_locked(&aviommu->gdomid_array, ndom->gdom_id);
|
||||
if (IS_ERR(gdom_info)) {
|
||||
xa_unlock(&aviommu->gdomid_array);
|
||||
ret = PTR_ERR(gdom_info);
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
/* Check if gDomID exist */
|
||||
if (refcount_inc_not_zero(&gdom_info->users)) {
|
||||
ndom->gdom_info = gdom_info;
|
||||
xa_unlock(&aviommu->gdomid_array);
|
||||
|
||||
pr_debug("%s: Found gdom_id=%#x, hdom_id=%#x\n",
|
||||
__func__, ndom->gdom_id, gdom_info->hdom_id);
|
||||
|
||||
return &ndom->domain;
|
||||
}
|
||||
|
||||
/* The gDomID does not exist. We allocate new hdom_id */
|
||||
gdom_info->hdom_id = amd_iommu_pdom_id_alloc();
|
||||
if (gdom_info->hdom_id <= 0) {
|
||||
__xa_cmpxchg(&aviommu->gdomid_array,
|
||||
ndom->gdom_id, gdom_info, NULL, GFP_ATOMIC);
|
||||
xa_unlock(&aviommu->gdomid_array);
|
||||
ret = -ENOSPC;
|
||||
goto out_err_gdom_info;
|
||||
}
|
||||
|
||||
ndom->gdom_info = gdom_info;
|
||||
refcount_set(&gdom_info->users, 1);
|
||||
|
||||
xa_unlock(&aviommu->gdomid_array);
|
||||
|
||||
pr_debug("%s: Allocate gdom_id=%#x, hdom_id=%#x\n",
|
||||
__func__, ndom->gdom_id, gdom_info->hdom_id);
|
||||
|
||||
return &ndom->domain;
|
||||
|
||||
out_err_gdom_info:
|
||||
kfree(gdom_info);
|
||||
out_err:
|
||||
kfree(ndom);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static void set_dte_nested(struct amd_iommu *iommu, struct iommu_domain *dom,
|
||||
struct iommu_dev_data *dev_data, struct dev_table_entry *new)
|
||||
{
|
||||
struct protection_domain *parent;
|
||||
struct nested_domain *ndom = to_ndomain(dom);
|
||||
struct iommu_hwpt_amd_guest *gdte = &ndom->gdte;
|
||||
struct pt_iommu_amdv1_hw_info pt_info;
|
||||
|
||||
/*
|
||||
* The nest parent domain is attached during the call to the
|
||||
* struct iommu_ops.viommu_init(), which will be stored as part
|
||||
* of the struct amd_iommu_viommu.parent.
|
||||
*/
|
||||
if (WARN_ON(!ndom->viommu || !ndom->viommu->parent))
|
||||
return;
|
||||
|
||||
parent = ndom->viommu->parent;
|
||||
amd_iommu_make_clear_dte(dev_data, new);
|
||||
|
||||
/* Retrieve the current pagetable info via the IOMMU PT API. */
|
||||
pt_iommu_amdv1_hw_info(&parent->amdv1, &pt_info);
|
||||
|
||||
/*
|
||||
* Use domain ID from nested domain to program DTE.
|
||||
* See amd_iommu_alloc_domain_nested().
|
||||
*/
|
||||
amd_iommu_set_dte_v1(dev_data, parent, ndom->gdom_info->hdom_id,
|
||||
&pt_info, new);
|
||||
|
||||
/* GV is required for nested page table */
|
||||
new->data[0] |= DTE_FLAG_GV;
|
||||
|
||||
/* Guest PPR */
|
||||
new->data[0] |= gdte->dte[0] & DTE_FLAG_PPR;
|
||||
|
||||
/* Guest translation stuff */
|
||||
new->data[0] |= gdte->dte[0] & (DTE_GLX | DTE_FLAG_GIOV);
|
||||
|
||||
/* GCR3 table */
|
||||
new->data[0] |= gdte->dte[0] & DTE_GCR3_14_12;
|
||||
new->data[1] |= gdte->dte[1] & (DTE_GCR3_30_15 | DTE_GCR3_51_31);
|
||||
|
||||
/* Guest paging mode */
|
||||
new->data[2] |= gdte->dte[2] & DTE_GPT_LEVEL_MASK;
|
||||
}
|
||||
|
||||
static int nested_attach_device(struct iommu_domain *dom, struct device *dev,
|
||||
struct iommu_domain *old)
|
||||
{
|
||||
struct dev_table_entry new = {0};
|
||||
struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev);
|
||||
struct amd_iommu *iommu = get_amd_iommu_from_dev_data(dev_data);
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* Needs to make sure PASID is not enabled
|
||||
* for this attach path.
|
||||
*/
|
||||
if (WARN_ON(dev_data->pasid_enabled))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&dev_data->mutex);
|
||||
|
||||
set_dte_nested(iommu, dom, dev_data, &new);
|
||||
|
||||
amd_iommu_update_dte(iommu, dev_data, &new);
|
||||
|
||||
mutex_unlock(&dev_data->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void nested_domain_free(struct iommu_domain *dom)
|
||||
{
|
||||
struct guest_domain_mapping_info *curr;
|
||||
struct nested_domain *ndom = to_ndomain(dom);
|
||||
struct amd_iommu_viommu *aviommu = ndom->viommu;
|
||||
|
||||
xa_lock(&aviommu->gdomid_array);
|
||||
|
||||
if (!refcount_dec_and_test(&ndom->gdom_info->users)) {
|
||||
xa_unlock(&aviommu->gdomid_array);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The refcount for the gdom_id to hdom_id mapping is zero.
|
||||
* It is now safe to remove the mapping.
|
||||
*/
|
||||
curr = __xa_cmpxchg(&aviommu->gdomid_array, ndom->gdom_id,
|
||||
ndom->gdom_info, NULL, GFP_ATOMIC);
|
||||
|
||||
xa_unlock(&aviommu->gdomid_array);
|
||||
if (WARN_ON(!curr || xa_err(curr)))
|
||||
return;
|
||||
|
||||
/* success */
|
||||
pr_debug("%s: Free gdom_id=%#x, hdom_id=%#x\n",
|
||||
__func__, ndom->gdom_id, curr->hdom_id);
|
||||
|
||||
amd_iommu_pdom_id_free(ndom->gdom_info->hdom_id);
|
||||
kfree(curr);
|
||||
kfree(ndom);
|
||||
}
|
||||
|
||||
static const struct iommu_domain_ops nested_domain_ops = {
|
||||
.attach_dev = nested_attach_device,
|
||||
.free = nested_domain_free,
|
||||
};
|
||||
@@ -121,7 +121,6 @@ config ARM_SMMU_V3_KUNIT_TEST
|
||||
|
||||
config TEGRA241_CMDQV
|
||||
bool "NVIDIA Tegra241 CMDQ-V extension support for ARM SMMUv3"
|
||||
depends on ACPI
|
||||
help
|
||||
Support for NVIDIA CMDQ-Virtualization extension for ARM SMMUv3. The
|
||||
CMDQ-V extension is similar to v3.3 ECMDQ for multi command queues
|
||||
|
||||
@@ -177,7 +177,9 @@ static int arm_smmu_attach_dev_nested(struct iommu_domain *domain,
|
||||
* config bit here base this off the EATS value in the STE. If the EATS
|
||||
* is set then the VM must generate ATC flushes.
|
||||
*/
|
||||
state.disable_ats = !nested_domain->enable_ats;
|
||||
if (FIELD_GET(STRTAB_STE_0_CFG, le64_to_cpu(nested_domain->ste[0])) ==
|
||||
STRTAB_STE_0_CFG_S1_TRANS)
|
||||
state.disable_ats = !nested_domain->enable_ats;
|
||||
ret = arm_smmu_attach_prepare(&state, domain);
|
||||
if (ret) {
|
||||
mutex_unlock(&arm_smmu_asid_lock);
|
||||
|
||||
@@ -33,18 +33,25 @@ static struct mm_struct sva_mm = {
|
||||
enum arm_smmu_test_master_feat {
|
||||
ARM_SMMU_MASTER_TEST_ATS = BIT(0),
|
||||
ARM_SMMU_MASTER_TEST_STALL = BIT(1),
|
||||
ARM_SMMU_MASTER_TEST_NESTED = BIT(2),
|
||||
};
|
||||
|
||||
static void arm_smmu_test_make_s2_ste(struct arm_smmu_ste *ste,
|
||||
enum arm_smmu_test_master_feat feat);
|
||||
|
||||
static bool arm_smmu_entry_differs_in_used_bits(const __le64 *entry,
|
||||
const __le64 *used_bits,
|
||||
const __le64 *target,
|
||||
const __le64 *safe,
|
||||
unsigned int length)
|
||||
{
|
||||
bool differs = false;
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < length; i++) {
|
||||
if ((entry[i] & used_bits[i]) != target[i])
|
||||
__le64 used = used_bits[i] & ~safe[i];
|
||||
|
||||
if ((entry[i] & used) != (target[i] & used))
|
||||
differs = true;
|
||||
}
|
||||
return differs;
|
||||
@@ -56,12 +63,24 @@ arm_smmu_test_writer_record_syncs(struct arm_smmu_entry_writer *writer)
|
||||
struct arm_smmu_test_writer *test_writer =
|
||||
container_of(writer, struct arm_smmu_test_writer, writer);
|
||||
__le64 *entry_used_bits;
|
||||
__le64 *safe_target;
|
||||
__le64 *safe_init;
|
||||
|
||||
entry_used_bits = kunit_kzalloc(
|
||||
test_writer->test, sizeof(*entry_used_bits) * NUM_ENTRY_QWORDS,
|
||||
GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_NULL(test_writer->test, entry_used_bits);
|
||||
|
||||
safe_target = kunit_kzalloc(test_writer->test,
|
||||
sizeof(*safe_target) * NUM_ENTRY_QWORDS,
|
||||
GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_NULL(test_writer->test, safe_target);
|
||||
|
||||
safe_init = kunit_kzalloc(test_writer->test,
|
||||
sizeof(*safe_init) * NUM_ENTRY_QWORDS,
|
||||
GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_NULL(test_writer->test, safe_init);
|
||||
|
||||
pr_debug("STE value is now set to: ");
|
||||
print_hex_dump_debug(" ", DUMP_PREFIX_NONE, 16, 8,
|
||||
test_writer->entry,
|
||||
@@ -79,14 +98,23 @@ arm_smmu_test_writer_record_syncs(struct arm_smmu_entry_writer *writer)
|
||||
* configuration.
|
||||
*/
|
||||
writer->ops->get_used(test_writer->entry, entry_used_bits);
|
||||
if (writer->ops->get_update_safe)
|
||||
writer->ops->get_update_safe(test_writer->entry,
|
||||
test_writer->init_entry,
|
||||
safe_init);
|
||||
if (writer->ops->get_update_safe)
|
||||
writer->ops->get_update_safe(test_writer->entry,
|
||||
test_writer->target_entry,
|
||||
safe_target);
|
||||
KUNIT_EXPECT_FALSE(
|
||||
test_writer->test,
|
||||
arm_smmu_entry_differs_in_used_bits(
|
||||
test_writer->entry, entry_used_bits,
|
||||
test_writer->init_entry, NUM_ENTRY_QWORDS) &&
|
||||
test_writer->init_entry, safe_init,
|
||||
NUM_ENTRY_QWORDS) &&
|
||||
arm_smmu_entry_differs_in_used_bits(
|
||||
test_writer->entry, entry_used_bits,
|
||||
test_writer->target_entry,
|
||||
test_writer->target_entry, safe_target,
|
||||
NUM_ENTRY_QWORDS));
|
||||
}
|
||||
}
|
||||
@@ -106,6 +134,7 @@ arm_smmu_v3_test_debug_print_used_bits(struct arm_smmu_entry_writer *writer,
|
||||
static const struct arm_smmu_entry_writer_ops test_ste_ops = {
|
||||
.sync = arm_smmu_test_writer_record_syncs,
|
||||
.get_used = arm_smmu_get_ste_used,
|
||||
.get_update_safe = arm_smmu_get_ste_update_safe,
|
||||
};
|
||||
|
||||
static const struct arm_smmu_entry_writer_ops test_cd_ops = {
|
||||
@@ -185,6 +214,18 @@ static void arm_smmu_test_make_cdtable_ste(struct arm_smmu_ste *ste,
|
||||
};
|
||||
|
||||
arm_smmu_make_cdtable_ste(ste, &master, ats_enabled, s1dss);
|
||||
if (feat & ARM_SMMU_MASTER_TEST_NESTED) {
|
||||
struct arm_smmu_ste s2ste;
|
||||
int i;
|
||||
|
||||
arm_smmu_test_make_s2_ste(&s2ste,
|
||||
feat & ~ARM_SMMU_MASTER_TEST_NESTED);
|
||||
ste->data[0] |= cpu_to_le64(
|
||||
FIELD_PREP(STRTAB_STE_0_CFG, STRTAB_STE_0_CFG_NESTED));
|
||||
ste->data[1] |= cpu_to_le64(STRTAB_STE_1_MEV);
|
||||
for (i = 2; i < NUM_ENTRY_QWORDS; i++)
|
||||
ste->data[i] = s2ste.data[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void arm_smmu_v3_write_ste_test_bypass_to_abort(struct kunit *test)
|
||||
@@ -542,6 +583,35 @@ static void arm_smmu_v3_write_ste_test_s2_to_s1_stall(struct kunit *test)
|
||||
NUM_EXPECTED_SYNCS(3));
|
||||
}
|
||||
|
||||
static void
|
||||
arm_smmu_v3_write_ste_test_nested_s1dssbypass_to_s1bypass(struct kunit *test)
|
||||
{
|
||||
struct arm_smmu_ste s1_ste;
|
||||
struct arm_smmu_ste s2_ste;
|
||||
|
||||
arm_smmu_test_make_cdtable_ste(
|
||||
&s1_ste, STRTAB_STE_1_S1DSS_BYPASS, fake_cdtab_dma_addr,
|
||||
ARM_SMMU_MASTER_TEST_ATS | ARM_SMMU_MASTER_TEST_NESTED);
|
||||
arm_smmu_test_make_s2_ste(&s2_ste, 0);
|
||||
/* Expect an additional sync to unset ignored bits: EATS and MEV */
|
||||
arm_smmu_v3_test_ste_expect_hitless_transition(test, &s1_ste, &s2_ste,
|
||||
NUM_EXPECTED_SYNCS(3));
|
||||
}
|
||||
|
||||
static void
|
||||
arm_smmu_v3_write_ste_test_nested_s1bypass_to_s1dssbypass(struct kunit *test)
|
||||
{
|
||||
struct arm_smmu_ste s1_ste;
|
||||
struct arm_smmu_ste s2_ste;
|
||||
|
||||
arm_smmu_test_make_cdtable_ste(
|
||||
&s1_ste, STRTAB_STE_1_S1DSS_BYPASS, fake_cdtab_dma_addr,
|
||||
ARM_SMMU_MASTER_TEST_ATS | ARM_SMMU_MASTER_TEST_NESTED);
|
||||
arm_smmu_test_make_s2_ste(&s2_ste, 0);
|
||||
arm_smmu_v3_test_ste_expect_hitless_transition(test, &s2_ste, &s1_ste,
|
||||
NUM_EXPECTED_SYNCS(2));
|
||||
}
|
||||
|
||||
static void arm_smmu_v3_write_cd_test_sva_clear(struct kunit *test)
|
||||
{
|
||||
struct arm_smmu_cd cd = {};
|
||||
@@ -588,6 +658,8 @@ static struct kunit_case arm_smmu_v3_test_cases[] = {
|
||||
KUNIT_CASE(arm_smmu_v3_write_cd_test_s1_change_asid),
|
||||
KUNIT_CASE(arm_smmu_v3_write_ste_test_s1_to_s2_stall),
|
||||
KUNIT_CASE(arm_smmu_v3_write_ste_test_s2_to_s1_stall),
|
||||
KUNIT_CASE(arm_smmu_v3_write_ste_test_nested_s1dssbypass_to_s1bypass),
|
||||
KUNIT_CASE(arm_smmu_v3_write_ste_test_nested_s1bypass_to_s1dssbypass),
|
||||
KUNIT_CASE(arm_smmu_v3_write_cd_test_sva_clear),
|
||||
KUNIT_CASE(arm_smmu_v3_write_cd_test_sva_release),
|
||||
{},
|
||||
|
||||
@@ -487,20 +487,26 @@ static void arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu)
|
||||
*/
|
||||
static void arm_smmu_cmdq_shared_lock(struct arm_smmu_cmdq *cmdq)
|
||||
{
|
||||
int val;
|
||||
|
||||
/*
|
||||
* We can try to avoid the cmpxchg() loop by simply incrementing the
|
||||
* lock counter. When held in exclusive state, the lock counter is set
|
||||
* to INT_MIN so these increments won't hurt as the value will remain
|
||||
* negative.
|
||||
* When held in exclusive state, the lock counter is set to INT_MIN
|
||||
* so these increments won't hurt as the value will remain negative.
|
||||
* The increment will also signal the exclusive locker that there are
|
||||
* shared waiters.
|
||||
*/
|
||||
if (atomic_fetch_inc_relaxed(&cmdq->lock) >= 0)
|
||||
return;
|
||||
|
||||
do {
|
||||
val = atomic_cond_read_relaxed(&cmdq->lock, VAL >= 0);
|
||||
} while (atomic_cmpxchg_relaxed(&cmdq->lock, val, val + 1) != val);
|
||||
/*
|
||||
* Someone else is holding the lock in exclusive state, so wait
|
||||
* for them to finish. Since we already incremented the lock counter,
|
||||
* no exclusive lock can be acquired until we finish. We don't need
|
||||
* the return value since we only care that the exclusive lock is
|
||||
* released (i.e. the lock counter is non-negative).
|
||||
* Once the exclusive locker releases the lock, the sign bit will
|
||||
* be cleared and our increment will make the lock counter positive,
|
||||
* allowing us to proceed.
|
||||
*/
|
||||
atomic_cond_read_relaxed(&cmdq->lock, VAL > 0);
|
||||
}
|
||||
|
||||
static void arm_smmu_cmdq_shared_unlock(struct arm_smmu_cmdq *cmdq)
|
||||
@@ -527,9 +533,14 @@ static bool arm_smmu_cmdq_shared_tryunlock(struct arm_smmu_cmdq *cmdq)
|
||||
__ret; \
|
||||
})
|
||||
|
||||
/*
|
||||
* Only clear the sign bit when releasing the exclusive lock this will
|
||||
* allow any shared_lock() waiters to proceed without the possibility
|
||||
* of entering the exclusive lock in a tight loop.
|
||||
*/
|
||||
#define arm_smmu_cmdq_exclusive_unlock_irqrestore(cmdq, flags) \
|
||||
({ \
|
||||
atomic_set_release(&cmdq->lock, 0); \
|
||||
atomic_fetch_andnot_release(INT_MIN, &cmdq->lock); \
|
||||
local_irq_restore(flags); \
|
||||
})
|
||||
|
||||
@@ -1082,6 +1093,49 @@ void arm_smmu_get_ste_used(const __le64 *ent, __le64 *used_bits)
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(arm_smmu_get_ste_used);
|
||||
|
||||
VISIBLE_IF_KUNIT
|
||||
void arm_smmu_get_ste_update_safe(const __le64 *cur, const __le64 *target,
|
||||
__le64 *safe_bits)
|
||||
{
|
||||
const __le64 eats_s1chk =
|
||||
FIELD_PREP(STRTAB_STE_1_EATS, STRTAB_STE_1_EATS_S1CHK);
|
||||
const __le64 eats_trans =
|
||||
FIELD_PREP(STRTAB_STE_1_EATS, STRTAB_STE_1_EATS_TRANS);
|
||||
|
||||
/*
|
||||
* When an STE changes EATS_TRANS, the sequencing code in the attach
|
||||
* logic already will have the PCI cap for ATS disabled. Thus at this
|
||||
* moment we can expect that the device will not generate ATS queries
|
||||
* and so we don't care about the sequencing of EATS. The purpose of
|
||||
* EATS_TRANS is to protect the system from hostile untrusted devices
|
||||
* that issue ATS when the PCI config space is disabled. However, if
|
||||
* EATS_TRANS is being changed, then we must have already trusted the
|
||||
* device as the EATS_TRANS security block is being disabled.
|
||||
*
|
||||
* Note: now the EATS_TRANS update is moved to the first entry_set().
|
||||
* Changing S2S and EATS might transiently result in S2S=1 and EATS=1
|
||||
* which is a bad STE (see "5.2 Stream Table Entry"). In such a case,
|
||||
* we can't do a hitless update. Also, it should not be added to the
|
||||
* safe bits with STRTAB_STE_1_EATS_S1CHK, because EATS=0b11 would be
|
||||
* effectively an errant 0b00 configuration.
|
||||
*/
|
||||
if (!((cur[1] | target[1]) & cpu_to_le64(eats_s1chk)) &&
|
||||
!((cur[2] | target[2]) & cpu_to_le64(STRTAB_STE_2_S2S)))
|
||||
safe_bits[1] |= cpu_to_le64(eats_trans);
|
||||
|
||||
/*
|
||||
* MEV does not meaningfully impact the operation of the HW, it only
|
||||
* changes how many fault events are generated, thus we can relax it
|
||||
* when computing the ordering. The spec notes the device can act like
|
||||
* MEV=1 anyhow:
|
||||
*
|
||||
* Note: Software must expect, and be able to deal with, coalesced
|
||||
* fault records even when MEV == 0.
|
||||
*/
|
||||
safe_bits[1] |= cpu_to_le64(STRTAB_STE_1_MEV);
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(arm_smmu_get_ste_update_safe);
|
||||
|
||||
/*
|
||||
* Figure out if we can do a hitless update of entry to become target. Returns a
|
||||
* bit mask where 1 indicates that qword needs to be set disruptively.
|
||||
@@ -1094,13 +1148,22 @@ static u8 arm_smmu_entry_qword_diff(struct arm_smmu_entry_writer *writer,
|
||||
{
|
||||
__le64 target_used[NUM_ENTRY_QWORDS] = {};
|
||||
__le64 cur_used[NUM_ENTRY_QWORDS] = {};
|
||||
__le64 safe[NUM_ENTRY_QWORDS] = {};
|
||||
u8 used_qword_diff = 0;
|
||||
unsigned int i;
|
||||
|
||||
writer->ops->get_used(entry, cur_used);
|
||||
writer->ops->get_used(target, target_used);
|
||||
if (writer->ops->get_update_safe)
|
||||
writer->ops->get_update_safe(entry, target, safe);
|
||||
|
||||
for (i = 0; i != NUM_ENTRY_QWORDS; i++) {
|
||||
/*
|
||||
* Safe is only used for bits that are used by both entries,
|
||||
* otherwise it is sequenced according to the unused entry.
|
||||
*/
|
||||
safe[i] &= target_used[i] & cur_used[i];
|
||||
|
||||
/*
|
||||
* Check that masks are up to date, the make functions are not
|
||||
* allowed to set a bit to 1 if the used function doesn't say it
|
||||
@@ -1109,6 +1172,7 @@ static u8 arm_smmu_entry_qword_diff(struct arm_smmu_entry_writer *writer,
|
||||
WARN_ON_ONCE(target[i] & ~target_used[i]);
|
||||
|
||||
/* Bits can change because they are not currently being used */
|
||||
cur_used[i] &= ~safe[i];
|
||||
unused_update[i] = (entry[i] & cur_used[i]) |
|
||||
(target[i] & ~cur_used[i]);
|
||||
/*
|
||||
@@ -1121,7 +1185,7 @@ static u8 arm_smmu_entry_qword_diff(struct arm_smmu_entry_writer *writer,
|
||||
return used_qword_diff;
|
||||
}
|
||||
|
||||
static bool entry_set(struct arm_smmu_entry_writer *writer, __le64 *entry,
|
||||
static void entry_set(struct arm_smmu_entry_writer *writer, __le64 *entry,
|
||||
const __le64 *target, unsigned int start,
|
||||
unsigned int len)
|
||||
{
|
||||
@@ -1137,7 +1201,6 @@ static bool entry_set(struct arm_smmu_entry_writer *writer, __le64 *entry,
|
||||
|
||||
if (changed)
|
||||
writer->ops->sync(writer);
|
||||
return changed;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1207,12 +1270,9 @@ void arm_smmu_write_entry(struct arm_smmu_entry_writer *writer, __le64 *entry,
|
||||
entry_set(writer, entry, target, 0, 1);
|
||||
} else {
|
||||
/*
|
||||
* No inuse bit changed. Sanity check that all unused bits are 0
|
||||
* in the entry. The target was already sanity checked by
|
||||
* compute_qword_diff().
|
||||
* No inuse bit changed, though safe bits may have changed.
|
||||
*/
|
||||
WARN_ON_ONCE(
|
||||
entry_set(writer, entry, target, 0, NUM_ENTRY_QWORDS));
|
||||
entry_set(writer, entry, target, 0, NUM_ENTRY_QWORDS);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(arm_smmu_write_entry);
|
||||
@@ -1543,6 +1603,7 @@ static void arm_smmu_ste_writer_sync_entry(struct arm_smmu_entry_writer *writer)
|
||||
static const struct arm_smmu_entry_writer_ops arm_smmu_ste_writer_ops = {
|
||||
.sync = arm_smmu_ste_writer_sync_entry,
|
||||
.get_used = arm_smmu_get_ste_used,
|
||||
.get_update_safe = arm_smmu_get_ste_update_safe,
|
||||
};
|
||||
|
||||
static void arm_smmu_write_ste(struct arm_smmu_master *master, u32 sid,
|
||||
@@ -2551,7 +2612,7 @@ static int arm_smmu_domain_finalise(struct arm_smmu_domain *smmu_domain,
|
||||
ARM_SMMU_FEAT_VAX) ? 52 : 48;
|
||||
|
||||
pgtbl_cfg.ias = min_t(unsigned long, ias, VA_BITS);
|
||||
pgtbl_cfg.oas = smmu->ias;
|
||||
pgtbl_cfg.oas = smmu->oas;
|
||||
if (enable_dirty)
|
||||
pgtbl_cfg.quirks |= IO_PGTABLE_QUIRK_ARM_HD;
|
||||
fmt = ARM_64_LPAE_S1;
|
||||
@@ -2561,7 +2622,7 @@ static int arm_smmu_domain_finalise(struct arm_smmu_domain *smmu_domain,
|
||||
case ARM_SMMU_DOMAIN_S2:
|
||||
if (enable_dirty)
|
||||
return -EOPNOTSUPP;
|
||||
pgtbl_cfg.ias = smmu->ias;
|
||||
pgtbl_cfg.ias = smmu->oas;
|
||||
pgtbl_cfg.oas = smmu->oas;
|
||||
fmt = ARM_64_LPAE_S2;
|
||||
finalise_stage_fn = arm_smmu_domain_finalise_s2;
|
||||
@@ -3125,7 +3186,8 @@ int arm_smmu_set_pasid(struct arm_smmu_master *master,
|
||||
struct arm_smmu_domain *smmu_domain, ioasid_t pasid,
|
||||
struct arm_smmu_cd *cd, struct iommu_domain *old)
|
||||
{
|
||||
struct iommu_domain *sid_domain = iommu_get_domain_for_dev(master->dev);
|
||||
struct iommu_domain *sid_domain =
|
||||
iommu_driver_get_domain_for_dev(master->dev);
|
||||
struct arm_smmu_attach_state state = {
|
||||
.master = master,
|
||||
.ssid = pasid,
|
||||
@@ -3191,7 +3253,7 @@ static int arm_smmu_blocking_set_dev_pasid(struct iommu_domain *new_domain,
|
||||
*/
|
||||
if (!arm_smmu_ssids_in_use(&master->cd_table)) {
|
||||
struct iommu_domain *sid_domain =
|
||||
iommu_get_domain_for_dev(master->dev);
|
||||
iommu_driver_get_domain_for_dev(master->dev);
|
||||
|
||||
if (sid_domain->type == IOMMU_DOMAIN_IDENTITY ||
|
||||
sid_domain->type == IOMMU_DOMAIN_BLOCKED)
|
||||
@@ -4395,13 +4457,7 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
|
||||
}
|
||||
|
||||
/* We only support the AArch64 table format at present */
|
||||
switch (FIELD_GET(IDR0_TTF, reg)) {
|
||||
case IDR0_TTF_AARCH32_64:
|
||||
smmu->ias = 40;
|
||||
fallthrough;
|
||||
case IDR0_TTF_AARCH64:
|
||||
break;
|
||||
default:
|
||||
if (!(FIELD_GET(IDR0_TTF, reg) & IDR0_TTF_AARCH64)) {
|
||||
dev_err(smmu->dev, "AArch64 table format not supported!\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
@@ -4514,8 +4570,6 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
|
||||
dev_warn(smmu->dev,
|
||||
"failed to set DMA mask for table walker\n");
|
||||
|
||||
smmu->ias = max(smmu->ias, smmu->oas);
|
||||
|
||||
if ((smmu->features & ARM_SMMU_FEAT_TRANS_S1) &&
|
||||
(smmu->features & ARM_SMMU_FEAT_TRANS_S2))
|
||||
smmu->features |= ARM_SMMU_FEAT_NESTING;
|
||||
@@ -4525,11 +4579,40 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
|
||||
if (arm_smmu_sva_supported(smmu))
|
||||
smmu->features |= ARM_SMMU_FEAT_SVA;
|
||||
|
||||
dev_info(smmu->dev, "ias %lu-bit, oas %lu-bit (features 0x%08x)\n",
|
||||
smmu->ias, smmu->oas, smmu->features);
|
||||
dev_info(smmu->dev, "oas %lu-bit (features 0x%08x)\n",
|
||||
smmu->oas, smmu->features);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_TEGRA241_CMDQV
|
||||
static void tegra_cmdqv_dt_probe(struct device_node *smmu_node,
|
||||
struct arm_smmu_device *smmu)
|
||||
{
|
||||
struct platform_device *pdev;
|
||||
struct device_node *np;
|
||||
|
||||
np = of_parse_phandle(smmu_node, "nvidia,cmdqv", 0);
|
||||
if (!np)
|
||||
return;
|
||||
|
||||
/* Tegra241 CMDQV driver is responsible for put_device() */
|
||||
pdev = of_find_device_by_node(np);
|
||||
of_node_put(np);
|
||||
if (!pdev)
|
||||
return;
|
||||
|
||||
smmu->impl_dev = &pdev->dev;
|
||||
smmu->options |= ARM_SMMU_OPT_TEGRA241_CMDQV;
|
||||
dev_dbg(smmu->dev, "found companion CMDQV device: %s\n",
|
||||
dev_name(smmu->impl_dev));
|
||||
}
|
||||
#else
|
||||
static void tegra_cmdqv_dt_probe(struct device_node *smmu_node,
|
||||
struct arm_smmu_device *smmu)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_ACPI
|
||||
#ifdef CONFIG_TEGRA241_CMDQV
|
||||
static void acpi_smmu_dsdt_probe_tegra241_cmdqv(struct acpi_iort_node *node,
|
||||
@@ -4542,10 +4625,11 @@ static void acpi_smmu_dsdt_probe_tegra241_cmdqv(struct acpi_iort_node *node,
|
||||
adev = acpi_dev_get_first_match_dev("NVDA200C", uid, -1);
|
||||
if (adev) {
|
||||
/* Tegra241 CMDQV driver is responsible for put_device() */
|
||||
smmu->impl_dev = &adev->dev;
|
||||
smmu->impl_dev = get_device(acpi_get_first_physical_node(adev));
|
||||
smmu->options |= ARM_SMMU_OPT_TEGRA241_CMDQV;
|
||||
dev_info(smmu->dev, "found companion CMDQV device: %s\n",
|
||||
dev_name(smmu->impl_dev));
|
||||
acpi_dev_put(adev);
|
||||
}
|
||||
kfree(uid);
|
||||
}
|
||||
@@ -4634,6 +4718,9 @@ static int arm_smmu_device_dt_probe(struct platform_device *pdev,
|
||||
if (of_dma_is_coherent(dev->of_node))
|
||||
smmu->features |= ARM_SMMU_FEAT_COHERENCY;
|
||||
|
||||
if (of_device_is_compatible(dev->of_node, "nvidia,tegra264-smmu"))
|
||||
tegra_cmdqv_dt_probe(dev->of_node, smmu);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ struct arm_vsmmu;
|
||||
#define IDR0_COHACC (1 << 4)
|
||||
#define IDR0_TTF GENMASK(3, 2)
|
||||
#define IDR0_TTF_AARCH64 2
|
||||
#define IDR0_TTF_AARCH32_64 3
|
||||
#define IDR0_S1P (1 << 1)
|
||||
#define IDR0_S2P (1 << 0)
|
||||
|
||||
@@ -784,7 +783,6 @@ struct arm_smmu_device {
|
||||
int gerr_irq;
|
||||
int combined_irq;
|
||||
|
||||
unsigned long ias; /* IPA */
|
||||
unsigned long oas; /* PA */
|
||||
unsigned long pgsize_bitmap;
|
||||
|
||||
@@ -900,6 +898,8 @@ struct arm_smmu_entry_writer {
|
||||
|
||||
struct arm_smmu_entry_writer_ops {
|
||||
void (*get_used)(const __le64 *entry, __le64 *used);
|
||||
void (*get_update_safe)(const __le64 *cur, const __le64 *target,
|
||||
__le64 *safe_bits);
|
||||
void (*sync)(struct arm_smmu_entry_writer *writer);
|
||||
};
|
||||
|
||||
@@ -911,6 +911,8 @@ void arm_smmu_make_s2_domain_ste(struct arm_smmu_ste *target,
|
||||
|
||||
#if IS_ENABLED(CONFIG_KUNIT)
|
||||
void arm_smmu_get_ste_used(const __le64 *ent, __le64 *used_bits);
|
||||
void arm_smmu_get_ste_update_safe(const __le64 *cur, const __le64 *target,
|
||||
__le64 *safe_bits);
|
||||
void arm_smmu_write_entry(struct arm_smmu_entry_writer *writer, __le64 *cur,
|
||||
const __le64 *target);
|
||||
void arm_smmu_get_cd_used(const __le64 *ent, __le64 *used_bits);
|
||||
|
||||
@@ -3,17 +3,15 @@
|
||||
|
||||
#define dev_fmt(fmt) "tegra241_cmdqv: " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/iommu.h>
|
||||
#include <linux/iommufd.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <uapi/linux/iommufd.h>
|
||||
|
||||
#include <acpi/acpixf.h>
|
||||
|
||||
#include "arm-smmu-v3.h"
|
||||
|
||||
/* CMDQV register page base and size defines */
|
||||
@@ -854,69 +852,6 @@ static struct arm_smmu_impl_ops tegra241_cmdqv_impl_ops = {
|
||||
|
||||
/* Probe Functions */
|
||||
|
||||
static int tegra241_cmdqv_acpi_is_memory(struct acpi_resource *res, void *data)
|
||||
{
|
||||
struct resource_win win;
|
||||
|
||||
return !acpi_dev_resource_address_space(res, &win);
|
||||
}
|
||||
|
||||
static int tegra241_cmdqv_acpi_get_irqs(struct acpi_resource *ares, void *data)
|
||||
{
|
||||
struct resource r;
|
||||
int *irq = data;
|
||||
|
||||
if (*irq <= 0 && acpi_dev_resource_interrupt(ares, 0, &r))
|
||||
*irq = r.start;
|
||||
return 1; /* No need to add resource to the list */
|
||||
}
|
||||
|
||||
static struct resource *
|
||||
tegra241_cmdqv_find_acpi_resource(struct device *dev, int *irq)
|
||||
{
|
||||
struct acpi_device *adev = to_acpi_device(dev);
|
||||
struct list_head resource_list;
|
||||
struct resource_entry *rentry;
|
||||
struct resource *res = NULL;
|
||||
int ret;
|
||||
|
||||
INIT_LIST_HEAD(&resource_list);
|
||||
ret = acpi_dev_get_resources(adev, &resource_list,
|
||||
tegra241_cmdqv_acpi_is_memory, NULL);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to get memory resource: %d\n", ret);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rentry = list_first_entry_or_null(&resource_list,
|
||||
struct resource_entry, node);
|
||||
if (!rentry) {
|
||||
dev_err(dev, "failed to get memory resource entry\n");
|
||||
goto free_list;
|
||||
}
|
||||
|
||||
/* Caller must free the res */
|
||||
res = kzalloc(sizeof(*res), GFP_KERNEL);
|
||||
if (!res)
|
||||
goto free_list;
|
||||
|
||||
*res = *rentry->res;
|
||||
|
||||
acpi_dev_free_resource_list(&resource_list);
|
||||
|
||||
INIT_LIST_HEAD(&resource_list);
|
||||
|
||||
if (irq)
|
||||
ret = acpi_dev_get_resources(adev, &resource_list,
|
||||
tegra241_cmdqv_acpi_get_irqs, irq);
|
||||
if (ret < 0 || !irq || *irq <= 0)
|
||||
dev_warn(dev, "no interrupt. errors will not be reported\n");
|
||||
|
||||
free_list:
|
||||
acpi_dev_free_resource_list(&resource_list);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int tegra241_cmdqv_init_structures(struct arm_smmu_device *smmu)
|
||||
{
|
||||
struct tegra241_cmdqv *cmdqv =
|
||||
@@ -1042,18 +977,23 @@ iounmap:
|
||||
|
||||
struct arm_smmu_device *tegra241_cmdqv_probe(struct arm_smmu_device *smmu)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(smmu->impl_dev);
|
||||
struct arm_smmu_device *new_smmu;
|
||||
struct resource *res = NULL;
|
||||
struct resource *res;
|
||||
int irq;
|
||||
|
||||
if (!smmu->dev->of_node)
|
||||
res = tegra241_cmdqv_find_acpi_resource(smmu->impl_dev, &irq);
|
||||
if (!res)
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(&pdev->dev, "no memory resource found for CMDQV\n");
|
||||
goto out_fallback;
|
||||
}
|
||||
|
||||
irq = platform_get_irq_optional(pdev, 0);
|
||||
if (irq <= 0)
|
||||
dev_warn(&pdev->dev,
|
||||
"no interrupt. errors will not be reported\n");
|
||||
|
||||
new_smmu = __tegra241_cmdqv_probe(smmu, res, irq);
|
||||
kfree(res);
|
||||
|
||||
if (new_smmu)
|
||||
return new_smmu;
|
||||
|
||||
|
||||
@@ -41,12 +41,40 @@ static const struct of_device_id qcom_smmu_actlr_client_of_match[] = {
|
||||
.data = (const void *) (PREFETCH_DEEP | CPRE | CMTLB) },
|
||||
{ .compatible = "qcom,fastrpc",
|
||||
.data = (const void *) (PREFETCH_DEEP | CPRE | CMTLB) },
|
||||
{ .compatible = "qcom,qcm2290-mdss",
|
||||
.data = (const void *) (PREFETCH_SHALLOW | CPRE | CMTLB) },
|
||||
{ .compatible = "qcom,sa8775p-mdss",
|
||||
.data = (const void *) (PREFETCH_DEFAULT | CMTLB) },
|
||||
{ .compatible = "qcom,sc7280-mdss",
|
||||
.data = (const void *) (PREFETCH_SHALLOW | CPRE | CMTLB) },
|
||||
{ .compatible = "qcom,sc7280-venus",
|
||||
.data = (const void *) (PREFETCH_SHALLOW | CPRE | CMTLB) },
|
||||
{ .compatible = "qcom,sc8180x-mdss",
|
||||
.data = (const void *) (PREFETCH_SHALLOW | CPRE | CMTLB) },
|
||||
{ .compatible = "qcom,sc8280xp-mdss",
|
||||
.data = (const void *) (PREFETCH_SHALLOW | CPRE | CMTLB) },
|
||||
{ .compatible = "qcom,sm6115-mdss",
|
||||
.data = (const void *) (PREFETCH_SHALLOW | CPRE | CMTLB) },
|
||||
{ .compatible = "qcom,sm6125-mdss",
|
||||
.data = (const void *) (PREFETCH_SHALLOW | CPRE | CMTLB) },
|
||||
{ .compatible = "qcom,sm6350-mdss",
|
||||
.data = (const void *) (PREFETCH_SHALLOW | CPRE | CMTLB) },
|
||||
{ .compatible = "qcom,sm8150-mdss",
|
||||
.data = (const void *) (PREFETCH_SHALLOW | CPRE | CMTLB) },
|
||||
{ .compatible = "qcom,sm8250-mdss",
|
||||
.data = (const void *) (PREFETCH_SHALLOW | CPRE | CMTLB) },
|
||||
{ .compatible = "qcom,sm8350-mdss",
|
||||
.data = (const void *) (PREFETCH_SHALLOW | CPRE | CMTLB) },
|
||||
{ .compatible = "qcom,sm8450-mdss",
|
||||
.data = (const void *) (PREFETCH_DEFAULT | CMTLB) },
|
||||
{ .compatible = "qcom,sm8550-mdss",
|
||||
.data = (const void *) (PREFETCH_DEFAULT | CMTLB) },
|
||||
{ .compatible = "qcom,sm8650-mdss",
|
||||
.data = (const void *) (PREFETCH_DEFAULT | CMTLB) },
|
||||
{ .compatible = "qcom,sm8750-mdss",
|
||||
.data = (const void *) (PREFETCH_DEFAULT | CMTLB) },
|
||||
{ .compatible = "qcom,x1e80100-mdss",
|
||||
.data = (const void *) (PREFETCH_DEFAULT | CMTLB) },
|
||||
{ }
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user