Skip to content

Commit

Permalink
DO NOT MERGE: iommu/samsung: Add IOMMU test driver
Browse files Browse the repository at this point in the history
Pull the IOMMU test driver as is from k5.4, where it was originally
implemented, and port it for using DMA-HEAP:

  - Convert ion_alloc() call to dma_heap_find() +
    dma_heap_buffer_alloc(), as it described at [1]
  - In case if DMA-HEAP module is loaded later, than this test
    driver, the dma_heap_find() function will fail with error. To
    handle that, just defer the probe of testing driver
  - Rework test driver to test exynos-sysmmu

[1] https://source.android.com/devices/architecture/kernel/dma-buf-heaps

Signed-off-by: Sam Protsenko <semen.protsenko@linaro.org>
  • Loading branch information
Sam Protsenko committed Jun 25, 2022
1 parent f075c7d commit bbadd46
Show file tree
Hide file tree
Showing 3 changed files with 365 additions and 0 deletions.
23 changes: 23 additions & 0 deletions drivers/iommu/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,29 @@ config QCOM_IOMMU
help
Support for IOMMU on certain Qualcomm SoCs.

config SAMSUNG_IOMMU_TEST
tristate "Samsung IOMMU test driver"
default m
depends on EXYNOS_IOMMU && DMABUF_HEAPS_SYSTEM
help
Driver for testing the Samsung IOMMU driver via DebugFS. User reads
/sys/kernel/debug/iommu_test/test file to start the testing procedure,
which involves next steps:

