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