mirror of
https://github.com/qemu/qemu.git
synced 2026-07-08 17:46:10 +00:00
Add a minimal PCI test device designed to exercise IOMMU translation (such as ARM SMMUv3) without requiring guest firmware or OS. The device provides MMIO registers to configure and trigger DMA operations with controllable attributes (security state, address space), enabling deterministic IOMMU testing. Key features: - Bare-metal IOMMU testing via simple MMIO interface - Configurable DMA attributes for security states and address spaces - Write-then-read verification pattern with automatic result checking The device performs a deterministic DMA test pattern: write a known value (0x12345678) to a configured GVA, read it back, and verify data integrity. Results are reported through a dedicated result register, eliminating the need for complex interrupt handling or driver infrastructure in tests. This is purely a test device and not intended for production use or machine realism. It complements existing test infrastructure like pci-testdev but focuses specifically on IOMMU translation path validation. Signed-off-by: Tao Tang <tangtao1634@phytium.com.cn> Reviewed-by: Pierrick Bouvier <pierrick.bouvier@linaro.org> Reviewed-by: Clément Mathieu--Drif <clement.mathieu--drif@eviden.com> Reviewed-by: Fabiano Rosas <farosas@suse.de> Message-ID: <20260119161112.3841386-4-tangtao1634@phytium.com.cn> [PMD: Add SPDX-License-Identifier: GPL-2.0-or-later tag] Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org>
319 lines
8.8 KiB
C
319 lines
8.8 KiB
C
/*
|
|
* A test device for IOMMU
|
|
*
|
|
* Copyright (c) 2026 Phytium Technology
|
|
*
|
|
* Author:
|
|
* Tao Tang <tangtao1634@phytium.com.cn>
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "system/address-spaces.h"
|
|
#include "system/memory.h"
|
|
#include "trace.h"
|
|
#include "hw/pci/pci_device.h"
|
|
#include "hw/core/qdev-properties.h"
|
|
#include "qom/object.h"
|
|
#include "hw/misc/iommu-testdev.h"
|
|
|
|
#define TYPE_IOMMU_TESTDEV "iommu-testdev"
|
|
OBJECT_DECLARE_SIMPLE_TYPE(IOMMUTestDevState, IOMMU_TESTDEV)
|
|
|
|
struct IOMMUTestDevState {
|
|
PCIDevice parent_obj;
|
|
MemoryRegion bar0;
|
|
uint64_t dma_vaddr;
|
|
uint64_t dma_paddr;
|
|
uint32_t dma_len;
|
|
uint32_t dma_result;
|
|
bool dma_armed; /* armed until a trigger consumes the request */
|
|
|
|
AddressSpace *dma_as; /* IOMMU-mediated DMA AS for this device */
|
|
uint32_t dma_attrs_cfg; /* bit0 secure, bits[2:1] space, bit3 valid */
|
|
};
|
|
|
|
static bool iommu_testdev_attrs_inconsistent(uint32_t cfg)
|
|
{
|
|
uint32_t space;
|
|
bool secure;
|
|
|
|
if (!ITD_ATTRS_GET_SPACE_VALID(cfg)) {
|
|
return false;
|
|
}
|
|
|
|
space = ITD_ATTRS_GET_SPACE(cfg);
|
|
secure = ITD_ATTRS_GET_SECURE(cfg);
|
|
|
|
if (space == ITD_ATTRS_SPACE_SECURE || space == ITD_ATTRS_SPACE_NONSECURE) {
|
|
return secure != (space == ITD_ATTRS_SPACE_SECURE);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void iommu_testdev_maybe_run_dma(IOMMUTestDevState *s)
|
|
{
|
|
uint32_t expected_val, actual_val;
|
|
g_autofree uint8_t *write_buf = NULL;
|
|
g_autofree uint8_t *read_buf = NULL;
|
|
MemTxResult write_res, read_res;
|
|
MemTxAttrs attrs = {};
|
|
AddressSpace *as;
|
|
bool space_valid;
|
|
|
|
if (!s->dma_armed) {
|
|
s->dma_result = ITD_DMA_ERR_NOT_ARMED;
|
|
trace_iommu_testdev_dma_result(s->dma_result);
|
|
return;
|
|
}
|
|
trace_iommu_testdev_dma_start();
|
|
|
|
if (!s->dma_len) {
|
|
s->dma_result = ITD_DMA_ERR_BAD_LEN;
|
|
goto out;
|
|
}
|
|
|
|
write_buf = g_malloc(s->dma_len);
|
|
read_buf = g_malloc(s->dma_len);
|
|
|
|
/* Initialize MemTxAttrs from generic register. */
|
|
attrs.secure = ITD_ATTRS_GET_SECURE(s->dma_attrs_cfg);
|
|
|
|
space_valid = ITD_ATTRS_GET_SPACE_VALID(s->dma_attrs_cfg);
|
|
if (space_valid) {
|
|
/* The 'space' field in MemTxAttrs is ARM-specific. */
|
|
attrs.space = ITD_ATTRS_GET_SPACE(s->dma_attrs_cfg);
|
|
} else {
|
|
/* Default to Non-Secure when space is not valid. */
|
|
attrs.space = ITD_ATTRS_SPACE_NONSECURE;
|
|
}
|
|
|
|
if (iommu_testdev_attrs_inconsistent(s->dma_attrs_cfg)) {
|
|
s->dma_result = ITD_DMA_ERR_BAD_ATTRS;
|
|
goto out;
|
|
}
|
|
|
|
as = s->dma_as;
|
|
|
|
/* Step 1: Write ITD_DMA_WRITE_VAL to DMA address */
|
|
trace_iommu_testdev_dma_write(s->dma_vaddr, s->dma_len);
|
|
|
|
for (int i = 0; i < s->dma_len; i++) {
|
|
/* Data is written in little-endian order */
|
|
write_buf[i] = (ITD_DMA_WRITE_VAL >> ((i % 4) * 8)) & 0xff;
|
|
}
|
|
write_res = dma_memory_write(as, s->dma_vaddr, write_buf,
|
|
s->dma_len, attrs);
|
|
|
|
if (write_res != MEMTX_OK) {
|
|
s->dma_result = ITD_DMA_ERR_TX_FAIL;
|
|
goto out;
|
|
}
|
|
|
|
/* Step 2: Read back from the same DMA address */
|
|
trace_iommu_testdev_dma_read(s->dma_vaddr, s->dma_len);
|
|
|
|
read_res = address_space_read(&address_space_memory, s->dma_paddr,
|
|
attrs, read_buf, s->dma_len);
|
|
|
|
if (read_res != MEMTX_OK) {
|
|
s->dma_result = ITD_DMA_ERR_RD_FAIL;
|
|
goto out;
|
|
}
|
|
|
|
/* Step 3: Verify the read data matches what we wrote */
|
|
for (int i = 0; i < s->dma_len; i += 4) {
|
|
int remaining_bytes = MIN(4, s->dma_len - i);
|
|
|
|
expected_val = 0;
|
|
actual_val = 0;
|
|
|
|
for (int j = 0; j < remaining_bytes; j++) {
|
|
expected_val |= ((uint32_t)write_buf[i + j]) << (j * 8);
|
|
actual_val |= ((uint32_t)read_buf[i + j]) << (j * 8);
|
|
}
|
|
|
|
trace_iommu_testdev_dma_verify(expected_val, actual_val);
|
|
|
|
if (expected_val != actual_val) {
|
|
s->dma_result = ITD_DMA_ERR_MISMATCH;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* All checks passed */
|
|
s->dma_result = 0;
|
|
out:
|
|
trace_iommu_testdev_dma_result(s->dma_result);
|
|
s->dma_armed = false;
|
|
}
|
|
|
|
static uint64_t iommu_testdev_mmio_read(void *opaque, hwaddr addr,
|
|
unsigned size)
|
|
{
|
|
IOMMUTestDevState *s = opaque;
|
|
uint64_t value = 0;
|
|
|
|
switch (addr) {
|
|
case ITD_REG_DMA_TRIGGERING:
|
|
/*
|
|
* This lets tests poll ITD_REG_DMA_RESULT to observe BUSY before
|
|
* consuming the DMA.
|
|
*/
|
|
iommu_testdev_maybe_run_dma(s);
|
|
value = 0;
|
|
break;
|
|
case ITD_REG_DMA_GVA_LO:
|
|
value = (uint32_t)(s->dma_vaddr & 0xffffffffu);
|
|
break;
|
|
case ITD_REG_DMA_GVA_HI:
|
|
value = (uint32_t)(s->dma_vaddr >> 32);
|
|
break;
|
|
case ITD_REG_DMA_GPA_LO:
|
|
value = (uint32_t)(s->dma_paddr & 0xffffffffu);
|
|
break;
|
|
case ITD_REG_DMA_GPA_HI:
|
|
value = (uint32_t)(s->dma_paddr >> 32);
|
|
break;
|
|
case ITD_REG_DMA_LEN:
|
|
value = s->dma_len;
|
|
break;
|
|
case ITD_REG_DMA_RESULT:
|
|
value = s->dma_result;
|
|
break;
|
|
case ITD_REG_DMA_ATTRS:
|
|
value = s->dma_attrs_cfg;
|
|
break;
|
|
default:
|
|
value = 0;
|
|
break;
|
|
}
|
|
|
|
trace_iommu_testdev_mmio_read(addr, value, size);
|
|
return value;
|
|
}
|
|
|
|
static void iommu_testdev_mmio_write(void *opaque, hwaddr addr, uint64_t val,
|
|
unsigned size)
|
|
{
|
|
IOMMUTestDevState *s = opaque;
|
|
uint32_t data = val;
|
|
|
|
trace_iommu_testdev_mmio_write(addr, val, size);
|
|
|
|
switch (addr) {
|
|
case ITD_REG_DMA_GVA_LO:
|
|
s->dma_vaddr = (s->dma_vaddr & ~0xffffffffull) | data;
|
|
break;
|
|
case ITD_REG_DMA_GVA_HI:
|
|
s->dma_vaddr = (s->dma_vaddr & 0xffffffffull) |
|
|
((uint64_t)data << 32);
|
|
break;
|
|
case ITD_REG_DMA_GPA_LO:
|
|
s->dma_paddr = (s->dma_paddr & ~0xffffffffull) | data;
|
|
break;
|
|
case ITD_REG_DMA_GPA_HI:
|
|
s->dma_paddr = (s->dma_paddr & 0xffffffffull) |
|
|
((uint64_t)data << 32);
|
|
break;
|
|
case ITD_REG_DMA_LEN:
|
|
s->dma_len = data;
|
|
break;
|
|
case ITD_REG_DMA_RESULT:
|
|
s->dma_result = data;
|
|
break;
|
|
case ITD_REG_DMA_DBELL:
|
|
if (data & ITD_DMA_DBELL_ARM) {
|
|
/* Arm the DMA operation; repeated arm is idempotent. */
|
|
s->dma_armed = true;
|
|
s->dma_result = ITD_DMA_RESULT_BUSY;
|
|
trace_iommu_testdev_dma_armed(true);
|
|
} else {
|
|
/* Disarm the DMA operation */
|
|
s->dma_armed = false;
|
|
s->dma_result = ITD_DMA_RESULT_IDLE;
|
|
trace_iommu_testdev_dma_armed(false);
|
|
}
|
|
break;
|
|
case ITD_REG_DMA_ATTRS:
|
|
s->dma_attrs_cfg = data;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const MemoryRegionOps iommu_testdev_mmio_ops = {
|
|
.read = iommu_testdev_mmio_read,
|
|
.write = iommu_testdev_mmio_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
.valid = {
|
|
.min_access_size = 4,
|
|
.max_access_size = 4,
|
|
},
|
|
};
|
|
|
|
static void iommu_testdev_realize(PCIDevice *pdev, Error **errp)
|
|
{
|
|
IOMMUTestDevState *s = IOMMU_TESTDEV(pdev);
|
|
|
|
s->dma_vaddr = 0;
|
|
s->dma_paddr = 0;
|
|
s->dma_len = 0;
|
|
s->dma_result = ITD_DMA_RESULT_IDLE;
|
|
s->dma_armed = false;
|
|
s->dma_attrs_cfg = ITD_ATTRS_SET_SPACE(0, ITD_ATTRS_SPACE_NONSECURE);
|
|
s->dma_as = pci_device_iommu_address_space(pdev);
|
|
|
|
memory_region_init_io(&s->bar0, OBJECT(pdev), &iommu_testdev_mmio_ops, s,
|
|
TYPE_IOMMU_TESTDEV ".bar0", BAR0_SIZE);
|
|
pci_register_bar(pdev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar0);
|
|
}
|
|
|
|
static void iommu_testdev_reset(DeviceState *dev)
|
|
{
|
|
IOMMUTestDevState *s = IOMMU_TESTDEV(dev);
|
|
|
|
s->dma_vaddr = 0;
|
|
s->dma_paddr = 0;
|
|
s->dma_len = 0;
|
|
s->dma_result = ITD_DMA_RESULT_IDLE;
|
|
s->dma_armed = false;
|
|
s->dma_attrs_cfg = ITD_ATTRS_SET_SPACE(0, ITD_ATTRS_SPACE_NONSECURE);
|
|
}
|
|
|
|
static void iommu_testdev_class_init(ObjectClass *klass, const void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass);
|
|
|
|
pc->realize = iommu_testdev_realize;
|
|
pc->vendor_id = IOMMU_TESTDEV_VENDOR_ID;
|
|
pc->device_id = IOMMU_TESTDEV_DEVICE_ID;
|
|
pc->revision = 0;
|
|
pc->class_id = PCI_CLASS_OTHERS;
|
|
dc->desc = "A test device for IOMMU";
|
|
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
|
|
device_class_set_legacy_reset(dc, iommu_testdev_reset);
|
|
}
|
|
|
|
static const TypeInfo iommu_testdev_info = {
|
|
.name = TYPE_IOMMU_TESTDEV,
|
|
.parent = TYPE_PCI_DEVICE,
|
|
.instance_size = sizeof(IOMMUTestDevState),
|
|
.class_init = iommu_testdev_class_init,
|
|
.interfaces = (const InterfaceInfo[]) {
|
|
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
|
|
{ }
|
|
},
|
|
};
|
|
|
|
static void iommu_testdev_register_types(void)
|
|
{
|
|
type_register_static(&iommu_testdev_info);
|
|
}
|
|
|
|
type_init(iommu_testdev_register_types);
|