From nobody Wed Apr 2 14:19:57 2025 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org ARC-Seal: i=1; a=rsa-sha256; t=1742810752; cv=none; d=zohomail.com; s=zohoarc; b=JqlhAbu5JTY/7XNXKfBPXgeF2NNp03MRRt7+6Di9jI7FvhNRFZl3ZUtcA3Uq0GRXtB6Bo9CUxmIF+04kDoiamqx1z10VOHcq8Z8iIAEYJHi8TYzK0Q/JPvtbvUQPTBOo9BY0x1eDf+JNZ9UJKL7NvTEn5KAmm3ZuDiyfS+Df0Mo= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1742810752; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=xmQc5peEEnmmeQLP727x20HyEvrn5Isa+kNm35zGjaQ=; b=KNfEUr2iW/mC1eNAyE8l3gUlPn+joIrBCc/sJOFlyxiktzzNXeLnMV5BBqekiQWRY5hYMiZ8z66YWjR1rdrV8/6p5yB70riFx3we4i4Gd3r+4uqEhOq1/0T6NKcP18mrJO9f6tZeiNNYZJy3iy77kxdBXhmZVjxVh+lyvj4PY+A= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1742810752084802.8185385258399; Mon, 24 Mar 2025 03:05:52 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1twegc-00064K-JP; Mon, 24 Mar 2025 06:05:38 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1twegS-000615-TZ for qemu-devel@nongnu.org; Mon, 24 Mar 2025 06:05:29 -0400 Received: from forward501d.mail.yandex.net ([2a02:6b8:c41:1300:1:45:d181:d501]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1twegN-00052C-Um for qemu-devel@nongnu.org; Mon, 24 Mar 2025 06:05:27 -0400 Received: from mail-nwsmtp-smtp-production-main-80.klg.yp-c.yandex.net (mail-nwsmtp-smtp-production-main-80.klg.yp-c.yandex.net [IPv6:2a02:6b8:c42:2347:0:640:6ae5:0]) by forward501d.mail.yandex.net (Yandex) with ESMTPS id 3856B60F45; Mon, 24 Mar 2025 13:05:17 +0300 (MSK) Received: by mail-nwsmtp-smtp-production-main-80.klg.yp-c.yandex.net (smtp/Yandex) with ESMTPSA id D5L6Pk2LeW20-5jGd8TrE; Mon, 24 Mar 2025 13:05:16 +0300 X-Yandex-Fwd: 1 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=maquefel.me; s=mail; t=1742810716; bh=xmQc5peEEnmmeQLP727x20HyEvrn5Isa+kNm35zGjaQ=; h=Message-ID:Date:In-Reply-To:Cc:Subject:References:To:From; b=iEnxEUTNr207xJd46T9bokc/hYRBmnokNf84PwiNDKk3Q4UapjwpeRFrJ7Y926Kbl PZpHp7gLERuyx68FwvjXVZTZNW/t523AOCHq4BX1J0cLfuC0qCh7asQyfqnyrE9i5w p6qUZxKM61Fa4hkTAjL7ca/vV7fXGRe3P/WUy220= Authentication-Results: mail-nwsmtp-smtp-production-main-80.klg.yp-c.yandex.net; dkim=pass header.i=@maquefel.me From: Nikita Shubin To: qemu-devel@nongnu.org Cc: Nikita Shubin , Fabiano Rosas , Laurent Vivier , Paolo Bonzini Subject: [PATCH 3/3] tests/qtest: add qtests for STM32 DMA Date: Mon, 24 Mar 2025 13:05:08 +0300 Message-ID: <20250324100508.2176-4-nikita.shubin@maquefel.me> X-Mailer: git-send-email 2.48.1 In-Reply-To: <20250324100508.2176-1-nikita.shubin@maquefel.me> References: <20250324100508.2176-1-nikita.shubin@maquefel.me> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2a02:6b8:c41:1300:1:45:d181:d501; envelope-from=nikita.shubin@maquefel.me; helo=forward501d.mail.yandex.net X-Spam_score_int: -27 X-Spam_score: -2.8 X-Spam_bar: -- X-Spam_report: (-2.8 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @maquefel.me) X-ZM-MESSAGEID: 1742810755698116600 Content-Type: text/plain; charset="utf-8" From: Nikita Shubin Signed-off-by: Nikita Shubin --- 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 =3D \ (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 + */ + +#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 =3D DMA_CCR(_idx), \ + .cndrt =3D DMA_CNDTR(_idx), \ + .cpar =3D DMA_CPAR(_idx), \ + .cmar =3D DMA_CMAR(_idx), \ + .irq_line =3D _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[] =3D { + 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 =3D { + .base_addr =3D DMA_BASE, + .isr =3D 0x00, + .ofcr =3D 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 valu= e) +{ + 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 =3D test_data; + QTestState *s =3D td->qts; + const uint32_t patt_len =3D 0xff; + char *pattern_check =3D g_malloc(patt_len); + char *pattern =3D g_malloc(patt_len); + uint8_t idx =3D 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 =3D dma_read_ccr(td, idx); + val |=3D BIT(1); /* TCIE */ + val |=3D BIT(6); /* PINC */ + val |=3D BIT(7); /* MINC */ + val |=3D 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 |=3D 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) =3D=3D 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[] =3D { + { 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 =3D test_data; + QTestState *s =3D td->qts; + const uint32_t patt =3D 0xffffffff; + const uint32_t patt_len =3D 4; + uint32_t dst; + uint8_t idx =3D 0; + uint32_t val; + + qmp("{'execute':'system_reset' }"); + + /* write addr */ + dma_write_cpar(td, idx, SRAM_BASE); + dma_write_cmar(td, idx, SRAM_BASE + patt_len); + + /* enable increment and M2M */ + val =3D dma_read_ccr(td, idx); + val |=3D BIT(6); /* PINC */ + val |=3D BIT(7); /* MINC */ + val |=3D BIT(14); /* M2M */ + dma_write_ccr(td, idx, val); + + for (int i =3D 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 |=3D BIT(0); /* enable channel */ + val =3D deposit32(val, 8, 2, patterns[i].swidth >> 1); + val =3D 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) =3D=3D= 0); + + /* disable chan */ + val &=3D ~BIT(0); + dma_write_ccr(td, idx, val); + } +} + +static void dma_set_irq(unsigned int idx, int num, int level) +{ + g_autofree char *name =3D 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 =3D test_data; + QTestState *s =3D td->qts; + const uint32_t patt =3D 0xffffffff; + const uint32_t patt_len =3D 4; + uint32_t dst; + uint32_t val; + + qmp("{'execute':'system_reset' }"); + + for (int i =3D 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 =3D dma_read_ccr(td, i); + + dma_write_cndrt(td, i, 1); + val |=3D BIT(0); /* enable channel */ + val =3D deposit32(val, 8, 2, patt_len >> 1); + val =3D 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) =3D=3D 0); + + /* disable chan */ + val &=3D ~BIT(0); + dma_write_ccr(td, i, val); + } +} + +static void test_interrupts(gconstpointer test_data) +{ + const TestData *td =3D test_data; + const uint32_t patt_len =3D 1024; + uint8_t idx =3D 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 =3D dma_read_ccr(td, idx); + val |=3D BIT(0); /* EN */ + val |=3D BIT(1); /* TCIE */ + val |=3D BIT(2); /* HTIE */ + val |=3D BIT(3); /* TEIE */ + val |=3D BIT(6); /* PINC */ + val |=3D 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 =3D 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 =3D 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 =3D 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 =3D 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 =3D dma_read_ccr(td, idx); + val &=3D ~BIT(0); + dma_write_ccr(td, idx, val); + + dma_write_cndrt(td, idx, 1); + dma_write_cpar(td, idx, 0xffffffff); + + val |=3D 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 =3D 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 =3D 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 =3D 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 =3D qtest_start("-machine stm32vldiscovery"); + g_test_set_nonfatal_assertions(); + + TestData *td =3D &testdata; + td->qts =3D s; + td->dma =3D &dma; + td->chans =3D dma_chans; + add_test(m2m, td); + add_test(width, td); + add_test(triggers, td); + add_test(interrupts, td); + + ret =3D g_test_run(); + qtest_end(); + + return ret; +} --=20 2.48.1