On Mon, Aug 24, 2026 at 9:55 AM <[email protected]> wrote:
>
> 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
../hw/misc/vmlaunchupdate.c: In function ‘restore_host_x86_igvm’:
../hw/misc/vmlaunchupdate.c:151:5: error: implicit declaration of
function ‘trace_restore_host_x86_igvm’; did you mean
‘restore_host_x86_igvm’? [-Wimplicit-function-declaration]
151 | trace_restore_host_x86_igvm();
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
bad conflict resolution, probably
> 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
>
>
--
Marc-André Lureau