[PULL v2 07/96] tests/qtest: add test for K230 gsdma
[email protected] Mon, 24 Aug 2026 15:46:37 +1000
Newsgroups
org.nongnu.qemu-devel
Message-ID
<[email protected] >
From: Tao Ding <[email protected] >
This commit adds test cases for GSDMA.
Imitate the behavior of the driver, set LLT and sdma registers.
After the data transmission is completed, check the destination address data.
Update MAINTAINERS for this test.
Run this qtest:
$ mkdir build && cd build && ../configure --target-list="riscv64-softmmu"
$ QTEST_QEMU_BINARY=./qemu-system-riscv64 tests/qtest/k230-gsdma-test
Signed-off-by: Tao Ding <[email protected] >
Reviewed-by: Daniel Henrique Barboza <[email protected] >
Reviewed-by: Junze Cao <[email protected] >
Message-ID: <[email protected] >
Signed-off-by: Alistair Francis <[email protected] >
---
MAINTAINERS | 3 +
docs/system/riscv/k230.rst | 1 +
include/hw/misc/k230_decomp_gzip.h | 92 ++++++
hw/misc/k230_decomp_gzip.c | 511 +++++++++++++++++++++++++++++
tests/qtest/k230-gsdma-test.c | 201 ++++++++++++
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/misc/trace-events | 8 +-
tests/qtest/meson.build | 2 +-
9 files changed, 816 insertions(+), 6 deletions(-)
create mode 100644 include/hw/misc/k230_decomp_gzip.h
create mode 100644 hw/misc/k230_decomp_gzip.c
create mode 100644 tests/qtest/k230-gsdma-test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 027db78b39..ff804b3310 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1848,13 +1848,16 @@ F: hw/misc/k230_ddr.c
F: hw/riscv/k230.c
F: hw/watchdog/k230_wdt.c
F: hw/dma/k230_gsdma.c
+F: hw/misc/k230_decomp_gzip.c
F: include/hw/misc/k230_ddr.h
F: include/hw/riscv/k230.h
F: include/hw/watchdog/k230_wdt.h
F: include/hw/dma/k230_gsdma.h
+F: include/hw/misc/k230_decomp_gzip.h
F: tests/functional/riscv64/test_k230.py
F: tests/qtest/k230-ddr-test.c
F: tests/qtest/k230-wdt-test.c
+F: tests/qtest/k230-gsdma-test.c
RX Machines
-----------
diff --git a/docs/system/riscv/k230.rst b/docs/system/riscv/k230.rst
index 5cbc0b14bc..237611eddf 100644
--- a/docs/system/riscv/k230.rst
+++ b/docs/system/riscv/k230.rst
@@ -22,6 +22,7 @@ The ``k230`` machine supports the following devices:
* 5 UART
* K230 DDRC CFG and DDR PHY
* System Direct Memory Access (SDMA)
+* GZIP Decompress Engine (Decomp_gzip)
Boot options
------------
diff --git a/include/hw/misc/k230_decomp_gzip.h b/include/hw/misc/k230_decomp_gzip.h
new file mode 100644
index 0000000000..9911af0095
--- /dev/null
+++ b/include/hw/misc/k230_decomp_gzip.h
@@ -0,0 +1,92 @@
+/*
+ * K230 Decompress Engine
+ *
+ * Copyright (c) 2026 Tao Ding <[email protected] >
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_K230_DECOMP_GZIP_H
+#define HW_MISC_K230_DECOMP_GZIP_H
+
+#include <zlib.h>
+#include "hw/core/sysbus.h"
+
+#define TYPE_K230_DECOMP_GZIP "riscv.k230.decomp-gzip"
+OBJECT_DECLARE_SIMPLE_TYPE(K230DecompGzipState, K230_DECOMP_GZIP)
+
+/* K230 DECOMP GZIP ACK input */
+enum {
+ K230_DECOMP_GZIP_GPIO_DMA_WRITE_ACK,
+ K230_DECOMP_GZIP_GPIO_DMA_READ_ACK,
+ K230_DECOMP_GZIP_NUM_GPIOS_IN,
+};
+
+/* K230 DECOMP GZIP REQ output */
+enum {
+ K230_DECOMP_GZIP_GPIO_DECOMP_CTRL_EN,
+ K230_DECOMP_GZIP_GPIO_DMA_WRITE_REQ,
+ K230_DECOMP_GZIP_GPIO_DMA_READ_REQ,
+ K230_DECOMP_GZIP_NUM_GPIOS_OUT,
+};
+
+#define K230_DECOMP_GZIP_MMIO_SIZE 0x4000
+
+/* K230 DECOMP GZIP Registers map */
+/* Start decompression controller */
+#define K230_DECOMP_GZIP_DECOMP_START 0x00
+/* Source data length register */
+#define K230_DECOMP_GZIP_GZIP_SRC_SIZE 0x04
+/* Output data length register */
+#define K230_DECOMP_GZIP_GZIP_OUT_SIZE 0x08
+/* Decompress status register */
+#define K230_DECOMP_GZIP_DECOMP_STAT 0x0c
+
+/* Start decompression controller */
+#define K230_DECOMP_GZIP_START (1U << 0)
+
+#define K230_DECOMP_GZIP_CTRL_EN (1U << 31)
+#define K230_DECOMP_GZIP_DMA_IN_MASK 0x7fffffffU
+
+#define K230_DECOMP_GZIP_STAT_CRC_OK (1U << 10)
+#define K230_DECOMP_GZIP_STAT_STATE_MASK 0xfU
+
+#define K230_DECOMP_GZIP_BLOCK_SIZE 0x00020000
+#define K230_DECOMP_GZIP_SRAM_IN_BASE 0x80000
+#define K230_DECOMP_GZIP_SRAM_OUT_BASE 0
+
+typedef struct K230DecompGzipSlotState {
+ uint32_t slot; /* Slot index */
+ /*
+ * Data offset in current slot.
+ * First valid data in input buffer. Last valid data in output buffer.
+ */
+ uint32_t current_offset;
+} K230DecompGzipSlotState;
+
+struct K230DecompGzipState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ hwaddr sram_base;
+ qemu_irq signal_out[K230_DECOMP_GZIP_NUM_GPIOS_OUT];
+ uint32_t decomp_start;
+ uint32_t gzip_src_size;
+ uint32_t gzip_out_size;
+ uint32_t decomp_stat;
+ K230DecompGzipSlotState input; /* decomp gzip ring input */
+ K230DecompGzipSlotState output; /* decomp gzip ring output */
+ uint32_t total_requested;
+ uint32_t total_produced;
+ bool active;
+ bool in_kick;
+ bool zstream_inited;
+ bool stream_end;
+ /* Read from ring input. */
+ uint8_t input_buf[K230_DECOMP_GZIP_BLOCK_SIZE];
+ /* Write to ring output. */
+ uint8_t output_buf[K230_DECOMP_GZIP_BLOCK_SIZE];
+ z_stream zs;
+};
+
+#endif
diff --git a/hw/misc/k230_decomp_gzip.c b/hw/misc/k230_decomp_gzip.c
new file mode 100644
index 0000000000..0d8a3b2d2e
--- /dev/null
+++ b/hw/misc/k230_decomp_gzip.c
@@ -0,0 +1,511 @@
+/*
+ * Kendryte K230 GZIP decompression engine
+ *
+ * Copyright (c) 2026 Tao Ding <[email protected] >
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Decompression accelerator is mainly used to implement hardware
+ * GZIP decompression function. (K230 TRM section 15)
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "hw/core/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "system/address-spaces.h"
+#include "hw/core/irq.h"
+#include "hw/misc/k230_decomp_gzip.h"
+#include "trace.h"
+
+#define GZIP_METHOD_DEFLATE 8
+#define GZIP_METHOD_VENDOR_9 9
+
+#define DEFLATE_BTYPE_DYNAMIC 2
+
+#define K230_DECOMP_GZIP_OUTPUT_SLOTS 4
+#define K230_DECOMP_GZIP_INPUT_SLOTS 2
+
+static void k230_decomp_gzip_set_output(K230DecompGzipState *s, int line,
+ int level)
+{
+ qemu_set_irq(s->signal_out[line], level);
+}
+
+static bool k230_decomp_gzip_read_mem(K230DecompGzipState *s, hwaddr addr,
+ void *buf, size_t len)
+{
+ MemTxResult ret = address_space_read(&address_space_memory,
+ s->sram_base + addr,
+ MEMTXATTRS_UNSPECIFIED, buf, len);
+
+ return ret == MEMTX_OK;
+}
+
+static bool k230_decomp_gzip_write_mem(K230DecompGzipState *s, hwaddr addr,
+ const void *buf, size_t len)
+{
+ return address_space_write(&address_space_memory, s->sram_base + addr,
+ MEMTXATTRS_UNSPECIFIED, buf, len) == MEMTX_OK;
+}
+
+static hwaddr k230_decomp_gzip_output_addr(K230DecompGzipState *s)
+{
+ return K230_DECOMP_GZIP_SRAM_OUT_BASE +
+ s->output.slot * K230_DECOMP_GZIP_BLOCK_SIZE +
+ s->output.current_offset;
+}
+
+static hwaddr k230_decomp_gzip_input_addr(K230DecompGzipState *s)
+{
+ return s->input.slot * K230_DECOMP_GZIP_BLOCK_SIZE +
+ K230_DECOMP_GZIP_SRAM_IN_BASE;
+}
+
+static uint32_t k230_decomp_gzip_current_input_size(K230DecompGzipState *s)
+{
+ if (s->total_requested == 0) {
+ return 0;
+ }
+
+ return ((s->total_requested - 1) % K230_DECOMP_GZIP_BLOCK_SIZE) + 1;
+}
+
+static void k230_decomp_gzip_update_ctrl_en(K230DecompGzipState *s)
+{
+ int level = s->active && !!(s->gzip_src_size & K230_DECOMP_GZIP_CTRL_EN);
+
+ k230_decomp_gzip_set_output(s, K230_DECOMP_GZIP_GPIO_DECOMP_CTRL_EN,
+ level);
+}
+
+static void k230_decomp_gzip_reset_stream(K230DecompGzipState *s)
+{
+ if (s->zstream_inited) {
+ inflateEnd(&s->zs);
+ s->zstream_inited = false;
+ }
+ memset(&s->zs, 0, sizeof(s->zs));
+}
+
+static void k230_decomp_gzip_finish(K230DecompGzipState *s, bool crc_ok)
+{
+ s->active = false;
+ k230_decomp_gzip_reset_stream(s);
+ k230_decomp_gzip_update_ctrl_en(s);
+ if (crc_ok) {
+ s->decomp_stat |= K230_DECOMP_GZIP_STAT_CRC_OK;
+ } else {
+ s->decomp_stat &= ~K230_DECOMP_GZIP_STAT_CRC_OK;
+ }
+}
+
+static bool k230_decomp_gzip_load_input(K230DecompGzipState *s,
+ uint32_t addr, uint32_t size)
+{
+ s->input.current_offset = 0;
+
+ return k230_decomp_gzip_read_mem(s, addr, s->input_buf, size);
+}
+
+static bool k230_decomp_gzip_request_input(K230DecompGzipState *s)
+{
+ uint32_t total_requested = s->total_requested;
+
+ k230_decomp_gzip_set_output(s, K230_DECOMP_GZIP_GPIO_DMA_WRITE_REQ, 1);
+ if (s->total_requested == total_requested) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: dma write request not acknowledged\n",
+ TYPE_K230_DECOMP_GZIP);
+ return false;
+ }
+ return true;
+}
+
+static bool k230_decomp_gzip_request_output(K230DecompGzipState *s)
+{
+ uint32_t slot = s->output.slot;
+ uint32_t current_offset = s->output.current_offset;
+
+ k230_decomp_gzip_set_output(s, K230_DECOMP_GZIP_GPIO_DMA_READ_REQ, 1);
+ if (s->output.slot == slot &&
+ s->output.current_offset == current_offset) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: dma read request not acknowledged\n",
+ TYPE_K230_DECOMP_GZIP);
+ return false;
+ }
+ return true;
+}
+
+static bool k230_decomp_gzip_init_stream(K230DecompGzipState *s)
+{
+ uint8_t btype = (s->input_buf[10] >> 1) & 0x3;
+ if (btype != DEFLATE_BTYPE_DYNAMIC) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: unsupported DEFLATE BTYPE %u\n",
+ TYPE_K230_DECOMP_GZIP, btype);
+ return false;
+ }
+
+ /* The compression script for K230 will modify the third byte to 0x9 */
+ if (s->input_buf[2] == GZIP_METHOD_VENDOR_9) {
+ s->input_buf[2] = GZIP_METHOD_DEFLATE;
+ }
+
+ if (inflateInit2(&s->zs, 15 + 16) != Z_OK) {
+ return false;
+ }
+
+ s->zstream_inited = true;
+ return true;
+}
+
+static bool k230_decomp_gzip_run_inflate(K230DecompGzipState *s)
+{
+ uint32_t output_size = s->gzip_out_size;
+ uint32_t current_input_size = k230_decomp_gzip_current_input_size(s);
+ uint32_t avail_in;
+ uint32_t avail_out;
+ uint32_t consumed;
+ uint32_t produced;
+ int ret;
+
+ avail_in = current_input_size - s->input.current_offset;
+ avail_out = K230_DECOMP_GZIP_BLOCK_SIZE - s->output.current_offset;
+
+ s->zs.next_in = s->input_buf + s->input.current_offset;
+ s->zs.avail_in = avail_in;
+ s->zs.next_out = s->output_buf;
+ s->zs.avail_out = avail_out;
+
+ ret = inflate(&s->zs, Z_NO_FLUSH);
+ if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) {
+ return false;
+ }
+
+ consumed = avail_in - s->zs.avail_in;
+ produced = avail_out - s->zs.avail_out;
+
+ if (produced) {
+ if (s->total_produced + produced > output_size ||
+ !k230_decomp_gzip_write_mem(s, k230_decomp_gzip_output_addr(s),
+ s->output_buf, produced)) {
+ return false;
+ }
+ s->output.current_offset += produced;
+ s->total_produced += produced;
+ }
+
+ if (consumed) {
+ s->input.current_offset += consumed;
+ }
+
+ if (ret == Z_STREAM_END) {
+ s->stream_end = true;
+ if (s->total_produced != output_size) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void k230_decomp_gzip_kick(K230DecompGzipState *s)
+{
+ if (!s->active || s->in_kick) {
+ return;
+ }
+
+ uint32_t input_size = s->gzip_src_size & K230_DECOMP_GZIP_DMA_IN_MASK;
+ s->in_kick = true;
+ while (s->active) {
+ uint32_t current_input_size = k230_decomp_gzip_current_input_size(s);
+ /* Output is full or last block */
+ if (s->output.current_offset == K230_DECOMP_GZIP_BLOCK_SIZE ||
+ (s->stream_end && s->output.current_offset != 0)) {
+ if (!k230_decomp_gzip_request_output(s)) {
+ k230_decomp_gzip_finish(s, false);
+ break;
+ }
+ continue;
+ }
+
+ if (!s->zstream_inited) {
+ if (!k230_decomp_gzip_request_input(s) ||
+ !k230_decomp_gzip_init_stream(s)) {
+ k230_decomp_gzip_finish(s, false);
+ break;
+ }
+ continue;
+ }
+
+ /* Transfer finish */
+ if (s->stream_end) {
+ k230_decomp_gzip_finish(s,
+ s->total_produced == s->gzip_out_size);
+ break;
+ }
+
+ /* Input buf has has been fully decompreed, need request more */
+ if (s->input.current_offset == current_input_size) {
+ if (s->total_requested < input_size) {
+ if (!k230_decomp_gzip_request_input(s)) {
+ k230_decomp_gzip_finish(s, false);
+ break;
+ }
+ continue;
+ }
+ k230_decomp_gzip_finish(s, false);
+ break;
+ }
+
+ /* Decompress the data, input buf or output buf are consumed in one block */
+ if (!k230_decomp_gzip_run_inflate(s)) {
+ k230_decomp_gzip_finish(s, false);
+ break;
+ }
+ }
+ s->in_kick = false;
+}
+
+static void k230_decomp_gzip_handle_ack(void *opaque, int n, int level)
+{
+ K230DecompGzipState *s = opaque;
+
+ if (!level || !s->active) {
+ return;
+ }
+
+ switch (n) {
+ case K230_DECOMP_GZIP_GPIO_DMA_WRITE_ACK:
+ {
+ uint32_t input_size = s->gzip_src_size & K230_DECOMP_GZIP_DMA_IN_MASK;
+ uint32_t chunk = MIN(K230_DECOMP_GZIP_BLOCK_SIZE,
+ input_size - s->total_requested);
+
+ if (!k230_decomp_gzip_load_input(s, k230_decomp_gzip_input_addr(s),
+ chunk)) {
+ k230_decomp_gzip_finish(s, false);
+ return;
+ }
+ s->total_requested += chunk;
+ s->input.slot = (s->input.slot + 1) % K230_DECOMP_GZIP_INPUT_SLOTS;
+ break;
+ }
+ case K230_DECOMP_GZIP_GPIO_DMA_READ_ACK:
+ if (s->output.current_offset == 0) {
+ k230_decomp_gzip_finish(s, false);
+ return;
+ }
+ s->output.slot = (s->output.slot + 1) % K230_DECOMP_GZIP_OUTPUT_SLOTS;
+ s->output.current_offset = 0;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: invalid DMA acknowledgment signal %d\n",
+ TYPE_K230_DECOMP_GZIP, n);
+ return;
+ }
+
+ k230_decomp_gzip_kick(s);
+}
+
+static void k230_decomp_gzip_start(K230DecompGzipState *s)
+{
+ uint32_t input_size = s->gzip_src_size & K230_DECOMP_GZIP_DMA_IN_MASK;
+
+ k230_decomp_gzip_reset_stream(s);
+ s->decomp_stat &= ~K230_DECOMP_GZIP_STAT_CRC_OK;
+ s->total_requested = 0;
+ s->total_produced = 0;
+ s->stream_end = false;
+ memset(&s->input, 0, sizeof(s->input));
+ memset(&s->output, 0, sizeof(s->output));
+
+ if (!(s->gzip_src_size & K230_DECOMP_GZIP_CTRL_EN) ||
+ input_size == 0 || s->gzip_out_size == 0) {
+ k230_decomp_gzip_finish(s, false);
+ return;
+ }
+
+ s->active = true;
+ k230_decomp_gzip_update_ctrl_en(s);
+ k230_decomp_gzip_kick(s);
+}
+
+static uint64_t k230_decomp_gzip_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ K230DecompGzipState *s = opaque;
+ uint64_t value = 0;
+
+ switch (offset) {
+ case K230_DECOMP_GZIP_DECOMP_START:
+ value = s->decomp_start & ~K230_DECOMP_GZIP_START; /* start bit is write only */
+ break;
+ case K230_DECOMP_GZIP_GZIP_SRC_SIZE:
+ value = s->gzip_src_size;
+ break;
+ case K230_DECOMP_GZIP_GZIP_OUT_SIZE:
+ value = s->gzip_out_size;
+ break;
+ case K230_DECOMP_GZIP_DECOMP_STAT:
+ value = s->decomp_stat;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: bad read offset 0x%" HWADDR_PRIx "\n",
+ TYPE_K230_DECOMP_GZIP, offset);
+ break;
+ }
+
+ trace_k230_decomp_gzip_read(offset, size, value);
+ return value;
+}
+
+static void k230_decomp_gzip_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ K230DecompGzipState *s = opaque;
+
+ trace_k230_decomp_gzip_write(offset, size, value);
+
+ switch (offset) {
+ case K230_DECOMP_GZIP_DECOMP_START:
+ s->decomp_start = value;
+ if (value & K230_DECOMP_GZIP_START) {
+ k230_decomp_gzip_start(s);
+ }
+ break;
+ case K230_DECOMP_GZIP_GZIP_SRC_SIZE:
+ s->gzip_src_size = value;
+ k230_decomp_gzip_update_ctrl_en(s);
+ break;
+ case K230_DECOMP_GZIP_GZIP_OUT_SIZE:
+ s->gzip_out_size = value;
+ break;
+ case K230_DECOMP_GZIP_DECOMP_STAT:
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: bad write offset 0x%" HWADDR_PRIx "\n",
+ TYPE_K230_DECOMP_GZIP, offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps k230_decomp_gzip_ops = {
+ .read = k230_decomp_gzip_read,
+ .write = k230_decomp_gzip_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+};
+
+static const VMStateDescription vmstate_k230_decomp_gzip_slot = {
+ .name = "k230.decomp-gzip.slot",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(slot, K230DecompGzipSlotState),
+ VMSTATE_UINT32(current_offset, K230DecompGzipSlotState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_k230_decomp_gzip = {
+ .name = "k230.decomp-gzip",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(decomp_start, K230DecompGzipState),
+ VMSTATE_UINT32(gzip_src_size, K230DecompGzipState),
+ VMSTATE_UINT32(gzip_out_size, K230DecompGzipState),
+ VMSTATE_UINT32(decomp_stat, K230DecompGzipState),
+ VMSTATE_STRUCT(input, K230DecompGzipState, 1,
+ vmstate_k230_decomp_gzip_slot,
+ K230DecompGzipSlotState),
+ VMSTATE_STRUCT(output, K230DecompGzipState, 1,
+ vmstate_k230_decomp_gzip_slot,
+ K230DecompGzipSlotState),
+ VMSTATE_UINT32(total_requested, K230DecompGzipState),
+ VMSTATE_UINT32(total_produced, K230DecompGzipState),
+ VMSTATE_BOOL(active, K230DecompGzipState),
+ VMSTATE_BOOL(in_kick, K230DecompGzipState),
+ VMSTATE_BOOL(zstream_inited, K230DecompGzipState),
+ VMSTATE_BOOL(stream_end, K230DecompGzipState),
+ VMSTATE_UINT8_ARRAY(input_buf, K230DecompGzipState,
+ K230_DECOMP_GZIP_BLOCK_SIZE),
+ VMSTATE_UINT8_ARRAY(output_buf, K230DecompGzipState,
+ K230_DECOMP_GZIP_BLOCK_SIZE),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void k230_decomp_gzip_reset_hold(Object *obj, ResetType type)
+{
+ K230DecompGzipState *s = K230_DECOMP_GZIP(obj);
+
+ k230_decomp_gzip_reset_stream(s);
+ memset(&s->zs, 0, sizeof(s->zs));
+ s->decomp_start = 0;
+ s->gzip_src_size = 0;
+ s->gzip_out_size = 0;
+ s->decomp_stat = 0;
+ memset(&s->input, 0, sizeof(s->input));
+ memset(&s->output, 0, sizeof(s->output));
+ s->total_requested = 0;
+ s->total_produced = 0;
+ s->active = false;
+ s->in_kick = false;
+ s->stream_end = false;
+}
+
+static void k230_decomp_gzip_realize(DeviceState *dev, Error **errp)
+{
+ K230DecompGzipState *s = K230_DECOMP_GZIP(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(dev), &k230_decomp_gzip_ops, s,
+ TYPE_K230_DECOMP_GZIP,
+ K230_DECOMP_GZIP_MMIO_SIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+ qdev_init_gpio_in(dev, k230_decomp_gzip_handle_ack,
+ K230_DECOMP_GZIP_NUM_GPIOS_IN);
+ qdev_init_gpio_out(dev, s->signal_out,
+ K230_DECOMP_GZIP_NUM_GPIOS_OUT);
+}
+
+static const Property k230_decomp_gzip_properties[] = {
+ DEFINE_PROP_UINT64("sram-base", K230DecompGzipState, sram_base, 0),
+};
+
+static void k230_decomp_gzip_class_init(ObjectClass *oc, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ ResettableClass *rc = RESETTABLE_CLASS(oc);
+
+ dc->realize = k230_decomp_gzip_realize;
+ rc->phases.hold = k230_decomp_gzip_reset_hold;
+ device_class_set_props(dc, k230_decomp_gzip_properties);
+ dc->vmsd = &vmstate_k230_decomp_gzip;
+ dc->desc = "Kendryte K230 GZIP decompression engine";
+}
+
+static const TypeInfo k230_decomp_gzip_info = {
+ .name = TYPE_K230_DECOMP_GZIP,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(K230DecompGzipState),
+ .class_init = k230_decomp_gzip_class_init,
+};
+
+static void k230_decomp_gzip_register_types(void)
+{
+ type_register_static(&k230_decomp_gzip_info);
+}
+
+type_init(k230_decomp_gzip_register_types)
diff --git a/tests/qtest/k230-gsdma-test.c b/tests/qtest/k230-gsdma-test.c
new file mode 100644
index 0000000000..44d4248739
--- /dev/null
+++ b/tests/qtest/k230-gsdma-test.c
@@ -0,0 +1,201 @@
+/*
+ * QTest testcase for K230 GSDMA
+ *
+ * Copyright (c) 2026 Tao Ding <[email protected] >
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/bitops.h"
+#include "exec/hwaddr.h"
+#include "hw/dma/k230_gsdma.h"
+#include "libqtest.h"
+
+#define K230_GSDMA_BASE 0x80800000
+#define TEST_LLT_ADDR0 0x10000
+#define TEST_LLT_ADDR1 0x10040
+#define TEST_LLT_ADDR2 0x10080
+
+#define TEST_SRC_ADDR0 0x20000
+#define TEST_SRC_ADDR1 0x22000
+#define TEST_SRC_ADDR2 0x23000
+#define TEST_DST_ADDR0 0x30000
+#define TEST_DST_ADDR1 0x32000
+#define TEST_DST_ADDR2 0x33000
+
+static inline uint64_t gsdma_reg(hwaddr off)
+{
+ return K230_GSDMA_BASE + off;
+}
+
+static inline uint64_t gsdma_ch_reg(unsigned int ch, hwaddr off)
+{
+ return K230_GSDMA_BASE + K230_GSDMA_CH_BASE +
+ ch * K230_GSDMA_CH_STRIDE + off;
+}
+
+static void write_sdma_llt_node_full(QTestState *qts, hwaddr addr,
+ uint32_t cfg, hwaddr src,
+ uint32_t size, hwaddr dst,
+ hwaddr next)
+{
+ qtest_writel(qts, addr + offsetof(K230GSDMALLT, cfg), cfg);
+ qtest_writel(qts, addr + offsetof(K230GSDMALLT, src_addr), src);
+ qtest_writel(qts, addr + offsetof(K230GSDMALLT, line_size), size);
+ qtest_writel(qts, addr + offsetof(K230GSDMALLT, line_cfg), 0x1);
+ qtest_writel(qts, addr + offsetof(K230GSDMALLT, dst_addr), dst);
+ qtest_writel(qts, addr + offsetof(K230GSDMALLT, next_llt_addr), next);
+}
+
+static void write_sdma_llt_node(QTestState *qts, hwaddr addr, uint32_t cfg,
+ uint32_t next)
+{
+ write_sdma_llt_node_full(qts, addr, cfg, 0x11110000, 0x200,
+ 0x22220000, next);
+}
+
+static void fill_pattern(uint8_t *buf, size_t size, uint8_t seed)
+{
+ for (size_t i = 0; i < size; i++) {
+ buf[i] = seed + i * 37;
+ }
+}
+
+static void test_global_registers(void)
+{
+ QTestState *qts = qtest_init("-machine k230");
+
+ g_assert_cmphex(qtest_readl(qts, gsdma_reg(K230_GSDMA_DMA_CFG)), ==,
+ K230_GSDMA_DMA_CFG_RESET);
+
+ qtest_writel(qts, gsdma_reg(K230_GSDMA_DMA_CH_EN), 0xff);
+ g_assert_cmphex(qtest_readl(qts, gsdma_reg(K230_GSDMA_DMA_CH_EN)), ==,
+ K230_GSDMA_DMA_CH_EN_MASK);
+
+ qtest_writel(qts, gsdma_reg(K230_GSDMA_DMA_INT_MASK), 0x12345);
+ g_assert_cmphex(qtest_readl(qts, gsdma_reg(K230_GSDMA_DMA_INT_MASK)), ==,
+ 0x12345);
+
+ qtest_writel(qts, gsdma_reg(K230_GSDMA_DMA_CFG), 0xa5a5a5a5);
+ g_assert_cmphex(qtest_readl(qts, gsdma_reg(K230_GSDMA_DMA_CFG)), ==,
+ 0xa5a5a5a5);
+
+ qtest_writel(qts, gsdma_reg(K230_GSDMA_DMA_WEIGHT), 0xff55aa55);
+ g_assert_cmphex(qtest_readl(qts, gsdma_reg(K230_GSDMA_DMA_WEIGHT)), ==,
+ 0x55aa55);
+
+ qtest_quit(qts);
+}
+
+static void test_sdma_pause_resume(void)
+{
+ uint32_t int_stat;
+ QTestState *qts = qtest_init("-machine k230");
+
+ write_sdma_llt_node(qts, TEST_LLT_ADDR0, K230_GSDMA_LLT_PAUSE,
+ TEST_LLT_ADDR1);
+ write_sdma_llt_node(qts, TEST_LLT_ADDR1, K230_GSDMA_LLT_NODE_INTR, 0);
+
+ qtest_writel(qts, gsdma_reg(K230_GSDMA_DMA_CH_EN), BIT(0));
+ qtest_writel(qts, gsdma_ch_reg(0, K230_GSDMA_CH_LLT_SADDR), TEST_LLT_ADDR0);
+ qtest_writel(qts, gsdma_ch_reg(0, K230_GSDMA_CH_CTL), K230_GSDMA_CTL_START);
+
+ g_assert_cmphex(qtest_readl(qts, gsdma_ch_reg(0, K230_GSDMA_CH_STATUS)), ==,
+ K230_GSDMA_SDMA_STATUS_PAUSE);
+ g_assert_cmphex(qtest_readl(qts, gsdma_ch_reg(0, K230_GSDMA_CH_CURRENT_LLT)),
+ ==, TEST_LLT_ADDR0);
+
+ int_stat = qtest_readl(qts, gsdma_reg(K230_GSDMA_DMA_INT_STAT));
+ g_assert_cmphex(int_stat & K230_GSDMA_SDMA_PAUSE_INT(0), ==,
+ K230_GSDMA_SDMA_PAUSE_INT(0));
+ g_assert_cmphex(int_stat & K230_GSDMA_SDMA_DONE_INT(0), ==, 0);
+
+ qtest_writel(qts, gsdma_reg(K230_GSDMA_DMA_INT_STAT), int_stat);
+ qtest_writel(qts, gsdma_ch_reg(0, K230_GSDMA_CH_CTL), K230_GSDMA_CTL_RESUME);
+
+ g_assert_cmphex(qtest_readl(qts, gsdma_ch_reg(0, K230_GSDMA_CH_STATUS)), ==,
+ 0);
+ g_assert_cmphex(qtest_readl(qts, gsdma_ch_reg(0, K230_GSDMA_CH_CURRENT_LLT)),
+ ==, TEST_LLT_ADDR1);
+
+ int_stat = qtest_readl(qts, gsdma_reg(K230_GSDMA_DMA_INT_STAT));
+ g_assert_cmphex(int_stat & K230_GSDMA_SDMA_DONE_INT(0), ==,
+ K230_GSDMA_SDMA_DONE_INT(0));
+ g_assert_cmphex(int_stat & K230_GSDMA_SDMA_ITEM_INT(0), ==,
+ K230_GSDMA_SDMA_ITEM_INT(0));
+
+ qtest_quit(qts);
+}
+
+static void test_sdma_three_llt_copy(void)
+{
+ uint8_t src0[0x1000];
+ uint8_t src1[1];
+ uint8_t src2[511];
+ uint8_t dst0[sizeof(src0)];
+ uint8_t dst1[sizeof(src1)];
+ uint8_t dst2[sizeof(src2)];
+ uint32_t int_stat;
+ QTestState *qts = qtest_init("-machine k230");
+
+ fill_pattern(src0, sizeof(src0), 0x10);
+ fill_pattern(src1, sizeof(src1), 0x31);
+ fill_pattern(src2, sizeof(src2), 0x52);
+ memset(dst0, 0xa5, sizeof(dst0));
+ memset(dst1, 0xa5, sizeof(dst1));
+ memset(dst2, 0xa5, sizeof(dst2));
+
+ qtest_memwrite(qts, TEST_SRC_ADDR0, src0, sizeof(src0));
+ qtest_memwrite(qts, TEST_SRC_ADDR1, src1, sizeof(src1));
+ qtest_memwrite(qts, TEST_SRC_ADDR2, src2, sizeof(src2));
+ qtest_memwrite(qts, TEST_DST_ADDR0, dst0, sizeof(dst0));
+ qtest_memwrite(qts, TEST_DST_ADDR1, dst1, sizeof(dst1));
+ qtest_memwrite(qts, TEST_DST_ADDR2, dst2, sizeof(dst2));
+
+ write_sdma_llt_node_full(qts, TEST_LLT_ADDR0, 0, TEST_SRC_ADDR0,
+ sizeof(src0), TEST_DST_ADDR0, TEST_LLT_ADDR1);
+ write_sdma_llt_node_full(qts, TEST_LLT_ADDR1, 0, TEST_SRC_ADDR1,
+ sizeof(src1), TEST_DST_ADDR1, TEST_LLT_ADDR2);
+ write_sdma_llt_node_full(qts, TEST_LLT_ADDR2, 0, TEST_SRC_ADDR2,
+ sizeof(src2), TEST_DST_ADDR2, 0);
+
+ qtest_writel(qts, gsdma_reg(K230_GSDMA_DMA_CH_EN), BIT(0));
+ qtest_writel(qts, gsdma_ch_reg(0, K230_GSDMA_CH_LLT_SADDR), TEST_LLT_ADDR0);
+ qtest_writel(qts, gsdma_ch_reg(0, K230_GSDMA_CH_CTL), K230_GSDMA_CTL_START);
+
+ g_assert_cmphex(qtest_readl(qts, gsdma_ch_reg(0, K230_GSDMA_CH_STATUS)), ==,
+ 0);
+ g_assert_cmphex(qtest_readl(qts, gsdma_ch_reg(0, K230_GSDMA_CH_CURRENT_LLT)),
+ ==, TEST_LLT_ADDR2);
+
+ int_stat = qtest_readl(qts, gsdma_reg(K230_GSDMA_DMA_INT_STAT));
+ g_assert_cmphex(int_stat & K230_GSDMA_SDMA_DONE_INT(0), ==,
+ K230_GSDMA_SDMA_DONE_INT(0));
+ g_assert_cmphex(int_stat & K230_GSDMA_SDMA_PAUSE_INT(0), ==, 0);
+
+ memset(dst0, 0, sizeof(dst0));
+ memset(dst1, 0, sizeof(dst1));
+ memset(dst2, 0, sizeof(dst2));
+ qtest_memread(qts, TEST_DST_ADDR0, dst0, sizeof(dst0));
+ qtest_memread(qts, TEST_DST_ADDR1, dst1, sizeof(dst1));
+ qtest_memread(qts, TEST_DST_ADDR2, dst2, sizeof(dst2));
+
+ g_assert_cmpmem(dst0, sizeof(dst0), src0, sizeof(src0));
+ g_assert_cmpmem(dst1, sizeof(dst1), src1, sizeof(src1));
+ g_assert_cmpmem(dst2, sizeof(dst2), src2, sizeof(src2));
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/k230-gsdma/global-registers", test_global_registers);
+ qtest_add_func("/k230-gsdma/sdma-pause-resume", test_sdma_pause_resume);
+ qtest_add_func("/k230-gsdma/sdma-three-llt-copy",
+ test_sdma_three_llt_copy);
+
+ return g_test_run();
+}
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index b8860dd3e7..46e3c03cc8 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -260,4 +260,7 @@ config XLNX_ZYNQ_DDRC
config AXIADO_CLK
bool
+config K230_DECOMP_GZIP
+ bool
+
source macio/Kconfig
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index cbeecbf1e0..54e07aacda 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -38,6 +38,7 @@ system_ss.add(when: 'CONFIG_SIFIVE_E_PRCI', if_true: files('sifive_e_prci.c'))
system_ss.add(when: 'CONFIG_SIFIVE_E_AON', if_true: files('sifive_e_aon.c'))
system_ss.add(when: 'CONFIG_SIFIVE_U_OTP', if_true: files('sifive_u_otp.c'))
system_ss.add(when: 'CONFIG_SIFIVE_U_PRCI', if_true: files('sifive_u_prci.c'))
+system_ss.add(when: 'CONFIG_K230_DECOMP_GZIP', if_true: files('k230_decomp_gzip.c'))
subdir('macio')
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index 2d6d2238c5..16db4ba0cc 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -443,8 +443,6 @@ iommu_testdev_dma_verify(uint32_t expected, uint32_t actual) "expected=0x%x actu
iommu_testdev_dma_result(uint32_t result) "DMA completed result=0x%x"
iommu_testdev_dma_armed(bool armed) "armed=%d"
-# vmlaunchupdate.c
-launch_update_write(void) ""
-vmlaunch_reset_enter(void) ""
-vm_launchupdate_finalize(void) ""
-restore_host_x86_igvm(void) ""
+# k230_decomp_gzip.c
+k230_decomp_gzip_read(uint64_t offset, unsigned int size, uint64_t value) "K230 DECOMP GZIP read: [0x%"PRIx64"] size %u -> 0x%"PRIx64
+k230_decomp_gzip_write(uint64_t offset, unsigned int size, uint64_t value) "K230 DECOMP GZIP write: [0x%"PRIx64"] size %u <- 0x%"PRIx64
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 663a66bded..84756ba823 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -300,7 +300,7 @@ qtests_riscv64 = ['riscv-csr-test'] + \
config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
['iommu-riscv-test'] : []) + \
(config_all_devices.has_key('CONFIG_K230') ?
- ['k230-ddr-test', 'k230-wdt-test'] : [])
+ ['k230-ddr-test', 'k230-wdt-test', 'k230-gsdma-test'] : [])
qtests_hexagon = ['boot-serial-test', 'l2vic-test', 'qct-qtimer-test']
--
2.54.0