From c83acef9b415d529c403ecf1b0abfb521788824a Mon Sep 17 00:00:00 2001 From: Thomas Hepworth Date: Tue, 5 Nov 2024 13:03:07 +0000 Subject: [PATCH 1/2] Evaluated value of CancellationToken before initiating timer If the token is evaluated after the timer checks it then the cancellation token source has changed, and the new value is checked. This caused every timer to cause an idle line interrupt. Evaluating the token before starting the timer means that the cancellation status is not overwritten by new tokens. --- src/Emulator/Peripherals/Peripherals/UART/STM32_UART.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Emulator/Peripherals/Peripherals/UART/STM32_UART.cs b/src/Emulator/Peripherals/Peripherals/UART/STM32_UART.cs index ae0aaa458..cf5d07c47 100644 --- a/src/Emulator/Peripherals/Peripherals/UART/STM32_UART.cs +++ b/src/Emulator/Peripherals/Peripherals/UART/STM32_UART.cs @@ -47,7 +47,9 @@ public void WriteChar(byte value) var idleLineIn = (8 * 1000000) / BaudRate; idleLineDetectedCancellationTokenSrc = new CancellationTokenSource(); - machine.ScheduleAction(TimeInterval.FromMicroseconds(idleLineIn), _ => ReportIdleLineDetected(idleLineDetectedCancellationTokenSrc.Token), name: $"{nameof(STM32_UART)} Idle line detected"); + + var token = idleLineDetectedCancellationTokenSrc.Token; + machine.ScheduleAction(TimeInterval.FromMicroseconds(idleLineIn), _ => ReportIdleLineDetected(token), name: $"{nameof(STM32_UART)} Idle line detected"); } Update(); From 6d681e62644fabdcd0828ea105c4ea87f9fb164e Mon Sep 17 00:00:00 2001 From: Thomas Hepworth Date: Tue, 5 Nov 2024 15:57:31 +0000 Subject: [PATCH 2/2] STM32 UART/DMA Transfer Request Fixes This commit attempts to reproduce the behaviour descrbed in Figure 316. "Reception using DMA" of the STM32F4 Referance Manual (RM0090) We implement the IDMA interface in the DMA, and from the UART we request a DMA transfer when value enters data register. If there is a dma peripheral assigned, then when a value appears in the DR, request a transfer. When a transfer is requested by a peripheral a counter keeps track of how many have been requested. The counter is decremented each time a transfer happens. When the DMA is re-enabled, any transfers that were requested while it was disabled are processed. This is useful for multi-buffering UART, eg with the Zephyr asynchronous UART API. --- .../Peripherals/Peripherals/DMA/STM32DMA.cs | 58 ++++++++++++++----- .../Peripherals/UART/STM32_UART.cs | 11 +++- 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/Emulator/Peripherals/Peripherals/DMA/STM32DMA.cs b/src/Emulator/Peripherals/Peripherals/DMA/STM32DMA.cs index bf2fc0a8e..120a75b36 100644 --- a/src/Emulator/Peripherals/Peripherals/DMA/STM32DMA.cs +++ b/src/Emulator/Peripherals/Peripherals/DMA/STM32DMA.cs @@ -11,10 +11,11 @@ using Antmicro.Renode.Core; using System.Collections.Generic; using System.Linq; +using Antmicro.Renode.Peripherals.DMA; namespace Antmicro.Renode.Peripherals.DMA { - public sealed class STM32DMA : IDoubleWordPeripheral, IKnownSize, IGPIOReceiver, INumberedGPIOOutput + public sealed class STM32DMA : IDoubleWordPeripheral, IKnownSize, IGPIOReceiver, INumberedGPIOOutput, IDMA { public STM32DMA(IMachine machine) { @@ -95,6 +96,20 @@ public void Reset() } } + public void RequestTransfer(int stream) + { + streams[stream].transferBacklog = streams[stream].transferBacklog + 1; + + if(streams[stream].Enabled) + { + streams[stream].DoPeripheralTransfer(); + } + else + { + this.Log(LogLevel.Warning, "IDMA peripheral request on stream {0} ignored", stream); + } + } + public void OnGPIO(int number, bool value) { if(number < 0 || number >= streams.Length) @@ -184,6 +199,11 @@ private enum Registers HighInterruptClear = 0xC // DMA_HIFCR } + public int NumberOfChannels + { + get { return 8; } + } + private class Stream { public Stream(STM32DMA parent, int streamNo) @@ -253,6 +273,7 @@ public void Reset() direction = Direction.PeripheralToMemory; interruptOnComplete = false; Enabled = false; + transferBacklog = 0u; } private Request CreateRequest(int? size = null, int? destinationOffset = null) @@ -299,21 +320,24 @@ public void DoTransfer() public void DoPeripheralTransfer() { - var request = CreateRequest((int)memoryTransferType, transferredSize); - transferredSize += (int)memoryTransferType; - if(request.Size > 0) - { - lock(parent.streamFinished) + while(transferBacklog > 0){ + var request = CreateRequest((int)memoryTransferType, transferredSize); + transferredSize += (int)memoryTransferType; + transferBacklog--; + if(request.Size > 0) { - parent.engine.IssueCopy(request); - if(transferredSize == numberOfData * (int)memoryTransferType) + lock(parent.streamFinished) { - transferredSize = 0; - parent.streamFinished[streamNo] = true; - Enabled = false; - if(interruptOnComplete) + parent.engine.IssueCopy(request); + if(transferredSize == numberOfData * (int)memoryTransferType) { - parent.machine.LocalTimeSource.ExecuteInNearestSyncedState(_ => IRQ.Set()); + transferredSize = 0; + parent.streamFinished[streamNo] = true; + Enabled = false; + if(interruptOnComplete) + { + parent.machine.LocalTimeSource.ExecuteInNearestSyncedState(_ => IRQ.Set()); + } } } } @@ -365,6 +389,12 @@ private void HandleConfigurationWrite(uint value) else { Enabled = true; + + // If there is data in the backlog when the DMA is re-enabled, process it. + if(direction == Direction.PeripheralToMemory && transferBacklog > 0) + { + DoPeripheralTransfer(); + } } } } @@ -399,6 +429,8 @@ private static uint FromTransferType(TransferType transferType) } throw new InvalidOperationException("Should not reach here."); } + + public uint transferBacklog; private uint memory0Address; private uint memory1Address; diff --git a/src/Emulator/Peripherals/Peripherals/UART/STM32_UART.cs b/src/Emulator/Peripherals/Peripherals/UART/STM32_UART.cs index cf5d07c47..f3b89fa01 100644 --- a/src/Emulator/Peripherals/Peripherals/UART/STM32_UART.cs +++ b/src/Emulator/Peripherals/Peripherals/UART/STM32_UART.cs @@ -14,15 +14,18 @@ using Antmicro.Migrant.Hooks; using Antmicro.Renode.Core.Structure.Registers; using Antmicro.Renode.Time; +using Antmicro.Renode.Peripherals.DMA; namespace Antmicro.Renode.Peripherals.UART { [AllowedTranslations(AllowedTranslation.WordToDoubleWord | AllowedTranslation.ByteToDoubleWord)] public class STM32_UART : BasicDoubleWordPeripheral, IUART { - public STM32_UART(IMachine machine, uint frequency = 8000000) : base(machine) + public STM32_UART(IMachine machine, uint frequency = 8000000, IDMA dmaPeripheral = null, int dmaChannel = 5) : base(machine) { this.frequency = frequency; + this.dmaPeripheral = dmaPeripheral; + this.dmaChannel = dmaChannel; DefineRegisters(); } @@ -51,6 +54,10 @@ public void WriteChar(byte value) var token = idleLineDetectedCancellationTokenSrc.Token; machine.ScheduleAction(TimeInterval.FromMicroseconds(idleLineIn), _ => ReportIdleLineDetected(token), name: $"{nameof(STM32_UART)} Idle line detected"); } + + // If there is an assigned dma peripheral, then generate a request + // when a byte appears in the data register + this.dmaPeripheral?.RequestTransfer(this.dmaChannel); Update(); } @@ -236,6 +243,8 @@ private void Update() private IFlagRegisterField transmissionComplete; private IValueRegisterField dividerMantissa; private IValueRegisterField dividerFraction; + private IDMA dmaPeripheral; + private readonly int dmaChannel; private readonly Queue receiveFifo = new Queue();