Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

STM32 UART/DMA Transfer Request Fixes #116

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 45 additions & 13 deletions src/Emulator/Peripherals/Peripherals/DMA/STM32DMA.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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());
}
}
}
}
Expand Down Expand Up @@ -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();
}
}
}
}
Expand Down Expand Up @@ -399,6 +429,8 @@ private static uint FromTransferType(TransferType transferType)
}
throw new InvalidOperationException("Should not reach here.");
}

public uint transferBacklog;
Copy link
Member

@mateusz-holenko mateusz-holenko Nov 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if having a counter here is correct.

DoPeripheralTransfer uses transfer parameters set by HandleConfigurationWrite. This means that once a channel is enabled, all the queued transfers will use the same configuration (which I believe can be different than the one when the transfer was "scheduled").

Is the counter logic necessary to pass your example?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good point which I have not tested.

It is necessary in the case where the DMA runs out of space in a buffer while the uart is still receiving data. In its original state the DMA will miss some bytes. This happens because during the buffer swap procedure in the zephyr driver a UART interrupt register is reset which causes a read from the UART data register, dequeuing a value. Unless the amount of requested transfers is kept track of and processed when the DMA is re-enabled and before the UART is reconfigured, then some data is lost.

I am not 100% sure what the real DMA hardware does in this case, but in the figure I mention in the PR description it shows a request being generated which I assumed would be queued/buffered somehow.

I will investigate

Copy link
Author

@tomhepworth tomhepworth Dec 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mateusz-holenko

Without the counter, wouldn't the dma just do the same thing but also lose the data that my implementation fixes?

If data is being streamed and the DMA is reconfigured halfway through, then the second half will have the new settings.


private uint memory0Address;
private uint memory1Address;
Expand Down
15 changes: 13 additions & 2 deletions src/Emulator/Peripherals/Peripherals/UART/STM32_UART.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand All @@ -47,8 +50,14 @@ 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");
}

// 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();
}
Expand Down Expand Up @@ -234,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<byte> receiveFifo = new Queue<byte>();

Expand Down