1. Create some memory mapping for IOMMU
2. Write some magic value to the mapped memory in RAM
3. Initiate the emulated IOMMU translation from software (so called
"traffic emulation"), which doesn't require any user IP-cores (like
DPU) to initiate translation on hardware level. Use IOVA (virtual
address) of mapped memory start for that translation, as we know it
and its PA (Physical Address) counterpart too.
4. Check the result of translation in IOMMU TLB registers to make sure
it matches IOVA and PA we know.
5. Read the magic from translated PA (via kernel's VA) and check if it
matches the value written before.

This is one way to prove that IOMMU driver is functional.

config HYPERV_IOMMU
bool "Hyper-V x2APIC IRQ Handling"
depends on HYPERV && X86
Expand Down
1 change: 1 addition & 0 deletions drivers/iommu/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ obj-$(CONFIG_S390_IOMMU) += s390-iommu.o
obj-$(CONFIG_HYPERV_IOMMU) += hyperv-iommu.o
obj-$(CONFIG_VIRTIO_IOMMU) += virtio-iommu.o
obj-$(CONFIG_IOMMU_SVA) += iommu-sva-lib.o io-pgfault.o
obj-$(CONFIG_SAMSUNG_IOMMU_TEST) += samsung-iommu-test.o
obj-$(CONFIG_SPRD_IOMMU) += sprd-iommu.o
obj-$(CONFIG_APPLE_DART) += apple-dart.o
341 changes: 341 additions & 0 deletions drivers/iommu/samsung-iommu-test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
// SPDX-License-Identifier: GPL-2.0

#include <linux/debugfs.h>
#include <linux/dma-buf.h>
#include <linux/dma-heap.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <asm/io.h>

/* Base address of DPU IOMMU (non-secure block) */
#define SYSMMU_BASE 0x130c0000
#define SYSMMU_SIZE 0x10000

/* Offsets for registers for initiating the "traffic emulation" */
#define MMU_STATUS 0x0008
#define MMU_EMU_PRELOAD 0x3040
#define MMU_EMU_SHOT 0x3044

/* Offsets for registers to check the translation result */
#define MMU_TLB_READ 0x3000
#define MMU_TLB_VPN 0x3004
#define MMU_TLB_PPN 0x3008
#define MMU_TLB_ATTR 0x300C

/* No ongoing translation or invalidation and AXI interface is unblocked */
#define STATUS_READY 0x0
/* RW bit; 1 - write request, 0 - read request */
#define PRELOAD_WRITE (1 << 20)
#define PRELOAD_READ (0 << 20)
/* Virtual page number for emulating address translation (VA[31:12]) */
#define PRELOAD_VPN_OFFSET 0
/* AXI port ID (AXI port information to be emulated) */
#define SHOT_PID_OFFSET 16
/* AXI ID (target stream ID for emulating address translation) */
#define SHOT_SID_OFFSET 0

#define MAPPING_SIZE SZ_16M
/* Test value */
#define MAGIC_NUMBER 0x11223344

struct iommu_test {
struct platform_device *pdev;
struct device *dev;
struct dentry *dir; /* DebugFS dir */

/* Objects needed for allocation and mapping the memory */
struct dma_heap *dma_heap;
struct dma_buf *dma_buf;
struct dma_buf_attachment *dma_att;
struct sg_table *sg_table;

/* Addresses used for testing (start of mapped memory) */
dma_addr_t iova; /* IOMMU VA address */
phys_addr_t pa; /* Physica Address in RAM */
u32 *va; /* Virtual Address in kernel */

void __iomem *reg_base; /* IOMMU registers (mapped) */
};

/* ---- private ------------------------------------------------------------- */

static void iommu_test_do_translation(const struct iommu_test *obj)
{
const u32 rw = PRELOAD_READ;
const u32 vpn = obj->iova >> 12; /* VPN = VA[31:12] */
const u32 pid = 0x0;
const u32 sid = 0x2;

pr_info("initiate IOMMU translation (\"traffic emulation\")\n");

/* Wait until emulation is available */
while (ioread32(obj->reg_base + MMU_STATUS) != STATUS_READY)
;

/* Preload for emulation */
iowrite32(rw | vpn, obj->reg_base + MMU_EMU_PRELOAD);

/* Issue emulation (initiate the translation) */
iowrite32(sid | (pid << SHOT_PID_OFFSET), obj->reg_base + MMU_EMU_SHOT);
}

static void iommu_test_check_result(const struct iommu_test *obj)
{
const u32 pg_off = obj->iova & 0xfff; /* "page offset" part of addr */
const size_t way = 0; /* only way #0 is available for all TLBs */
size_t tlb, set, entry;
u32 read_val;
u32 vpn, ppn, attr;
u32 iova;
u64 pa;
u32 *va;

/*
* Dump TLBs.
* - We used SID = 0x2, which matches TLB #1
* - TLB #1: 1 way, 2 sets, 4 lines
* - Check TLB #0 (default one) and TLB #1
*/
pr_info("Dump TLBs:\n");
pr_info(" [TLB][SET][ENTRY]: VPN, PPN, ATTR\n");
for (tlb = 0; tlb < 2; ++tlb) {
for (set = 0; set < 2; ++set) {
for (entry = 0; entry < 4; ++entry) {
read_val = (tlb << 20) | (way << 8) | (entry << 16) |
(set << 0);
while (ioread32(obj->reg_base + MMU_STATUS) != STATUS_READY)
;

iowrite32(read_val, obj->reg_base + MMU_TLB_READ);
vpn = ioread32(obj->reg_base + MMU_TLB_VPN);
ppn = ioread32(obj->reg_base + MMU_TLB_PPN);
attr = ioread32(obj->reg_base + MMU_TLB_ATTR);
pr_info(" [%zu][%zu][%zu]: 0x%x, 0x%x, 0x%x\n",
tlb, set, entry, vpn, ppn, attr);
}
}
}

/* Check IOVA and PA for correctness */
tlb = 0;
set = 0;
entry = 0;
read_val = (tlb << 20) | (way << 8) | (entry << 16) | (set << 0);
while (ioread32(obj->reg_base + MMU_STATUS) != STATUS_READY)
;
iowrite32(read_val, obj->reg_base + MMU_TLB_READ);
vpn = ioread32(obj->reg_base + MMU_TLB_VPN) & 0x0fffff;
ppn = ioread32(obj->reg_base + MMU_TLB_PPN) & 0xffffff;
iova = (vpn << 12) + pg_off;
pa = ((u64)ppn << 12) + pg_off;
va = phys_to_virt(pa);
pr_info("Actual addresses:\n");
pr_info("Translated: IOVA = 0x%x, PA = 0x%llx\n", iova, pa);
pr_info("Mapped: IOVA = %pad, PA = %pa\n", &obj->iova, &obj->pa);
pr_info("Verifying the magic by reading: %s\n",
*va == MAGIC_NUMBER ? "OK" : "FAIL");
}

static int iommu_test_create_mapping(struct iommu_test *obj)
{
const size_t size = PAGE_ALIGN(MAPPING_SIZE);
int ret;

obj->dma_heap = dma_heap_find("system");
if (!obj->dma_heap) {
dev_warn(obj->dev, "can't find system heap, deferring probe\n");
return -EPROBE_DEFER;
}

obj->dma_buf = dma_heap_buffer_alloc(obj->dma_heap, size, O_RDWR, 0);
if (IS_ERR(obj->dma_buf)) {
ret = -ENOMEM;
dev_err(obj->dev, "dma_heap_buffer_alloc() failed\n");
goto err_buf_alloc;
}

obj->dma_att = dma_buf_attach(obj->dma_buf, obj->dev);
if (IS_ERR_OR_NULL(obj->dma_att)) {
ret = PTR_ERR(obj->dma_att);
dev_err(obj->dev, "dma_buf_attach() failed: %d\n", ret);
goto err_buf_attach;
}

/* This call leads to samsung_sysmmu_map() */
obj->sg_table = dma_buf_map_attachment(obj->dma_att, DMA_TO_DEVICE);
if (IS_ERR_OR_NULL(obj->sg_table)) {
ret = PTR_ERR(obj->sg_table);
dev_err(obj->dev, "dma_buf_map_attachment() failed: %d\n", ret);
goto err_buf_map;
}

/* Get IOVA start address of mapped memory, will be used by IOMMU */
obj->iova = sg_dma_address(obj->sg_table->sgl);
if (IS_ERR_VALUE(obj->iova)) {
ret = -EINVAL;
dev_err(obj->dev, "sg_dma_address() failed: %pa\n", &obj->iova);
goto err_buf_map;
}

/* Get PA start address of mapped memory */
obj->pa = sg_phys(obj->sg_table->sgl);
/* RAM addresses are already mapped in kernel pgtable */
obj->va = phys_to_virt(obj->pa);
/* Print mapped mem start addr */
dev_info(obj->dev, "IOVA = %pad, PA = %pa\n", &obj->iova, &obj->pa);

return 0;

err_buf_map:
dma_buf_detach(obj->dma_buf, obj->dma_att);
err_buf_attach:
dma_buf_put(obj->dma_buf);
err_buf_alloc:
dma_heap_put(obj->dma_heap);
return ret;
}

static void iommu_test_free_mapping(struct iommu_test *obj)
{
dma_buf_unmap_attachment(obj->dma_att, obj->sg_table, DMA_TO_DEVICE);
dma_buf_detach(obj->dma_buf, obj->dma_att);
dma_buf_put(obj->dma_buf);
dma_heap_put(obj->dma_heap);
}

/* ---- DebugFS ------------------------------------------------------------- */

static inline struct iommu_test *to_iommu_test(struct file *file)
{
/*
* file->private_data is set in simple_open(), in turn using data set in
* debugfs_create_file().
*/
return (struct iommu_test *)file->private_data;
}

static ssize_t iommu_test_read(struct file *file, char __user *buf,
size_t count, loff_t *pos)
{
struct iommu_test *obj = to_iommu_test(file);

*obj->va = MAGIC_NUMBER; /* write magic number to tested RAM addr */
iommu_test_do_translation(obj);
iommu_test_check_result(obj);

return 0;
}

static const struct file_operations iommu_test_fops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = iommu_test_read,
.write = NULL,
};

