From: Fabiano Rosas <farosas@suse.de>
To: Nikita Shubin <nikita.shubin@maquefel.me>, qemu-devel@nongnu.org
Cc: Nikita Shubin <n.shubin@yadro.com>,
Laurent Vivier <lvivier@redhat.com>,
Paolo Bonzini <pbonzini@redhat.com>
Subject: Re: [PATCH 3/3] tests/qtest: add qtests for STM32 DMA
Date: Tue, 01 Apr 2025 11:11:32 -0300 [thread overview]
Message-ID: <87jz84ezu3.fsf@suse.de> (raw)
In-Reply-To: <20250324100508.2176-4-nikita.shubin@maquefel.me>
Nikita Shubin <nikita.shubin@maquefel.me> writes:
> From: Nikita Shubin <n.shubin@yadro.com>
>
> Signed-off-by: Nikita Shubin <n.shubin@yadro.com>
> ---
> tests/qtest/meson.build | 1 +
> tests/qtest/stm32-dma-test.c | 415 +++++++++++++++++++++++++++++++++++
> 2 files changed, 416 insertions(+)
> create mode 100644 tests/qtest/stm32-dma-test.c
>
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index 5a8c1f102c..6c45692f9d 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -240,6 +240,7 @@ qtests_arm = \
> (config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test'] : []) + \
> (config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \
> (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
> + (config_all_devices.has_key('CONFIG_STM32F100_SOC') ? ['stm32-dma-test'] : []) + \
> (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') ? qtests_stm32l4x5 : []) + \
> (config_all_devices.has_key('CONFIG_FSI_APB2OPB_ASPEED') ? ['aspeed_fsi-test'] : []) + \
> (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') and
> diff --git a/tests/qtest/stm32-dma-test.c b/tests/qtest/stm32-dma-test.c
> new file mode 100644
> index 0000000000..74b81fa434
> --- /dev/null
> +++ b/tests/qtest/stm32-dma-test.c
> @@ -0,0 +1,415 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * QTest testcase for STM32 DMA engine.
> + *
> + * This includes STM32F1xxxx, STM32F2xxxx and GD32F30x
> + *
> + * Author: 2025 Nikita Shubin <n.shubin@yadro.com>
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/bitops.h"
> +#include "libqtest-single.h"
> +#include "libqos/libqos.h"
> +
> +/* Offsets in stm32vldiscovery platform: */
> +#define DMA_BASE 0x40020000
> +#define SRAM_BASE 0x20000000
> +
> +/* Global interrupt flag */
> +#define DMA_ISR_GIF BIT(0)
> +/* Full transfer finish */
> +#define DMA_ISR_TCIF BIT(1)
> +/* Half transfer finish */
> +#define DMA_ISR_HTIF BIT(2)
> +/* Transfer error */
> +#define DMA_ISR_TEIF BIT(3)
> +
> +/* Used register/fields definitions */
> +#define DMA_CCR(idx) (0x08 + 0x14 * idx)
> +#define DMA_CNDTR(idx) (0x0C + 0x14 * idx)
> +#define DMA_CPAR(idx) (0x10 + 0x14 * idx)
> +#define DMA_CMAR(idx) (0x14 + 0x14 * idx)
> +
> +#define DMA_MAX_CHAN 7
> +
> +/* Register offsets for a dma chan0 within a dma block. */
> +#define DMA_CHAN(_idx, _irq) \
> + { \
> + .ccr = DMA_CCR(_idx), \
> + .cndrt = DMA_CNDTR(_idx), \
> + .cpar = DMA_CPAR(_idx), \
> + .cmar = DMA_CMAR(_idx), \
> + .irq_line = _irq,\
> + }
> +
> +typedef struct DMAChan {
> + uint32_t ccr;
> + uint32_t cndrt;
> + uint32_t cpar;
> + uint32_t cmar;
> + uint8_t irq_line;
> +} DMAChan;
> +
> +const DMAChan dma_chans[] = {
> + DMA_CHAN(0, 11),
> + DMA_CHAN(1, 12),
> + DMA_CHAN(2, 12),
> + DMA_CHAN(3, 13),
> + DMA_CHAN(4, 14),
> + DMA_CHAN(5, 16),
> + DMA_CHAN(6, 17),
> +};
> +
> +/* Register offsets for a dma within a dma block. */
> +typedef struct DMA {
> + uint32_t base_addr;
> + uint32_t isr;
> + uint32_t ofcr;
> +} DMA;
> +
> +const DMA dma = {
> + .base_addr = DMA_BASE,
> + .isr = 0x00,
> + .ofcr = 0x04,
> +};
> +
> +typedef struct TestData {
> + QTestState *qts;
> + const DMA *dma;
> + const DMAChan *chans;
> +} TestData;
> +
> +#define NVIC_ISER 0xE000E100
> +#define NVIC_ISPR 0xE000E200
> +#define NVIC_ICPR 0xE000E280
> +
> +static void enable_nvic_irq(unsigned int n)
> +{
> + writel(NVIC_ISER, 1 << n);
> +}
> +
> +static void unpend_nvic_irq(unsigned int n)
> +{
> + writel(NVIC_ICPR, 1 << n);
> +}
> +
> +static bool check_nvic_pending(unsigned int n)
> +{
> + return readl(NVIC_ISPR) & (1 << n);
> +}
> +
> +static uint32_t dma_read(const TestData *td, uint32_t offset)
> +{
> + return qtest_readl(td->qts, td->dma->base_addr + offset);
> +}
> +
> +static void dma_write(const TestData *td, uint32_t offset, uint32_t value)
> +{
> + qtest_writel(td->qts, td->dma->base_addr + offset, value);
> +}
> +
> +static void dma_write_ofcr(const TestData *td, uint32_t value)
> +{
> + return dma_write(td, td->dma->ofcr, value);
> +}
> +
> +static uint32_t dma_read_isr(const TestData *td)
> +{
> + return dma_read(td, td->dma->isr);
> +}
> +
> +static void dma_write_ccr(const TestData *td, uint8_t idx, uint32_t value)
> +{
> + dma_write(td, td->chans[idx].ccr, value);
> +}
> +
> +static uint32_t dma_read_ccr(const TestData *td, uint8_t idx)
> +{
> + return dma_read(td, td->chans[idx].ccr);
> +}
> +
> +static void dma_write_cndrt(const TestData *td, uint8_t idx, uint32_t value)
> +{
> + dma_write(td, td->chans[idx].cndrt, value);
> +}
> +
> +static void dma_write_cpar(const TestData *td, uint8_t idx, uint32_t value)
> +{
> + dma_write(td, td->chans[idx].cpar, value);
> +}
> +
> +static void dma_write_cmar(const TestData *td, uint8_t idx, uint32_t value)
> +{
> + dma_write(td, td->chans[idx].cmar, value);
> +}
> +
> +static void test_m2m(gconstpointer test_data)
> +{
> + const TestData *td = test_data;
> + QTestState *s = td->qts;
> + const uint32_t patt_len = 0xff;
> + char *pattern_check = g_malloc(patt_len);
> + char *pattern = g_malloc(patt_len);
These^ two should be g_autofree.
> + uint8_t idx = 0;
> + uint32_t val;
> +
> + enable_nvic_irq(td->chans[idx].irq_line);
> + qtest_irq_intercept_in(global_qtest, "/machine/soc/dma[0]");
> +
> + /* write addr */
> + dma_write_cpar(td, idx, SRAM_BASE);
> + dma_write_cmar(td, idx, SRAM_BASE + patt_len);
> +
> + /* enable increment and M2M */
> + val = dma_read_ccr(td, idx);
> + val |= BIT(1); /* TCIE */
> + val |= BIT(6); /* PINC */
> + val |= BIT(7); /* MINC */
> + val |= BIT(14); /* M2M */
> + dma_write_ccr(td, idx, val);
> +
> + generate_pattern(pattern, patt_len, patt_len);
> + qtest_memwrite(s, SRAM_BASE, pattern, patt_len);
> +
> + dma_write_cndrt(td, idx, patt_len);
> +
> + val |= BIT(0); /* enable channel */
> + dma_write_ccr(td, idx, val);
> +
> + qtest_memread(s, SRAM_BASE + patt_len, pattern_check, patt_len);
> +
> + g_assert(memcmp(pattern, pattern_check, patt_len) == 0);
> +
> + g_assert_true(check_nvic_pending(td->chans[idx].irq_line));
> +}
> +
> +typedef struct width_pattern {
> + uint32_t src;
> + uint8_t swidth;
> + uint32_t dst;
> + uint8_t dwidth;
> +} width_pattern;
> +
> +static void test_width(gconstpointer test_data)
> +{
> + const width_pattern patterns[] = {
> + { 0xb0, 1, 0xb0, 1 },
> + { 0xb0, 1, 0x00b0, 2 },
> + { 0xb0, 1, 0x000000b0, 4 },
> + { 0xb1b0, 2, 0xb0, 1 },
> + { 0xb1b0, 2, 0xb1b0, 2 },
> + { 0xb1b0, 2, 0x0000b1b0, 4 },
> + { 0xb3b2b1b0, 4, 0xb0, 1 },
> + { 0xb3b2b1b0, 4, 0xb1b0, 2 },
> + { 0xb3b2b1b0, 4, 0xb3b2b1b0, 4 },
> + };
> +
> + const TestData *td = test_data;
> + QTestState *s = td->qts;
> + const uint32_t patt = 0xffffffff;
> + const uint32_t patt_len = 4;
> + uint32_t dst;
> + uint8_t idx = 0;
> + uint32_t val;
> +
> + qmp("{'execute':'system_reset' }");
These calls to qmp() all leak the response dict. You need to catch the
return value and unref.
QDict *response;
response = qmp("{'execute':'system_reset' }");
qobject_unref(response);
> +
> + /* write addr */
> + dma_write_cpar(td, idx, SRAM_BASE);
> + dma_write_cmar(td, idx, SRAM_BASE + patt_len);
> +
> + /* enable increment and M2M */
> + val = dma_read_ccr(td, idx);
> + val |= BIT(6); /* PINC */
> + val |= BIT(7); /* MINC */
> + val |= BIT(14); /* M2M */
> + dma_write_ccr(td, idx, val);
> +
> + for (int i = 0; i < ARRAY_SIZE(patterns); i++) {
> + /* fill destination and source with pattern */
> + qtest_memwrite(s, SRAM_BASE, &patt, patt_len);
> + qtest_memwrite(s, SRAM_BASE + patt_len, &patt, patt_len);
> +
> + qtest_memwrite(s, SRAM_BASE, &patterns[i].src, patterns[i].swidth);
> +
> + dma_write_cndrt(td, idx, 1);
> + val |= BIT(0); /* enable channel */
> + val = deposit32(val, 8, 2, patterns[i].swidth >> 1);
> + val = deposit32(val, 10, 2, patterns[i].dwidth >> 1);
> + dma_write_ccr(td, idx, val);
> +
> + qtest_memread(s, SRAM_BASE + patt_len, &dst, patterns[i].dwidth);
> +
> + g_assert(memcmp(&dst, &patterns[i].dst, patterns[i].dwidth) == 0);
> +
> + /* disable chan */
> + val &= ~BIT(0);
> + dma_write_ccr(td, idx, val);
> + }
> +}
> +
> +static void dma_set_irq(unsigned int idx, int num, int level)
> +{
> + g_autofree char *name = g_strdup_printf("/machine/soc/dma[%u]",
> + idx);
> + qtest_set_irq_in(global_qtest, name, NULL, num, level);
> +}
> +
> +static void test_triggers(gconstpointer test_data)
> +{
> + const TestData *td = test_data;
> + QTestState *s = td->qts;
> + const uint32_t patt = 0xffffffff;
> + const uint32_t patt_len = 4;
> + uint32_t dst;
> + uint32_t val;
> +
> + qmp("{'execute':'system_reset' }");
> +
> + for (int i = 0; i < ARRAY_SIZE(dma_chans); i++) {
> + qtest_memset(s, SRAM_BASE, 0, patt_len * 2);
> + qtest_memwrite(s, SRAM_BASE, &patt, patt_len);
> +
> + /* write addr */
> + dma_write_cpar(td, i, SRAM_BASE);
> + dma_write_cmar(td, i, SRAM_BASE + patt_len);
> +
> + val = dma_read_ccr(td, i);
> +
> + dma_write_cndrt(td, i, 1);
> + val |= BIT(0); /* enable channel */
> + val = deposit32(val, 8, 2, patt_len >> 1);
> + val = deposit32(val, 10, 2, patt_len >> 1);
> + dma_write_ccr(td, i, val);
> +
> + dma_set_irq(0, i, 1);
> +
> + qtest_memread(s, SRAM_BASE + patt_len, &dst, patt_len);
> +
> + g_assert(memcmp(&dst, &patt, patt_len) == 0);
> +
> + /* disable chan */
> + val &= ~BIT(0);
> + dma_write_ccr(td, i, val);
> + }
> +}
> +
> +static void test_interrupts(gconstpointer test_data)
> +{
> + const TestData *td = test_data;
> + const uint32_t patt_len = 1024;
> + uint8_t idx = 0;
> + uint32_t val;
> +
> + qmp("{'execute':'system_reset' }");
> +
> + enable_nvic_irq(td->chans[idx].irq_line);
> +
> + /* write addr */
> + dma_write_cpar(td, idx, SRAM_BASE);
> + dma_write_cmar(td, idx, SRAM_BASE + patt_len);
> +
> + /* write counter */
> + dma_write_cndrt(td, idx, 2);
> +
> + /* enable increment and M2M */
> + val = dma_read_ccr(td, idx);
> + val |= BIT(0); /* EN */
> + val |= BIT(1); /* TCIE */
> + val |= BIT(2); /* HTIE */
> + val |= BIT(3); /* TEIE */
> + val |= BIT(6); /* PINC */
> + val |= BIT(7); /* MINC */
> + dma_write_ccr(td, idx, val);
> +
> + /* Half-transfer */
> + dma_set_irq(0, idx, 1);
> + g_assert_true(check_nvic_pending(td->chans[idx].irq_line));
> + val = dma_read_isr(td);
> +
> + g_assert_true(val & DMA_ISR_GIF);
> + g_assert_true(val & DMA_ISR_HTIF);
> + unpend_nvic_irq(td->chans[idx].irq_line);
> +
> + dma_write_ofcr(td, 0xffffffff);
> + val = dma_read_isr(td);
> + g_assert_false(val & DMA_ISR_GIF);
> + g_assert_false(val & DMA_ISR_HTIF);
> +
> + /* Full-transfer */
> + dma_set_irq(0, idx, 1);
> + g_assert_true(check_nvic_pending(td->chans[idx].irq_line));
> + val = dma_read_isr(td);
> +
> + g_assert_true(val & DMA_ISR_GIF);
> + g_assert_true(val & DMA_ISR_HTIF);
> + g_assert_true(val & DMA_ISR_TCIF);
> + unpend_nvic_irq(td->chans[idx].irq_line);
> +
> + dma_write_ofcr(td, 0xffffffff);
> + val = dma_read_isr(td);
> + g_assert_false(val & DMA_ISR_GIF);
> + g_assert_false(val & DMA_ISR_HTIF);
> + g_assert_false(val & DMA_ISR_TCIF);
> +
> + /* Error-on-transfer */
> + val = dma_read_ccr(td, idx);
> + val &= ~BIT(0);
> + dma_write_ccr(td, idx, val);
> +
> + dma_write_cndrt(td, idx, 1);
> + dma_write_cpar(td, idx, 0xffffffff);
> +
> + val |= BIT(0);
> + dma_write_ccr(td, idx, val);
> +
> + dma_set_irq(0, idx, 1);
> + g_assert_true(check_nvic_pending(td->chans[idx].irq_line));
> + val = dma_read_isr(td);
> +
> + g_assert_true(val & DMA_ISR_GIF);
> + g_assert_true(val & DMA_ISR_TEIF);
> + unpend_nvic_irq(td->chans[idx].irq_line);
> +
> + dma_write_ofcr(td, 0xffffffff);
> + val = dma_read_isr(td);
> + g_assert_false(val & DMA_ISR_GIF);
> + g_assert_false(val & DMA_ISR_TEIF);
> +}
> +
> +static void stm32_add_test(const char *name, const TestData *td,
> + GTestDataFunc fn)
> +{
> + g_autofree char *full_name = g_strdup_printf(
> + "stm32_dma/%s", name);
> + qtest_add_data_func(full_name, td, fn);
> +}
> +
> +/* Convenience macro for adding a test with a predictable function name. */
> +#define add_test(name, td) stm32_add_test(#name, td, test_##name)
> +
> +int main(int argc, char **argv)
> +{
> + TestData testdata;
> + QTestState *s;
> + int ret;
> +
> + g_test_init(&argc, &argv, NULL);
> + s = qtest_start("-machine stm32vldiscovery");
> + g_test_set_nonfatal_assertions();
> +
> + TestData *td = &testdata;
> + td->qts = s;
> + td->dma = &dma;
> + td->chans = dma_chans;
> + add_test(m2m, td);
> + add_test(width, td);
> + add_test(triggers, td);
> + add_test(interrupts, td);
> +
> + ret = g_test_run();
> + qtest_end();
> +
> + return ret;
> +}
prev parent reply other threads:[~2025-04-01 14:12 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-03-24 10:05 [PATCH 0/3] Add STM32 DMA model Nikita Shubin
2025-03-24 10:05 ` [PATCH 1/3] hw/dma: Add STM32 platfrom DMA controller emulation Nikita Shubin
2025-03-24 10:05 ` [PATCH 2/3] hw/arm/stm32f100: Add DMA support for stm32f100 Nikita Shubin
2025-03-24 10:05 ` [PATCH 3/3] tests/qtest: add qtests for STM32 DMA Nikita Shubin
2025-04-01 14:11 ` Fabiano Rosas [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87jz84ezu3.fsf@suse.de \
--to=farosas@suse.de \
--cc=lvivier@redhat.com \
--cc=n.shubin@yadro.com \
--cc=nikita.shubin@maquefel.me \
--cc=pbonzini@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.