From nobody Tue Apr 1 08:43:38 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=1742810762; cv=none; d=zohomail.com; s=zohoarc; b=VYbuBIUFXDGyefKRvLgA70BzKtu1ldEb58+YehB5O2oQIn9hRRd1YAw0P5IxvS7TK2FanRrJAi32Fx2cHoNQ+Ly7LfqBHAbsxvG49g295xA+O33VBiqtnP40AKT0Je7vABRK8HVWH0WsAcmVXZb/9EkGDWuzvijA5duN1YrQtgI= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1742810762; 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=QqJrsjo0/Jnyhmcx9AVbwB4QSRAf6LZwRSaeKYTnp7E=; b=JbFakY3W9M2C8RgnAzgFNUqHd5L63T8mAOe7Iqim0feaNhIB41d8NF+XMNxOpJyBgQDfjfra49Lr1/K0nJYh7gxjRqFomo1wBm847VzmSMytwDWF4+peoasncYgwXKjRdY+2vgK8cz+mkLK27/jsmdyQw9USUOzoHc2el+Mctpc= 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 1742810762112188.87543613943456; Mon, 24 Mar 2025 03:06:02 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1twegq-0006Db-A2; Mon, 24 Mar 2025 06:05:53 -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 1twegU-00062c-5e; Mon, 24 Mar 2025 06:05:31 -0400 Received: from forward500d.mail.yandex.net ([178.154.239.208]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1twegO-000521-9g; Mon, 24 Mar 2025 06:05:29 -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 forward500d.mail.yandex.net (Yandex) with ESMTPS id EDDB36105E; Mon, 24 Mar 2025 13:05:15 +0300 (MSK) Received: by mail-nwsmtp-smtp-production-main-80.klg.yp-c.yandex.net (smtp/Yandex) with ESMTPSA id D5L6Pk2LeW20-UMVCtpRF; Mon, 24 Mar 2025 13:05:15 +0300 X-Yandex-Fwd: 1 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=maquefel.me; s=mail; t=1742810715; bh=QqJrsjo0/Jnyhmcx9AVbwB4QSRAf6LZwRSaeKYTnp7E=; h=Message-ID:Date:In-Reply-To:Cc:Subject:References:To:From; b=m8LUbjYiKh+6D5ngihA/n4m9ZvGqywhKNI+3dn9VpH1jVavJUz/toLmRL6325DKcP 4sFtcDpGhmv+lE0CxvodeTgJVGWnsSycQMEvJzQG+FZN+7AzYHAMdKfd+3/BmARIlg jkToMIVsUQrvwMaVFA2LX/GZzVdIHaIWsPiYv6V8= 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: Ilya Chichkov , Nikita Shubin , Paolo Bonzini , Alistair Francis , Peter Maydell , qemu-arm@nongnu.org Subject: [PATCH 1/3] hw/dma: Add STM32 platfrom DMA controller emulation Date: Mon, 24 Mar 2025 13:05:06 +0300 Message-ID: <20250324100508.2176-2-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=178.154.239.208; envelope-from=nikita.shubin@maquefel.me; helo=forward500d.mail.yandex.net X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 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_NONE=-0.0001, RCVD_IN_MSPIKE_H4=-0.01, RCVD_IN_MSPIKE_WL=-0.01, RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, RCVD_IN_VALIDITY_SAFE_BLOCKED=0.001, 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: 1742810763852116600 Content-Type: text/plain; charset="utf-8" From: Nikita Shubin STMicroelectronics STM32 SoCs integrate DMA engine that supports: * Independent concurrent DMA transfers using 7/5 DMA channels * Generation of interrupts on various conditions during execution * PERIPH to MEMORY transactions, invoked by peripheral device models * MEMORY to MEMORY transactions, invoked by software This model is compatible with STM32F1xxxx and GD32F30x. Co-developed-by: Ilya Chichkov Signed-off-by: Ilya Chichkov Signed-off-by: Nikita Shubin --- hw/dma/Kconfig | 4 + hw/dma/meson.build | 1 + hw/dma/stm32_dma.c | 564 +++++++++++++++++++++++++++++++++++++ hw/dma/trace-events | 15 + include/hw/dma/stm32_dma.h | 46 +++ 5 files changed, 630 insertions(+) create mode 100644 hw/dma/stm32_dma.c create mode 100644 include/hw/dma/stm32_dma.h diff --git a/hw/dma/Kconfig b/hw/dma/Kconfig index 98fbb1bb04..f1483a1ce5 100644 --- a/hw/dma/Kconfig +++ b/hw/dma/Kconfig @@ -30,3 +30,7 @@ config SIFIVE_PDMA config XLNX_CSU_DMA bool select REGISTER + +config STM32_DMA + bool + select REGISTER \ No newline at end of file diff --git a/hw/dma/meson.build b/hw/dma/meson.build index cc7810beb8..4db1aca955 100644 --- a/hw/dma/meson.build +++ b/hw/dma/meson.build @@ -12,3 +12,4 @@ system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_d= ma.c', 'soc_dma.c')) system_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_dma.c')) system_ss.add(when: 'CONFIG_SIFIVE_PDMA', if_true: files('sifive_pdma.c')) system_ss.add(when: 'CONFIG_XLNX_CSU_DMA', if_true: files('xlnx_csu_dma.c'= )) +system_ss.add(when: 'CONFIG_STM32_DMA', if_true: files('stm32_dma.c')) diff --git a/hw/dma/stm32_dma.c b/hw/dma/stm32_dma.c new file mode 100644 index 0000000000..b4d588664d --- /dev/null +++ b/hw/dma/stm32_dma.c @@ -0,0 +1,564 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * QEMU STM32 Direct memory access controller (DMA). + * + * This includes STM32F1xxxx, STM32F2xxxx and GD32F30x + * + * Author: 2025 Nikita Shubin + */ +#include "qemu/osdep.h" + +#include "qapi/error.h" +#include "hw/qdev-properties.h" +#include "hw/qdev-properties-system.h" +#include "hw/sysbus.h" +#include "migration/vmstate.h" +#include "hw/irq.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "hw/register.h" +#include "system/dma.h" + +#include "trace.h" + +#include "hw/dma/stm32_dma.h" + +#define STM32_DMA_APERTURE_SIZE 0x400 + +/* 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) + +/* Interrupt flag register (DMA_ISR) */ +REG32(DMA_ISR, 0x00) + FIELD(DMA_ISR, CHAN0, 0, 4) + FIELD(DMA_ISR, CHAN1, 4, 4) + FIELD(DMA_ISR, CHAN2, 8, 4) + FIELD(DMA_ISR, CHAN3, 12, 4) + FIELD(DMA_ISR, CHAN4, 16, 4) + FIELD(DMA_ISR, CHAN5, 20, 4) + FIELD(DMA_ISR, CHAN6, 24, 4) + FIELD(DMA_ISR, RSVD, 28, 4) + +/* Interrupt flag clear register (DMA_IFCR) */ +REG32(DMA_IFCR, 0x04) + FIELD(DMA_IFCR, CHAN0, 0, 4) + FIELD(DMA_IFCR, CHAN1, 4, 4) + FIELD(DMA_IFCR, CHAN2, 8, 4) + FIELD(DMA_IFCR, CHAN3, 12, 4) + FIELD(DMA_IFCR, CHAN4, 16, 4) + FIELD(DMA_IFCR, CHAN5, 20, 4) + FIELD(DMA_IFCR, CHAN6, 24, 4) + FIELD(DMA_IFCR, RSVD, 28, 4) + +/* Channel x control register (DMA_CCRx) */ +/* Address offset: 0x08 + 0x14 * x */ +REG32(DMA_CCR, 0x08) + FIELD(DMA_CCR, EN, 0, 1) + FIELD(DMA_CCR, TCIE, 1, 1) + FIELD(DMA_CCR, HTIE, 2, 1) + FIELD(DMA_CCR, TEIE, 3, 1) + + /* all below RO if chan enabled */ + FIELD(DMA_CCR, DIR, 4, 1) + FIELD(DMA_CCR, CIRC, 5, 1) + FIELD(DMA_CCR, PINC, 6, 1) + FIELD(DMA_CCR, MINC, 7, 1) + FIELD(DMA_CCR, PSIZE, 8, 2) + FIELD(DMA_CCR, MSIZE, 10, 2) + FIELD(DMA_CCR, PL, 12, 2) + FIELD(DMA_CCR, M2M, 14, 1) + FIELD(DMA_CCR, RSVD, 15, 17) + +#define DMA_CCR_RO_MASK \ + (R_DMA_CCR_DIR_MASK \ + | R_DMA_CCR_CIRC_MASK \ + | R_DMA_CCR_PINC_MASK \ + | R_DMA_CCR_MINC_MASK \ + | R_DMA_CCR_PSIZE_MASK \ + | R_DMA_CCR_MSIZE_MASK \ + | R_DMA_CCR_PL_MASK \ + | R_DMA_CCR_M2M_MASK) + +/* Channel x counter register (DMA_CNDTRx) */ +/* Address offset: 0x0C + 0x14 * x */ +REG32(DMA_CNDTR, 0x0c) + FIELD(DMA_CNDTR, NDT, 0, 16) + FIELD(DMA_CNDTR, RSVD, 16, 16) + +/* Channel x peripheral base address register (DMA_CPARx) */ +/* Address offset: 0x10 + 0x14 * x */ +REG32(DMA_CPAR, 0x10) + FIELD(DMA_CPAR, PA, 0, 32) + +/* Channel x memory base address register (DMA_CMARx) */ +/* 0x14 + 0x14 * x */ +REG32(DMA_CMAR, 0x14) + FIELD(DMA_CMAR, MA, 0, 32) + +#define A_DMA_CCR0 0x08 +#define A_DMA_CMAR7 0xa0 + +static void stm32_dma_chan_update_intr(STM32DmaState *s, uint8_t idx) +{ + if (extract32(s->intf, idx * 4, 4)) { + /* set GIFCx */ + set_bit32(idx * 4, &s->intf); + qemu_irq_raise(s->output[idx]); + } +} + +static MemTxResult stm32_dma_transfer_one(STM32DmaState *s, uint8_t idx) +{ + STM32DmaChannel *chan =3D &s->chan[idx]; + uint8_t pwidth =3D 1 << FIELD_EX32(chan->chctl, DMA_CCR, PSIZE); + uint8_t mwidth =3D 1 << FIELD_EX32(chan->chctl, DMA_CCR, MSIZE); + hwaddr paddr, maddr; + MemTxResult res =3D MEMTX_OK; + uint32_t data =3D 0; + + paddr =3D chan->chpaddr; + if (FIELD_EX32(chan->chctl, DMA_CCR, PINC)) { + /* increment algorithm enabled */ + uint32_t incr =3D chan->chcnt_shadow - chan->chcnt; + + paddr +=3D pwidth * incr; + } + + maddr =3D chan->chmaddr; + if (FIELD_EX32(chan->chctl, DMA_CCR, MINC)) { + /* increment algorithm enabled */ + uint32_t incr =3D chan->chcnt_shadow - chan->chcnt; + + maddr +=3D mwidth * incr; + } + + /* issue transaction */ + if (FIELD_EX32(chan->chctl, DMA_CCR, DIR)) { + /* FROM memory TO peripheral */ + res =3D dma_memory_read(&address_space_memory, maddr, &data, + pwidth, MEMTXATTRS_UNSPECIFIED); + if (res) { + goto fail_rw; + } + + res =3D dma_memory_write(&address_space_memory, paddr, &data, + mwidth, MEMTXATTRS_UNSPECIFIED); + if (res) { + goto fail_rw; + } + + trace_stm32_dma_transfer(idx, maddr, mwidth, + paddr, pwidth, data); + } else { + /* FROM peripheral TO memory */ + res =3D dma_memory_read(&address_space_memory, paddr, &data, + pwidth, MEMTXATTRS_UNSPECIFIED); + if (res) { + goto fail_rw; + } + + res =3D dma_memory_write(&address_space_memory, maddr, &data, + mwidth, MEMTXATTRS_UNSPECIFIED); + if (res) { + goto fail_rw; + } + + trace_stm32_dma_transfer(idx, paddr, pwidth, + maddr, mwidth, data); + } + + if (FIELD_EX32(chan->chctl, DMA_CCR, HTIE)) { + /* Issue completed half transfer interrupt */ + trace_stm32_dma_raise(idx, "HTIE"); + set_bit(idx * 4 + 2, (unsigned long *)&s->intf); + } + + return res; + +fail_rw: + if (FIELD_EX32(chan->chctl, DMA_CCR, TEIE)) { + trace_stm32_dma_raise(idx, "TEIE"); + set_bit(idx * 4 + 3, (unsigned long *)&s->intf); + } + + trace_stm32_dma_transfer_fail(idx, paddr, maddr); + return res; +} + +static void stm32_dma_transfer(STM32DmaState *s, uint8_t idx, bool m2m) +{ + STM32DmaChannel *chan =3D &s->chan[idx]; + MemTxResult res =3D 0; + + if (!chan->enabled || chan->chcnt =3D=3D 0) { + trace_stm32_dma_disabled(idx, chan->enabled, chan->chcnt); + return; + } + + do { + res =3D stm32_dma_transfer_one(s, idx); + if (res) { + goto fail_rw; + } + + chan->chcnt--; + } while (chan->chcnt && m2m); + + /* rearm counter */ + if (chan->chcnt =3D=3D 0) { + if (FIELD_EX32(chan->chctl, DMA_CCR, TCIE)) { + /* Issue completed full transfer interrupt */ + trace_stm32_dma_raise(idx, "TCIE"); + set_bit(idx * 4 + 1, (unsigned long *)&s->intf); + } + + /* M2M mode can't be circular */ + if (!FIELD_EX32(chan->chctl, DMA_CCR, M2M) && + FIELD_EX32(chan->chctl, DMA_CCR, CIRC)) { + chan->chcnt =3D chan->chcnt_shadow; + trace_stm32_dma_cmen(idx, chan->chcnt); + } + } + +fail_rw: + stm32_dma_chan_update_intr(s, idx); + return; +} + +static uint32_t stm32_dma_chan_read(STM32DmaState *s, hwaddr addr) +{ + uint8_t idx =3D (addr - A_DMA_CCR0) / 0x14 /* STRIDE */; + uint8_t reg =3D (addr - 0x14 /* STRIDE */ * idx); + uint32_t val =3D 0; + + if (idx > s->nr_chans) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: chan_idx %d exceed %d number of channels\n", + __func__, idx, s->nr_chans); + return val; + } + + switch (reg) { + case A_DMA_CCR: + val =3D s->chan[idx].chctl; + break; + case A_DMA_CNDTR: + val =3D s->chan[idx].chcnt; + break; + case A_DMA_CPAR: + val =3D s->chan[idx].chpaddr; + break; + case A_DMA_CMAR: + val =3D s->chan[idx].chmaddr; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: unknown reg 0x%x\n", + __func__, reg); + break; + } + + trace_stm32_dma_chan_read(addr, idx, reg, val); + + return val; +} + +static uint64_t stm32_dma_read(void *opaque, hwaddr addr, unsigned int siz= e) +{ + STM32DmaState *s =3D STM32_DMA(opaque); + uint32_t val =3D 0; + + switch (addr) { + case A_DMA_ISR: + val =3D s->intf; + break; + case A_DMA_CCR0 ... A_DMA_CMAR7: + val =3D stm32_dma_chan_read(s, addr); + break; + /* write-only */ + case A_DMA_IFCR: + default: + /* + * TODO: WARN_ONCE ? If left as is produces spam, because many + * people use '|=3D' on write-only registers. + * qemu_log_mask(LOG_GUEST_ERROR, + * "%s: read to unimplemented register " \ + * "at address: 0x%" PRIx64 " size %d\n", + * __func__, addr, size); + */ + break; + }; + + trace_stm32_dma_read(addr); + + return val; +} + +static void stm32_dma_update_chan_ctrl(STM32DmaState *s, uint8_t idx, + uint32_t val) +{ + int is_enabled, was_enabled; + + was_enabled =3D !!FIELD_EX32(s->chan[idx].chctl, DMA_CCR, EN); + is_enabled =3D !!FIELD_EX32(val, DMA_CCR, EN); + + if (was_enabled && is_enabled) { + uint32_t protected =3D (s->chan[idx].chctl ^ val) & DMA_CCR_RO_MAS= K; + if (protected) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: attempt to write enabled chan_idx %d settin= gs " + "with val 0x%x\n", __func__, idx, val); + } + + val &=3D ~DMA_CCR_RO_MASK; + val |=3D DMA_CCR_RO_MASK & s->chan[idx].chctl; + } + + s->chan[idx].chctl =3D val; + s->chan[idx].enabled =3D !!FIELD_EX32(s->chan[idx].chctl, DMA_CCR, EN); + + if (!was_enabled && is_enabled) { + if (FIELD_EX32(s->chan[idx].chctl, DMA_CCR, M2M)) { + stm32_dma_transfer(s, idx, true); + } + } +} + +static void stm32_dma_chan_write(STM32DmaState *s, hwaddr addr, + uint64_t val) +{ + uint8_t idx =3D (addr - A_DMA_CCR0) / 0x14 /* STRIDE */; + uint8_t reg =3D (addr - 0x14 /* STRIDE */ * idx); + + if (idx > s->nr_chans) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: chan_idx %d exceed %d number of channels\n", + __func__, idx, s->nr_chans); + return; + } + + trace_stm32_dma_chan_write(addr, idx, reg, val); + + if (reg =3D=3D A_DMA_CCR) { + stm32_dma_update_chan_ctrl(s, idx, val); + return; + } + + if (s->chan[idx].enabled) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: attempt to changed enabled chan_idx %d settings= \n", + __func__, idx); + return; + } + + switch (reg) { + case A_DMA_CNDTR: + s->chan[idx].chcnt =3D FIELD_EX32(val, DMA_CNDTR, NDT); + s->chan[idx].chcnt_shadow =3D s->chan[idx].chcnt; + break; + case A_DMA_CPAR: + s->chan[idx].chpaddr =3D val; + break; + case A_DMA_CMAR: + s->chan[idx].chmaddr =3D val; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: unknown reg 0x%x\n", + __func__, reg); + break; + } +} + +static void stm32_dma_intr_ack(STM32DmaState *s, uint32_t val) +{ + int i, j; + uint32_t changed =3D val & s->intf; + + if (!changed) { + return; + } + + for (i =3D 0, j =3D 0; i < R_DMA_IFCR_RSVD_SHIFT; i +=3D 4, j++) { + uint8_t bits_changed =3D extract32(changed, i, 4); + if (bits_changed) { + /* clear bits */ + uint8_t bits =3D 0; + + /* Clear global interrupt flag of channel */ + if (!(bits_changed & BIT(0))) { + bits =3D extract32(s->intf, i, 4) & ~bits_changed; + } + + s->intf =3D deposit32(s->intf, i , 4, bits); + if (!bits) { + trace_stm32_dma_lower(j); + qemu_irq_lower(s->output[j]); + } + } + } +} + +static void stm32_dma_write(void *opaque, hwaddr addr, + uint64_t val, unsigned int size) +{ + STM32DmaState *s =3D STM32_DMA(opaque); + + switch (addr) { + case A_DMA_IFCR: + stm32_dma_intr_ack(s, val); + break; + case A_DMA_CCR0 ... A_DMA_CMAR7: + stm32_dma_chan_write(s, addr, val); + break; + /* read-only */ + case A_DMA_ISR: + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: write to unimplemented regist= er " \ + "at address: 0x%" PRIx64 " size=3D%d val=3D0x%" PRIx= 64 "\n", + __func__, addr, size, val); + break; + }; + + trace_stm32_dma_write(addr, val); +} + +static const MemoryRegionOps dma_ops =3D { + .read =3D stm32_dma_read, + .write =3D stm32_dma_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void stm32_dma_reset_enter(Object *obj, ResetType type) +{ + STM32DmaState *s =3D STM32_DMA(obj); + + s->intf =3D 0x0; + + for (int i =3D 0; i < s->nr_chans; i++) { + s->chan[i].chctl =3D 0; + s->chan[i].chcnt =3D 0; + s->chan[i].chpaddr =3D 0; + s->chan[i].chmaddr =3D 0; + + s->chan[i].enabled =3D false; + } + + trace_stm32_dma_reset("reset_enter"); +} + +static void stm32_dma_reset_hold(Object *obj, ResetType type) +{ + STM32DmaState *s =3D STM32_DMA(obj); + + for (int i =3D 0; i < s->nr_chans; i++) { + qemu_irq_lower(s->output[i]); + } + + trace_stm32_dma_reset("reset_hold"); +} + +/* irq from peripheral */ +static void stm32_dma_set(void *opaque, int line, int value) +{ + STM32DmaState *s =3D STM32_DMA(opaque); + + if (line > s->nr_chans) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: requested non-existant line %d > %d\n", + __func__, line, s->nr_chans); + return; + } + + /* start dma transfer */ + stm32_dma_transfer(s, line, false); + + trace_stm32_dma_set(line, value); +} + +static void stm32_dma_realize(DeviceState *dev, Error **errp) +{ + STM32DmaState *s =3D STM32_DMA(dev); + + memory_region_init_io(&s->mmio, OBJECT(dev), &dma_ops, s, + TYPE_STM32_DMA, STM32_DMA_APERTURE_SIZE); + + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); + + qdev_init_gpio_in(DEVICE(s), stm32_dma_set, s->nr_chans); + for (int i =3D 0; i < s->nr_chans; i++) { + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->output[i]); + } +} + +static const VMStateDescription vmstate_stm32_dma_channel =3D { + .name =3D "stm32_dma_channel", + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_BOOL(enabled, STM32DmaChannel), + VMSTATE_UINT32(chctl, STM32DmaChannel), + VMSTATE_UINT32(chcnt, STM32DmaChannel), + VMSTATE_UINT32(chpaddr, STM32DmaChannel), + VMSTATE_UINT32(chmaddr, STM32DmaChannel), + VMSTATE_UINT32(chcnt_shadow, STM32DmaChannel), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_stm32_dma =3D { + .name =3D TYPE_STM32_DMA, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_UINT8(nr_chans, STM32DmaState), + VMSTATE_UINT32(intf, STM32DmaState), + VMSTATE_STRUCT_ARRAY(chan, STM32DmaState, STM32_DMA_CHAN_NUMBER, + 1, vmstate_stm32_dma_channel, STM32DmaChannel= ), + VMSTATE_END_OF_LIST(), + } +}; + +static const Property stm32_dma_properties[] =3D { + DEFINE_PROP_UINT8("nchans", STM32DmaState, nr_chans, STM32_DMA_CHAN_NU= MBER), +}; + +static void stm32_dma_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + ResettableClass *rc =3D RESETTABLE_CLASS(klass); + + rc->phases.enter =3D stm32_dma_reset_enter; + rc->phases.hold =3D stm32_dma_reset_hold; + + dc->vmsd =3D &vmstate_stm32_dma; + dc->realize =3D stm32_dma_realize; + dc->desc =3D "STM32 DMA"; + + device_class_set_props(dc, stm32_dma_properties); +} + +static const TypeInfo stm32_dma_info =3D { + .name =3D TYPE_STM32_DMA, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(STM32DmaState), + .class_init =3D stm32_dma_class_init, +}; + +static void stm32_dma_register_types(void) +{ + type_register_static(&stm32_dma_info); +} + +type_init(stm32_dma_register_types) diff --git a/hw/dma/trace-events b/hw/dma/trace-events index 4c09790f9a..8d70fea582 100644 --- a/hw/dma/trace-events +++ b/hw/dma/trace-events @@ -47,3 +47,18 @@ pl330_iomem_read(uint32_t addr, uint32_t data) "addr: 0x= %08"PRIx32" data: 0x%08" =20 # xilinx_axidma.c xilinx_axidma_loading_desc_fail(uint32_t res) "error:%u" + +# stm32_dma.c +stm32_dma_reset(const char *type) "%s" +stm32_dma_set(int line, int value) "line %d, value %d" +stm32_dma_transfer(uint8_t idx, uint64_t src, uint8_t swidth, uint64_t dst= , uint8_t dwidth, uint32_t data) "idx: %d 0x%08" PRIx64"[%u] -> 0x%08" PRIx= 64 "[%u] 0x%x" +stm32_dma_read(uint64_t addr) "address: 0x%08"PRIx64 +stm32_dma_write(uint64_t addr, uint64_t val) "address: 0x%08"PRIx64" data:= 0x%08"PRIx64 +stm32_dma_chan_read(uint64_t addr, uint8_t idx, uint8_t reg, uint64_t val)= "address: 0x%08"PRIx64" idx: %u reg: %x data: 0x%08"PRIx64 +stm32_dma_chan_write(uint64_t addr, uint8_t idx, uint8_t reg, uint64_t val= ) "address: 0x%08"PRIx64" idx: %u reg: %x data: 0x%08"PRIx64 +stm32_dma_transfer_fail(uint8_t idx, uint64_t src, uint64_t dst) "idx: %d = 0x%08" PRIx64" -> 0x%08" PRIx64 +stm32_dma_raise(uint8_t idx, const char* type) "idx %d: %s" +stm32_dma_lower(uint8_t idx) "idx %d" +stm32_dma_cmen(uint8_t idx, uint32_t cnt) "idx %d cnt %u" +stm32_dma_disabled(uint8_t idx, bool enabled, uint32_t cnt) "idx %d enable= d %u cnt %u" + diff --git a/include/hw/dma/stm32_dma.h b/include/hw/dma/stm32_dma.h new file mode 100644 index 0000000000..4777037a71 --- /dev/null +++ b/include/hw/dma/stm32_dma.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * QEMU STM32 Direct memory access controller (DMA). + * + * This includes STM32F1xxxx, STM32F2xxxx and STM32F30x + * + * Author: 2025 Nikita Shubin + */ +#ifndef HW_STM32_DMA_H +#define HW_STM32_DMA_H + +#include "hw/sysbus.h" +#include "qom/object.h" + +#define STM32_DMA_CHAN_NUMBER 7 + +#define TYPE_STM32_DMA "stm32-dma" +OBJECT_DECLARE_SIMPLE_TYPE(STM32DmaState, STM32_DMA) + +typedef struct STM32DmaChannel { + bool enabled; + + uint32_t chctl; + uint32_t chcnt; + uint32_t chpaddr; + uint32_t chmaddr; + + uint32_t chcnt_shadow; +} STM32DmaChannel; + +typedef struct STM32DmaState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + uint8_t nr_chans; + + uint32_t intf; + + STM32DmaChannel chan[STM32_DMA_CHAN_NUMBER]; + + qemu_irq output[STM32_DMA_CHAN_NUMBER]; +} STM32DmaState; + +#endif /* HW_STM32_DMA_H */ --=20 2.48.1 From nobody Tue Apr 1 08:43:38 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=GjFk5TCI5srkqz9UpmlDzlTw0dtH+oDLOycIesZEvOjDsfY58rkrGobqa6rtLE7JOg4AYBaLik+UTrZ6T4IBjyxjOozS7vwjVdWdItsl70VKNgkXtKPiNBg7HFWQkloV4NQMKeG8fZvqhKfHh0uAPjkIzff7lu3NmJ/vT4XH004= 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=1wwVqIBAWpMO8Fgyh4dVKtbSrboBqVcDA7QCQp5Kcts=; b=MOaozSLrYyn/ManWuJ18yy+Cjtl0ea9vteFkZF3xwNh3k3NWrNI3EcY8UiENLYTY4DYI7LRwsZAa20QqHPhTkGW/klEk9t8zR8Ld1m9A3kbutA1sx44E2ydTnIEWdsqOcfiRtsDX5tj0pxtcee3XfDZ9JNP4tujo3Gvnewon9cc= 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 1742810752550990.9544348725669; 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 1twegf-00068V-V8; Mon, 24 Mar 2025 06:05:42 -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 1twegR-0005z1-H0; Mon, 24 Mar 2025 06:05:28 -0400 Received: from forward502d.mail.yandex.net ([178.154.239.210]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1twegO-000524-8U; 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 forward502d.mail.yandex.net (Yandex) with ESMTPS id 98774610E9; Mon, 24 Mar 2025 13:05:16 +0300 (MSK) Received: by mail-nwsmtp-smtp-production-main-80.klg.yp-c.yandex.net (smtp/Yandex) with ESMTPSA id D5L6Pk2LeW20-qoyq0kPG; 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=1wwVqIBAWpMO8Fgyh4dVKtbSrboBqVcDA7QCQp5Kcts=; h=Message-ID:Date:In-Reply-To:Cc:Subject:References:To:From; b=Cfc5fK8wNcJplJXq8pHvm2XXo/q94nLWC8RN8DtYsarMZUqpy7Hp1FUrBTtvB6JyZ LZADGPRGKtXNbOIv4hgMDkjKyJMqNYtYUof/ldrS+sT7tNFBH/AQ+KRYUGKNhTfYWr HJ0SahhqPeYIwCsMCSTYs/Z0TQ+VizBFpmNCvEq8= 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 , Paolo Bonzini , Peter Maydell , Alexandre Iooss , Alistair Francis , qemu-arm@nongnu.org Subject: [PATCH 2/3] hw/arm/stm32f100: Add DMA support for stm32f100 Date: Mon, 24 Mar 2025 13:05:07 +0300 Message-ID: <20250324100508.2176-3-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=178.154.239.210; envelope-from=nikita.shubin@maquefel.me; helo=forward502d.mail.yandex.net X-Spam_score_int: -30 X-Spam_score: -3.1 X-Spam_bar: --- X-Spam_report: (-3.1 / 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_NONE=-0.0001, RCVD_IN_MSPIKE_H5=-1, RCVD_IN_MSPIKE_WL=-0.01, RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, RCVD_IN_VALIDITY_SAFE_BLOCKED=0.001, 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: 1742810755711116600 Content-Type: text/plain; charset="utf-8" From: Nikita Shubin Add STM32 DMA support for stm32f100 SoC. Signals from periphery to DMA are not connected, as no STM32 periphery currently supports DMA. Signed-off-by: Nikita Shubin --- hw/arm/Kconfig | 1 + hw/arm/stm32f100_soc.c | 51 ++++++++++++++++++++++++++++++++++ include/hw/arm/stm32f100_soc.h | 3 ++ 3 files changed, 55 insertions(+) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 15200a2d7e..a51a8b8b9a 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -383,6 +383,7 @@ config STM32F100_SOC select ARM_V7M select STM32F2XX_USART select STM32F2XX_SPI + select STM32_DMA =20 config STM32F205_SOC bool diff --git a/hw/arm/stm32f100_soc.c b/hw/arm/stm32f100_soc.c index 53b5636452..dc864ef403 100644 --- a/hw/arm/stm32f100_soc.c +++ b/hw/arm/stm32f100_soc.c @@ -39,9 +39,29 @@ static const uint32_t usart_addr[STM_NUM_USARTS] =3D { 0x40013800, 0x40004= 400, 0x40004800 }; static const uint32_t spi_addr[STM_NUM_SPIS] =3D { 0x40013000, 0x40003800 = }; +static const uint32_t dma_addr[STM_NUM_DMA] =3D { 0x40020000, 0x40020400 }; =20 static const int usart_irq[STM_NUM_USARTS] =3D {37, 38, 39}; static const int spi_irq[STM_NUM_SPIS] =3D {35, 36}; +static const uint8_t dma1_irq[] =3D { + 11, /* DMA1 channel0 global interrupt */ + 12, /* DMA1 channel1 global interrupt */ + 13, /* DMA1 channel2 global interrupt */ + 14, /* DMA1 channel3 global interrupt */ + 15, /* DMA1 channel4 global interrupt */ + 16, /* DMA1 channel5 global interrupt */ + 17, /* DMA1 channel6 global interrupt */ +}; + +static const uint8_t dma2_irq[] =3D { + 56, /* DMA2 channel0 global interrupt */ + 57, /* DMA2 channel1 global interrupt */ + 58, /* DMA2 channel2 global interrupt */ + 59, /* DMA2 channel3 global interrupt */ + 59, /* DMA2 channel4/5 global interrupt */ +}; + +static const uint8_t dma_chan_num[STM_NUM_DMA] =3D { 7, 6 }; =20 static void stm32f100_soc_initfn(Object *obj) { @@ -59,6 +79,10 @@ static void stm32f100_soc_initfn(Object *obj) object_initialize_child(obj, "spi[*]", &s->spi[i], TYPE_STM32F2XX_= SPI); } =20 + for (i =3D 0; i < STM_NUM_DMA; i++) { + object_initialize_child(obj, "dma[*]", &s->dma[i], TYPE_STM32_DMA); + } + s->sysclk =3D qdev_init_clock_in(DEVICE(s), "sysclk", NULL, NULL, 0); s->refclk =3D qdev_init_clock_in(DEVICE(s), "refclk", NULL, NULL, 0); } @@ -126,6 +150,33 @@ static void stm32f100_soc_realize(DeviceState *dev_soc= , Error **errp) return; } =20 + /* DMA 1 */ + dev =3D DEVICE(&(s->dma[0])); + qdev_prop_set_uint8(dev, "nchans", dma_chan_num[0]); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->dma[0]), errp)) { + return; + } + busdev =3D SYS_BUS_DEVICE(dev); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, dma_addr[0]); + for (i =3D 0; i < ARRAY_SIZE(dma1_irq); i++) { + sysbus_connect_irq(busdev, i, qdev_get_gpio_in(armv7m, dma1_irq[i]= )); + } + + /* DMA 2 */ + dev =3D DEVICE(&(s->dma[1])); + qdev_prop_set_uint8(dev, "nchans", dma_chan_num[1]); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->dma[1]), errp)) { + return; + } + busdev =3D SYS_BUS_DEVICE(dev); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, dma_addr[1]); + for (i =3D 0; i < ARRAY_SIZE(dma2_irq); i++) { + sysbus_connect_irq(busdev, i, qdev_get_gpio_in(armv7m, dma2_irq[i]= )); + } + + /* DMA channel 4 and 5 have shared irq */ + sysbus_connect_irq(busdev, i, qdev_get_gpio_in(armv7m, dma2_irq[i - 1]= )); + /* Attach UART (uses USART registers) and USART controllers */ for (i =3D 0; i < STM_NUM_USARTS; i++) { dev =3D DEVICE(&(s->usart[i])); diff --git a/include/hw/arm/stm32f100_soc.h b/include/hw/arm/stm32f100_soc.h index a74d7b369c..0ee02e157c 100644 --- a/include/hw/arm/stm32f100_soc.h +++ b/include/hw/arm/stm32f100_soc.h @@ -26,6 +26,7 @@ #define HW_ARM_STM32F100_SOC_H =20 #include "hw/char/stm32f2xx_usart.h" +#include "hw/dma/stm32_dma.h" #include "hw/ssi/stm32f2xx_spi.h" #include "hw/arm/armv7m.h" #include "qom/object.h" @@ -36,6 +37,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(STM32F100State, STM32F100_SOC) =20 #define STM_NUM_USARTS 3 #define STM_NUM_SPIS 2 +#define STM_NUM_DMA 2 =20 #define FLASH_BASE_ADDRESS 0x08000000 #define FLASH_SIZE (128 * 1024) @@ -49,6 +51,7 @@ struct STM32F100State { =20 STM32F2XXUsartState usart[STM_NUM_USARTS]; STM32F2XXSPIState spi[STM_NUM_SPIS]; + STM32DmaState dma[STM_NUM_DMA]; =20 MemoryRegion sram; MemoryRegion flash; --=20 2.48.1 From nobody Tue Apr 1 08:43:38 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