/* ---- Module -------------------------------------------------------------- */

static int iommu_test_probe(struct platform_device *pdev)
{
struct iommu_test *drv;
struct dentry *file;
int ret;

/*
* Don't try to init this module before system heap is ready (fail
* fast). That prevents unnecessary SysMMU init/deinit sequence and
* simplifies tracing of SysMMU driver IO operations if needed.
*/
if (!dma_heap_find("system")) {
dev_warn(&pdev->dev, "can't find system heap, deferring\n");
return -EPROBE_DEFER;
}

drv = kzalloc(sizeof(*drv), GFP_KERNEL);
if (ZERO_OR_NULL_PTR(drv))
return -ENOMEM;

platform_set_drvdata(pdev, drv);
drv->pdev = pdev;
drv->dev = &pdev->dev;

pm_runtime_enable(drv->dev);
pm_runtime_get_sync(drv->dev);

ret = iommu_test_create_mapping(drv);
if (ret)
goto err_create_mapping;

drv->dir = debugfs_create_dir("iommu_test", NULL);
if (IS_ERR_OR_NULL(drv->dir)) {
ret = PTR_ERR(drv->dir);
dev_err(drv->dev, "Error creating DebugFS dir: %d\n", ret);
goto err_create_dir;
}

file = debugfs_create_file("test", S_IRUGO, drv->dir, drv,
&iommu_test_fops);
if (IS_ERR_OR_NULL(file)) {
ret = PTR_ERR(file);
dev_err(drv->dev, "Error creating DebugFS file: %d\n", ret);
goto err_create_file;
}

drv->reg_base = ioremap(SYSMMU_BASE, SYSMMU_SIZE);
if (!drv->reg_base) {
ret = -ENOMEM;
goto err_create_file;
}

return 0;

err_create_file:
debugfs_remove_recursive(drv->dir);
err_create_dir:
iommu_test_free_mapping(drv);
err_create_mapping:
pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
kfree(drv);
return ret;
}

static int iommu_test_remove(struct platform_device *pdev)
{
struct iommu_test *drv = platform_get_drvdata(pdev);

pm_runtime_put_sync(&pdev->dev);
pm_runtime_disable(&pdev->dev);
debugfs_remove_recursive(drv->dir);
iounmap(drv->reg_base);
iommu_test_free_mapping(drv);
kfree(drv);

return 0;
}

static const struct of_device_id iommu_test_of_match[] = {
{ .compatible = "samsung,iommu-test" },
{},
};
MODULE_DEVICE_TABLE(of, iommu_test_of_match);

static struct platform_driver iommu_test_driver = {
.probe = iommu_test_probe,
.remove = iommu_test_remove,
.driver = {
.name = "samsung-iommu-test",
.of_match_table = of_match_ptr(iommu_test_of_match),
},
};

module_platform_driver(iommu_test_driver);

MODULE_AUTHOR("Sam Protsenko <semen.protsenko@linaro.org>");
MODULE_DESCRIPTION("Samsung IOMMU test driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:samsung-iommu-test");
MODULE_IMPORT_NS(DMA_BUF);

0 comments on commit bbadd46

Please sign in to comment.