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.Collections.Generic; 10 using Antmicro.Migrant.Hooks; 11 using Antmicro.Migrant; 12 using System.Linq; 13 using Antmicro.Renode.Peripherals; 14 using Antmicro.Renode.Peripherals.CPU; 15 using Antmicro.Renode.Utilities; 16 using Antmicro.Renode.Core.Structure; 17 using Antmicro.Renode.Logging; 18 using Antmicro.Renode.Exceptions; 19 using Antmicro.Renode.Time; 20 using Antmicro.Renode.Utilities.Collections; 21 using System.Threading; 22 23 namespace Antmicro.Renode.Core 24 { 25 public class Emulation : IDisposable 26 { Emulation()27 public Emulation() 28 { 29 MasterTimeSource = new MasterTimeSource(); 30 HostMachine = new HostMachine(); 31 MACRepository = new MACRepository(); 32 ExternalsManager = new ExternalsManager(); 33 ExternalsManager.AddExternal(HostMachine, HostMachine.HostMachineName); 34 Connector = new Connector(); 35 FileFetcher = new CachingFileFetcher(); 36 CurrentLogger = Logger.GetLogger(); 37 randomGenerator = new Lazy<PseudorandomNumberGenerator>(() => new PseudorandomNumberGenerator()); 38 nameCache = new LRUCache<object, Tuple<string, string>>(NameCacheSize); 39 peripheralToMachineCache = new LRUCache<IPeripheral, IMachine>(PeripheralToMachineCacheSize); 40 41 machs = new FastReadConcurrentTwoWayDictionary<string, IMachine>(); 42 machs.ItemAdded += (name, machine) => 43 { 44 machine.StateChanged += OnMachineStateChanged; 45 machine.PeripheralsChanged += (m, e) => 46 { 47 if (e.Operation != PeripheralsChangedEventArgs.PeripheralChangeType.Addition) 48 { 49 nameCache.Invalidate(); 50 peripheralToMachineCache.Invalidate(); 51 } 52 }; 53 54 OnMachineAdded(machine); 55 }; 56 57 machs.ItemRemoved += (name, machine) => 58 { 59 machine.StateChanged -= OnMachineStateChanged; 60 nameCache.Invalidate(); 61 peripheralToMachineCache.Invalidate(); 62 63 OnMachineRemoved(machine); 64 }; 65 BackendManager = new BackendManager(); 66 BlobManager = new BlobManager(); 67 theBag = new Dictionary<string, object>(); 68 SnapshotTracker = new SnapshotTracker(); 69 } 70 71 public MasterTimeSource MasterTimeSource { get; private set; } 72 73 public BackendManager BackendManager { get; private set; } 74 75 public SnapshotTracker SnapshotTracker { get; } 76 77 public BlobManager BlobManager { get; set; } 78 79 private EmulationMode mode; 80 public EmulationMode Mode 81 { 82 get => mode; 83 84 set 85 { 86 lock(machLock) 87 { 88 if(mode != EmulationMode.SynchronizedIO) 89 { 90 var machine = machs.Rights.FirstOrDefault(m => m.HasPlayer || m.HasRecorder); 91 if(machine != null) 92 { 93 throw new RecoverableException($"Could not set the new emulation mode because an event player/recorder is active for machine {machs[machine]}"); 94 } 95 } 96 mode = value; 97 } 98 } 99 } 100 101 private readonly object machLock = new object(); 102 103 public bool AllMachinesStarted 104 { 105 get { lock (machLock) { return machs.Rights.All(x => !x.IsPaused); } } 106 } 107 108 public bool AnyMachineStarted 109 { 110 get { lock (machLock) { return machs.Rights.Any(x => !x.IsPaused); } } 111 } 112 113 // This property should only be set while holding `machLock` 114 public bool IsStarted 115 { 116 get { lock (machLock) { return isStarted; } } 117 private set 118 { 119 if(isStarted == value) 120 { 121 return; 122 } 123 124 isStarted = value; 125 IsStartedChanged?.Invoke(this, value); 126 } 127 } 128 129 // Do not access this field directly, use the IsStarted property setter 130 [Transient] 131 private bool isStarted; 132 133 public IMachine this[String key] 134 { 135 get { return machs[key]; } 136 } 137 138 public String this[IMachine machine] 139 { 140 get { return machs[machine]; } 141 } 142 143 public CachingFileFetcher FileFetcher 144 { 145 get { return fileFetcher; } 146 set { fileFetcher = value; } 147 } 148 149 public ExternalsManager ExternalsManager { get; private set; } 150 151 public Connector Connector { get; private set; } 152 153 public MACRepository MACRepository { get; private set; } 154 155 public HostMachine HostMachine { get; private set; } 156 TryGetMachine(string key, out IMachine machine)157 public bool TryGetMachine(string key, out IMachine machine) 158 { 159 return machs.TryGetValue(key, out machine); 160 } 161 TryGetMachineName(IMachine machine, out string name)162 public bool TryGetMachineName(IMachine machine, out string name) 163 { 164 return machs.TryGetValue(machine, out name); 165 } 166 167 public int MachinesCount 168 { 169 get { return machs.Count; } 170 } 171 172 public IEnumerable<IMachine> Machines 173 { 174 get { return machs.Rights; } 175 } 176 177 public IEnumerable<string> Names 178 { 179 get { return machs.Lefts; } 180 } 181 TryGetExecutionContext(out IMachine machine, out ICPU cpu)182 public bool TryGetExecutionContext(out IMachine machine, out ICPU cpu) 183 { 184 foreach(var m in Machines) 185 { 186 if(m.SystemBus.TryGetCurrentCPU(out cpu)) 187 { 188 machine = m; 189 return true; 190 } 191 } 192 193 machine = null; 194 cpu = null; 195 return false; 196 } 197 198 /// <summary> 199 /// Adds the machine to emulation. 200 /// </summary> 201 /// <param name='machine'> 202 /// Machine to add. 203 /// </param> 204 /// <param name='name'> 205 /// Name of the machine. If null or empty (as default), the name is automatically given. 206 /// </param> AddMachine(IMachine machine, string name = R)207 public void AddMachine(IMachine machine, string name = "") 208 { 209 if(!TryAddMachine(machine, name)) 210 { 211 throw new RecoverableException("Given machine is already added or name is already taken."); 212 } 213 } 214 TryGetMachineByName(string name, out IMachine machine)215 public bool TryGetMachineByName(string name, out IMachine machine) 216 { 217 return machs.TryGetValue(name, out machine); 218 } 219 GetNextMachineName(Platform platform, HashSet<string> reserved = null)220 public string GetNextMachineName(Platform platform, HashSet<string> reserved = null) 221 { 222 lock(machLock) 223 { 224 string name; 225 var counter = 0; 226 do 227 { 228 name = string.Format("{0}-{1}", platform != null ? platform.Name : Machine.MachineKeyword, counter); 229 counter++; 230 } 231 while(machs.Exists(name) || (reserved != null && reserved.Contains(name))); 232 233 return name; 234 } 235 } 236 TryAddMachine(IMachine machine, string name)237 public bool TryAddMachine(IMachine machine, string name) 238 { 239 lock(machLock) 240 { 241 if(string.IsNullOrEmpty(name)) 242 { 243 name = GetNextMachineName(machine.Platform); 244 } 245 else if (machs.ExistsEither(name, machine)) 246 { 247 return false; 248 } 249 250 machs.Add(name, machine); 251 252 if(machine.LocalTimeSource is ITimeSink machineTimeSink) 253 { 254 MasterTimeSource.RegisterSink(machineTimeSink); 255 } 256 else 257 { 258 machine.LocalTimeSource = MasterTimeSource; 259 } 260 261 return true; 262 } 263 } 264 SetSeed(int seed)265 public void SetSeed(int seed) 266 { 267 RandomGenerator.ResetSeed(seed); 268 } 269 GetSeed()270 public int GetSeed() 271 { 272 return RandomGenerator.GetCurrentSeed(); 273 } 274 RunFor(TimeInterval period)275 public void RunFor(TimeInterval period) 276 { 277 if(IsStarted) 278 { 279 throw new RecoverableException("This action is not available when emulation is already started"); 280 } 281 InnerStartAll(); 282 MasterTimeSource.RunFor(period); 283 PauseAll(); 284 } 285 RunToNearestSyncPoint()286 public void RunToNearestSyncPoint() 287 { 288 if(IsStarted) 289 { 290 throw new RecoverableException("This action is not available when emulation is already started"); 291 } 292 293 InnerStartAll(); 294 MasterTimeSource.Run(); 295 PauseAll(); 296 } 297 StartAll()298 public void StartAll() 299 { 300 lock(machLock) 301 { 302 InnerStartAll(); 303 MasterTimeSource.Start(); 304 IsStarted = true; 305 } 306 307 System.Threading.Thread.Sleep(100); 308 } 309 InnerStartAll()310 private void InnerStartAll() 311 { 312 //ToList cast is a precaution for a situation where the list of machines changes 313 //during start up procedure. It might happen on rare occasions. E.g. when a script loads them, and user 314 //hits the pause button. 315 //Otherwise it would crash. 316 ExternalsManager.Start(); 317 foreach(var machine in Machines.ToList()) 318 { 319 machine.Start(); 320 } 321 } 322 PauseAll()323 public void PauseAll() 324 { 325 lock(machLock) 326 { 327 MasterTimeSource.Stop(); 328 Array.ForEach(machs.Rights, x => x.Pause()); 329 ExternalsManager.Pause(); 330 IsStarted = false; 331 } 332 } 333 ObtainPausedState()334 public IDisposable ObtainPausedState() 335 { 336 return new PausedState(this); 337 } 338 ObtainSafeState()339 public IDisposable ObtainSafeState() 340 { 341 // check if we are on a safe thread that executes sync phase 342 if(MasterTimeSource.IsOnSyncPhaseThread) 343 { 344 return null; 345 } 346 347 return ObtainPausedState(); 348 } 349 GetStartedStateChangedEvent(bool requiredStartedState, bool waitForTransition = true)350 public AutoResetEvent GetStartedStateChangedEvent(bool requiredStartedState, bool waitForTransition = true) 351 { 352 var evt = new AutoResetEvent(false); 353 lock(machLock) 354 { 355 if(IsStarted == requiredStartedState && !waitForTransition) 356 { 357 evt.Set(); 358 return evt; 359 } 360 361 Action<Emulation, bool> startedChanged = null; 362 startedChanged = (e, started) => 363 { 364 if(started == requiredStartedState) 365 { 366 e.IsStartedChanged -= startedChanged; 367 evt.Set(); 368 } 369 }; 370 371 IsStartedChanged += startedChanged; 372 return evt; 373 } 374 } 375 376 public ILogger CurrentLogger { get; private set; } 377 378 public PseudorandomNumberGenerator RandomGenerator 379 { 380 get 381 { 382 return randomGenerator.Value; 383 } 384 } 385 386 public bool SingleStepBlocking 387 { 388 get => singleStepBlocking; 389 set 390 { 391 if(singleStepBlocking == value) 392 { 393 return; 394 } 395 singleStepBlocking = value; 396 SingleStepBlockingChanged?.Invoke(); 397 } 398 } 399 400 public event Action SingleStepBlockingChanged; 401 SetNameForMachine(string name, IMachine machine)402 public void SetNameForMachine(string name, IMachine machine) 403 { 404 // TODO: locking issues 405 IMachine oldMachine; 406 machs.TryRemove(name, out oldMachine); 407 408 AddMachine(machine, name); 409 410 var machineExchanged = MachineExchanged; 411 if(machineExchanged != null) 412 { 413 machineExchanged(oldMachine, machine); 414 } 415 416 (oldMachine as IDisposable)?.Dispose(); 417 } 418 RemoveMachine(string name)419 public void RemoveMachine(string name) 420 { 421 if(!TryRemoveMachine(name)) 422 { 423 throw new ArgumentException(string.Format("Given machine '{0}' does not exists.", name)); 424 } 425 } 426 RemoveMachine(IMachine machine)427 public void RemoveMachine(IMachine machine) 428 { 429 machs.Remove(machine); 430 (machine as IDisposable)?.Dispose(); 431 } 432 TryRemoveMachine(string name)433 public bool TryRemoveMachine(string name) 434 { 435 IMachine machine; 436 var result = machs.TryRemove(name, out machine); 437 if(result) 438 { 439 (machine as IDisposable)?.Dispose(); 440 } 441 return result; 442 } 443 TryGetMachineForPeripheral(IPeripheral p, out IMachine machine)444 public bool TryGetMachineForPeripheral(IPeripheral p, out IMachine machine) 445 { 446 if(peripheralToMachineCache.TryGetValue(p, out machine)) 447 { 448 return true; 449 } 450 451 foreach(var candidate in Machines) 452 { 453 var candidateAsMachine = candidate; 454 if(candidateAsMachine != null && candidateAsMachine.IsRegistered(p)) 455 { 456 machine = candidateAsMachine; 457 peripheralToMachineCache.Add(p, machine); 458 return true; 459 } 460 } 461 462 machine = null; 463 return false; 464 } 465 TryGetEmulationElementName(object obj, out string name)466 public bool TryGetEmulationElementName(object obj, out string name) 467 { 468 string localName, localContainerName; 469 var result = TryGetEmulationElementName(obj, out localName, out localContainerName); 470 name = (localContainerName != null) ? string.Format("{0}:{1}", localContainerName, localName) : localName; 471 return result; 472 } 473 TryGetEmulationElementName(object obj, out string name, out string containerName)474 public bool TryGetEmulationElementName(object obj, out string name, out string containerName) 475 { 476 if(obj == null) 477 { 478 name = null; 479 containerName = null; 480 return false; 481 } 482 483 Tuple<string, string> result; 484 if(nameCache.TryGetValue(obj, out result)) 485 { 486 name = result.Item1; 487 containerName = result.Item2; 488 return true; 489 } 490 491 containerName = null; 492 var objAsIPeripheral = obj as IPeripheral; 493 if(objAsIPeripheral != null) 494 { 495 IMachine machine; 496 string machName; 497 498 if(TryGetMachineForPeripheral(objAsIPeripheral, out machine) && TryGetMachineName(machine, out machName)) 499 { 500 containerName = machName; 501 if(Misc.IsPythonObject(obj)) 502 { 503 name = Misc.GetPythonName(obj); 504 } 505 else 506 { 507 if(!machine.TryGetAnyName(objAsIPeripheral, out name)) 508 { 509 name = Machine.UnnamedPeripheral; 510 } 511 } 512 nameCache.Add(obj, Tuple.Create(name, containerName)); 513 return true; 514 } 515 } 516 var objAsMachine = obj as Machine; 517 if(objAsMachine != null) 518 { 519 if(EmulationManager.Instance.CurrentEmulation.TryGetMachineName(objAsMachine, out name)) 520 { 521 nameCache.Add(obj, Tuple.Create(name, containerName)); 522 return true; 523 } 524 } 525 var objAsIExternal = obj as IExternal; 526 if(objAsIExternal != null) 527 { 528 if(ExternalsManager.TryGetName(objAsIExternal, out name)) 529 { 530 nameCache.Add(obj, Tuple.Create(name, containerName)); 531 return true; 532 } 533 } 534 535 var objAsIHostMachineElement = obj as IHostMachineElement; 536 if(objAsIHostMachineElement != null) 537 { 538 if(HostMachine.TryGetName(objAsIHostMachineElement, out name)) 539 { 540 containerName = HostMachine.HostMachineName; 541 nameCache.Add(obj, Tuple.Create(name, containerName)); 542 return true; 543 } 544 } 545 546 name = null; 547 return false; 548 } 549 TryGetEmulationElementByName(string name, object context, out IEmulationElement element)550 public bool TryGetEmulationElementByName(string name, object context, out IEmulationElement element) 551 { 552 if(name == null) 553 { 554 element = null; 555 return false; 556 } 557 var machineContext = context as Machine; 558 if(machineContext != null) 559 { 560 IPeripheral outputPeripheral; 561 if((machineContext.TryGetByName(name, out outputPeripheral) || machineContext.TryGetByName(string.Format("sysbus.{0}", name), out outputPeripheral))) 562 { 563 element = outputPeripheral; 564 return true; 565 } 566 } 567 568 IMachine machine; 569 if(TryGetMachineByName(name, out machine)) 570 { 571 element = machine; 572 return true; 573 } 574 575 IExternal external; 576 if(ExternalsManager.TryGetByName(name, out external)) 577 { 578 element = external; 579 return true; 580 } 581 582 IHostMachineElement hostMachineElement; 583 if(name.StartsWith(string.Format("{0}.", HostMachine.HostMachineName)) 584 && HostMachine.TryGetByName(name.Substring(HostMachine.HostMachineName.Length + 1), out hostMachineElement)) 585 { 586 element = hostMachineElement; 587 return true; 588 } 589 590 element = null; 591 return false; 592 } 593 Dispose()594 public void Dispose() 595 { 596 FileFetcher.CancelDownload(); 597 lock(machLock) 598 { 599 PauseAll(); 600 // dispose externals before machines; 601 // some externals, e.g. execution tracer, 602 // require access to peripherals when operating 603 ExternalsManager.Clear(); 604 BackendManager.Dispose(); 605 Array.ForEach(machs.Rights, x => (x as IDisposable)?.Dispose()); 606 MasterTimeSource.Dispose(); 607 machs.Dispose(); 608 HostMachine.Dispose(); 609 CurrentLogger.Dispose(); 610 FileFetcher.Dispose(); 611 } 612 } 613 614 public void AddOrUpdateInBag<T>(string name, T value) where T : class 615 { lock(theBag)616 lock(theBag) 617 { 618 theBag[name] = value; 619 } 620 } 621 TryRemoveFromBag(string name)622 public void TryRemoveFromBag(string name) 623 { 624 lock(theBag) 625 { 626 if(theBag.ContainsKey(name)) 627 { 628 theBag.Remove(name); 629 } 630 } 631 } 632 633 public bool TryGetFromBag<T>(string name, out T value) where T : class 634 { lock(theBag)635 lock(theBag) 636 { 637 if(theBag.ContainsKey(name)) 638 { 639 value = theBag[name] as T; 640 if(value != null) 641 { 642 return true; 643 } 644 } 645 value = null; 646 return false; 647 } 648 } 649 650 651 [field: Transient] 652 public event Action<IMachine, IMachine> MachineExchanged; 653 654 [PostDeserialization] AfterDeserialization()655 private void AfterDeserialization() 656 { 657 // recreate events 658 foreach(var mach in machs.Rights) 659 { 660 mach.StateChanged += OnMachineStateChanged; 661 } 662 singleStepBlocking = true; 663 } 664 665 #region Event processors 666 OnMachineStateChanged(IMachine machine, MachineStateChangedEventArgs ea)667 private void OnMachineStateChanged(IMachine machine, MachineStateChangedEventArgs ea) 668 { 669 var msc = MachineStateChanged; 670 if(msc != null) 671 { 672 msc(machine, ea); 673 } 674 } 675 OnMachineAdded(IMachine machine)676 private void OnMachineAdded(IMachine machine) 677 { 678 var ma = MachineAdded; 679 if(ma != null) 680 { 681 ma(machine); 682 } 683 } 684 OnMachineRemoved(IMachine machine)685 private void OnMachineRemoved(IMachine machine) 686 { 687 var mr = MachineRemoved; 688 if(mr != null) 689 { 690 mr(machine); 691 } 692 } 693 694 #endregion 695 696 [field: Transient] 697 public event Action<IMachine, MachineStateChangedEventArgs> MachineStateChanged; 698 699 [field: Transient] 700 public event Action<IMachine> MachineAdded; 701 [field: Transient] 702 public event Action<IMachine> MachineRemoved; 703 704 [field: Transient] 705 public event Action<Emulation, bool> IsStartedChanged; 706 707 [Constructor] 708 private CachingFileFetcher fileFetcher; 709 710 [field: Transient] 711 private bool singleStepBlocking = true; 712 713 [Constructor(NameCacheSize)] 714 private readonly LRUCache<object, Tuple<string, string>> nameCache; 715 716 [Constructor(PeripheralToMachineCacheSize)] 717 private readonly LRUCache<IPeripheral, IMachine> peripheralToMachineCache; 718 719 private readonly Lazy<PseudorandomNumberGenerator> randomGenerator; 720 private readonly Dictionary<string, object> theBag; 721 private readonly FastReadConcurrentTwoWayDictionary<string, IMachine> machs; 722 723 private const int NameCacheSize = 100; 724 private const int PeripheralToMachineCacheSize = 100; 725 726 public enum EmulationMode 727 { 728 SynchronizedIO, 729 SynchronizedTimers 730 } 731 732 private class PausedState : IDisposable 733 { PausedState(Emulation emulation)734 public PausedState(Emulation emulation) 735 { 736 wasStarted = emulation.IsStarted; 737 this.emulation = emulation; 738 739 if(wasStarted) 740 { 741 emulation.MasterTimeSource.Stop(); 742 machineStates = emulation.Machines.Select(x => x.ObtainPausedState()).ToArray(); 743 emulation.ExternalsManager.Pause(); 744 } 745 } 746 Dispose()747 public void Dispose() 748 { 749 if(!wasStarted) 750 { 751 return; 752 } 753 754 emulation.MasterTimeSource.Start(); 755 foreach(var state in machineStates) 756 { 757 state.Dispose(); 758 } 759 emulation.ExternalsManager.Start(); 760 } 761 762 private readonly IDisposable[] machineStates; 763 private readonly Emulation emulation; 764 private readonly bool wasStarted; 765 } 766 } 767 } 768 769