1 //
2 // Copyright (c) 2010-2024 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using System;
9 using System.Linq;
10 using Antmicro.Renode.Debugging;
11 using Antmicro.Renode.Peripherals.Bus;
12 using Antmicro.Renode.Peripherals.Memory;
13 
14 namespace Antmicro.Renode.Peripherals.DMA
15 {
16     public sealed class DmaEngine
17     {
DmaEngine(IBusController systemBus)18         public DmaEngine(IBusController systemBus)
19         {
20             sysbus = systemBus;
21         }
22 
IssueCopy(Request request, CPU.ICPU context = null)23         public Response IssueCopy(Request request, CPU.ICPU context = null)
24         {
25             var response = new Response
26             {
27                 ReadAddress = request.Source.Address,
28                 WriteAddress = request.Destination.Address,
29             };
30 
31             var readLengthInBytes = (int)request.ReadTransferType;
32             var writeLengthInBytes = (int)request.WriteTransferType;
33 
34             // some sanity checks
35             if((request.Size % readLengthInBytes) != 0 || (request.Size % writeLengthInBytes) != 0)
36             {
37                 throw new ArgumentException("Request size is not aligned properly to given read or write transfer type (or both).");
38             }
39 
40             var buffer = new byte[request.Size];
41             var sourceAddress = request.Source.Address ?? 0;
42             var whatIsAtSource = sysbus.WhatIsAt(sourceAddress, context);
43             var isSourceContinuousMemory = (whatIsAtSource == null || whatIsAtSource.Peripheral is MappedMemory) // Not a peripheral
44                                                         && readLengthInBytes == request.SourceIncrementStep; // Consistent memory region
45             if(!request.Source.Address.HasValue)
46             {
47                 // request array based copy
48                 Array.Copy(request.Source.Array, request.Source.StartIndex.Value, buffer, 0, request.Size);
49             }
50             else if(isSourceContinuousMemory)
51             {
52                 if(request.IncrementReadAddress)
53                 {
54                         // Transfer Units |  1  |  2  |  3  |  4  |
55                         // Source         |  A  |  B  |  C  |  D  |
56                         // Copied         |  A  |  B  |  C  |  D  |
57                     sysbus.ReadBytes(sourceAddress, request.Size, buffer, 0, context: context);
58                     response.ReadAddress += (ulong)request.Size;
59                 }
60                 else
61                 {
62                     // When reading from the memory with IncrementReadAddress unset, effectively, only the last unit will be used
63                     // Transfer Units |  1  |  2  |  3  |  4  |
64                     // Source         |  A  |  B  |  C  |  D  |
65                     // Copied         |  D  |     |     |     |
66                     sysbus.ReadBytes(sourceAddress, readLengthInBytes, buffer, 0, context: context);
67                 }
68             }
69             else if(whatIsAtSource != null)
70             {
71                 // Read from peripherals
72                 var transferred = 0;
73                 var offset = 0UL;
74                 while(transferred < request.Size)
75                 {
76                     var readAddress = sourceAddress + offset;
77                     switch(request.ReadTransferType)
78                     {
79                     case TransferType.Byte:
80                         buffer[transferred] = sysbus.ReadByte(readAddress, context);
81                         break;
82                     case TransferType.Word:
83                         BitConverter.GetBytes(sysbus.ReadWord(readAddress, context)).CopyTo(buffer, transferred);
84                         break;
85                     case TransferType.DoubleWord:
86                         BitConverter.GetBytes(sysbus.ReadDoubleWord(readAddress, context)).CopyTo(buffer, transferred);
87                         break;
88                     case TransferType.QuadWord:
89                         BitConverter.GetBytes(sysbus.ReadQuadWord(readAddress, context)).CopyTo(buffer, transferred);
90                         break;
91                     default:
92                         throw new ArgumentOutOfRangeException($"Requested read transfer size: {request.ReadTransferType} is not supported by DmaEngine");
93                     }
94                     transferred += readLengthInBytes;
95                     if(request.IncrementReadAddress)
96                     {
97                         offset += request.SourceIncrementStep;
98                         response.ReadAddress += request.SourceIncrementStep;
99                     }
100                 }
101             }
102 
103             var destinationAddress = request.Destination.Address ?? 0;
104             var whatIsAtDestination = sysbus.WhatIsAt(destinationAddress, context);
105             var isDestinationContinuousMemory = (whatIsAtDestination == null || whatIsAtDestination.Peripheral is MappedMemory) // Not a peripheral
106                                                         && writeLengthInBytes == request.DestinationIncrementStep;  // Consistent memory region
107             if(!request.Destination.Address.HasValue)
108             {
109                 // request array based copy
110                 Array.Copy(buffer, 0, request.Destination.Array, request.Destination.StartIndex.Value, request.Size);
111             }
112             else if(isDestinationContinuousMemory)
113             {
114                 if(request.IncrementWriteAddress)
115                 {
116                     if(request.IncrementReadAddress || !isSourceContinuousMemory)
117                     {
118                         // Transfer Units |  1  |  2  |  3  |  4  |
119                         // Source         |  A  |  B  |  C  |  D  |
120                         // Destination    |  A  |  B  |  C  |  D  |
121                         sysbus.WriteBytes(buffer, destinationAddress, context: context);
122                     }
123                     else
124                     {
125                         // When writing memory with IncrementReadAddress unset all destination units are written with the first source unit
126                         // Transfer Units |  1  |  2  |  3  |  4  |
127                         // Source         |  A  |  B  |  C  |  D  |
128                         // Destination    |  A  |  A  |  A  |  A  |
129                         var chunkStartOffset = 0UL;
130                         var chunk = buffer.Take(writeLengthInBytes).ToArray();
131                         while(chunkStartOffset < (ulong)request.Size)
132                         {
133                             var writeAddress = destinationAddress + chunkStartOffset;
134                             sysbus.WriteBytes(chunk, writeAddress, context: context);
135                             chunkStartOffset += (ulong)writeLengthInBytes;
136                         }
137                     }
138                     response.WriteAddress += (ulong)request.Size;
139                 }
140                 else
141                 {
142                     // When writing to memory with IncrementWriteAddress unset, effectively, only the last unit is written with the last unit of source
143                     // Transfer Units |  1  |  2  |  3  |  4  |
144                     // Source         |  A  |  B  |  C  |  D  |
145                     // Destination    |  D  |     |     |     |
146                     var skipCount = (request.Size == writeLengthInBytes) ? 0 : request.Size - writeLengthInBytes;
147                     DebugHelper.Assert((skipCount + request.Size) <= buffer.Length);
148                     sysbus.WriteBytes(buffer.Skip(skipCount).ToArray(), destinationAddress, context: context);
149                 }
150             }
151             else if(whatIsAtDestination != null)
152             {
153                 // Write to peripheral
154                 var transferred = 0;
155                 var offset = 0UL;
156                 while(transferred < request.Size)
157                 {
158                     switch(request.WriteTransferType)
159                     {
160                     case TransferType.Byte:
161                         sysbus.WriteByte(destinationAddress + offset, buffer[transferred], context);
162                         break;
163                     case TransferType.Word:
164                         sysbus.WriteWord(destinationAddress + offset, BitConverter.ToUInt16(buffer, transferred), context);
165                         break;
166                     case TransferType.DoubleWord:
167                         sysbus.WriteDoubleWord(destinationAddress + offset, BitConverter.ToUInt32(buffer, transferred), context);
168                         break;
169                     case TransferType.QuadWord:
170                         sysbus.WriteQuadWord(destinationAddress + offset, BitConverter.ToUInt64(buffer, transferred), context);
171                         break;
172                     default:
173                         throw new ArgumentOutOfRangeException($"Requested write transfer size: {request.WriteTransferType} is not supported by DmaEngine");
174                     }
175                     transferred += writeLengthInBytes;
176                     if(request.IncrementWriteAddress)
177                     {
178                         offset += request.DestinationIncrementStep;
179                         response.WriteAddress += request.DestinationIncrementStep;
180                     }
181                 }
182             }
183 
184             return response;
185         }
186 
187         private readonly IBusController sysbus;
188     }
189 }
190 
191