Merge branches 'fixes', 'arm/smmu/updates', 'arm/smmu/bindings', 'riscv', 'intel/vt-d', 'amd/amd-vi' and 'core' into next

This commit is contained in:
Will Deacon
2026-04-09 13:18:27 +01:00
43 changed files with 2167 additions and 799 deletions
+7 -1
View File
@@ -207,8 +207,14 @@ stable kernels.
+----------------+-----------------+-----------------+-----------------------------+
| ARM | MMU-600 | #1076982,1209401| N/A |
+----------------+-----------------+-----------------+-----------------------------+
| ARM | MMU-700 | #2268618,2812531| N/A |
| ARM | MMU-700 | #2133013, | N/A |
| | | #2268618, | |
| | | #2812531, | |
| | | #3777127 | |
+----------------+-----------------+-----------------+-----------------------------+
| ARM | MMU L1 | #3878312 | N/A |
+----------------+-----------------+-----------------+-----------------------------+
| ARM | MMU S3 | #3995052 | N/A |
+----------------+-----------------+-----------------+-----------------------------+
| ARM | GIC-700 | #2941627 | ARM64_ERRATUM_2941627 |
+----------------+-----------------+-----------------+-----------------------------+
@@ -35,6 +35,7 @@ properties:
- description: Qcom SoCs implementing "qcom,smmu-500" and "arm,mmu-500"
items:
- enum:
- qcom,eliza-smmu-500
- qcom,glymur-smmu-500
- qcom,kaanapali-smmu-500
- qcom,milos-smmu-500
@@ -92,6 +93,7 @@ properties:
items:
- enum:
- qcom,glymur-smmu-500
- qcom,hawi-smmu-500
- qcom,kaanapali-smmu-500
- qcom,milos-smmu-500
- qcom,qcm2290-smmu-500
+31 -32
View File
@@ -26,22 +26,19 @@ static ssize_t iommu_mmio_write(struct file *filp, const char __user *ubuf,
{
struct seq_file *m = filp->private_data;
struct amd_iommu *iommu = m->private;
int ret;
iommu->dbg_mmio_offset = -1;
int ret, dbg_mmio_offset = iommu->dbg_mmio_offset = -1;
if (cnt > OFS_IN_SZ)
return -EINVAL;
ret = kstrtou32_from_user(ubuf, cnt, 0, &iommu->dbg_mmio_offset);
ret = kstrtou32_from_user(ubuf, cnt, 0, &dbg_mmio_offset);
if (ret)
return ret;
if (iommu->dbg_mmio_offset > iommu->mmio_phys_end - sizeof(u64)) {
iommu->dbg_mmio_offset = -1;
return -EINVAL;
}
if (dbg_mmio_offset > iommu->mmio_phys_end - sizeof(u64))
return -EINVAL;
iommu->dbg_mmio_offset = dbg_mmio_offset;
return cnt;
}
@@ -49,14 +46,16 @@ static int iommu_mmio_show(struct seq_file *m, void *unused)
{
struct amd_iommu *iommu = m->private;
u64 value;
int dbg_mmio_offset = iommu->dbg_mmio_offset;
if (iommu->dbg_mmio_offset < 0) {
if (dbg_mmio_offset < 0 || dbg_mmio_offset >
iommu->mmio_phys_end - sizeof(u64)) {
seq_puts(m, "Please provide mmio register's offset\n");
return 0;
}
value = readq(iommu->mmio_base + iommu->dbg_mmio_offset);
seq_printf(m, "Offset:0x%x Value:0x%016llx\n", iommu->dbg_mmio_offset, value);
value = readq(iommu->mmio_base + dbg_mmio_offset);
seq_printf(m, "Offset:0x%x Value:0x%016llx\n", dbg_mmio_offset, value);
return 0;
}
@@ -67,23 +66,20 @@ static ssize_t iommu_capability_write(struct file *filp, const char __user *ubuf
{
struct seq_file *m = filp->private_data;
struct amd_iommu *iommu = m->private;
int ret;
iommu->dbg_cap_offset = -1;
int ret, dbg_cap_offset = iommu->dbg_cap_offset = -1;
if (cnt > OFS_IN_SZ)
return -EINVAL;
ret = kstrtou32_from_user(ubuf, cnt, 0, &iommu->dbg_cap_offset);
ret = kstrtou32_from_user(ubuf, cnt, 0, &dbg_cap_offset);
if (ret)
return ret;
/* Capability register at offset 0x14 is the last IOMMU capability register. */
if (iommu->dbg_cap_offset > 0x14) {
iommu->dbg_cap_offset = -1;
if (dbg_cap_offset > 0x14)
return -EINVAL;
}
iommu->dbg_cap_offset = dbg_cap_offset;
return cnt;
}
@@ -91,21 +87,21 @@ static int iommu_capability_show(struct seq_file *m, void *unused)
{
struct amd_iommu *iommu = m->private;
u32 value;
int err;
int err, dbg_cap_offset = iommu->dbg_cap_offset;
if (iommu->dbg_cap_offset < 0) {
if (dbg_cap_offset < 0 || dbg_cap_offset > 0x14) {
seq_puts(m, "Please provide capability register's offset in the range [0x00 - 0x14]\n");
return 0;
}
err = pci_read_config_dword(iommu->dev, iommu->cap_ptr + iommu->dbg_cap_offset, &value);
err = pci_read_config_dword(iommu->dev, iommu->cap_ptr + dbg_cap_offset, &value);
if (err) {
seq_printf(m, "Not able to read capability register at 0x%x\n",
iommu->dbg_cap_offset);
dbg_cap_offset);
return 0;
}
seq_printf(m, "Offset:0x%x Value:0x%08x\n", iommu->dbg_cap_offset, value);
seq_printf(m, "Offset:0x%x Value:0x%08x\n", dbg_cap_offset, value);
return 0;
}
@@ -197,10 +193,11 @@ static ssize_t devid_write(struct file *filp, const char __user *ubuf,
static int devid_show(struct seq_file *m, void *unused)
{
u16 devid;
int sbdf_shadow = sbdf;
if (sbdf >= 0) {
devid = PCI_SBDF_TO_DEVID(sbdf);
seq_printf(m, "%04x:%02x:%02x.%x\n", PCI_SBDF_TO_SEGID(sbdf),
if (sbdf_shadow >= 0) {
devid = PCI_SBDF_TO_DEVID(sbdf_shadow);
seq_printf(m, "%04x:%02x:%02x.%x\n", PCI_SBDF_TO_SEGID(sbdf_shadow),
PCI_BUS_NUM(devid), PCI_SLOT(devid), PCI_FUNC(devid));
} else
seq_puts(m, "No or Invalid input provided\n");
@@ -237,13 +234,14 @@ static int iommu_devtbl_show(struct seq_file *m, void *unused)
{
struct amd_iommu_pci_seg *pci_seg;
u16 seg, devid;
int sbdf_shadow = sbdf;
if (sbdf < 0) {
if (sbdf_shadow < 0) {
seq_puts(m, "Enter a valid device ID to 'devid' file\n");
return 0;
}
seg = PCI_SBDF_TO_SEGID(sbdf);
devid = PCI_SBDF_TO_DEVID(sbdf);
seg = PCI_SBDF_TO_SEGID(sbdf_shadow);
devid = PCI_SBDF_TO_DEVID(sbdf_shadow);
for_each_pci_segment(pci_seg) {
if (pci_seg->id != seg)
@@ -336,19 +334,20 @@ static int iommu_irqtbl_show(struct seq_file *m, void *unused)
{
struct amd_iommu_pci_seg *pci_seg;
u16 devid, seg;
int sbdf_shadow = sbdf;
if (!irq_remapping_enabled) {
seq_puts(m, "Interrupt remapping is disabled\n");
return 0;
}
if (sbdf < 0) {
if (sbdf_shadow < 0) {
seq_puts(m, "Enter a valid device ID to 'devid' file\n");
return 0;
}
seg = PCI_SBDF_TO_SEGID(sbdf);
devid = PCI_SBDF_TO_DEVID(sbdf);
seg = PCI_SBDF_TO_SEGID(sbdf_shadow);
devid = PCI_SBDF_TO_DEVID(sbdf_shadow);
for_each_pci_segment(pci_seg) {
if (pci_seg->id != seg)
+6 -3
View File
@@ -848,10 +848,11 @@ static void __init free_command_buffer(struct amd_iommu *iommu)
void *__init iommu_alloc_4k_pages(struct amd_iommu *iommu, gfp_t gfp,
size_t size)
{
int nid = iommu->dev ? dev_to_node(&iommu->dev->dev) : NUMA_NO_NODE;
void *buf;
size = PAGE_ALIGN(size);
buf = iommu_alloc_pages_sz(gfp, size);
buf = iommu_alloc_pages_node_sz(nid, gfp, size);
if (!buf)
return NULL;
if (check_feature(FEATURE_SNP) &&
@@ -954,14 +955,16 @@ static int iommu_ga_log_enable(struct amd_iommu *iommu)
static int iommu_init_ga_log(struct amd_iommu *iommu)
{
int nid = iommu->dev ? dev_to_node(&iommu->dev->dev) : NUMA_NO_NODE;
if (!AMD_IOMMU_GUEST_IR_VAPIC(amd_iommu_guest_ir))
return 0;
iommu->ga_log = iommu_alloc_pages_sz(GFP_KERNEL, GA_LOG_SIZE);
iommu->ga_log = iommu_alloc_pages_node_sz(nid, GFP_KERNEL, GA_LOG_SIZE);
if (!iommu->ga_log)
goto err_out;
iommu->ga_log_tail = iommu_alloc_pages_sz(GFP_KERNEL, 8);
iommu->ga_log_tail = iommu_alloc_pages_node_sz(nid, GFP_KERNEL, 8);
if (!iommu->ga_log_tail)
goto err_out;
+33 -10
View File
@@ -403,11 +403,12 @@ struct iommu_dev_data *search_dev_data(struct amd_iommu *iommu, u16 devid)
return NULL;
}
static int clone_alias(struct pci_dev *pdev, u16 alias, void *data)
static int clone_alias(struct pci_dev *pdev_origin, u16 alias, void *data)
{
struct dev_table_entry new;
struct amd_iommu *iommu;
struct iommu_dev_data *dev_data, *alias_data;
struct pci_dev *pdev = data;
u16 devid = pci_dev_id(pdev);
int ret = 0;
@@ -454,9 +455,9 @@ static void clone_aliases(struct amd_iommu *iommu, struct device *dev)
* part of the PCI DMA aliases if it's bus differs
* from the original device.
*/
clone_alias(pdev, iommu->pci_seg->alias_table[pci_dev_id(pdev)], NULL);
clone_alias(pdev, iommu->pci_seg->alias_table[pci_dev_id(pdev)], pdev);
pci_for_each_dma_alias(pdev, clone_alias, NULL);
pci_for_each_dma_alias(pdev, clone_alias, pdev);
}
static void setup_aliases(struct amd_iommu *iommu, struct device *dev)
@@ -2991,13 +2992,17 @@ static bool amd_iommu_capable(struct device *dev, enum iommu_cap cap)
return amdr_ivrs_remap_support;
case IOMMU_CAP_ENFORCE_CACHE_COHERENCY:
return true;
case IOMMU_CAP_DEFERRED_FLUSH:
return true;
case IOMMU_CAP_DIRTY_TRACKING: {
struct amd_iommu *iommu = get_amd_iommu_from_dev(dev);
return amd_iommu_hd_support(iommu);
}
case IOMMU_CAP_PCI_ATS_SUPPORTED: {
struct iommu_dev_data *dev_data = dev_iommu_priv_get(dev);
return amd_iommu_iotlb_sup &&
(dev_data->flags & AMD_IOMMU_DEVICE_FLAG_ATS_SUP);
}
default:
break;
}
@@ -3179,26 +3184,44 @@ const struct iommu_ops amd_iommu_ops = {
static struct irq_chip amd_ir_chip;
static DEFINE_SPINLOCK(iommu_table_lock);
static int iommu_flush_dev_irt(struct pci_dev *unused, u16 devid, void *data)
{
int ret;
struct iommu_cmd cmd;
struct amd_iommu *iommu = data;
build_inv_irt(&cmd, devid);
ret = __iommu_queue_command_sync(iommu, &cmd, true);
return ret;
}
static void iommu_flush_irt_and_complete(struct amd_iommu *iommu, u16 devid)
{
int ret;
u64 data;
unsigned long flags;
struct iommu_cmd cmd, cmd2;
struct iommu_cmd cmd;
struct pci_dev *pdev = NULL;
struct iommu_dev_data *dev_data = search_dev_data(iommu, devid);
if (iommu->irtcachedis_enabled)
return;
build_inv_irt(&cmd, devid);
if (dev_data && dev_data->dev && dev_is_pci(dev_data->dev))
pdev = to_pci_dev(dev_data->dev);
raw_spin_lock_irqsave(&iommu->lock, flags);
data = get_cmdsem_val(iommu);
build_completion_wait(&cmd2, iommu, data);
build_completion_wait(&cmd, iommu, data);
ret = __iommu_queue_command_sync(iommu, &cmd, true);
if (pdev)
ret = pci_for_each_dma_alias(pdev, iommu_flush_dev_irt, iommu);
else
ret = iommu_flush_dev_irt(NULL, devid, iommu);
if (ret)
goto out_err;
ret = __iommu_queue_command_sync(iommu, &cmd2, false);
ret = __iommu_queue_command_sync(iommu, &cmd, false);
if (ret)
goto out_err;
raw_spin_unlock_irqrestore(&iommu->lock, flags);
@@ -122,15 +122,6 @@ void arm_smmu_make_sva_cd(struct arm_smmu_cd *target,
}
EXPORT_SYMBOL_IF_KUNIT(arm_smmu_make_sva_cd);
/*
* Cloned from the MAX_TLBI_OPS in arch/arm64/include/asm/tlbflush.h, this
* is used as a threshold to replace per-page TLBI commands to issue in the
* command queue with an address-space TLBI command, when SMMU w/o a range
* invalidation feature handles too many per-page TLBI commands, which will
* otherwise result in a soft lockup.
*/
#define CMDQ_MAX_TLBI_OPS (1 << (PAGE_SHIFT - 3))
static void arm_smmu_mm_arch_invalidate_secondary_tlbs(struct mmu_notifier *mn,
struct mm_struct *mm,
unsigned long start,
@@ -146,21 +137,8 @@ static void arm_smmu_mm_arch_invalidate_secondary_tlbs(struct mmu_notifier *mn,
* range. So do a simple translation here by calculating size correctly.
*/
size = end - start;
if (!(smmu_domain->smmu->features & ARM_SMMU_FEAT_RANGE_INV)) {
if (size >= CMDQ_MAX_TLBI_OPS * PAGE_SIZE)
size = 0;
} else {
if (size == ULONG_MAX)
size = 0;
}
if (!size)
arm_smmu_tlb_inv_asid(smmu_domain->smmu, smmu_domain->cd.asid);
else
arm_smmu_tlb_inv_range_asid(start, size, smmu_domain->cd.asid,
PAGE_SIZE, false, smmu_domain);
arm_smmu_atc_inv_domain(smmu_domain, start, size);
arm_smmu_domain_inv_range(smmu_domain, start, size, PAGE_SIZE, false);
}
static void arm_smmu_mm_release(struct mmu_notifier *mn, struct mm_struct *mm)
@@ -191,13 +169,13 @@ static void arm_smmu_mm_release(struct mmu_notifier *mn, struct mm_struct *mm)
}
spin_unlock_irqrestore(&smmu_domain->devices_lock, flags);
arm_smmu_tlb_inv_asid(smmu_domain->smmu, smmu_domain->cd.asid);
arm_smmu_atc_inv_domain(smmu_domain, 0, 0);
arm_smmu_domain_inv(smmu_domain);
}
static void arm_smmu_mmu_notifier_free(struct mmu_notifier *mn)
{
kfree(container_of(mn, struct arm_smmu_domain, mmu_notifier));
arm_smmu_domain_free(
container_of(mn, struct arm_smmu_domain, mmu_notifier));
}
static const struct mmu_notifier_ops arm_smmu_mmu_notifier_ops = {
@@ -301,7 +279,7 @@ static void arm_smmu_sva_domain_free(struct iommu_domain *domain)
/*
* Ensure the ASID is empty in the iommu cache before allowing reuse.
*/
arm_smmu_tlb_inv_asid(smmu_domain->smmu, smmu_domain->cd.asid);
arm_smmu_domain_inv(smmu_domain);
/*
* Notice that the arm_smmu_mm_arch_invalidate_secondary_tlbs op can
@@ -346,6 +324,7 @@ struct iommu_domain *arm_smmu_sva_domain_alloc(struct device *dev,
* ARM_SMMU_FEAT_RANGE_INV is present
*/
smmu_domain->domain.pgsize_bitmap = PAGE_SIZE;
smmu_domain->stage = ARM_SMMU_DOMAIN_SVA;
smmu_domain->smmu = smmu;
ret = xa_alloc(&arm_smmu_asid_xa, &asid, smmu_domain,
@@ -364,6 +343,6 @@ struct iommu_domain *arm_smmu_sva_domain_alloc(struct device *dev,
err_asid:
xa_erase(&arm_smmu_asid_xa, smmu_domain->cd.asid);
err_free:
kfree(smmu_domain);
arm_smmu_domain_free(smmu_domain);
return ERR_PTR(ret);
}
@@ -637,6 +637,140 @@ static void arm_smmu_v3_write_cd_test_sva_release(struct kunit *test)
NUM_EXPECTED_SYNCS(2));
}
static void arm_smmu_v3_invs_test_verify(struct kunit *test,
struct arm_smmu_invs *invs,
int num_invs, const int num_trashes,
const int *ids, const int *users,
const int *ssids)
{
KUNIT_EXPECT_EQ(test, invs->num_invs, num_invs);
KUNIT_EXPECT_EQ(test, invs->num_trashes, num_trashes);
while (num_invs--) {
KUNIT_EXPECT_EQ(test, invs->inv[num_invs].id, ids[num_invs]);
KUNIT_EXPECT_EQ(test, READ_ONCE(invs->inv[num_invs].users),
users[num_invs]);
KUNIT_EXPECT_EQ(test, invs->inv[num_invs].ssid, ssids[num_invs]);
}
}
static struct arm_smmu_invs invs1 = {
.num_invs = 3,
.inv = { { .type = INV_TYPE_S2_VMID, .id = 1, },
{ .type = INV_TYPE_S2_VMID_S1_CLEAR, .id = 1, },
{ .type = INV_TYPE_ATS, .id = 3, }, },
};
static struct arm_smmu_invs invs2 = {
.num_invs = 3,
.inv = { { .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
{ .type = INV_TYPE_ATS, .id = 4, },
{ .type = INV_TYPE_ATS, .id = 5, }, },
};
static struct arm_smmu_invs invs3 = {
.num_invs = 3,
.inv = { { .type = INV_TYPE_S2_VMID, .id = 1, }, /* duplicated */
{ .type = INV_TYPE_ATS, .id = 5, }, /* recover a trash */
{ .type = INV_TYPE_ATS, .id = 6, }, },
};
static struct arm_smmu_invs invs4 = {
.num_invs = 3,
.inv = { { .type = INV_TYPE_ATS, .id = 10, .ssid = 1 },
{ .type = INV_TYPE_ATS, .id = 10, .ssid = 3 },
{ .type = INV_TYPE_ATS, .id = 12, .ssid = 1 }, },
};
static struct arm_smmu_invs invs5 = {
.num_invs = 3,
.inv = { { .type = INV_TYPE_ATS, .id = 10, .ssid = 2 },
{ .type = INV_TYPE_ATS, .id = 10, .ssid = 3 }, /* duplicate */
{ .type = INV_TYPE_ATS, .id = 12, .ssid = 2 }, },
};
static void arm_smmu_v3_invs_test(struct kunit *test)
{
const int results1[3][3] = { { 1, 1, 3, }, { 1, 1, 1, }, { 0, 0, 0, } };
const int results2[3][5] = { { 1, 1, 3, 4, 5, }, { 2, 1, 1, 1, 1, }, { 0, 0, 0, 0, 0, } };
const int results3[3][3] = { { 1, 1, 3, }, { 1, 1, 1, }, { 0, 0, 0, } };
const int results4[3][5] = { { 1, 1, 3, 5, 6, }, { 2, 1, 1, 1, 1, }, { 0, 0, 0, 0, 0, } };
const int results5[3][5] = { { 1, 1, 3, 5, 6, }, { 1, 0, 0, 1, 1, }, { 0, 0, 0, 0, 0, } };
const int results6[3][3] = { { 1, 5, 6, }, { 1, 1, 1, }, { 0, 0, 0, } };
const int results7[3][3] = { { 10, 10, 12, }, { 1, 1, 1, }, { 1, 3, 1, } };
const int results8[3][5] = { { 10, 10, 10, 12, 12, }, { 1, 1, 2, 1, 1, }, { 1, 2, 3, 1, 2, } };
const int results9[3][4] = { { 10, 10, 10, 12, }, { 1, 0, 1, 1, }, { 1, 2, 3, 1, } };
const int results10[3][3] = { { 10, 10, 12, }, { 1, 1, 1, }, { 1, 3, 1, } };
struct arm_smmu_invs *test_a, *test_b;
/* New array */
test_a = arm_smmu_invs_alloc(0);
KUNIT_EXPECT_EQ(test, test_a->num_invs, 0);
/* Test1: merge invs1 (new array) */
test_b = arm_smmu_invs_merge(test_a, &invs1);
kfree(test_a);
arm_smmu_v3_invs_test_verify(test, test_b, ARRAY_SIZE(results1[0]), 0,
results1[0], results1[1], results1[2]);
/* Test2: merge invs2 (new array) */
test_a = arm_smmu_invs_merge(test_b, &invs2);
kfree(test_b);
arm_smmu_v3_invs_test_verify(test, test_a, ARRAY_SIZE(results2[0]), 0,
results2[0], results2[1], results2[2]);
/* Test3: unref invs2 (same array) */
arm_smmu_invs_unref(test_a, &invs2);
arm_smmu_v3_invs_test_verify(test, test_a, ARRAY_SIZE(results3[0]), 0,
results3[0], results3[1], results3[2]);
/* Test4: merge invs3 (new array) */
test_b = arm_smmu_invs_merge(test_a, &invs3);
kfree(test_a);
arm_smmu_v3_invs_test_verify(test, test_b, ARRAY_SIZE(results4[0]), 0,
results4[0], results4[1], results4[2]);
/* Test5: unref invs1 (same array) */
arm_smmu_invs_unref(test_b, &invs1);
arm_smmu_v3_invs_test_verify(test, test_b, ARRAY_SIZE(results5[0]), 2,
results5[0], results5[1], results5[2]);
/* Test6: purge test_b (new array) */
test_a = arm_smmu_invs_purge(test_b);
kfree(test_b);
arm_smmu_v3_invs_test_verify(test, test_a, ARRAY_SIZE(results6[0]), 0,
results6[0], results6[1], results6[2]);
/* Test7: unref invs3 (same array) */
arm_smmu_invs_unref(test_a, &invs3);
KUNIT_EXPECT_EQ(test, test_a->num_invs, 0);
KUNIT_EXPECT_EQ(test, test_a->num_trashes, 0);
/* Test8: merge invs4 (new array) */
test_b = arm_smmu_invs_merge(test_a, &invs4);
kfree(test_a);
arm_smmu_v3_invs_test_verify(test, test_b, ARRAY_SIZE(results7[0]), 0,
results7[0], results7[1], results7[2]);
/* Test9: merge invs5 (new array) */
test_a = arm_smmu_invs_merge(test_b, &invs5);
kfree(test_b);
arm_smmu_v3_invs_test_verify(test, test_a, ARRAY_SIZE(results8[0]), 0,
results8[0], results8[1], results8[2]);
/* Test10: unref invs5 (same array) */
arm_smmu_invs_unref(test_a, &invs5);
arm_smmu_v3_invs_test_verify(test, test_a, ARRAY_SIZE(results9[0]), 1,
results9[0], results9[1], results9[2]);
/* Test11: purge test_a (new array) */
test_b = arm_smmu_invs_purge(test_a);
kfree(test_a);
arm_smmu_v3_invs_test_verify(test, test_b, ARRAY_SIZE(results10[0]), 0,
results10[0], results10[1], results10[2]);
kfree(test_b);
}
static struct kunit_case arm_smmu_v3_test_cases[] = {
KUNIT_CASE(arm_smmu_v3_write_ste_test_bypass_to_abort),
KUNIT_CASE(arm_smmu_v3_write_ste_test_abort_to_bypass),
@@ -662,6 +796,7 @@ static struct kunit_case arm_smmu_v3_test_cases[] = {
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),
KUNIT_CASE(arm_smmu_v3_invs_test),
{},
};
File diff suppressed because it is too large Load Diff
+136 -6
View File
@@ -648,6 +648,93 @@ struct arm_smmu_cmdq_batch {
int num;
};
/*
* The order here also determines the sequence in which commands are sent to the
* command queue. E.g. TLBI must be done before ATC_INV.
*/
enum arm_smmu_inv_type {
INV_TYPE_S1_ASID,
INV_TYPE_S2_VMID,
INV_TYPE_S2_VMID_S1_CLEAR,
INV_TYPE_ATS,
INV_TYPE_ATS_FULL,
};
struct arm_smmu_inv {
struct arm_smmu_device *smmu;
u8 type;
u8 size_opcode;
u8 nsize_opcode;
u32 id; /* ASID or VMID or SID */
union {
size_t pgsize; /* ARM_SMMU_FEAT_RANGE_INV */
u32 ssid; /* INV_TYPE_ATS */
};
int users; /* users=0 to mark as a trash to be purged */
};
static inline bool arm_smmu_inv_is_ats(const struct arm_smmu_inv *inv)
{
return inv->type == INV_TYPE_ATS || inv->type == INV_TYPE_ATS_FULL;
}
/**
* struct arm_smmu_invs - Per-domain invalidation array
* @max_invs: maximum capacity of the flexible array
* @num_invs: number of invalidations in the flexible array. May be smaller than
* @max_invs after a tailing trash entry is excluded, but must not be
* greater than @max_invs
* @num_trashes: number of trash entries in the array for arm_smmu_invs_purge().
* Must not be greater than @num_invs
* @rwlock: optional rwlock to fence ATS operations
* @has_ats: flag if the array contains an INV_TYPE_ATS or INV_TYPE_ATS_FULL
* @rcu: rcu head for kfree_rcu()
* @inv: flexible invalidation array
*
* The arm_smmu_invs is an RCU data structure. During a ->attach_dev callback,
* arm_smmu_invs_merge(), arm_smmu_invs_unref() and arm_smmu_invs_purge() will
* be used to allocate a new copy of an old array for addition and deletion in
* the old domain's and new domain's invs arrays.
*
* The arm_smmu_invs_unref() mutates a given array, by internally reducing the
* users counts of some given entries. This exists to support a no-fail routine
* like attaching to an IOMMU_DOMAIN_BLOCKED. And it could pair with a followup
* arm_smmu_invs_purge() call to generate a new clean array.
*
* Concurrent invalidation thread will push every invalidation described in the
* array into the command queue for each invalidation event. It is designed like
* this to optimize the invalidation fast path by avoiding locks.
*
* A domain can be shared across SMMU instances. When an instance gets removed,
* it would delete all the entries that belong to that SMMU instance. Then, a
* synchronize_rcu() would have to be called to sync the array, to prevent any
* concurrent invalidation thread accessing the old array from issuing commands
* to the command queue of a removed SMMU instance.
*/
struct arm_smmu_invs {
size_t max_invs;
size_t num_invs;
size_t num_trashes;
rwlock_t rwlock;
bool has_ats;
struct rcu_head rcu;
struct arm_smmu_inv inv[] __counted_by(max_invs);
};
static inline struct arm_smmu_invs *arm_smmu_invs_alloc(size_t num_invs)
{
struct arm_smmu_invs *new_invs;
new_invs = kzalloc(struct_size(new_invs, inv, num_invs), GFP_KERNEL);
if (!new_invs)
return NULL;
new_invs->max_invs = num_invs;
new_invs->num_invs = num_invs;
rwlock_init(&new_invs->rwlock);
return new_invs;
}
struct arm_smmu_evtq {
struct arm_smmu_queue q;
struct iopf_queue *iopf;
@@ -841,6 +928,14 @@ struct arm_smmu_master {
struct arm_smmu_device *smmu;
struct device *dev;
struct arm_smmu_stream *streams;
/*
* Scratch memory for a to_merge or to_unref array to build a per-domain
* invalidation array. It'll be pre-allocated with enough enries for all
* possible build scenarios. It can be used by only one caller at a time
* until the arm_smmu_invs_merge/unref() finishes. Must be locked by the
* iommu_group mutex.
*/
struct arm_smmu_invs *build_invs;
struct arm_smmu_vmaster *vmaster; /* use smmu->streams_mutex */
/* Locked by the iommu core using the group mutex */
struct arm_smmu_ctx_desc_cfg cd_table;
@@ -856,6 +951,7 @@ struct arm_smmu_master {
enum arm_smmu_domain_stage {
ARM_SMMU_DOMAIN_S1 = 0,
ARM_SMMU_DOMAIN_S2,
ARM_SMMU_DOMAIN_SVA,
};
struct arm_smmu_domain {
@@ -872,6 +968,8 @@ struct arm_smmu_domain {
struct iommu_domain domain;
struct arm_smmu_invs __rcu *invs;
/* List of struct arm_smmu_master_domain */
struct list_head devices;
spinlock_t devices_lock;
@@ -924,6 +1022,12 @@ void arm_smmu_make_cdtable_ste(struct arm_smmu_ste *target,
void arm_smmu_make_sva_cd(struct arm_smmu_cd *target,
struct arm_smmu_master *master, struct mm_struct *mm,
u16 asid);
struct arm_smmu_invs *arm_smmu_invs_merge(struct arm_smmu_invs *invs,
struct arm_smmu_invs *to_merge);
void arm_smmu_invs_unref(struct arm_smmu_invs *invs,
struct arm_smmu_invs *to_unref);
struct arm_smmu_invs *arm_smmu_invs_purge(struct arm_smmu_invs *invs);
#endif
struct arm_smmu_master_domain {
@@ -955,6 +1059,13 @@ extern struct mutex arm_smmu_asid_lock;
struct arm_smmu_domain *arm_smmu_domain_alloc(void);
static inline void arm_smmu_domain_free(struct arm_smmu_domain *smmu_domain)
{
/* No concurrency with invalidation is possible at this point */
kfree(rcu_dereference_protected(smmu_domain->invs, true));
kfree(smmu_domain);
}
void arm_smmu_clear_cd(struct arm_smmu_master *master, ioasid_t ssid);
struct arm_smmu_cd *arm_smmu_get_cd_ptr(struct arm_smmu_master *master,
u32 ssid);
@@ -969,12 +1080,14 @@ 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);
void arm_smmu_tlb_inv_asid(struct arm_smmu_device *smmu, u16 asid);
void arm_smmu_tlb_inv_range_asid(unsigned long iova, size_t size, int asid,
size_t granule, bool leaf,
struct arm_smmu_domain *smmu_domain);
int arm_smmu_atc_inv_domain(struct arm_smmu_domain *smmu_domain,
unsigned long iova, size_t size);
void arm_smmu_domain_inv_range(struct arm_smmu_domain *smmu_domain,
unsigned long iova, size_t size,
unsigned int granule, bool leaf);
static inline void arm_smmu_domain_inv(struct arm_smmu_domain *smmu_domain)
{
arm_smmu_domain_inv_range(smmu_domain, 0, 0, 0, false);
}
void __arm_smmu_cmdq_skip_err(struct arm_smmu_device *smmu,
struct arm_smmu_cmdq *cmdq);
@@ -991,6 +1104,21 @@ static inline bool arm_smmu_master_canwbs(struct arm_smmu_master *master)
IOMMU_FWSPEC_PCI_RC_CANWBS;
}
/**
* struct arm_smmu_inv_state - Per-domain invalidation array state
* @invs_ptr: points to the domain->invs (unwinding nesting/etc.) or is NULL if
* no change should be made
* @old_invs: the original invs array
* @new_invs: for new domain, this is the new invs array to update domain->invs;
* for old domain, this is the master->build_invs to pass in as the
* to_unref argument to an arm_smmu_invs_unref() call
*/
struct arm_smmu_inv_state {
struct arm_smmu_invs __rcu **invs_ptr;
struct arm_smmu_invs *old_invs;
struct arm_smmu_invs *new_invs;
};
struct arm_smmu_attach_state {
/* Inputs */
struct iommu_domain *old_domain;
@@ -1000,6 +1128,8 @@ struct arm_smmu_attach_state {
ioasid_t ssid;
/* Resulting state */
struct arm_smmu_vmaster *vmaster;
struct arm_smmu_inv_state old_domain_invst;
struct arm_smmu_inv_state new_domain_invst;
bool ats_enabled;
};
@@ -479,6 +479,10 @@ static int tegra241_vcmdq_hw_init(struct tegra241_vcmdq *vcmdq)
/* Reset VCMDQ */
tegra241_vcmdq_hw_deinit(vcmdq);
/* vintf->hyp_own is a HW state finalized in tegra241_vintf_hw_init() */
if (!vcmdq->vintf->hyp_own)
vcmdq->cmdq.supports_cmd = tegra241_guest_vcmdq_supports_cmd;
/* Configure and enable VCMDQ */
writeq_relaxed(vcmdq->cmdq.q.q_base, REG_VCMDQ_PAGE1(vcmdq, BASE));
@@ -639,9 +643,6 @@ static int tegra241_vcmdq_alloc_smmu_cmdq(struct tegra241_vcmdq *vcmdq)
q->q_base = q->base_dma & VCMDQ_ADDR;
q->q_base |= FIELD_PREP(VCMDQ_LOG2SIZE, q->llq.max_n_shift);
if (!vcmdq->vintf->hyp_own)
cmdq->supports_cmd = tegra241_guest_vcmdq_supports_cmd;
return arm_smmu_cmdq_init(smmu, cmdq);
}
+12 -1
View File
@@ -14,6 +14,7 @@
#include <linux/device.h>
#include <linux/dma-direct.h>
#include <linux/dma-map-ops.h>
#include <linux/generic_pt/iommu.h>
#include <linux/gfp.h>
#include <linux/huge_mm.h>
#include <linux/iommu.h>
@@ -648,6 +649,15 @@ static void iommu_dma_init_options(struct iommu_dma_options *options,
}
}
static bool iommu_domain_supports_fq(struct device *dev,
struct iommu_domain *domain)
{
/* iommupt always supports DMA-FQ */
if (iommupt_from_domain(domain))
return true;
return device_iommu_capable(dev, IOMMU_CAP_DEFERRED_FLUSH);
}
/**
* iommu_dma_init_domain - Initialise a DMA mapping domain
* @domain: IOMMU domain previously prepared by iommu_get_dma_cookie()
@@ -706,7 +716,8 @@ static int iommu_dma_init_domain(struct iommu_domain *domain, struct device *dev
/* If the FQ fails we can simply fall back to strict mode */
if (domain->type == IOMMU_DOMAIN_DMA_FQ &&
(!device_iommu_capable(dev, IOMMU_CAP_DEFERRED_FLUSH) || iommu_dma_init_fq(domain)))
(!iommu_domain_supports_fq(dev, domain) ||
iommu_dma_init_fq(domain)))
domain->type = IOMMU_DOMAIN_DMA;
return iova_reserve_iommu_regions(dev, domain);
+1
View File
@@ -5,6 +5,7 @@ CONFIG_DEBUG_GENERIC_PT=y
CONFIG_IOMMU_PT=y
CONFIG_IOMMU_PT_AMDV1=y
CONFIG_IOMMU_PT_VTDSS=y
CONFIG_IOMMU_PT_RISCV64=y
CONFIG_IOMMU_PT_X86_64=y
CONFIG_IOMMU_PT_KUNIT_TEST=y
+11
View File
@@ -52,6 +52,16 @@ config IOMMU_PT_VTDSS
Selected automatically by an IOMMU driver that uses this format.
config IOMMU_PT_RISCV64
tristate "IOMMU page table for RISC-V 64 bit Sv57/Sv48/Sv39"
depends on !GENERIC_ATOMIC64 # for cmpxchg64
help
iommu_domain implementation for RISC-V 64 bit 3/4/5 level page table.
It supports 4K/2M/1G/512G/256T page sizes and can decode a sign
extended portion of the 64 bit IOVA space.
Selected automatically by an IOMMU driver that uses this format.
config IOMMU_PT_X86_64
tristate "IOMMU page table for x86 64-bit, 4/5 levels"
depends on !GENERIC_ATOMIC64 # for cmpxchg64
@@ -66,6 +76,7 @@ config IOMMU_PT_KUNIT_TEST
tristate "IOMMU Page Table KUnit Test" if !KUNIT_ALL_TESTS
depends on KUNIT
depends on IOMMU_PT_AMDV1 || !IOMMU_PT_AMDV1
depends on IOMMU_PT_RISCV64 || !IOMMU_PT_RISCV64
depends on IOMMU_PT_X86_64 || !IOMMU_PT_X86_64
depends on IOMMU_PT_VTDSS || !IOMMU_PT_VTDSS
default KUNIT_ALL_TESTS
+2
View File
@@ -5,6 +5,8 @@ iommu_pt_fmt-$(CONFIG_IOMMUFD_TEST) += mock
iommu_pt_fmt-$(CONFIG_IOMMU_PT_VTDSS) += vtdss
iommu_pt_fmt-$(CONFIG_IOMMU_PT_RISCV64) += riscv64
iommu_pt_fmt-$(CONFIG_IOMMU_PT_X86_64) += x86_64
IOMMU_PT_KUNIT_TEST :=
+1 -1
View File
@@ -191,7 +191,7 @@ static inline enum pt_entry_type amdv1pt_load_entry_raw(struct pt_state *pts)
}
#define pt_load_entry_raw amdv1pt_load_entry_raw
static inline void
static __always_inline void
amdv1pt_install_leaf_entry(struct pt_state *pts, pt_oaddr_t oa,
unsigned int oasz_lg2,
const struct pt_write_attrs *attrs)
+29
View File
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES
*
*/
#ifndef __GENERIC_PT_FMT_DEFS_RISCV_H
#define __GENERIC_PT_FMT_DEFS_RISCV_H
#include <linux/generic_pt/common.h>
#include <linux/types.h>
#ifdef PT_RISCV_32BIT
typedef u32 pt_riscv_entry_t;
#define riscvpt_write_attrs riscv32pt_write_attrs
#else
typedef u64 pt_riscv_entry_t;
#define riscvpt_write_attrs riscv64pt_write_attrs
#endif
typedef pt_riscv_entry_t pt_vaddr_t;
typedef u64 pt_oaddr_t;
struct riscvpt_write_attrs {
pt_riscv_entry_t descriptor_bits;
gfp_t gfp;
};
#define pt_write_attrs riscvpt_write_attrs
#endif
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES
*/
#define PT_FMT riscv
#define PT_FMT_VARIANT 64
#define PT_SUPPORTED_FEATURES \
(BIT(PT_FEAT_SIGN_EXTEND) | BIT(PT_FEAT_FLUSH_RANGE) | \
BIT(PT_FEAT_RISCV_SVNAPOT_64K))
#include "iommu_template.h"
+313
View File
@@ -0,0 +1,313 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES
*
* RISC-V page table
*
* This is described in Sections:
* 12.3. Sv32: Page-Based 32-bit Virtual-Memory Systems
* 12.4. Sv39: Page-Based 39-bit Virtual-Memory System
* 12.5. Sv48: Page-Based 48-bit Virtual-Memory System
* 12.6. Sv57: Page-Based 57-bit Virtual-Memory System
* of the "The RISC-V Instruction Set Manual: Volume II"
*
* This includes the contiguous page extension from:
* Chapter 13. "Svnapot" Extension for NAPOT Translation Contiguity,
* Version 1.0
*
* The table format is sign extended and supports leafs in every level. The spec
* doesn't talk a lot about levels, but level here is the same as i=LEVELS-1 in
* the spec.
*/
#ifndef __GENERIC_PT_FMT_RISCV_H
#define __GENERIC_PT_FMT_RISCV_H
#include "defs_riscv.h"
#include "../pt_defs.h"
#include <linux/bitfield.h>
#include <linux/container_of.h>
#include <linux/log2.h>
#include <linux/sizes.h>
enum {
PT_ITEM_WORD_SIZE = sizeof(pt_riscv_entry_t),
#ifdef PT_RISCV_32BIT
PT_MAX_VA_ADDRESS_LG2 = 32,
PT_MAX_OUTPUT_ADDRESS_LG2 = 34,
PT_MAX_TOP_LEVEL = 1,
#else
PT_MAX_VA_ADDRESS_LG2 = 57,
PT_MAX_OUTPUT_ADDRESS_LG2 = 56,
PT_MAX_TOP_LEVEL = 4,
#endif
PT_GRANULE_LG2SZ = 12,
PT_TABLEMEM_LG2SZ = 12,
/* fsc.PPN is 44 bits wide, all PPNs are 4k aligned */
PT_TOP_PHYS_MASK = GENMASK_ULL(55, 12),
};
/* PTE bits */
enum {
RISCVPT_V = BIT(0),
RISCVPT_R = BIT(1),
RISCVPT_W = BIT(2),
RISCVPT_X = BIT(3),
RISCVPT_U = BIT(4),
RISCVPT_G = BIT(5),
RISCVPT_A = BIT(6),
RISCVPT_D = BIT(7),
RISCVPT_RSW = GENMASK(9, 8),
RISCVPT_PPN32 = GENMASK(31, 10),
RISCVPT_PPN64 = GENMASK_ULL(53, 10),
RISCVPT_PPN64_64K = GENMASK_ULL(53, 14),
RISCVPT_PBMT = GENMASK_ULL(62, 61),
RISCVPT_N = BIT_ULL(63),
/* Svnapot encodings for ppn[0] */
RISCVPT_PPN64_64K_SZ = BIT(13),
};
#ifdef PT_RISCV_32BIT
#define RISCVPT_PPN RISCVPT_PPN32
#define pt_riscv pt_riscv_32
#else
#define RISCVPT_PPN RISCVPT_PPN64
#define pt_riscv pt_riscv_64
#endif
#define common_to_riscvpt(common_ptr) \
container_of_const(common_ptr, struct pt_riscv, common)
#define to_riscvpt(pts) common_to_riscvpt((pts)->range->common)
static inline pt_oaddr_t riscvpt_table_pa(const struct pt_state *pts)
{
return oalog2_mul(FIELD_GET(RISCVPT_PPN, pts->entry), PT_GRANULE_LG2SZ);
}
#define pt_table_pa riscvpt_table_pa
static inline pt_oaddr_t riscvpt_entry_oa(const struct pt_state *pts)
{
if (pts_feature(pts, PT_FEAT_RISCV_SVNAPOT_64K) &&
pts->entry & RISCVPT_N) {
PT_WARN_ON(pts->level != 0);
return oalog2_mul(FIELD_GET(RISCVPT_PPN64_64K, pts->entry),
ilog2(SZ_64K));
}
return oalog2_mul(FIELD_GET(RISCVPT_PPN, pts->entry), PT_GRANULE_LG2SZ);
}
#define pt_entry_oa riscvpt_entry_oa
static inline bool riscvpt_can_have_leaf(const struct pt_state *pts)
{
return true;
}
#define pt_can_have_leaf riscvpt_can_have_leaf
/* Body in pt_fmt_defaults.h */
static inline unsigned int pt_table_item_lg2sz(const struct pt_state *pts);
static inline unsigned int
riscvpt_entry_num_contig_lg2(const struct pt_state *pts)
{
if (PT_SUPPORTED_FEATURE(PT_FEAT_RISCV_SVNAPOT_64K) &&
pts->entry & RISCVPT_N) {
PT_WARN_ON(!pts_feature(pts, PT_FEAT_RISCV_SVNAPOT_64K));
PT_WARN_ON(pts->level);
return ilog2(16);
}
return ilog2(1);
}
#define pt_entry_num_contig_lg2 riscvpt_entry_num_contig_lg2
static inline unsigned int riscvpt_num_items_lg2(const struct pt_state *pts)
{
return PT_TABLEMEM_LG2SZ - ilog2(sizeof(u64));
}
#define pt_num_items_lg2 riscvpt_num_items_lg2
static inline unsigned short
riscvpt_contig_count_lg2(const struct pt_state *pts)
{
if (pts->level == 0 && pts_feature(pts, PT_FEAT_RISCV_SVNAPOT_64K))
return ilog2(16);
return ilog2(1);
}
#define pt_contig_count_lg2 riscvpt_contig_count_lg2
static inline enum pt_entry_type riscvpt_load_entry_raw(struct pt_state *pts)
{
const pt_riscv_entry_t *tablep = pt_cur_table(pts, pt_riscv_entry_t);
pt_riscv_entry_t entry;
pts->entry = entry = READ_ONCE(tablep[pts->index]);
if (!(entry & RISCVPT_V))
return PT_ENTRY_EMPTY;
if (pts->level == 0 ||
((entry & (RISCVPT_X | RISCVPT_W | RISCVPT_R)) != 0))
return PT_ENTRY_OA;
return PT_ENTRY_TABLE;
}
#define pt_load_entry_raw riscvpt_load_entry_raw
static inline void
riscvpt_install_leaf_entry(struct pt_state *pts, pt_oaddr_t oa,
unsigned int oasz_lg2,
const struct pt_write_attrs *attrs)
{
pt_riscv_entry_t *tablep = pt_cur_table(pts, pt_riscv_entry_t);
pt_riscv_entry_t entry;
if (!pt_check_install_leaf_args(pts, oa, oasz_lg2))
return;
entry = RISCVPT_V |
FIELD_PREP(RISCVPT_PPN, log2_div(oa, PT_GRANULE_LG2SZ)) |
attrs->descriptor_bits;
if (pts_feature(pts, PT_FEAT_RISCV_SVNAPOT_64K) && pts->level == 0 &&
oasz_lg2 != PT_GRANULE_LG2SZ) {
u64 *end;
entry |= RISCVPT_N | RISCVPT_PPN64_64K_SZ;
tablep += pts->index;
end = tablep + log2_div(SZ_64K, PT_GRANULE_LG2SZ);
for (; tablep != end; tablep++)
WRITE_ONCE(*tablep, entry);
} else {
/* FIXME does riscv need this to be cmpxchg? */
WRITE_ONCE(tablep[pts->index], entry);
}
pts->entry = entry;
}
#define pt_install_leaf_entry riscvpt_install_leaf_entry
static inline bool riscvpt_install_table(struct pt_state *pts,
pt_oaddr_t table_pa,
const struct pt_write_attrs *attrs)
{
pt_riscv_entry_t entry;
entry = RISCVPT_V |
FIELD_PREP(RISCVPT_PPN, log2_div(table_pa, PT_GRANULE_LG2SZ));
return pt_table_install64(pts, entry);
}
#define pt_install_table riscvpt_install_table
static inline void riscvpt_attr_from_entry(const struct pt_state *pts,
struct pt_write_attrs *attrs)
{
attrs->descriptor_bits =
pts->entry & (RISCVPT_R | RISCVPT_W | RISCVPT_X | RISCVPT_U |
RISCVPT_G | RISCVPT_A | RISCVPT_D);
}
#define pt_attr_from_entry riscvpt_attr_from_entry
/* --- iommu */
#include <linux/generic_pt/iommu.h>
#include <linux/iommu.h>
#define pt_iommu_table pt_iommu_riscv_64
/* The common struct is in the per-format common struct */
static inline struct pt_common *common_from_iommu(struct pt_iommu *iommu_table)
{
return &container_of(iommu_table, struct pt_iommu_table, iommu)
->riscv_64pt.common;
}
static inline struct pt_iommu *iommu_from_common(struct pt_common *common)
{
return &container_of(common, struct pt_iommu_table, riscv_64pt.common)
->iommu;
}
static inline int riscvpt_iommu_set_prot(struct pt_common *common,
struct pt_write_attrs *attrs,
unsigned int iommu_prot)
{
u64 pte;
pte = RISCVPT_A | RISCVPT_U;
if (iommu_prot & IOMMU_WRITE)
pte |= RISCVPT_W | RISCVPT_R | RISCVPT_D;
if (iommu_prot & IOMMU_READ)
pte |= RISCVPT_R;
if (!(iommu_prot & IOMMU_NOEXEC))
pte |= RISCVPT_X;
/* Caller must specify a supported combination of flags */
if (unlikely((pte & (RISCVPT_X | RISCVPT_W | RISCVPT_R)) == 0))
return -EOPNOTSUPP;
attrs->descriptor_bits = pte;
return 0;
}
#define pt_iommu_set_prot riscvpt_iommu_set_prot
static inline int
riscvpt_iommu_fmt_init(struct pt_iommu_riscv_64 *iommu_table,
const struct pt_iommu_riscv_64_cfg *cfg)
{
struct pt_riscv *table = &iommu_table->riscv_64pt;
switch (cfg->common.hw_max_vasz_lg2) {
case 39:
pt_top_set_level(&table->common, 2);
break;
case 48:
pt_top_set_level(&table->common, 3);
break;
case 57:
pt_top_set_level(&table->common, 4);
break;
default:
return -EINVAL;
}
table->common.max_oasz_lg2 =
min(PT_MAX_OUTPUT_ADDRESS_LG2, cfg->common.hw_max_oasz_lg2);
return 0;
}
#define pt_iommu_fmt_init riscvpt_iommu_fmt_init
static inline void
riscvpt_iommu_fmt_hw_info(struct pt_iommu_riscv_64 *table,
const struct pt_range *top_range,
struct pt_iommu_riscv_64_hw_info *info)
{
phys_addr_t top_phys = virt_to_phys(top_range->top_table);
info->ppn = oalog2_div(top_phys, PT_GRANULE_LG2SZ);
PT_WARN_ON(top_phys & ~PT_TOP_PHYS_MASK);
/*
* See Table 3. Encodings of iosatp.MODE field" for DC.tx.SXL = 0:
* 8 = Sv39 = top level 2
* 9 = Sv38 = top level 3
* 10 = Sv57 = top level 4
*/
info->fsc_iosatp_mode = top_range->top_level + 6;
}
#define pt_iommu_fmt_hw_info riscvpt_iommu_fmt_hw_info
#if defined(GENERIC_PT_KUNIT)
static const struct pt_iommu_riscv_64_cfg riscv_64_kunit_fmt_cfgs[] = {
[0] = { .common.features = BIT(PT_FEAT_RISCV_SVNAPOT_64K),
.common.hw_max_oasz_lg2 = 56,
.common.hw_max_vasz_lg2 = 39 },
[1] = { .common.features = 0,
.common.hw_max_oasz_lg2 = 56,
.common.hw_max_vasz_lg2 = 48 },
[2] = { .common.features = BIT(PT_FEAT_RISCV_SVNAPOT_64K),
.common.hw_max_oasz_lg2 = 56,
.common.hw_max_vasz_lg2 = 57 },
};
#define kunit_fmt_cfgs riscv_64_kunit_fmt_cfgs
enum {
KUNIT_FMT_FEATURES = BIT(PT_FEAT_RISCV_SVNAPOT_64K),
};
#endif
#endif
+106 -85
View File
@@ -51,16 +51,27 @@ static void gather_range_pages(struct iommu_iotlb_gather *iotlb_gather,
iommu_pages_stop_incoherent_list(free_list,
iommu_table->iommu_device);
if (pt_feature(common, PT_FEAT_FLUSH_RANGE_NO_GAPS) &&
iommu_iotlb_gather_is_disjoint(iotlb_gather, iova, len)) {
iommu_iotlb_sync(&iommu_table->domain, iotlb_gather);
/*
* Note that the sync frees the gather's free list, so we must
* not have any pages on that list that are covered by iova/len
*/
/*
* If running in DMA-FQ mode then the unmap will be followed by an IOTLB
* flush all so we need to optimize by never flushing the IOTLB here.
*
* For NO_GAPS the user gets to pick if flushing all or doing micro
* flushes is better for their work load by choosing DMA vs DMA-FQ
* operation. Drivers should also see shadow_on_flush.
*/
if (!iommu_iotlb_gather_queued(iotlb_gather)) {
if (pt_feature(common, PT_FEAT_FLUSH_RANGE_NO_GAPS) &&
iommu_iotlb_gather_is_disjoint(iotlb_gather, iova, len)) {
iommu_iotlb_sync(&iommu_table->domain, iotlb_gather);
/*
* Note that the sync frees the gather's free list, so
* we must not have any pages on that list that are
* covered by iova/len
*/
}
iommu_iotlb_gather_add_range(iotlb_gather, iova, len);
}
iommu_iotlb_gather_add_range(iotlb_gather, iova, len);
iommu_pages_list_splice(free_list, &iotlb_gather->freelist);
}
@@ -466,6 +477,7 @@ struct pt_iommu_map_args {
pt_oaddr_t oa;
unsigned int leaf_pgsize_lg2;
unsigned int leaf_level;
pt_vaddr_t num_leaves;
};
/*
@@ -518,11 +530,15 @@ static int clear_contig(const struct pt_state *start_pts,
static int __map_range_leaf(struct pt_range *range, void *arg,
unsigned int level, struct pt_table_p *table)
{
struct pt_iommu *iommu_table = iommu_from_common(range->common);
struct pt_state pts = pt_init(range, level, table);
struct pt_iommu_map_args *map = arg;
unsigned int leaf_pgsize_lg2 = map->leaf_pgsize_lg2;
unsigned int start_index;
pt_oaddr_t oa = map->oa;
unsigned int num_leaves;
unsigned int orig_end;
pt_vaddr_t last_va;
unsigned int step;
bool need_contig;
int ret = 0;
@@ -536,6 +552,15 @@ static int __map_range_leaf(struct pt_range *range, void *arg,
_pt_iter_first(&pts);
start_index = pts.index;
orig_end = pts.end_index;
if (pts.index + map->num_leaves < pts.end_index) {
/* Need to stop in the middle of the table to change sizes */
pts.end_index = pts.index + map->num_leaves;
num_leaves = 0;
} else {
num_leaves = map->num_leaves - (pts.end_index - pts.index);
}
do {
pts.type = pt_load_entry_raw(&pts);
if (pts.type != PT_ENTRY_EMPTY || need_contig) {
@@ -561,7 +586,40 @@ static int __map_range_leaf(struct pt_range *range, void *arg,
flush_writes_range(&pts, start_index, pts.index);
map->oa = oa;
return ret;
map->num_leaves = num_leaves;
if (ret || num_leaves)
return ret;
/* range->va is not valid if we reached the end of the table */
pts.index -= step;
pt_index_to_va(&pts);
pts.index += step;
last_va = range->va + log2_to_int(leaf_pgsize_lg2);
if (last_va - 1 == range->last_va) {
PT_WARN_ON(pts.index != orig_end);
return 0;
}
/*
* Reached a point where the page size changed, compute the new
* parameters.
*/
map->leaf_pgsize_lg2 = pt_compute_best_pgsize(
iommu_table->domain.pgsize_bitmap, last_va, range->last_va, oa);
map->leaf_level =
pt_pgsz_lg2_to_level(range->common, map->leaf_pgsize_lg2);
map->num_leaves = pt_pgsz_count(iommu_table->domain.pgsize_bitmap,
last_va, range->last_va, oa,
map->leaf_pgsize_lg2);
/* Didn't finish this table level, caller will repeat it */
if (pts.index != orig_end) {
if (pts.index != start_index)
pt_index_to_va(&pts);
return -EAGAIN;
}
return 0;
}
static int __map_range(struct pt_range *range, void *arg, unsigned int level,
@@ -584,14 +642,9 @@ static int __map_range(struct pt_range *range, void *arg, unsigned int level,
if (pts.type != PT_ENTRY_EMPTY)
return -EADDRINUSE;
ret = pt_iommu_new_table(&pts, &map->attrs);
if (ret) {
/*
* Racing with another thread installing a table
*/
if (ret == -EAGAIN)
continue;
/* EAGAIN on a race will loop again */
if (ret)
return ret;
}
} else {
pts.table_lower = pt_table_ptr(&pts);
/*
@@ -615,10 +668,12 @@ static int __map_range(struct pt_range *range, void *arg, unsigned int level,
* The already present table can possibly be shared with another
* concurrent map.
*/
if (map->leaf_level == level - 1)
ret = pt_descend(&pts, arg, __map_range_leaf);
else
ret = pt_descend(&pts, arg, __map_range);
do {
if (map->leaf_level == level - 1)
ret = pt_descend(&pts, arg, __map_range_leaf);
else
ret = pt_descend(&pts, arg, __map_range);
} while (ret == -EAGAIN);
if (ret)
return ret;
@@ -626,6 +681,14 @@ static int __map_range(struct pt_range *range, void *arg, unsigned int level,
pt_index_to_va(&pts);
if (pts.index >= pts.end_index)
break;
/*
* This level is currently running __map_range_leaf() which is
* not correct if the target level has been updated to this
* level. Have the caller invoke __map_range_leaf.
*/
if (map->leaf_level == level)
return -EAGAIN;
} while (true);
return 0;
}
@@ -797,12 +860,13 @@ static int check_map_range(struct pt_iommu *iommu_table, struct pt_range *range,
static int do_map(struct pt_range *range, struct pt_common *common,
bool single_page, struct pt_iommu_map_args *map)
{
int ret;
/*
* The __map_single_page() fast path does not support DMA_INCOHERENT
* flushing to keep its .text small.
*/
if (single_page && !pt_feature(common, PT_FEAT_DMA_INCOHERENT)) {
int ret;
ret = pt_walk_range(range, __map_single_page, map);
if (ret != -EAGAIN)
@@ -810,50 +874,25 @@ static int do_map(struct pt_range *range, struct pt_common *common,
/* EAGAIN falls through to the full path */
}
if (map->leaf_level == range->top_level)
return pt_walk_range(range, __map_range_leaf, map);
return pt_walk_range(range, __map_range, map);
do {
if (map->leaf_level == range->top_level)
ret = pt_walk_range(range, __map_range_leaf, map);
else
ret = pt_walk_range(range, __map_range, map);
} while (ret == -EAGAIN);
return ret;
}
/**
* map_pages() - Install translation for an IOVA range
* @domain: Domain to manipulate
* @iova: IO virtual address to start
* @paddr: Physical/Output address to start
* @pgsize: Length of each page
* @pgcount: Length of the range in pgsize units starting from @iova
* @prot: A bitmap of IOMMU_READ/WRITE/CACHE/NOEXEC/MMIO
* @gfp: GFP flags for any memory allocations
* @mapped: Total bytes successfully mapped
*
* The range starting at IOVA will have paddr installed into it. The caller
* must specify a valid pgsize and pgcount to segment the range into compatible
* blocks.
*
* On error the caller will probably want to invoke unmap on the range from iova
* up to the amount indicated by @mapped to return the table back to an
* unchanged state.
*
* Context: The caller must hold a write range lock that includes the whole
* range.
*
* Returns: -ERRNO on failure, 0 on success. The number of bytes of VA that were
* mapped are added to @mapped, @mapped is not zerod first.
*/
int DOMAIN_NS(map_pages)(struct iommu_domain *domain, unsigned long iova,
phys_addr_t paddr, size_t pgsize, size_t pgcount,
int prot, gfp_t gfp, size_t *mapped)
static int NS(map_range)(struct pt_iommu *iommu_table, dma_addr_t iova,
phys_addr_t paddr, dma_addr_t len, unsigned int prot,
gfp_t gfp, size_t *mapped)
{
struct pt_iommu *iommu_table =
container_of(domain, struct pt_iommu, domain);
pt_vaddr_t pgsize_bitmap = iommu_table->domain.pgsize_bitmap;
struct pt_common *common = common_from_iommu(iommu_table);
struct iommu_iotlb_gather iotlb_gather;
pt_vaddr_t len = pgsize * pgcount;
struct pt_iommu_map_args map = {
.iotlb_gather = &iotlb_gather,
.oa = paddr,
.leaf_pgsize_lg2 = vaffs(pgsize),
};
bool single_page = false;
struct pt_range range;
@@ -881,13 +920,13 @@ int DOMAIN_NS(map_pages)(struct iommu_domain *domain, unsigned long iova,
return ret;
/* Calculate target page size and level for the leaves */
if (pt_has_system_page_size(common) && pgsize == PAGE_SIZE &&
pgcount == 1) {
if (pt_has_system_page_size(common) && len == PAGE_SIZE) {
PT_WARN_ON(!(pgsize_bitmap & PAGE_SIZE));
if (log2_mod(iova | paddr, PAGE_SHIFT))
return -ENXIO;
map.leaf_pgsize_lg2 = PAGE_SHIFT;
map.leaf_level = 0;
map.num_leaves = 1;
single_page = true;
} else {
map.leaf_pgsize_lg2 = pt_compute_best_pgsize(
@@ -896,6 +935,9 @@ int DOMAIN_NS(map_pages)(struct iommu_domain *domain, unsigned long iova,
return -ENXIO;
map.leaf_level =
pt_pgsz_lg2_to_level(common, map.leaf_pgsize_lg2);
map.num_leaves = pt_pgsz_count(pgsize_bitmap, range.va,
range.last_va, paddr,
map.leaf_pgsize_lg2);
}
ret = check_map_range(iommu_table, &range, &map);
@@ -918,7 +960,6 @@ int DOMAIN_NS(map_pages)(struct iommu_domain *domain, unsigned long iova,
*mapped += map.oa - paddr;
return ret;
}
EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(map_pages), "GENERIC_PT_IOMMU");
struct pt_unmap_args {
struct iommu_pages_list free_list;
@@ -1020,34 +1061,12 @@ start_oa:
return ret;
}
/**
* unmap_pages() - Make a range of IOVA empty/not present
* @domain: Domain to manipulate
* @iova: IO virtual address to start
* @pgsize: Length of each page
* @pgcount: Length of the range in pgsize units starting from @iova
* @iotlb_gather: Gather struct that must be flushed on return
*
* unmap_pages() will remove a translation created by map_pages(). It cannot
* subdivide a mapping created by map_pages(), so it should be called with IOVA
* ranges that match those passed to map_pages(). The IOVA range can aggregate
* contiguous map_pages() calls so long as no individual range is split.
*
* Context: The caller must hold a write range lock that includes
* the whole range.
*
* Returns: Number of bytes of VA unmapped. iova + res will be the point
* unmapping stopped.
*/
size_t DOMAIN_NS(unmap_pages)(struct iommu_domain *domain, unsigned long iova,
size_t pgsize, size_t pgcount,
static size_t NS(unmap_range)(struct pt_iommu *iommu_table, dma_addr_t iova,
dma_addr_t len,
struct iommu_iotlb_gather *iotlb_gather)
{
struct pt_iommu *iommu_table =
container_of(domain, struct pt_iommu, domain);
struct pt_unmap_args unmap = { .free_list = IOMMU_PAGES_LIST_INIT(
unmap.free_list) };
pt_vaddr_t len = pgsize * pgcount;
struct pt_range range;
int ret;
@@ -1057,12 +1076,11 @@ size_t DOMAIN_NS(unmap_pages)(struct iommu_domain *domain, unsigned long iova,
pt_walk_range(&range, __unmap_range, &unmap);
gather_range_pages(iotlb_gather, iommu_table, iova, len,
gather_range_pages(iotlb_gather, iommu_table, iova, unmap.unmapped,
&unmap.free_list);
return unmap.unmapped;
}
EXPORT_SYMBOL_NS_GPL(DOMAIN_NS(unmap_pages), "GENERIC_PT_IOMMU");
static void NS(get_info)(struct pt_iommu *iommu_table,
struct pt_iommu_info *info)
@@ -1110,6 +1128,8 @@ static void NS(deinit)(struct pt_iommu *iommu_table)
}
static const struct pt_iommu_ops NS(ops) = {
.map_range = NS(map_range),
.unmap_range = NS(unmap_range),
#if IS_ENABLED(CONFIG_IOMMUFD_DRIVER) && defined(pt_entry_is_write_dirty) && \
IS_ENABLED(CONFIG_IOMMUFD_TEST) && defined(pt_entry_make_write_dirty)
.set_dirty = NS(set_dirty),
@@ -1172,6 +1192,7 @@ static int pt_iommu_init_domain(struct pt_iommu *iommu_table,
domain->type = __IOMMU_DOMAIN_PAGING;
domain->pgsize_bitmap = info.pgsize_bitmap;
domain->is_iommupt = true;
if (pt_feature(common, PT_FEAT_DYNAMIC_TOP))
range = _pt_top_range(common,
@@ -312,6 +312,17 @@ static void test_best_pgsize(struct kunit *test)
}
}
static void test_pgsz_count(struct kunit *test)
{
KUNIT_EXPECT_EQ(test,
pt_pgsz_count(SZ_4K, 0, SZ_1G - 1, 0, ilog2(SZ_4K)),
SZ_1G / SZ_4K);
KUNIT_EXPECT_EQ(test,
pt_pgsz_count(SZ_2M | SZ_4K, SZ_4K, SZ_1G - 1, SZ_4K,
ilog2(SZ_4K)),
(SZ_2M - SZ_4K) / SZ_4K);
}
/*
* Check that pt_install_table() and pt_table_pa() match
*/
@@ -770,6 +781,7 @@ static struct kunit_case generic_pt_test_cases[] = {
KUNIT_CASE_FMT(test_init),
KUNIT_CASE_FMT(test_bitops),
KUNIT_CASE_FMT(test_best_pgsize),
KUNIT_CASE_FMT(test_pgsz_count),
KUNIT_CASE_FMT(test_table_ptr),
KUNIT_CASE_FMT(test_max_va),
KUNIT_CASE_FMT(test_table_radix),

Some files were not shown because too many files have changed in this diff Show More