1 //
2 // Copyright (c) 2010-2025 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 
9 using System;
10 using System.Runtime.InteropServices;
11 using Microsoft.CSharp.RuntimeBinder;
12 using Antmicro.Renode.Peripherals.Bus;
13 using Antmicro.Renode.Utilities;
14 using System.Linq;
15 using System.Collections.Generic;
16 using System.Diagnostics;
17 using Antmicro.Migrant;
18 using Antmicro.Renode.Logging;
19 using System.IO;
20 using System.IO.MemoryMappedFiles;
21 using System.Threading.Tasks;
22 using System.Threading;
23 #if NET
24 // LZ4 library (lz4net) used on mono/.NET Framework relies on dll dynamically generated in
25 // /tmp/f82a68ada1d833f8838dd859bcb27a61/50495300240756128a79c0c0850f98e5.dll.
26 // That generated dll caused problems when switching from dotnet to mono build (in that order),
27 // as dll stayed in /tmp/ and was not regenerated. Such auto-generated dll
28 // was incompatible with mono and caused crash at runtime during serialization.
29 // Newer version of library doesn't rely on autogenerated dll so it doesn't conflict with mono.
30 using K4os.Compression.LZ4;
31 #else
32 using LZ4;
33 #endif
34 using Antmicro.Renode.UserInterface;
35 using Antmicro.Renode.Core;
36 using Antmicro.Renode.Peripherals.CPU;
37 #if PLATFORM_WINDOWS
38 using System.Reflection.Emit;
39 using System.Reflection;
40 #endif
41 using Antmicro.Renode.Exceptions;
42 using Endianess = ELFSharp.ELF.Endianess;
43 
44 namespace Antmicro.Renode.Peripherals.Memory
45 {
46     [Icon("memory")]
47     public sealed class MappedMemory : IBytePeripheral, IWordPeripheral, IDoubleWordPeripheral, IQuadWordPeripheral, IMapped, IDisposable, IKnownSize, ISpeciallySerializable, IMemory, IMultibyteWritePeripheral, ICanLoadFiles, IEndiannessAware
48     {
49 #if PLATFORM_WINDOWS
MappedMemory()50         static MappedMemory()
51         {
52             var dynamicMethod = new DynamicMethod("Memset", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard,
53                 null, new [] { typeof(IntPtr), typeof(byte), typeof(int) }, typeof(MappedMemory), true);
54 
55             var generator = dynamicMethod.GetILGenerator();
56             generator.Emit(OpCodes.Ldarg_0);
57             generator.Emit(OpCodes.Ldarg_1);
58             generator.Emit(OpCodes.Ldarg_2);
59             generator.Emit(OpCodes.Initblk);
60             generator.Emit(OpCodes.Ret);
61 
62             MemsetDelegate = (Action<IntPtr, byte, int>)dynamicMethod.CreateDelegate(typeof(Action<IntPtr, byte, int>));
63         }
64 #endif
65 
MappedMemory(IMachine machine, long size, int? segmentSize = null, string sharedMemoryFileRoot = null)66         public MappedMemory(IMachine machine, long size, int? segmentSize = null, string sharedMemoryFileRoot = null)
67         {
68             if(size == 0)
69             {
70                 throw new ConstructionException("Memory size cannot be 0");
71             }
72 
73             if(segmentSize == null)
74             {
75                 var proposedSegmentSize = Math.Min(MaximalSegmentSize, Math.Max(MinimalSegmentSize, size / RecommendedNumberOfSegments));
76                 // align it
77                 segmentSize = (int)(Math.Ceiling(1.0 * proposedSegmentSize / MinimalSegmentSize) * MinimalSegmentSize);
78                 this.DebugLog("Segment size automatically calculated to value {0}B", Misc.NormalizeBinary(segmentSize.Value));
79             }
80             this.machine = machine;
81             this.size = size;
82             SegmentSize = segmentSize.Value;
83 
84             if(sharedMemoryFileRoot != null && !Directory.Exists(sharedMemoryFileRoot))
85             {
86                 throw new ConstructionException($"Requested {nameof(sharedMemoryFileRoot)} path {sharedMemoryFileRoot} doesn't exist.");
87             }
88             this.sharedMemoryFileRoot = sharedMemoryFileRoot;
89 
90             Init();
91         }
92 
93         public event Action<int> SegmentTouched;
94 
95         public int SegmentCount
96         {
97             get
98             {
99                 return segments.Length;
100             }
101         }
102 
103         public int SegmentSize { get; private set; }
104 
105         public bool UsingSharedMemory
106         {
107             get
108             {
109                 return sharedMemoryFileRoot != null;
110             }
111         }
112 
113         public IEnumerable<IMappedSegment> MappedSegments
114         {
115             get
116             {
117                 return describedSegments;
118             }
119         }
120 
ReadByte(long offset)121         public byte ReadByte(long offset)
122         {
123             if(offset < 0 || offset >= size)
124             {
125                 this.Log(LogLevel.Error, "Tried to read byte at offset 0x{0:X} outside the range of the peripheral 0x0 - 0x{1:X}", offset, size);
126                 return 0;
127             }
128 
129             var localOffset = GetLocalOffset(offset);
130             var segment = segments[GetSegmentNo(offset)];
131             return Marshal.ReadByte(new IntPtr(segment.ToInt64() + localOffset));
132         }
133 
WriteByte(long offset, byte value)134         public void WriteByte(long offset, byte value)
135         {
136             if(offset < 0 || offset >= size)
137             {
138                 this.Log(LogLevel.Error, "Tried to write byte value 0x{0:X} to offset 0x{1:X} outside the range of the peripheral 0x0 - 0x{2:X}", value, offset, size);
139                 return;
140             }
141 
142             var localOffset = GetLocalOffset(offset);
143             var segment = segments[GetSegmentNo(offset)];
144             Marshal.WriteByte(new IntPtr(segment.ToInt64() + localOffset), value);
145             InvalidateMemoryFragment(offset, 1);
146         }
147 
ReadWord(long offset)148         public ushort ReadWord(long offset)
149         {
150             if(offset < 0 || offset > size - sizeof(ushort))
151             {
152                 this.Log(LogLevel.Error, "Tried to read word at offset 0x{0:X} outside the range of the peripheral 0x0 - 0x{1:X}", offset, size);
153                 return 0;
154             }
155 
156             var localOffset = GetLocalOffset(offset);
157             var segment = segments[GetSegmentNo(offset)];
158             if(localOffset > SegmentSize - sizeof(ushort)) // cross segment read
159             {
160                 var bytes = new byte[2];
161                 bytes[0] = Marshal.ReadByte(new IntPtr(segment.ToInt64() + localOffset));
162                 var secondSegment = segments[GetSegmentNo(offset + 1)];
163                 bytes[1] = Marshal.ReadByte(secondSegment);
164                 return BitConverter.ToUInt16(bytes, 0);
165             }
166             return unchecked((ushort)Marshal.ReadInt16(new IntPtr(segment.ToInt64() + localOffset)));
167         }
168 
WriteWord(long offset, ushort value)169         public void WriteWord(long offset, ushort value)
170         {
171             if(offset < 0 || offset > size - sizeof(ushort))
172             {
173                 this.Log(LogLevel.Error, "Tried to write word value 0x{0:X} to offset 0x{1:X} outside the range of the peripheral 0x0 - 0x{2:X}", value, offset, size);
174                 return;
175             }
176 
177             var localOffset = GetLocalOffset(offset);
178             var segment = segments[GetSegmentNo(offset)];
179             if(localOffset > SegmentSize - sizeof(ushort)) // cross segment write
180             {
181                 var bytes = BitConverter.GetBytes(value);
182                 Marshal.WriteByte(new IntPtr(segment.ToInt64() + localOffset), bytes[0]);
183                 var secondSegment = segments[GetSegmentNo(offset + 1)];
184                 Marshal.WriteByte(secondSegment, bytes[1]);
185                 InvalidateMemoryFragment(offset, 1);
186                 InvalidateMemoryFragment(offset + 1, 1);
187             }
188             else
189             {
190                 Marshal.WriteInt16(new IntPtr(segment.ToInt64() + localOffset), unchecked((short)value));
191                 InvalidateMemoryFragment(offset, sizeof(ushort));
192             }
193         }
194 
ReadDoubleWord(long offset)195         public uint ReadDoubleWord(long offset)
196         {
197             if(offset < 0 || offset > size - sizeof(uint))
198             {
199                 this.Log(LogLevel.Error, "Tried to read double word at offset 0x{0:X} outside the range of the peripheral 0x0 - 0x{1:X}", offset, size);
200                 return 0;
201             }
202 
203             var localOffset = GetLocalOffset(offset);
204             var segment = segments[GetSegmentNo(offset)];
205             if(localOffset > SegmentSize - sizeof(uint)) // cross segment read
206             {
207                 var bytes = ReadBytes(offset, sizeof(uint));
208                 return BitConverter.ToUInt32(bytes, 0);
209             }
210             return unchecked((uint)Marshal.ReadInt32(new IntPtr(segment.ToInt64() + localOffset)));
211         }
212 
WriteDoubleWord(long offset, uint value)213         public void WriteDoubleWord(long offset, uint value)
214         {
215             if(offset < 0 || offset > size - sizeof(uint))
216             {
217                 this.Log(LogLevel.Error, "Tried to write double word value 0x{0:X} to offset 0x{1:X} outside the range of the peripheral 0x0 - 0x{2:X}", value, offset, size);
218                 return;
219             }
220 
221             var localOffset = GetLocalOffset(offset);
222             var segment = segments[GetSegmentNo(offset)];
223             if(localOffset > SegmentSize - sizeof(uint)) // cross segment write
224             {
225                 var bytes = BitConverter.GetBytes(value);
226                 WriteBytes(offset, bytes);
227                 // the memory will be invalidated by `WriteBytes`
228             }
229             else
230             {
231                 Marshal.WriteInt32(new IntPtr(segment.ToInt64() + localOffset), unchecked((int)value));
232                 InvalidateMemoryFragment(offset, sizeof(uint));
233             }
234         }
235 
ReadQuadWord(long offset)236         public ulong ReadQuadWord(long offset)
237         {
238             if(offset < 0 || offset > size - sizeof(ulong))
239             {
240                 this.Log(LogLevel.Error, "Tried to read quad word at offset 0x{0:X} outside the range of the peripheral 0x0 - 0x{1:X}", offset, size);
241                 return 0;
242             }
243 
244             var localOffset = GetLocalOffset(offset);
245             var segment = segments[GetSegmentNo(offset)];
246             if(localOffset > SegmentSize  - sizeof(ulong)) // cross segment read
247             {
248                 var bytes = ReadBytes(offset, sizeof(ulong));
249                 return BitConverter.ToUInt64(bytes, 0);
250             }
251             return unchecked((ulong)Marshal.ReadInt64(new IntPtr(segment.ToInt64() + localOffset)));
252         }
253 
WriteQuadWord(long offset, ulong value)254         public void WriteQuadWord(long offset, ulong value)
255         {
256             if(offset < 0 || offset > size - sizeof(ulong))
257             {
258                 this.Log(LogLevel.Error, "Tried to write quad word value 0x{0:X} to offset 0x{1:X} outside the range of the peripheral 0x0 - 0x{2:X}", value, offset, size);
259                 return;
260             }
261 
262             var localOffset = GetLocalOffset(offset);
263             var segment = segments[GetSegmentNo(offset)];
264             if(localOffset > SegmentSize - sizeof(ulong)) // cross segment write
265             {
266                 var bytes = BitConverter.GetBytes(value);
267                 WriteBytes(offset, bytes);
268                 // the memory will be invalidated by `WriteBytes`
269             }
270             else
271             {
272                 Marshal.WriteInt64(new IntPtr(segment.ToInt64() + localOffset), unchecked((long)value));
273                 InvalidateMemoryFragment(offset, sizeof(ulong));
274             }
275         }
276 
ReadBytes(long offset, int count, byte[] destination, int startIndex)277         public void ReadBytes(long offset, int count, byte[] destination, int startIndex)
278         {
279             if(offset < 0 || offset > size - count)
280             {
281                 this.Log(LogLevel.Error, "Tried to read {0} bytes at offset 0x{1:X} outside the range of the peripheral 0x0 - 0x{2:X}", count, offset, size);
282                 return;
283             }
284 
285             var read = 0;
286             while(read < count)
287             {
288                 var currentOffset = offset + read;
289                 var localOffset = GetLocalOffset(currentOffset);
290                 var segment = segments[GetSegmentNo(currentOffset)];
291                 var length = Math.Min(count - read, (int)(SegmentSize - localOffset));
292                 Marshal.Copy(new IntPtr(segment.ToInt64() + localOffset), destination, read + startIndex, length);
293                 read += length;
294             }
295         }
296 
ReadBytes(long offset, int count, IPeripheral context = null)297         public byte[] ReadBytes(long offset, int count, IPeripheral context = null)
298         {
299             var result = new byte[count];
300             ReadBytes(offset, count, result, 0);
301             return result;
302         }
303 
WriteBytes(long offset, byte[] value)304         public void WriteBytes(long offset, byte[] value)
305         {
306             WriteBytes(offset, value, 0, value.Length);
307         }
308 
WriteBytes(long offset, byte[] value, int count)309         public void WriteBytes(long offset, byte[] value, int count)
310         {
311             WriteBytes(offset, value, 0, count);
312         }
313 
WriteBytes(long offset, byte[] array, int startingIndex, int count, IPeripheral context = null)314         public void WriteBytes(long offset, byte[] array, int startingIndex, int count, IPeripheral context = null)
315         {
316             if(offset < 0 || offset > size - count)
317             {
318                 this.Log(LogLevel.Error, "Tried to write {0} bytes at offset 0x{1:X} outside the range of the peripheral 0x0 - 0x{2:X}", count, offset, size);
319                 return;
320             }
321 
322             var written = 0;
323             while(written < count)
324             {
325                 var currentOffset = offset + written;
326                 var localOffset = GetLocalOffset(currentOffset);
327                 var segment = segments[GetSegmentNo(currentOffset)];
328                 var length = Math.Min(count - written, (int)(SegmentSize - localOffset));
329                 Marshal.Copy(array, startingIndex + written, new IntPtr(segment.ToInt64() + localOffset), length);
330                 written += length;
331 
332                 InvalidateMemoryFragment(currentOffset, length);
333             }
334         }
335 
WriteString(long offset, string value)336         public void WriteString(long offset, string value)
337         {
338             WriteBytes(offset, new System.Text.ASCIIEncoding().GetBytes(value).Concat(new []{ (byte)'\0' }).ToArray());
339         }
340 
LoadFileChunks(string path, IEnumerable<FileChunk> chunks, ICPU cpu)341         public void LoadFileChunks(string path, IEnumerable<FileChunk> chunks, ICPU cpu)
342         {
343             this.LoadFileChunks(chunks, cpu);
344         }
345 
Reset()346         public void Reset()
347         {
348             // nothing happens with memory
349             // we do not reset segments (as we do in init), since we do in init only
350             // to have deterministic behaviour (i.e. given script executed two times will
351             // give the same results; not zeroing during reset will however is not necessary
352             // (starting values are not random anyway)
353         }
354 
GetSegment(int segmentNo)355         public IntPtr GetSegment(int segmentNo)
356         {
357             if(segmentNo < 0 || segmentNo > segments.Length)
358             {
359                 throw new ArgumentOutOfRangeException("segmentNo");
360             }
361             return segments[segmentNo];
362         }
363 
TouchAllSegments()364         public void TouchAllSegments()
365         {
366             for(var i = 0; i < segments.Length; i++)
367             {
368                 TouchSegment(i);
369             }
370         }
371 
IsTouched(int segmentNo)372         public bool IsTouched(int segmentNo)
373         {
374             CheckSegmentNo(segmentNo);
375             return segments[segmentNo] != IntPtr.Zero;
376         }
377 
TouchSegment(int segmentNo)378         public void TouchSegment(int segmentNo)
379         {
380             CheckSegmentNo(segmentNo);
381             if(segments[segmentNo] == IntPtr.Zero)
382             {
383                 var allocSeg = AllocateSegment(segmentNo);
384                 var originalPointer = (long)allocSeg;
385                 var alignedPointer = (IntPtr)((originalPointer + Alignment) & ~(Alignment - 1));
386                 segments[segmentNo] = alignedPointer;
387                 if(UsingSharedMemory)
388                 {
389                     sharedSegments[segmentNo].AlignmentOffset = (ulong)alignedPointer - (ulong)allocSeg;
390                 }
391                 this.NoisyLog(string.Format("Segment no {1} allocated at 0x{0:X} (aligned to 0x{2:X}).",
392                     allocSeg.ToInt64(), segmentNo, alignedPointer.ToInt64()));
393                 originalPointers[segmentNo] = allocSeg;
394                 MemSet(alignedPointer, ResetByte, SegmentSize);
395                 var segmentTouched = SegmentTouched;
396                 if(segmentTouched != null)
397                 {
398                     segmentTouched(segmentNo);
399                 }
400             }
401         }
402 
403         public byte ResetByte { get; set; }
404 
405         public long Size
406         {
407             get
408             {
409                 return size;
410             }
411         }
412 
413         // The endianness of MappedMemory matches the host endianness because it is directly backed by host memory
414         public Endianess Endianness => BitConverter.IsLittleEndian ? Endianess.LittleEndian : Endianess.BigEndian;
415 
InitWithRandomData()416         public void InitWithRandomData()
417         {
418             var rand = EmulationManager.Instance.CurrentEmulation.RandomGenerator;
419             var buf = new byte[SegmentSize];
420 
421             for(var i = 0; i < segments.Length; ++i)
422             {
423                 rand.NextBytes(buf);
424                 WriteBytes(i * SegmentSize, buf);
425             }
426         }
427 
ZeroAll()428         public void ZeroAll()
429         {
430             foreach(var segment in segments.Where(x => x != IntPtr.Zero))
431             {
432                 MemSet(segment, ResetByte, SegmentSize);
433             }
434         }
435 
ZeroRange(long rangeStart, long rangeLength)436         public void ZeroRange(long rangeStart, long rangeLength)
437         {
438             SetRange(rangeStart, rangeLength, ResetByte);
439         }
440 
SetRange(long rangeStart, long rangeLength, byte value)441         public void SetRange(long rangeStart, long rangeLength, byte value)
442         {
443             var array = new byte[rangeLength];
444             for(long i = 0; i < rangeLength; ++i)
445             {
446                 array[i] = value;
447             }
448             WriteBytes(rangeStart, array);
449         }
450 
Dispose()451         public void Dispose()
452         {
453             Free();
454             GC.SuppressFinalize(this);
455         }
456 
Load(PrimitiveReader reader)457         public void Load(PrimitiveReader reader)
458         {
459             // checking magic
460             var magic = reader.ReadUInt32();
461             if(magic != Magic)
462             {
463                 throw new InvalidOperationException("Memory: Cannot resume state from stream: Invalid magic.");
464             }
465             SegmentSize = reader.ReadInt32();
466             size = reader.ReadInt64();
467             ResetByte = reader.ReadByte();
468             if(emptyCtorUsed)
469             {
470                 Init();
471             }
472             var realSegmentsCount = 0;
473             for(var i = 0; i < segments.Length; i++)
474             {
475                 var isTouched = reader.ReadBoolean();
476                 if(!isTouched)
477                 {
478                     continue;
479                 }
480                 var compressedSegmentSize = reader.ReadInt32();
481                 var compressedBuffer = reader.ReadBytes(compressedSegmentSize);
482                 TouchSegment(i);
483                 realSegmentsCount++;
484 #if NET
485                 var decodedBuffer = new byte[SegmentSize];
486                 var decodedLength = LZ4Codec.Decode(compressedBuffer, 0, compressedBuffer.Length, decodedBuffer, 0, decodedBuffer.Length);
487 #else
488                 var decodedBuffer = LZ4Codec.Decode(compressedBuffer, 0, compressedBuffer.Length, SegmentSize);
489                 var decodedLength = decodedBuffer.Length;
490 #endif
491                 Marshal.Copy(decodedBuffer, 0, segments[i], decodedLength);
492             }
493             this.NoisyLog(string.Format("{0} segments loaded from stream, of which {1} had content.", segments.Length, realSegmentsCount));
494         }
495 
Save(PrimitiveWriter writer)496         public void Save(PrimitiveWriter writer)
497         {
498             var globalStopwatch = Stopwatch.StartNew();
499             var realSegmentsCount = 0;
500 
501             writer.Write(Magic);
502             writer.Write(SegmentSize);
503             writer.Write(size);
504             writer.Write(ResetByte);
505             byte[][] outputBuffers = new byte[segments.Length][];
506             int[] encodedLengths = new int[segments.Length];
507             Parallel.For(0, segments.Length, i =>
508             {
509                 if(segments[i] == IntPtr.Zero)
510                 {
511                     return;
512                 }
513                 Interlocked.Increment(ref realSegmentsCount);
514                 var localBuffer = new byte[SegmentSize];
515                 Marshal.Copy(segments[i], localBuffer, 0, localBuffer.Length);
516 #if NET
517                 var outputBuffer = new byte[LZ4Codec.MaximumOutputSize(localBuffer.Length)];
518                 encodedLengths[i] = LZ4Codec.Encode(localBuffer, 0, localBuffer.Length, outputBuffer, 0, outputBuffer.Length);
519                 outputBuffers[i] = outputBuffer;
520 #else
521                 outputBuffers[i] = LZ4Codec.Encode(localBuffer, 0, localBuffer.Length);
522                 encodedLengths[i] = outputBuffers[i].Length;
523 #endif
524             });
525             for(var i = 0; i < segments.Length; i++)
526             {
527                 if(segments[i] == IntPtr.Zero)
528                 {
529                     writer.Write(false);
530                     continue;
531                 }
532                 writer.Write(true);
533                 writer.Write(encodedLengths[i]);
534                 writer.Write(outputBuffers[i], 0, encodedLengths[i]);
535             }
536             this.NoisyLog(string.Format("{0} segments saved to stream, of which {1} had contents.", segments.Length, realSegmentsCount));
537             globalStopwatch.Stop();
538             this.NoisyLog("Memory serialization ended in {0}s.", Misc.NormalizeDecimal(globalStopwatch.Elapsed.TotalSeconds));
539         }
540 
GetSegmentPath(int segmentNo)541         public string GetSegmentPath(int segmentNo)
542         {
543             if(segmentNo >= 0 && segmentNo < sharedSegments.Length)
544             {
545                 return sharedSegments[segmentNo].MMFPath;
546             }
547             return "";
548         }
549 
GetSegmentAlignmentOffset(int segmentNo)550         public ulong GetSegmentAlignmentOffset(int segmentNo)
551         {
552             if(segmentNo >= 0 && segmentNo < sharedSegments.Length)
553             {
554                 return sharedSegments[segmentNo].AlignmentOffset;
555             }
556             return 0UL;
557         }
558 
559         /// <summary>
560         /// This constructor is only to be used with serialization. Deserializer has to invoke Load method after such
561         /// construction.
562         /// </summary>
MappedMemory()563         private MappedMemory()
564         {
565             emptyCtorUsed = true;
566         }
567 
CheckAlignment(IntPtr segment)568         private void CheckAlignment(IntPtr segment)
569         {
570             if((segment.ToInt64() & 7) != 0)
571             {
572                 throw new ArgumentException(string.Format("Segment address has to be aligned to 8 bytes, but it is 0x{0:X}.", segment));
573             }
574         }
575 
Init()576         private void Init()
577         {
578             PrepareSegments();
579         }
580 
Free()581         private void Free()
582         {
583             if(!disposed)
584             {
585                 if(UsingSharedMemory)
586                 {
587                     for(var i = 0; i < segments.Length; i++)
588                     {
589                         sharedSegments[i].Free(this);
590                         this.NoisyLog("Shared segment {0} freed.", i);
591                     }
592                 }
593                 else
594                 {
595                     for(var i = 0; i < segments.Length; i++)
596                     {
597                         if(segments[i] != IntPtr.Zero)
598                         {
599                             var segment = originalPointers[i];
600                             Marshal.FreeHGlobal(segment);
601                             segments[i] = IntPtr.Zero;
602                             originalPointers[i] = IntPtr.Zero;
603                             this.NoisyLog("Segment {0} freed.", i);
604                         }
605                     }
606                 }
607             }
608             disposed = true;
609         }
610 
GetLocalOffset(long offset)611         private long GetLocalOffset(long offset)
612         {
613             return (offset % SegmentSize);
614         }
615 
CheckSegmentNo(int segmentNo)616         void CheckSegmentNo(int segmentNo)
617         {
618             if(segmentNo < 0 || segmentNo >= SegmentCount)
619             {
620                 throw new ArgumentOutOfRangeException("segmentNo");
621             }
622         }
623 
PrepareSegments()624         private void PrepareSegments()
625         {
626             if(segments != null)
627             {
628                 // this is because in case of loading the starting memory snapshot
629                 // after deserialization (i.e. resetting after deserialization)
630                 // memory segments would have been lost
631                 return;
632             }
633             // how many segments we need?
634             var segmentsNo = size / SegmentSize + (size % SegmentSize != 0 ? 1 : 0);
635             this.NoisyLog(string.Format("Preparing {0} segments for {1} bytes of memory, each {2} bytes long.",
636                 segmentsNo, size, SegmentSize));
637             segments = new IntPtr[segmentsNo];
638             originalPointers = new IntPtr[segmentsNo];
639             // segments are not allocated until they are used by read, write, load etc (or touched)
640             describedSegments = new IMappedSegment[segmentsNo];
641             for(var i = 0; i < describedSegments.Length - 1; i++)
642             {
643                 describedSegments[i] = new MappedSegment(this, i, (uint)SegmentSize);
644             }
645             var last = describedSegments.Length - 1;
646             var sizeOfLast = (uint)(size % SegmentSize);
647             if(sizeOfLast == 0)
648             {
649                 sizeOfLast = (uint)SegmentSize;
650             }
651             describedSegments[last] = new MappedSegment(this, last, sizeOfLast);
652             sharedSegments = new SharedSegment[segmentsNo];
653             for(var i = 0; i < sharedSegments.Length; i++)
654             {
655                 sharedSegments[i] = new SharedSegment(sharedMemoryFileRoot);
656             }
657         }
658 
GetSegmentNo(long offset)659         private int GetSegmentNo(long offset)
660         {
661             var segmentNo = (int)(offset / SegmentSize);
662 #if DEBUG
663             // check bounds
664             if(segmentNo >= segments.Length || segmentNo < 0)
665             {
666                 throw new IndexOutOfRangeException(string.Format(
667                     "Memory: Attemption to use segment number {0}, which does not exist. Total number of segments is {1}.",
668                     segmentNo,
669                     segments.Length
670                 ));
671             }
672 #endif
673             // if such segment is not currently allocated,
674             // allocate it
675             TouchSegment(segmentNo);
676             return segmentNo;
677         }
678 
AllocateSegment(int segmentNo)679         private IntPtr AllocateSegment(int segmentNo)
680         {
681             this.NoisyLog("Allocating segment of size {0}.", SegmentSize);
682             if(UsingSharedMemory)
683             {
684                 return sharedSegments[segmentNo].Allocate(SegmentSize + Alignment);
685             }
686             else
687             {
688                 return Marshal.AllocHGlobal(SegmentSize + Alignment);
689             }
690         }
691 
InvalidateMemoryFragment(long start, int length)692         private void InvalidateMemoryFragment(long start, int length)
693         {
694             if(machine == null)
695             {
696                 // this peripheral is not connected to any machine, so there is nothing we can do
697                 return;
698             }
699 
700             this.NoisyLog("Invalidating memory fragment at 0x{0:X} of size {1} bytes.", start, length);
701 
702             var registrationPoints = GetRegistrationPoints();
703             foreach(var cpu in machine.SystemBus.GetCPUs().OfType<CPU.ICPU>())
704             {
705                 foreach(var regPoint in registrationPoints)
706                 {
707                     try
708                     {
709                         //it's dynamic to avoid cyclic dependency to TranslationCPU
710                         ((dynamic)cpu).InvalidateTranslationBlocks(new IntPtr(regPoint + start), new IntPtr(regPoint + start + length));
711                     }
712                     catch(RuntimeBinderException)
713                     {
714                         // CPU does not implement `InvalidateTranslationBlocks`, there is not much we can do
715                     }
716                 }
717             }
718         }
719 
GetRegistrationPoints()720         private List<long> GetRegistrationPoints()
721         {
722             if(registrationPointsCached == null)
723             {
724                 registrationPointsCached = machine.SystemBus.GetRegistrationPoints(this).Select(x => (long)(x.Range.StartAddress + x.Offset)).ToList();
725             }
726             return registrationPointsCached;
727         }
728 
729 #if PLATFORM_WINDOWS
MemSet(IntPtr pointer, byte value, int length)730         private static void MemSet(IntPtr pointer, byte value, int length)
731         {
732             MemsetDelegate(pointer, value, length);
733         }
734 
735         private static Action<IntPtr, byte, int> MemsetDelegate;
736 #else
737         [DllImport("libc", EntryPoint = "memset")]
MemSet(IntPtr pointer, byte value, int length)738         private static extern IntPtr MemSet(IntPtr pointer, byte value, int length);
739 #endif
740 
741         private readonly bool emptyCtorUsed;
742         private IntPtr[] segments;
743         private SharedSegment[] sharedSegments;
744         private IntPtr[] originalPointers;
745         private IMappedSegment[] describedSegments;
746         private bool disposed;
747         private long size;
748         private string sharedMemoryFileRoot;
749         private List<long> registrationPointsCached;
750         private readonly IMachine machine;
751 
752         private const uint Magic = 0xABCD6366;
753         private const int Alignment = 0x1000;
754         private const int MinimalSegmentSize = 64 * 1024;
755         private const int MaximalSegmentSize = 16 * 1024 * 1024;
756         private const int RecommendedNumberOfSegments = 16;
757 
758         private class MappedSegment : IMappedSegment
759         {
760             public IntPtr Pointer
761             {
762                 get
763                 {
764                     return parent.GetSegment(index);
765                 }
766             }
767 
768             public ulong Size
769             {
770                 get
771                 {
772                     return size;
773                 }
774             }
775 
776             public ulong StartingOffset
777             {
778                 get
779                 {
780                     return checked((ulong)index * (ulong)parent.SegmentSize);
781                 }
782             }
783 
MappedSegment(MappedMemory parent, int index, uint size)784             public MappedSegment(MappedMemory parent, int index, uint size)
785             {
786                 this.index = index;
787                 this.parent = parent;
788                 this.size = size;
789             }
790 
Touch()791             public void Touch()
792             {
793                 parent.TouchSegment(index);
794             }
795 
ToString()796             public override string ToString()
797             {
798                 return string.Format("[MappedSegment: Size=0x{0:X}, StartingOffset=0x{1:X}]", Size, StartingOffset);
799             }
800 
801             private readonly MappedMemory parent;
802             private readonly int index;
803             private readonly uint size;
804         }
805 
806         private class SharedSegment
807         {
SharedSegment(string sharedMemoryFileRoot)808             public SharedSegment(string sharedMemoryFileRoot)
809             {
810                 this.sharedMemoryFileRoot = sharedMemoryFileRoot;
811             }
812 
813             private string MMFFileName
814             {
815                 get
816                 {
817                     return "renode-sharedSegment-" + guid.Value.ToString();
818                 }
819             }
820 
821             public string MMFPath
822             {
823                 get
824                 {
825                     return Path.Combine(sharedMemoryFileRoot, MMFFileName);
826                 }
827             }
828 
829             public ulong AlignmentOffset { get; set; }
830 
Allocate(int bytes)831             public unsafe IntPtr Allocate(int bytes)
832             {
833                 if(Directory.Exists(sharedMemoryFileRoot))
834                 {
835                     guid = Guid.NewGuid(); // generate a Guid to be used in a unique filename for this segment
836                     // NOTE: It would be preferable to use shared memory objects here instead of files, but
837                     // this is currently not supported for Linux on both Mono (v. 6.12.0.200) and dotnet (8.0.403)
838                     mmf = MemoryMappedFile.CreateFromFile(MMFPath, FileMode.CreateNew, null, bytes);
839                     mmva = mmf.CreateViewAccessor();
840                     byte* ptr = null;
841                     mmva.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
842                     return (IntPtr)ptr;
843                 }
844                 else
845                 {
846                     throw new InvalidOperationException(string.Format("Directory {0} not found", sharedMemoryFileRoot));
847                 }
848             }
849 
Free(MappedMemory mem)850             public void Free(MappedMemory mem)
851             {
852                 if(!disposed && guid != null)
853                 {
854                     if(mmva != null)
855                     {
856                         mmva.SafeMemoryMappedViewHandle.ReleasePointer();
857                     }
858 
859                     try
860                     {
861                         File.Delete(MMFPath);
862                     }
863                     catch
864                     {
865                         mem.Log(LogLevel.Warning, "Failed to delete temporary file {0}", MMFPath);
866                     }
867                     guid = null;
868                     mmva = null;
869                     mmf = null;
870                     disposed = true;
871                 }
872             }
873 
874             private string sharedMemoryFileRoot;
875             private Nullable<Guid> guid;
876             private MemoryMappedFile mmf;
877             private MemoryMappedViewAccessor mmva;
878             private bool disposed;
879         }
880     }
881 }
882 
883