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 using System; 9 using System.Collections.Generic; 10 using System.Collections.ObjectModel; 11 using System.IO; 12 using System.Linq; 13 using System.Net.Sockets; 14 using System.Reflection; 15 using System.Runtime.InteropServices; 16 using System.Text; 17 using System.Threading; 18 using Antmicro.Migrant; 19 using Antmicro.Migrant.Hooks; 20 using Antmicro.Renode.Core.Structure; 21 using Antmicro.Renode.EventRecording; 22 using Antmicro.Renode.Exceptions; 23 using Antmicro.Renode.Logging; 24 using Antmicro.Renode.Logging.Profiling; 25 using Antmicro.Renode.Peripherals; 26 using Antmicro.Renode.Peripherals.Bus; 27 using Antmicro.Renode.Peripherals.CPU; 28 using Antmicro.Renode.Time; 29 using Antmicro.Renode.UserInterface; 30 using Antmicro.Renode.Utilities; 31 using Antmicro.Renode.Utilities.Collections; 32 using Antmicro.Renode.Utilities.GDB; 33 using Microsoft.CSharp.RuntimeBinder; 34 using Monitor = System.Threading.Monitor; 35 36 namespace Antmicro.Renode.Core 37 { 38 public class Machine : IMachine, IDisposable 39 { Machine(bool createLocalTimeSource = false)40 public Machine(bool createLocalTimeSource = false) 41 { 42 InitAtomicMemoryState(); 43 44 collectionSync = new object(); 45 pausingSync = new object(); 46 disposedSync = new object(); 47 clockSource = new BaseClockSource(); 48 localNames = new Dictionary<IPeripheral, string>(); 49 PeripheralsGroups = new PeripheralsGroupsManager(this); 50 ownLifes = new HashSet<IHasOwnLife>(); 51 pausedState = new PausedState(this); 52 SystemBus = new SystemBus(this); 53 registeredPeripherals = new MultiTree<IPeripheral, IRegistrationPoint>(SystemBus); 54 peripheralsBusControllers = new Dictionary<IBusPeripheral, BusControllerWrapper>(); 55 userStateHook = delegate 56 { 57 }; 58 userState = string.Empty; 59 SetLocalName(SystemBus, SystemBusName); 60 gdbStubs = new Dictionary<int, GdbStub>(); 61 62 invalidatedAddressesByCpu = new Dictionary<ICPU, List<long>>(); 63 invalidatedAddressesByArchitecture = new Dictionary<string, List<long>>(); 64 invalidatedAddressesLock = new object(); 65 firstUnbroadcastedDirtyAddressIndex = new Dictionary<ICPU, int>(); 66 67 if(createLocalTimeSource) 68 { 69 LocalTimeSource = new SlaveTimeSource(); 70 } 71 72 machineCreatedAt = new DateTime(CustomDateTime.Now.Ticks, DateTimeKind.Local); 73 } 74 75 [PreSerialization] SerializeAtomicMemoryState()76 private void SerializeAtomicMemoryState() 77 { 78 atomicMemoryState = new byte[AtomicMemoryStateSize]; 79 Marshal.Copy(atomicMemoryStatePointer, atomicMemoryState, 0, atomicMemoryState.Length); 80 // the first byte of an atomic memory state contains value 0 or 1 81 // indicating if the mutex has already been initialized; 82 // the mutex must be restored after each deserialization, so here we force this value to 0 83 atomicMemoryState[0] = 0; 84 } 85 86 [PostDeserialization] InitAtomicMemoryState()87 public void InitAtomicMemoryState() 88 { 89 atomicMemoryStatePointer = Marshal.AllocHGlobal(AtomicMemoryStateSize); 90 91 // the beginning of an atomic memory state contains two 8-bit flags: 92 // byte 0: information if the mutex has already been initialized 93 // byte 1: information if the reservations array has already been initialized 94 // 95 // the first byte must be set to 0 at start and after each deserialization 96 // as this is crucial for proper memory initialization; 97 // 98 // the second one must be set to 0 at start, but should not be overwritten after deserialization; 99 // this is handled when saving `atomicMemoryState` 100 if(atomicMemoryState != null) 101 { 102 Marshal.Copy(atomicMemoryState, 0, atomicMemoryStatePointer, atomicMemoryState.Length); 103 atomicMemoryState = null; 104 } 105 else 106 { 107 // this write spans two 8-byte flags 108 Marshal.WriteInt16(atomicMemoryStatePointer, 0); 109 } 110 } 111 112 public IntPtr AtomicMemoryStatePointer => atomicMemoryStatePointer; 113 114 [Transient] 115 private IntPtr atomicMemoryStatePointer; 116 private byte[] atomicMemoryState; 117 118 // TODO: this probably should be dynamically get from Tlib, but how to nicely do that in `Machine` class? 119 private const int AtomicMemoryStateSize = 25600; 120 GetParentPeripherals(IPeripheral peripheral)121 public IEnumerable<IPeripheral> GetParentPeripherals(IPeripheral peripheral) 122 { 123 var node = registeredPeripherals.TryGetNode(peripheral); 124 return node == null ? new IPeripheral[0] : node.Parents.Select(x => x.Value).Distinct(); 125 } 126 GetChildrenPeripherals(IPeripheral peripheral)127 public IEnumerable<IPeripheral> GetChildrenPeripherals(IPeripheral peripheral) 128 { 129 var node = registeredPeripherals.TryGetNode(peripheral); 130 return node == null ? new IPeripheral[0] : node.Children.Select(x => x.Value).Distinct(); 131 } 132 GetPeripheralRegistrationPoints(IPeripheral parentPeripheral, IPeripheral childPeripheral)133 public IEnumerable<IRegistrationPoint> GetPeripheralRegistrationPoints(IPeripheral parentPeripheral, IPeripheral childPeripheral) 134 { 135 var parentNode = registeredPeripherals.TryGetNode(parentPeripheral); 136 return parentNode == null ? new IRegistrationPoint[0] : parentNode.GetConnectionWays(childPeripheral); 137 } 138 RegisterAsAChildOf(IPeripheral peripheralParent, IPeripheral peripheralChild, IRegistrationPoint registrationPoint)139 public void RegisterAsAChildOf(IPeripheral peripheralParent, IPeripheral peripheralChild, IRegistrationPoint registrationPoint) 140 { 141 Register(peripheralChild, registrationPoint, peripheralParent); 142 } 143 UnregisterAsAChildOf(IPeripheral peripheralParent, IPeripheral peripheralChild)144 public void UnregisterAsAChildOf(IPeripheral peripheralParent, IPeripheral peripheralChild) 145 { 146 lock(collectionSync) 147 { 148 CollectGarbageStamp(); 149 IPeripheralsGroup group; 150 if(PeripheralsGroups.TryGetActiveGroupContaining(peripheralChild, out group)) 151 { 152 throw new RegistrationException(string.Format("Given peripheral is a member of '{0}' peripherals group and cannot be directly removed.", group.Name)); 153 } 154 155 var parentNode = registeredPeripherals.GetNode(peripheralParent); 156 parentNode.RemoveChild(peripheralChild); 157 EmulationManager.Instance.CurrentEmulation.BackendManager.HideAnalyzersFor(peripheralChild); 158 CollectGarbage(); 159 } 160 } 161 UnregisterAsAChildOf(IPeripheral peripheralParent, IRegistrationPoint registrationPoint)162 public void UnregisterAsAChildOf(IPeripheral peripheralParent, IRegistrationPoint registrationPoint) 163 { 164 lock(collectionSync) 165 { 166 CollectGarbageStamp(); 167 try 168 { 169 var parentNode = registeredPeripherals.GetNode(peripheralParent); 170 IPeripheral removedPeripheral = null; 171 parentNode.RemoveChild(registrationPoint, p => 172 { 173 IPeripheralsGroup group; 174 if(PeripheralsGroups.TryGetActiveGroupContaining(p, out group)) 175 { 176 throw new RegistrationException(string.Format("Given peripheral is a member of '{0}' peripherals group and cannot be directly removed.", group.Name)); 177 } 178 removedPeripheral = p; 179 return true; 180 }); 181 CollectGarbage(); 182 if(removedPeripheral != null && registeredPeripherals.TryGetNode(removedPeripheral) == null) 183 { 184 EmulationManager.Instance.CurrentEmulation.BackendManager.HideAnalyzersFor(removedPeripheral); 185 } 186 } 187 catch(RegistrationException) 188 { 189 CollectGarbage(); 190 throw; 191 } 192 } 193 } 194 UnregisterFromParent(IPeripheral peripheral)195 public void UnregisterFromParent(IPeripheral peripheral) 196 { 197 InnerUnregisterFromParent(peripheral); 198 var operation = PeripheralsChangedEventArgs.PeripheralChangeType.CompleteRemoval; 199 PeripheralsChanged?.Invoke(this, PeripheralsChangedEventArgs.Create(peripheral, operation)); 200 } 201 GetPeripheralsOfType()202 public IEnumerable<T> GetPeripheralsOfType<T>() 203 { 204 return GetPeripheralsOfType(typeof(T)).Cast<T>(); 205 } 206 GetPeripheralsOfType(Type t)207 public IEnumerable<IPeripheral> GetPeripheralsOfType(Type t) 208 { 209 lock(collectionSync) 210 { 211 return registeredPeripherals.Values.Where(t.IsInstanceOfType).ToList(); 212 } 213 } 214 GetRegisteredPeripherals()215 public IEnumerable<PeripheralTreeEntry> GetRegisteredPeripherals() 216 { 217 var result = new List<PeripheralTreeEntry>(); 218 lock(collectionSync) 219 { 220 registeredPeripherals.TraverseWithConnectionWaysParentFirst((currentNode, regPoint, parent, level) => 221 { 222 string localName; 223 TryGetLocalName(currentNode.Value, out localName); 224 result.Add(new PeripheralTreeEntry(currentNode.Value, parent, currentNode.Value.GetType(), regPoint, localName, level)); 225 }, 0); 226 } 227 return result; 228 } 229 230 public bool TryGetByName<T>(string name, out T peripheral, out string longestMatch) where T : class, IPeripheral 231 { 232 if(name == null) 233 { 234 longestMatch = string.Empty; 235 peripheral = null; 236 return false; 237 } 238 var splitPath = name.Split(new [] { '.' }, 2); 239 if(splitPath.Length == 1 && name == SystemBusName) 240 { 241 longestMatch = name; 242 peripheral = (T)(IPeripheral)SystemBus; 243 return true; 244 } 245 246 if(splitPath[0] != SystemBusName) 247 { 248 longestMatch = string.Empty; 249 peripheral = null; 250 return false; 251 } 252 253 MultiTreeNode<IPeripheral, IRegistrationPoint> result; 254 if(TryFindSubnodeByName(registeredPeripherals.GetNode(SystemBus), splitPath[1], out result, SystemBusName, out longestMatch)) 255 { 256 peripheral = (T)result.Value; 257 return true; 258 } 259 peripheral = null; 260 return false; 261 } 262 263 public bool TryGetByName<T>(string name, out T peripheral) where T : class, IPeripheral 264 { 265 string fake; 266 return TryGetByName(name, out peripheral, out fake); 267 } 268 GetLocalName(IPeripheral peripheral)269 public string GetLocalName(IPeripheral peripheral) 270 { 271 string result; 272 lock(collectionSync) 273 { 274 if(!TryGetLocalName(peripheral, out result)) 275 { 276 throw new KeyNotFoundException(); 277 } 278 return result; 279 } 280 } 281 TryGetLocalName(IPeripheral peripheral, out string name)282 public bool TryGetLocalName(IPeripheral peripheral, out string name) 283 { 284 lock(collectionSync) 285 { 286 return localNames.TryGetValue(peripheral, out name); 287 } 288 } 289 SetLocalName(IPeripheral peripheral, string name)290 public void SetLocalName(IPeripheral peripheral, string name) 291 { 292 if(string.IsNullOrEmpty(name)) 293 { 294 throw new RecoverableException("The name of the peripheral cannot be null nor empty."); 295 } 296 lock(collectionSync) 297 { 298 if(!registeredPeripherals.ContainsValue(peripheral)) 299 { 300 throw new RecoverableException("Cannot name peripheral which is not registered."); 301 } 302 if(localNames.ContainsValue(name)) 303 { 304 throw new RecoverableException(string.Format("Given name '{0}' is already used.", name)); 305 } 306 localNames[peripheral] = name; 307 } 308 var operation = PeripheralsChangedEventArgs.PeripheralChangeType.NameChanged; 309 PeripheralsChanged?.Invoke(this, PeripheralsChangedEventArgs.Create(peripheral, operation)); 310 } 311 GetAllNames()312 public IEnumerable<string> GetAllNames() 313 { 314 var nameSegments = new AutoResizingList<string>(); 315 var names = new List<string>(); 316 lock(collectionSync) 317 { 318 registeredPeripherals.TraverseParentFirst((x, y) => 319 { 320 if(!localNames.ContainsKey(x)) 321 { 322 // unnamed node 323 return; 324 } 325 var localName = localNames[x]; 326 nameSegments[y] = localName; 327 var globalName = new StringBuilder(); 328 for(var i = 0; i < y; i++) 329 { 330 globalName.Append(nameSegments[i]); 331 globalName.Append(PathSeparator); 332 } 333 globalName.Append(localName); 334 names.Add(globalName.ToString()); 335 }, 0); 336 } 337 return new ReadOnlyCollection<string>(names); 338 } 339 TryGetAnyName(IPeripheral peripheral, out string name)340 public bool TryGetAnyName(IPeripheral peripheral, out string name) 341 { 342 var names = GetNames(peripheral); 343 if(names.Count > 0) 344 { 345 name = names[0]; 346 return true; 347 } 348 name = null; 349 return false; 350 } 351 GetAnyNameOrTypeName(IPeripheral peripheral)352 public string GetAnyNameOrTypeName(IPeripheral peripheral) 353 { 354 string name; 355 if(!TryGetAnyName(peripheral, out name)) 356 { 357 var managedThread = peripheral as IManagedThread; 358 return managedThread != null ? managedThread.ToString() : peripheral.GetType().Name; 359 } 360 return name; 361 } 362 RegisterBusController(IBusPeripheral peripheral, IBusController controller)363 public IBusController RegisterBusController(IBusPeripheral peripheral, IBusController controller) 364 { 365 using(ObtainPausedState(true)) 366 { 367 if(!peripheralsBusControllers.TryGetValue(peripheral, out var wrapper)) 368 { 369 wrapper = new BusControllerWrapper(controller); 370 peripheralsBusControllers.Add(peripheral, wrapper); 371 } 372 else 373 { 374 if(wrapper.ParentController != SystemBus && wrapper.ParentController != controller) 375 { 376 throw new RecoverableException($"Trying to change the BusController from {wrapper.ParentController} to {controller} for the {peripheral} peripheral."); 377 } 378 wrapper.ChangeWrapped(controller); 379 } 380 return wrapper; 381 } 382 } 383 TryGetBusController(IBusPeripheral peripheral, out IBusController controller)384 public bool TryGetBusController(IBusPeripheral peripheral, out IBusController controller) 385 { 386 var exists = peripheralsBusControllers.TryGetValue(peripheral, out var wrapper); 387 controller = wrapper; 388 return exists; 389 } 390 GetSystemBus(IBusPeripheral peripheral)391 public IBusController GetSystemBus(IBusPeripheral peripheral) 392 { 393 if(!TryGetBusController(peripheral, out var controller)) 394 { 395 controller = RegisterBusController(peripheral, SystemBus); 396 } 397 return controller; 398 } 399 IsRegistered(IPeripheral peripheral)400 public bool IsRegistered(IPeripheral peripheral) 401 { 402 lock(collectionSync) 403 { 404 return registeredPeripherals.ContainsValue(peripheral); 405 } 406 } 407 408 /// <summary> 409 /// Pauses the machine and returns an <see cref="IDisposable">, disposing which will resume the machine. 410 /// Can be nested, in this case the machine will only be resumed once the last paused state is disposed. 411 /// </summary> 412 /// <param name="internalPause">Specifies whether this pause is due to internal reasons and should not be visible to 413 /// external software, such as GDB. For example, the pause to register a new peripheral is internal; the pause triggered 414 /// by a CPU breakpoint is not.</param> ObtainPausedState(bool internalPause = false)415 public IDisposable ObtainPausedState(bool internalPause = false) 416 { 417 InternalPause = internalPause; 418 pausedState.Enter(); 419 return DisposableWrapper.New(() => 420 { 421 pausedState.Dispose(); 422 // Does not handle nesting, but only the outermost pause could possibly invoke halt callbacks 423 InternalPause = false; 424 }); 425 } 426 427 /// <param name="startFilter">A function to test each own life whether it should be started along with the machine. 428 /// Useful when unpausing the machine but we don't want to unpause what's already been paused before.</param> Start(Func<IHasOwnLife, bool> startFilter)429 private void Start(Func<IHasOwnLife, bool> startFilter) 430 { 431 lock(pausingSync) 432 { 433 switch(state) 434 { 435 case State.Started: 436 return; 437 case State.Paused: 438 Resume(); 439 return; 440 } 441 foreach(var ownLife in ownLifes.OrderBy(x => x is ICPU ? 1 : 0)) 442 { 443 if(startFilter(ownLife)) 444 { 445 this.NoisyLog("Starting {0}.", GetNameForOwnLife(ownLife)); 446 ownLife.Start(); 447 } 448 } 449 (LocalTimeSource as SlaveTimeSource)?.Resume(); 450 this.Log(LogLevel.Info, "Machine started."); 451 state = State.Started; 452 var machineStarted = StateChanged; 453 if(machineStarted != null) 454 { 455 machineStarted(this, new MachineStateChangedEventArgs(MachineStateChangedEventArgs.State.Started)); 456 } 457 } 458 } 459 Start()460 public void Start() 461 { 462 Start(_ => true); 463 } 464 Pause()465 public void Pause() 466 { 467 lock(pausingSync) 468 { 469 switch(state) 470 { 471 case State.Paused: 472 return; 473 case State.NotStarted: 474 goto case State.Paused; 475 } 476 (LocalTimeSource as SlaveTimeSource)?.Pause(); 477 foreach(var ownLife in ownLifes.OrderBy(x => x is ICPU ? 0 : 1)) 478 { 479 var ownLifeName = GetNameForOwnLife(ownLife); 480 this.NoisyLog("Pausing {0}.", ownLifeName); 481 ownLife.Pause(); 482 this.NoisyLog("{0} paused.", ownLifeName); 483 } 484 state = State.Paused; 485 var machinePaused = StateChanged; 486 if(machinePaused != null) 487 { 488 machinePaused(this, new MachineStateChangedEventArgs(MachineStateChangedEventArgs.State.Paused)); 489 } 490 this.Log(LogLevel.Info, "Machine paused."); 491 } 492 } 493 PauseAndRequestEmulationPause(bool precise = false)494 public void PauseAndRequestEmulationPause(bool precise = false) 495 { 496 lock(pausingSync) 497 { 498 // Nothing to do if the emulation is already paused 499 if(!EmulationManager.Instance.CurrentEmulation.IsStarted) 500 { 501 return; 502 } 503 504 // Precise mode is only available when this method is run on the CPU thread 505 // We silence the logging that would happen otherwise (for example if we came 506 // here from a GPIO state change triggered by a timer) - in that case we log 507 // our own warning. 508 // We only attempt to prepare the CPU for a precise pause if the machine is currently running 509 if(precise && !IsPaused) 510 { 511 if(!TryRestartTranslationBlockOnCurrentCpu(quiet: true)) 512 { 513 this.Log(LogLevel.Warning, "Failed to restart translation block for precise pause, " + 514 "the pause will happen at the end of the current block"); 515 } 516 } 517 518 // We will pause this machine right now, but the whole emulation at the next sync point 519 Action pauseEmulation = null; 520 pauseEmulation = () => 521 { 522 EmulationManager.Instance.CurrentEmulation.PauseAll(); 523 LocalTimeSource.SinksReportedHook -= pauseEmulation; 524 }; 525 LocalTimeSource.SinksReportedHook += pauseEmulation; 526 527 // Pause is harmless to call even if the machine is already paused 528 Pause(); 529 } 530 } 531 Reset()532 public void Reset() 533 { 534 lock(pausingSync) 535 { 536 using(ObtainPausedState(true)) 537 { 538 foreach(var resetable in registeredPeripherals.Distinct().ToList()) 539 { 540 if(resetable == this) 541 { 542 continue; 543 } 544 resetable.Reset(); 545 } 546 var machineReset = MachineReset; 547 if(machineReset != null) 548 { 549 machineReset(this); 550 } 551 } 552 } 553 } 554 555 public bool InternalPause { get; private set; } 556 public bool IgnorePeripheralRegistrationConditions { get; set; } 557 RequestResetInSafeState(Action postReset = null, ICollection<IPeripheral> unresetable = null)558 public void RequestResetInSafeState(Action postReset = null, ICollection<IPeripheral> unresetable = null) 559 { 560 Action softwareRequestedReset = null; 561 softwareRequestedReset = () => 562 { 563 LocalTimeSource.SinksReportedHook -= softwareRequestedReset; 564 using(ObtainPausedState(true)) 565 { 566 foreach(var peripheral in registeredPeripherals.Distinct().Where(p => p != this && !(unresetable?.Contains(p) ?? false))) 567 { 568 peripheral.Reset(); 569 } 570 } 571 postReset?.Invoke(); 572 }; 573 LocalTimeSource.SinksReportedHook += softwareRequestedReset; 574 } 575 RequestReset()576 public void RequestReset() 577 { 578 LocalTimeSource.ExecuteInNearestSyncedState(_ => Reset()); 579 } 580 Dispose()581 public void Dispose() 582 { 583 lock(disposedSync) 584 { 585 if(alreadyDisposed) 586 { 587 return; 588 } 589 alreadyDisposed = true; 590 } 591 Pause(); 592 if(recorder != null) 593 { 594 recorder.Dispose(); 595 } 596 if(player != null) 597 { 598 player.Dispose(); 599 LocalTimeSource.SyncHook -= player.Play; 600 } 601 foreach(var stub in gdbStubs) 602 { 603 stub.Value.Dispose(); 604 } 605 gdbStubs.Clear(); 606 607 // ordering below is due to the fact that the CPU can use other peripherals, e.g. Memory so it should be disposed first 608 // Mapped memory can be used as storage by other disposable peripherals which may want to read it while being disposed 609 foreach(var peripheral in GetPeripheralsOfType<IDisposable>().OrderBy(x => x is ICPU ? 0 : x is IMapped ? 2 : 1)) 610 { 611 this.DebugLog("Disposing {0}.", GetAnyNameOrTypeName((IPeripheral)peripheral)); 612 peripheral.Dispose(); 613 } 614 (LocalTimeSource as SlaveTimeSource)?.Dispose(); 615 this.Log(LogLevel.Info, "Disposed."); 616 var disposed = StateChanged; 617 if(disposed != null) 618 { 619 disposed(this, new MachineStateChangedEventArgs(MachineStateChangedEventArgs.State.Disposed)); 620 } 621 Profiler?.Dispose(); 622 Profiler = null; 623 624 Marshal.FreeHGlobal(AtomicMemoryStatePointer); 625 626 EmulationManager.Instance.CurrentEmulation.BackendManager.HideAnalyzersFor(this); 627 } 628 ObtainManagedThread(Action action, uint frequency, string name = R, IEmulationElement owner = null, Func<bool> stopCondition = null)629 public IManagedThread ObtainManagedThread(Action action, uint frequency, string name = "managed thread", IEmulationElement owner = null, Func<bool> stopCondition = null) 630 { 631 return new ManagedThreadWrappingClockEntry(this, action, frequency, name, owner, stopCondition); 632 } 633 ObtainManagedThread(Action action, TimeInterval period, string name = R, IEmulationElement owner = null, Func<bool> stopCondition = null)634 public IManagedThread ObtainManagedThread(Action action, TimeInterval period, string name = "managed thread", IEmulationElement owner = null, Func<bool> stopCondition = null) 635 { 636 return new ManagedThreadWrappingClockEntry(this, action, period, name, owner, stopCondition); 637 } 638 639 private class ManagedThreadWrappingClockEntry : IManagedThread 640 { ManagedThreadWrappingClockEntry(IMachine machine, Action action, uint frequency, string name, IEmulationElement owner, Func<bool> stopCondition = null)641 public ManagedThreadWrappingClockEntry(IMachine machine, Action action, uint frequency, string name, IEmulationElement owner, Func<bool> stopCondition = null) 642 : this(machine, action, stopCondition) 643 { 644 machine.ClockSource.AddClockEntry(new ClockEntry(1, frequency, this.action, owner ?? machine, name, enabled: false)); 645 } 646 ManagedThreadWrappingClockEntry(IMachine machine, Action action, TimeInterval period, string name, IEmulationElement owner, Func<bool> stopCondition = null)647 public ManagedThreadWrappingClockEntry(IMachine machine, Action action, TimeInterval period, string name, IEmulationElement owner, Func<bool> stopCondition = null) 648 : this(machine, action, stopCondition) 649 { 650 machine.ClockSource.AddClockEntry(new ClockEntry(period.Ticks, (long)TimeInterval.TicksPerSecond, this.action, owner ?? machine, name, enabled: false)); 651 } 652 Dispose()653 public void Dispose() 654 { 655 machine.ClockSource.TryRemoveClockEntry(action); 656 } 657 Start()658 public void Start() 659 { 660 machine.ClockSource.ExchangeClockEntryWith( 661 action, x => x.With(enabled: true)); 662 } 663 StartDelayed(TimeInterval delay)664 public void StartDelayed(TimeInterval delay) 665 { 666 Action<TimeInterval> startThread = ts => 667 { 668 Start(); 669 670 // Let's have the first action run precisely at the specified time. 671 action(); 672 }; 673 var name = machine.ClockSource.GetClockEntry(action).LocalName; 674 machine.ScheduleAction(delay, startThread, name); 675 } 676 Stop()677 public void Stop() 678 { 679 machine.ClockSource.ExchangeClockEntryWith(action, x => x.With(enabled: false)); 680 } 681 682 public uint Frequency 683 { 684 get => (uint)machine.ClockSource.GetClockEntry(action).Frequency; 685 set => machine.ClockSource.ExchangeClockEntryWith(action, entry => entry.With(frequency: value)); 686 } 687 ManagedThreadWrappingClockEntry(IMachine machine, Action action, Func<bool> stopCondition = null)688 private ManagedThreadWrappingClockEntry(IMachine machine, Action action, Func<bool> stopCondition = null) 689 { 690 this.action = () => 691 { 692 if(stopCondition?.Invoke() ?? false) 693 { 694 Stop(); 695 } 696 else 697 { 698 action(); 699 } 700 }; 701 this.machine = machine; 702 } 703 704 private readonly Action action; 705 private readonly IMachine machine; 706 } 707 708 private BaseClockSource clockSource; 709 public IClockSource ClockSource { get { return clockSource; } } 710 711 [UiAccessible] GetClockSourceInfo()712 public string[,] GetClockSourceInfo() 713 { 714 var entries = ClockSource.GetAllClockEntries(); 715 716 var table = new Table().AddRow("Owner", "Enabled", "Frequency", "Limit", "Value", "Step", "Event frequency", "Event period"); 717 table.AddRows(entries, 718 x => 719 { 720 var owner = x.Handler.Target; 721 var ownerAsPeripheral = owner as IPeripheral; 722 if(x.Owner != null) 723 { 724 if(EmulationManager.Instance.CurrentEmulation.TryGetEmulationElementName(x.Owner, out var name, out var _)) 725 { 726 return name + (String.IsNullOrWhiteSpace(x.LocalName) ? String.Empty : $": {x.LocalName}"); 727 } 728 } 729 return ownerAsPeripheral != null 730 ? GetAnyNameOrTypeName(ownerAsPeripheral) 731 : owner.GetType().Name; 732 }, 733 x => x.Enabled.ToString(), 734 x => Misc.NormalizeDecimal(x.Frequency) + "Hz", 735 x => x.Period.ToString(), 736 x => x.Value.ToString(), 737 x => x.Step.ToString(), 738 x => x.Period == 0 ? "---" : Misc.NormalizeDecimal((ulong)(x.Frequency * x.Step) / (double)x.Period) + "Hz", 739 x => (x.Frequency == 0 || x.Period == 0) ? "---" : Misc.NormalizeDecimal((ulong)x.Period / (x.Frequency * (double)x.Step)) + "s" 740 ); 741 return table.ToArray(); 742 } 743 AttachGPIO(IPeripheral source, int sourceNumber, IGPIOReceiver destination, int destinationNumber, int? localReceiverNumber = null)744 public void AttachGPIO(IPeripheral source, int sourceNumber, IGPIOReceiver destination, int destinationNumber, int? localReceiverNumber = null) 745 { 746 var sourceByNumber = source as INumberedGPIOOutput; 747 IGPIO igpio; 748 if(sourceByNumber == null) 749 { 750 throw new RecoverableException("Source peripheral cannot be connected by number."); 751 } 752 if(!sourceByNumber.Connections.TryGetValue(sourceNumber, out igpio)) 753 { 754 throw new RecoverableException(string.Format("Source peripheral has no GPIO number: {0}", source)); 755 } 756 var actualDestination = GetActualReceiver(destination, localReceiverNumber); 757 igpio.Connect(actualDestination, destinationNumber); 758 } 759 AttachGPIO(IPeripheral source, IGPIOReceiver destination, int destinationNumber, int? localReceiverNumber = null)760 public void AttachGPIO(IPeripheral source, IGPIOReceiver destination, int destinationNumber, int? localReceiverNumber = null) 761 { 762 var connectors = source.GetType() 763 .GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => typeof(GPIO).IsAssignableFrom(x.PropertyType)).ToArray(); 764 var actualDestination = GetActualReceiver(destination, localReceiverNumber); 765 DoAttachGPIO(source, connectors, actualDestination, destinationNumber); 766 } 767 AttachGPIO(IPeripheral source, string connectorName, IGPIOReceiver destination, int destinationNumber, int? localReceiverNumber = null)768 public void AttachGPIO(IPeripheral source, string connectorName, IGPIOReceiver destination, int destinationNumber, int? localReceiverNumber = null) 769 { 770 var connectors = source.GetType() 771 .GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.Name == connectorName && typeof(GPIO).IsAssignableFrom(x.PropertyType)).ToArray(); 772 var actualDestination = GetActualReceiver(destination, localReceiverNumber); 773 DoAttachGPIO(source, connectors, actualDestination, destinationNumber); 774 } 775 HandleTimeDomainEvent(Action<T> handler, T handlerArgument, TimeStamp eventTime, Action postAction = null)776 public void HandleTimeDomainEvent<T>(Action<T> handler, T handlerArgument, TimeStamp eventTime, Action postAction = null) 777 { 778 switch(EmulationManager.Instance.CurrentEmulation.Mode) 779 { 780 case Emulation.EmulationMode.SynchronizedIO: 781 { 782 LocalTimeSource.ExecuteInSyncedState(ts => 783 { 784 HandleTimeDomainEvent(handler, handlerArgument, ts.Domain == LocalTimeSource.Domain); 785 postAction?.Invoke(); 786 }, eventTime); 787 break; 788 } 789 790 case Emulation.EmulationMode.SynchronizedTimers: 791 { 792 handler(handlerArgument); 793 postAction?.Invoke(); 794 break; 795 } 796 797 default: 798 throw new Exception("Should not reach here"); 799 } 800 } 801 HandleTimeDomainEvent(Action<T1, T2> handler, T1 handlerArgument1, T2 handlerArgument2, TimeStamp eventTime, Action postAction = null)802 public void HandleTimeDomainEvent<T1, T2>(Action<T1, T2> handler, T1 handlerArgument1, T2 handlerArgument2, TimeStamp eventTime, Action postAction = null) 803 { 804 switch(EmulationManager.Instance.CurrentEmulation.Mode) 805 { 806 case Emulation.EmulationMode.SynchronizedIO: 807 { 808 LocalTimeSource.ExecuteInSyncedState(ts => 809 { 810 HandleTimeDomainEvent(handler, handlerArgument1, handlerArgument2, ts.Domain == LocalTimeSource.Domain); 811 postAction?.Invoke(); 812 }, eventTime); 813 break; 814 } 815 816 case Emulation.EmulationMode.SynchronizedTimers: 817 { 818 handler(handlerArgument1, handlerArgument2); 819 postAction?.Invoke(); 820 break; 821 } 822 823 default: 824 throw new Exception("Should not reach here"); 825 } 826 } 827 HandleTimeDomainEvent(Action<T> handler, T handlerArgument, bool timeDomainInternalEvent)828 public void HandleTimeDomainEvent<T>(Action<T> handler, T handlerArgument, bool timeDomainInternalEvent) 829 { 830 switch(EmulationManager.Instance.CurrentEmulation.Mode) 831 { 832 case Emulation.EmulationMode.SynchronizedIO: 833 { 834 ReportForeignEventInner( 835 recorder == null ? (Action<TimeInterval, bool>)null : (timestamp, eventNotFromDomain) => recorder.Record(handlerArgument, handler, timestamp, eventNotFromDomain), 836 () => handler(handlerArgument), timeDomainInternalEvent); 837 break; 838 } 839 840 case Emulation.EmulationMode.SynchronizedTimers: 841 { 842 handler(handlerArgument); 843 break; 844 } 845 846 default: 847 throw new Exception("Should not reach here"); 848 } 849 } 850 GetNewDirtyAddressesForCore(ICPU cpu)851 public long[] GetNewDirtyAddressesForCore(ICPU cpu) 852 { 853 if(!firstUnbroadcastedDirtyAddressIndex.ContainsKey(cpu)) 854 { 855 throw new RecoverableException($"No entries for a cpu: {cpu.GetName()}. Was the cpu registered properly?"); 856 } 857 858 long[] newAddresses; 859 lock(invalidatedAddressesLock) 860 { 861 var firstUnsentIndex = firstUnbroadcastedDirtyAddressIndex[cpu]; 862 var addressesCount = invalidatedAddressesByCpu[cpu].Count - firstUnsentIndex; 863 newAddresses = invalidatedAddressesByCpu[cpu].GetRange(firstUnsentIndex, addressesCount).ToArray(); 864 firstUnbroadcastedDirtyAddressIndex[cpu] += addressesCount; 865 } 866 return newAddresses; 867 } 868 AppendDirtyAddresses(ICPU cpu, long[] addresses)869 public void AppendDirtyAddresses(ICPU cpu, long[] addresses) 870 { 871 if(!invalidatedAddressesByCpu.ContainsKey(cpu)) 872 { 873 throw new RecoverableException($"Invalid cpu: {cpu.GetName()}"); 874 } 875 876 lock(invalidatedAddressesLock) 877 { 878 if(invalidatedAddressesByCpu[cpu].Count + addresses.Length > invalidatedAddressesByCpu[cpu].Capacity) 879 { 880 TryReduceBroadcastedDirtyAddresses(cpu); 881 } 882 invalidatedAddressesByCpu[cpu].AddRange(addresses); 883 } 884 } 885 HandleTimeDomainEvent(Action<T1, T2> handler, T1 handlerArgument1, T2 handlerArgument2, bool timeDomainInternalEvent)886 public void HandleTimeDomainEvent<T1, T2>(Action<T1, T2> handler, T1 handlerArgument1, T2 handlerArgument2, bool timeDomainInternalEvent) 887 { 888 switch(EmulationManager.Instance.CurrentEmulation.Mode) 889 { 890 case Emulation.EmulationMode.SynchronizedIO: 891 { 892 ReportForeignEventInner( 893 recorder == null ? (Action<TimeInterval, bool>)null : (timestamp, eventNotFromDomain) => recorder.Record(handlerArgument1, handlerArgument2, handler, timestamp, eventNotFromDomain), 894 () => handler(handlerArgument1, handlerArgument2), timeDomainInternalEvent); 895 break; 896 } 897 898 case Emulation.EmulationMode.SynchronizedTimers: 899 { 900 handler(handlerArgument1, handlerArgument2); 901 break; 902 } 903 904 default: 905 throw new Exception("Should not reach here"); 906 } 907 } 908 909 public bool HasRecorder => recorder != null; 910 911 private object recorderPlayerLock = new object(); 912 RecordTo(string fileName, RecordingBehaviour recordingBehaviour)913 public void RecordTo(string fileName, RecordingBehaviour recordingBehaviour) 914 { 915 lock(recorderPlayerLock) 916 { 917 if(EmulationManager.Instance.CurrentEmulation.Mode != Emulation.EmulationMode.SynchronizedIO) 918 { 919 throw new RecoverableException($"Recording events only allowed in the synchronized IO emulation mode (current mode is {EmulationManager.Instance.CurrentEmulation.Mode}"); 920 } 921 922 recorder = new Recorder(File.Create(fileName), this, recordingBehaviour); 923 } 924 } 925 926 public bool HasPlayer => player != null; 927 PlayFrom(ReadFilePath fileName)928 public void PlayFrom(ReadFilePath fileName) 929 { 930 lock(recorderPlayerLock) 931 { 932 if(EmulationManager.Instance.CurrentEmulation.Mode != Emulation.EmulationMode.SynchronizedIO) 933 { 934 throw new RecoverableException($"Replying events only allowed in the synchronized IO emulation mode (current mode is {EmulationManager.Instance.CurrentEmulation.Mode}"); 935 } 936 937 player = new Player(File.OpenRead(fileName), this); 938 LocalTimeSource.SyncHook += player.Play; 939 } 940 } 941 AddUserStateHook(Func<string, bool> predicate, Action<string> hook)942 public void AddUserStateHook(Func<string, bool> predicate, Action<string> hook) 943 { 944 userStateHook += currentState => 945 { 946 if(predicate(currentState)) 947 { 948 hook(currentState); 949 } 950 }; 951 } 952 EnableGdbLogging(int port, bool enabled)953 public void EnableGdbLogging(int port, bool enabled) 954 { 955 if(!gdbStubs.ContainsKey(port)) 956 { 957 return; 958 } 959 gdbStubs[port].LogsEnabled = enabled; 960 } 961 StartGdbServer(int port, bool autostartEmulation = true, string cpuCluster = R)962 public void StartGdbServer(int port, bool autostartEmulation = true, string cpuCluster = "") 963 { 964 var cpus = SystemBus.GetCPUs().OfType<ICpuSupportingGdb>(); 965 if(!cpus.Any()) 966 { 967 throw new RecoverableException("Cannot start GDB server with no CPUs. Did you forget to load the platform description first?"); 968 } 969 try 970 { 971 // If all the CPUs are only of one architecture, implicitly allow to connect, without prompting about anything 972 if(cpus.Select(cpu => cpu.Model).Distinct().Count() <= 1) 973 { 974 if(!String.IsNullOrEmpty(cpuCluster)) 975 { 976 this.Log(LogLevel.Warning, "{0} setting has no effect on non-heterogenous systems, and will be ignored", nameof(cpuCluster)); 977 } 978 AddCpusToGdbStub(port, autostartEmulation, cpus); 979 this.Log(LogLevel.Info, "GDB server with all CPUs started on port :{0}", port); 980 } 981 else 982 { 983 // It's not recommended to connect GDB to all CPUs in heterogeneous platforms 984 // but let's permit this, if the user insists, with a log 985 if(cpuCluster.ToLowerInvariant() == "all") 986 { 987 this.Log(LogLevel.Info, "Starting GDB server for CPUs of different architectures. Make sure, that your debugger supports this configuration"); 988 AddCpusToGdbStub(port, autostartEmulation, cpus); 989 return; 990 } 991 992 // Otherwise, simple clustering, based on architecture 993 var cpusOfArch = cpus.Where(cpu => cpu.Model == cpuCluster); 994 if(!cpusOfArch.Any()) 995 { 996 var response = new StringBuilder(); 997 if(String.IsNullOrEmpty(cpuCluster)) 998 { 999 response.AppendLine("CPUs of different architectures are present in this platform. Specify cluster of CPUs to debug, or \"all\" to connect to all CPUs."); 1000 response.AppendLine("NOTE: when selecting \"all\" make sure that your debugger can handle CPUs of different architectures."); 1001 } 1002 else 1003 { 1004 response.AppendFormat("No CPUs available or no cluster named: \"{0}\" exists.\n", cpuCluster); 1005 } 1006 response.Append("Available clusters are: "); 1007 response.Append(Misc.PrettyPrintCollection(cpus.Select(c => c.Model).Distinct().Append("all"), c => $"\"{c}\"")); 1008 throw new RecoverableException(response.ToString()); 1009 } 1010 AddCpusToGdbStub(port, autostartEmulation, cpusOfArch); 1011 } 1012 } 1013 catch(SocketException e) 1014 { 1015 throw new RecoverableException(string.Format("Could not start GDB server: {0}", e.Message)); 1016 } 1017 } 1018 1019 // Name of the last parameter is kept as 'cpu' for backward compatibility. StartGdbServer(int port, bool autostartEmulation, ICluster<ICpuSupportingGdb> cpu)1020 public void StartGdbServer(int port, bool autostartEmulation, ICluster<ICpuSupportingGdb> cpu) 1021 { 1022 var cluster = cpu; 1023 foreach(var cpuSupportingGdb in cluster.Clustered) 1024 { 1025 try 1026 { 1027 AddCpusToGdbStub(port, autostartEmulation, new [] { cpuSupportingGdb }); 1028 } 1029 catch(SocketException e) 1030 { 1031 throw new RecoverableException(string.Format("Could not start GDB server for {0}: {1}", cpuSupportingGdb.GetName(), e.Message)); 1032 } 1033 } 1034 } 1035 StopGdbServer(int? port = null)1036 public void StopGdbServer(int? port = null) 1037 { 1038 if(!gdbStubs.Any()) 1039 { 1040 throw new RecoverableException("Nothing to stop."); 1041 } 1042 if(!port.HasValue) 1043 { 1044 if(gdbStubs.Count > 1) 1045 { 1046 throw new RecoverableException("Port number required to stop a GDB server."); 1047 } 1048 gdbStubs.Single().Value.Dispose(); 1049 gdbStubs.Clear(); 1050 return; 1051 } 1052 if(!gdbStubs.ContainsKey(port.Value)) 1053 { 1054 throw new RecoverableException(string.Format("There is no GDB server on port :{0}.", port.Value)); 1055 } 1056 gdbStubs[port.Value].Dispose(); 1057 gdbStubs.Remove(port.Value); 1058 } 1059 AttachConnectionAcceptedListenerToGdbStub(int port, Action<Stream> listener)1060 public bool AttachConnectionAcceptedListenerToGdbStub(int port, Action<Stream> listener) 1061 { 1062 if(!gdbStubs.TryGetValue(port, out var gdbStub)) 1063 { 1064 return false; 1065 } 1066 gdbStub.ConnectionAccepted += listener; 1067 return true; 1068 } 1069 DetachConnectionAcceptedListenerFromGdbStub(int port, Action<Stream> listener)1070 public bool DetachConnectionAcceptedListenerFromGdbStub(int port, Action<Stream> listener) 1071 { 1072 if(!gdbStubs.TryGetValue(port, out var gdbStub)) 1073 { 1074 return false; 1075 } 1076 gdbStub.ConnectionAccepted -= listener; 1077 return true; 1078 } 1079 IsGdbConnectedToServer(int port)1080 public bool IsGdbConnectedToServer(int port) 1081 { 1082 if(!gdbStubs.TryGetValue(port, out var gdbStub)) 1083 { 1084 return false; 1085 } 1086 return gdbStub.GdbClientConnected; 1087 } 1088 ToString()1089 public override string ToString() 1090 { 1091 if(EmulationManager.Instance.CurrentEmulation.TryGetMachineName(this, out var machineName)) 1092 { 1093 return machineName; 1094 } 1095 return "Unregistered machine"; 1096 } 1097 EnableProfiler(string outputPath = null)1098 public void EnableProfiler(string outputPath = null) 1099 { 1100 Profiler?.Dispose(); 1101 Profiler = new Profiler(this, outputPath ?? TemporaryFilesManager.Instance.GetTemporaryFile("renode_profiler")); 1102 } 1103 CheckRecorderPlayer()1104 public void CheckRecorderPlayer() 1105 { 1106 lock(recorderPlayerLock) 1107 { 1108 if(EmulationManager.Instance.CurrentEmulation.Mode != Emulation.EmulationMode.SynchronizedIO) 1109 { 1110 if(recorder != null) 1111 { 1112 throw new RecoverableException("Detected existing event recorder attached to the machine - it won't work in the non-deterministic mode"); 1113 } 1114 if(player != null) 1115 { 1116 throw new RecoverableException("Detected existing event player attached to the machine - it won't work in the non-deterministic mode"); 1117 } 1118 } 1119 } 1120 } 1121 ScheduleAction(TimeInterval delay, Action<TimeInterval> action, string name = null)1122 public void ScheduleAction(TimeInterval delay, Action<TimeInterval> action, string name = null) 1123 { 1124 if(SystemBus.TryGetCurrentCPU(out var cpu)) 1125 { 1126 cpu.SyncTime(); 1127 } 1128 else 1129 { 1130 this.Log(LogLevel.Debug, "Couldn't synchronize time before scheduling action; a slight inaccuracy might occur."); 1131 } 1132 1133 var currentTime = ElapsedVirtualTime.TimeElapsed; 1134 var startTime = currentTime + delay; 1135 1136 // We can't do this in 1 assignment because we need to refer to clockEntryHandler 1137 // within the body of the lambda 1138 Action clockEntryHandler = null; 1139 clockEntryHandler = () => 1140 { 1141 this.Log(LogLevel.Noisy, "{0}: Executing action scheduled at {1} (current time: {2})", name ?? "unnamed", startTime, currentTime); 1142 action(currentTime); 1143 if(!ClockSource.TryRemoveClockEntry(clockEntryHandler)) 1144 { 1145 this.Log(LogLevel.Error, "{0}: Failed to remove clock entry after running scheduled action", name ?? "unnamed"); 1146 } 1147 }; 1148 1149 ClockSource.AddClockEntry(new ClockEntry(delay.Ticks, (long)TimeInterval.TicksPerSecond, clockEntryHandler, this, name, workMode: WorkMode.OneShot)); 1150 1151 // ask CPU to return to C# to recalculate internal timers and make the scheduled action trigger as soon as possible 1152 (cpu as IControllableCPU)?.RequestReturn(); 1153 } 1154 1155 // This method will only be effective when called from the CPU thread with the pause guard held. 1156 // In use cases where one of these conditions may sometimes not be met (for example a GPIO 1157 // state change callback, which can be triggered by a MMIO write or a timer limit event) and the 1158 // restart is not critical, the quiet parameter should be used to silence logging. TryRestartTranslationBlockOnCurrentCpu(bool quiet = false)1159 public bool TryRestartTranslationBlockOnCurrentCpu(bool quiet = false) 1160 { 1161 if(!SystemBus.TryGetCurrentCPU(out var icpu)) 1162 { 1163 if(!quiet) 1164 { 1165 this.Log(LogLevel.Error, "Couldn't find the CPU requesting translation block restart."); 1166 } 1167 return false; 1168 } 1169 1170 try 1171 { 1172 var cpu = (dynamic)icpu; 1173 if(!cpu.RequestTranslationBlockRestart(quiet)) 1174 { 1175 if(!quiet) 1176 { 1177 Logger.LogAs(icpu, LogLevel.Error, "Failed to restart translation block."); 1178 } 1179 return false; 1180 } 1181 } 1182 catch(RuntimeBinderException) 1183 { 1184 Logger.LogAs(icpu, LogLevel.Warning, "Translation block restarting is not supported by '{0}'", icpu.GetType().FullName); 1185 return false; 1186 } 1187 1188 return true; 1189 } 1190 1191 public Profiler Profiler { get; private set; } 1192 1193 public IPeripheral this[string name] 1194 { 1195 get 1196 { 1197 return GetByName(name); 1198 } 1199 } 1200 1201 public string UserState 1202 { 1203 get 1204 { 1205 return userState; 1206 } 1207 set 1208 { 1209 userState = value; 1210 userStateHook(userState); 1211 } 1212 } 1213 1214 public IBusController SystemBus { get; private set; } 1215 1216 public IPeripheralsGroupsManager PeripheralsGroups { get; private set; } 1217 1218 public Platform Platform { get; set; } 1219 1220 public bool IsPaused 1221 { 1222 get 1223 { 1224 // locking on pausingSync can cause a deadlock (when mach.Start() and AllMachineStarted are called together) 1225 var stateCopy = state; 1226 return stateCopy == State.Paused || stateCopy == State.NotStarted; 1227 } 1228 } 1229 1230 public TimeStamp ElapsedVirtualTime 1231 { 1232 get 1233 { 1234 return new TimeStamp(LocalTimeSource.ElapsedVirtualTime, LocalTimeSource.Domain); 1235 } 1236 } 1237 1238 public TimeSourceBase LocalTimeSource 1239 { 1240 get 1241 { 1242 return localTimeSource; 1243 } 1244 1245 set 1246 { 1247 if(localTimeSource != null) 1248 { 1249 throw new RecoverableException("Tried to set LocalTimeSource again."); 1250 } 1251 if(value == null) 1252 { 1253 throw new RecoverableException("Tried to set LocalTimeSource to null."); 1254 } 1255 localTimeSource = value; 1256 localTimeSource.TimePassed += HandleTimeProgress; 1257 foreach(var timeSink in ownLifes.OfType<ITimeSink>()) 1258 { 1259 localTimeSource.RegisterSink(timeSink); 1260 } 1261 } 1262 } 1263 1264 public DateTime RealTimeClockDateTime 1265 { 1266 get 1267 { 1268 if(SystemBus.TryGetCurrentCPU(out var cpu)) 1269 { 1270 cpu.SyncTime(); 1271 } 1272 return RealTimeClockStart + ElapsedVirtualTime.TimeElapsed.ToTimeSpan(); 1273 } 1274 } 1275 1276 public RealTimeClockMode RealTimeClockMode 1277 { 1278 get => realTimeClockMode; 1279 set 1280 { 1281 realTimeClockMode = value; 1282 var realTimeClockModeChanged = RealTimeClockModeChanged; 1283 if(realTimeClockModeChanged != null) 1284 { 1285 realTimeClockModeChanged(this); 1286 } 1287 } 1288 } 1289 1290 public DateTime RealTimeClockStart 1291 { 1292 get 1293 { 1294 switch(RealTimeClockMode) 1295 { 1296 case RealTimeClockMode.Epoch: 1297 return Misc.UnixEpoch; 1298 case RealTimeClockMode.HostTimeLocal: 1299 return machineCreatedAt; 1300 case RealTimeClockMode.HostTimeUTC: 1301 return TimeZoneInfo.ConvertTimeToUtc(machineCreatedAt, sourceTimeZone: TimeZoneInfo.Local); 1302 default: 1303 throw new ArgumentOutOfRangeException(); 1304 } 1305 } 1306 } 1307 1308 [field: Transient] 1309 public event Action<IMachine> MachineReset; 1310 [field: Transient] 1311 public event Action<IMachine, PeripheralsChangedEventArgs> PeripheralsChanged; 1312 [field: Transient] 1313 public event Action<IMachine> RealTimeClockModeChanged; 1314 [field: Transient] 1315 public event Action<IMachine, MachineStateChangedEventArgs> StateChanged; 1316 1317 public const char PathSeparator = '.'; 1318 public const string SystemBusName = "sysbus"; 1319 public const string UnnamedPeripheral = "[no-name]"; 1320 public const string MachineKeyword = "machine"; 1321 CheckIsCpuAlreadyAttached(ICpuSupportingGdb cpu)1322 private void CheckIsCpuAlreadyAttached(ICpuSupportingGdb cpu) 1323 { 1324 var owningStub = gdbStubs.Values.FirstOrDefault(x => x.IsCPUAttached(cpu)); 1325 if(owningStub != null) 1326 { 1327 throw new RecoverableException($"CPU: {cpu.GetName()} is already attached to an existing GDB server, running on port :{owningStub.Port}"); 1328 } 1329 } 1330 AddCpusToGdbStub(int port, bool autostartEmulation, IEnumerable<ICpuSupportingGdb> cpus)1331 private void AddCpusToGdbStub(int port, bool autostartEmulation, IEnumerable<ICpuSupportingGdb> cpus) 1332 { 1333 foreach(var cpu in cpus) 1334 { 1335 CheckIsCpuAlreadyAttached(cpu); 1336 } 1337 if(gdbStubs.ContainsKey(port)) 1338 { 1339 foreach(var cpu in cpus) 1340 { 1341 gdbStubs[port].AttachCPU(cpu); 1342 this.Log(LogLevel.Info, "CPU: {0} was added to GDB server running on port :{1}", cpu.GetName(), port); 1343 } 1344 } 1345 else 1346 { 1347 gdbStubs.Add(port, new GdbStub(this, cpus, port, autostartEmulation)); 1348 this.Log(LogLevel.Info, "CPUs: {0} were added to a new GDB server created on port :{1}", Misc.PrettyPrintCollection(cpus, c => $"\"{c.GetName()}\""), port); 1349 } 1350 } 1351 TryReduceBroadcastedDirtyAddresses(ICPU cpu)1352 private void TryReduceBroadcastedDirtyAddresses(ICPU cpu) 1353 { 1354 var firstUnread = firstUnbroadcastedDirtyAddressIndex.Values.Min(); 1355 if(firstUnread == 0) 1356 { 1357 return; 1358 } 1359 1360 invalidatedAddressesByCpu[cpu].RemoveRange(0, (int)firstUnread); 1361 foreach(var key in firstUnbroadcastedDirtyAddressIndex.Keys.ToArray()) 1362 { 1363 firstUnbroadcastedDirtyAddressIndex[key] -= firstUnread; 1364 } 1365 } 1366 InnerUnregisterFromParent(IPeripheral peripheral)1367 private void InnerUnregisterFromParent(IPeripheral peripheral) 1368 { 1369 using(ObtainPausedState(true)) 1370 { 1371 lock(collectionSync) 1372 { 1373 var parents = GetParents(peripheral); 1374 if(parents.Count > 1) 1375 { 1376 throw new RegistrationException(string.Format("Given peripheral is connected to more than one different parent, at least '{0}' and '{1}'.", 1377 parents.Select(x => GetAnyNameOrTypeName(x)).Take(2).ToArray())); 1378 } 1379 1380 IPeripheralsGroup group; 1381 if(PeripheralsGroups.TryGetActiveGroupContaining(peripheral, out group)) 1382 { 1383 throw new RegistrationException(string.Format("Given peripheral is a member of '{0}' peripherals group and cannot be directly removed.", group.Name)); 1384 } 1385 1386 var parent = parents.FirstOrDefault(); 1387 if(parent == null) 1388 { 1389 throw new RecoverableException(string.Format("Cannot unregister peripheral {0} since it does not have any parent.", peripheral)); 1390 } 1391 ((dynamic)parent).Unregister((dynamic)peripheral); 1392 EmulationManager.Instance.CurrentEmulation.BackendManager.HideAnalyzersFor(peripheral); 1393 } 1394 } 1395 } 1396 InitializeInvalidatedAddressesList(ICPU cpu)1397 private void InitializeInvalidatedAddressesList(ICPU cpu) 1398 { 1399 lock(invalidatedAddressesLock) 1400 { 1401 if(!invalidatedAddressesByArchitecture.TryGetValue(cpu.Architecture, out var newInvalidatedAddressesList)) 1402 { 1403 newInvalidatedAddressesList = new List<long>() { Capacity = InitialDirtyListLength }; 1404 invalidatedAddressesByArchitecture.Add(cpu.Architecture, newInvalidatedAddressesList); 1405 } 1406 invalidatedAddressesByCpu[cpu] = newInvalidatedAddressesList; 1407 } 1408 } 1409 Register(IPeripheral peripheral, IRegistrationPoint registrationPoint, IPeripheral parent)1410 private void Register(IPeripheral peripheral, IRegistrationPoint registrationPoint, IPeripheral parent) 1411 { 1412 using(ObtainPausedState(true)) 1413 { 1414 Action executeAfterLock = null; 1415 lock(collectionSync) 1416 { 1417 var parentNode = registeredPeripherals.GetNode(parent); 1418 parentNode.AddChild(peripheral, registrationPoint); 1419 var ownLife = peripheral as IHasOwnLife; 1420 if(peripheral is ICPU cpu) 1421 { 1422 if(cpu.Architecture == null) 1423 { 1424 throw new RecoverableException($"{cpu.Model ?? "Unknown model"}: CPU architecture not provided"); 1425 } 1426 InitializeInvalidatedAddressesList(cpu); 1427 firstUnbroadcastedDirtyAddressIndex[cpu] = 0; 1428 } 1429 if(ownLife != null) 1430 { 1431 ownLifes.Add(ownLife); 1432 if(state == State.Paused) 1433 { 1434 executeAfterLock = delegate 1435 { 1436 ownLife.Start(); 1437 ownLife.Pause(); 1438 }; 1439 } 1440 } 1441 } 1442 if(executeAfterLock != null) 1443 { 1444 executeAfterLock(); 1445 } 1446 1447 if(peripheral is ITimeSink timeSink) 1448 { 1449 LocalTimeSource?.RegisterSink(timeSink); 1450 } 1451 } 1452 1453 PeripheralsChanged?.Invoke(this, PeripheralsAddedEventArgs.Create(peripheral, registrationPoint)); 1454 EmulationManager.Instance.CurrentEmulation.BackendManager.TryCreateBackend(peripheral); 1455 } 1456 TryFindSubnodeByName(MultiTreeNode<IPeripheral, IRegistrationPoint> from, string path, out MultiTreeNode<IPeripheral, IRegistrationPoint> subnode, string currentMatching, out string longestMatching)1457 private bool TryFindSubnodeByName(MultiTreeNode<IPeripheral, IRegistrationPoint> from, string path, out MultiTreeNode<IPeripheral, IRegistrationPoint> subnode, 1458 string currentMatching, out string longestMatching) 1459 { 1460 lock(collectionSync) 1461 { 1462 var subpath = path.Split(new [] { PathSeparator }, 2); 1463 subnode = null; 1464 longestMatching = currentMatching; 1465 foreach(var currentChild in from.Children) 1466 { 1467 string name; 1468 if(!TryGetLocalName(currentChild.Value, out name)) 1469 { 1470 continue; 1471 } 1472 1473 if(name == subpath[0]) 1474 { 1475 subnode = currentChild; 1476 if(subpath.Length == 1) 1477 { 1478 return true; 1479 } 1480 return TryFindSubnodeByName(currentChild, subpath[1], out subnode, Subname(currentMatching, subpath[0]), out longestMatching); 1481 } 1482 } 1483 return false; 1484 } 1485 } 1486 GetByName(string path)1487 private IPeripheral GetByName(string path) 1488 { 1489 IPeripheral result; 1490 string longestMatching; 1491 if(!TryGetByName(path, out result, out longestMatching)) 1492 { 1493 throw new InvalidOperationException(string.Format( 1494 "Could not find node '{0}', the longest matching was '{1}'.", path, longestMatching)); 1495 } 1496 return result; 1497 } 1498 GetParents(IPeripheral child)1499 private HashSet<IPeripheral> GetParents(IPeripheral child) 1500 { 1501 var parents = new HashSet<IPeripheral>(); 1502 registeredPeripherals.TraverseChildrenFirst((parent, children, level) => 1503 { 1504 if(children.Any(x => x.Value.Equals(child))) 1505 { 1506 parents.Add(parent.Value); 1507 } 1508 }, 0); 1509 return parents; 1510 } 1511 GetNames(IPeripheral peripheral)1512 private ReadOnlyCollection<string> GetNames(IPeripheral peripheral) 1513 { 1514 lock(collectionSync) 1515 { 1516 var paths = new List<string>(); 1517 if(peripheral == SystemBus) 1518 { 1519 paths.Add(SystemBusName); 1520 } 1521 else 1522 { 1523 FindPaths(SystemBusName, peripheral, registeredPeripherals.GetNode(SystemBus), paths); 1524 } 1525 return new ReadOnlyCollection<string>(paths); 1526 } 1527 } 1528 FindPaths(string nameSoFar, IPeripheral peripheralToFind, MultiTreeNode<IPeripheral, IRegistrationPoint> currentNode, List<string> paths)1529 private void FindPaths(string nameSoFar, IPeripheral peripheralToFind, MultiTreeNode<IPeripheral, IRegistrationPoint> currentNode, List<string> paths) 1530 { 1531 foreach(var child in currentNode.Children) 1532 { 1533 var currentPeripheral = child.Value; 1534 string localName; 1535 if(!TryGetLocalName(currentPeripheral, out localName)) 1536 { 1537 continue; 1538 } 1539 var name = Subname(nameSoFar, localName); 1540 if(currentPeripheral == peripheralToFind) 1541 { 1542 paths.Add(name); 1543 return; // shouldn't be attached to itself 1544 } 1545 FindPaths(name, peripheralToFind, child, paths); 1546 } 1547 } 1548 Subname(string parent, string child)1549 private static string Subname(string parent, string child) 1550 { 1551 return string.Format("{0}{1}{2}", parent, string.IsNullOrEmpty(parent) ? string.Empty : PathSeparator.ToString(), child); 1552 } 1553 GetNameForOwnLife(IHasOwnLife ownLife)1554 private string GetNameForOwnLife(IHasOwnLife ownLife) 1555 { 1556 var peripheral = ownLife as IPeripheral; 1557 if(peripheral != null) 1558 { 1559 return GetAnyNameOrTypeName(peripheral); 1560 } 1561 return ownLife.ToString(); 1562 } 1563 DoAttachGPIO(IPeripheral source, PropertyInfo[] gpios, IGPIOReceiver destination, int destinationNumber)1564 private static void DoAttachGPIO(IPeripheral source, PropertyInfo[] gpios, IGPIOReceiver destination, int destinationNumber) 1565 { 1566 if(gpios.Length == 0) 1567 { 1568 throw new RecoverableException("No GPIO connector found."); 1569 } 1570 if(gpios.Length > 1) 1571 { 1572 throw new RecoverableException("Ambiguous GPIO connector. Available connectors are: {0}." 1573 .FormatWith(gpios.Select(x => x.Name).Aggregate((x, y) => x + ", " + y))); 1574 } 1575 (gpios[0].GetValue(source, null) as GPIO).Connect(destination, destinationNumber); 1576 } 1577 GetActualReceiver(IGPIOReceiver receiver, int? localReceiverNumber)1578 private static IGPIOReceiver GetActualReceiver(IGPIOReceiver receiver, int? localReceiverNumber) 1579 { 1580 var localReceiver = receiver as ILocalGPIOReceiver; 1581 if(localReceiverNumber.HasValue) 1582 { 1583 if(localReceiver != null) 1584 { 1585 return localReceiver.GetLocalReceiver(localReceiverNumber.Value); 1586 } 1587 throw new RecoverableException("The specified receiver does not support localReceiverNumber."); 1588 } 1589 return receiver; 1590 } 1591 ReportForeignEventInner(Action<TimeInterval, bool> recordMethod, Action handlerMethod, bool timeDomainInternalEvent)1592 private void ReportForeignEventInner(Action<TimeInterval, bool> recordMethod, Action handlerMethod, bool timeDomainInternalEvent) 1593 { 1594 LocalTimeSource.ExecuteInNearestSyncedState(ts => 1595 { 1596 recordMethod?.Invoke(ts.TimeElapsed, timeDomainInternalEvent); 1597 handlerMethod(); 1598 }, true); 1599 } 1600 CollectGarbageStamp()1601 private void CollectGarbageStamp() 1602 { 1603 currentStampLevel++; 1604 if(currentStampLevel != 1) 1605 { 1606 return; 1607 } 1608 currentStamp = new List<IPeripheral>(); 1609 registeredPeripherals.TraverseParentFirst((peripheral, level) => currentStamp.Add(peripheral), 0); 1610 } 1611 CollectGarbage()1612 private void CollectGarbage() 1613 { 1614 currentStampLevel--; 1615 if(currentStampLevel != 0) 1616 { 1617 return; 1618 } 1619 var toDelete = currentStamp.Where(x => !IsRegistered(x)).ToArray(); 1620 DetachIncomingInterrupts(toDelete); 1621 DetachOutgoingInterrupts(toDelete); 1622 foreach(var value in toDelete) 1623 { 1624 ((PeripheralsGroupsManager)PeripheralsGroups).RemoveFromAllGroups(value); 1625 var ownLife = value as IHasOwnLife; 1626 if(ownLife != null) 1627 { 1628 ownLifes.Remove(ownLife); 1629 } 1630 EmulationManager.Instance.CurrentEmulation.Connector.DisconnectFromAll(value); 1631 1632 localNames.Remove(value); 1633 var disposable = value as IDisposable; 1634 if(disposable != null) 1635 { 1636 disposable.Dispose(); 1637 } 1638 } 1639 currentStamp = null; 1640 } 1641 DetachIncomingInterrupts(IPeripheral[] detachedPeripherals)1642 private void DetachIncomingInterrupts(IPeripheral[] detachedPeripherals) 1643 { 1644 foreach(var detachedPeripheral in detachedPeripherals) 1645 { 1646 // find all peripherials' GPIOs and check which one is connected to detachedPeripherial 1647 foreach(var peripheral in registeredPeripherals.Children.Select(x => x.Value).Distinct()) 1648 { 1649 foreach(var gpio in peripheral.GetGPIOs().Select(x => x.Item2)) 1650 { 1651 var endpoints = gpio.Endpoints; 1652 for(var i = 0; i < endpoints.Count; ++i) 1653 { 1654 if(endpoints[i].Receiver == detachedPeripheral) 1655 { 1656 gpio.Disconnect(endpoints[i]); 1657 } 1658 } 1659 } 1660 } 1661 } 1662 } 1663 DetachOutgoingInterrupts(IEnumerable<IPeripheral> peripherals)1664 private static void DetachOutgoingInterrupts(IEnumerable<IPeripheral> peripherals) 1665 { 1666 foreach(var peripheral in peripherals) 1667 { 1668 foreach(var gpio in peripheral.GetGPIOs().Select(x => x.Item2)) 1669 { 1670 gpio.Disconnect(); 1671 } 1672 } 1673 } 1674 Resume()1675 private void Resume() 1676 { 1677 lock(pausingSync) 1678 { 1679 (LocalTimeSource as SlaveTimeSource)?.Resume(); 1680 foreach(var ownLife in ownLifes.OrderBy(x => x is ICPU ? 1 : 0)) 1681 { 1682 this.NoisyLog("Resuming {0}.", GetNameForOwnLife(ownLife)); 1683 ownLife.Resume(); 1684 } 1685 this.Log(LogLevel.Info, "Machine resumed."); 1686 state = State.Started; 1687 var machineStarted = StateChanged; 1688 if(machineStarted != null) 1689 { 1690 machineStarted(this, new MachineStateChangedEventArgs(MachineStateChangedEventArgs.State.Started)); 1691 } 1692 } 1693 } 1694 HandleTimeProgress(TimeInterval diff)1695 public void HandleTimeProgress(TimeInterval diff) 1696 { 1697 clockSource.Advance(diff); 1698 } 1699 PostCreationActions()1700 public void PostCreationActions() 1701 { 1702 // Enable broadcasting dirty addresses on multicore platforms 1703 var cpus = SystemBus.GetCPUs().OfType<ICPUWithMappedMemory>().ToArray(); 1704 if(cpus.Length > 1) 1705 { 1706 foreach(var cpu in cpus) 1707 { 1708 cpu.SetBroadcastDirty(true); 1709 } 1710 } 1711 1712 // Register io_executable flags for all ArrayMemory peripherals 1713 foreach(var context in SystemBus.GetAllContextKeys()) 1714 { 1715 foreach(var registration in SystemBus.GetRegistrationsForPeripheralType<Peripherals.Memory.ArrayMemory>(context)) 1716 { 1717 var range = registration.RegistrationPoint.Range; 1718 var perCore = registration.RegistrationPoint.Initiator; 1719 if(perCore == null) 1720 { 1721 cpus = SystemBus.GetCPUs().OfType<ICPUWithMappedMemory>().ToArray(); 1722 foreach(var cpu in cpus) 1723 { 1724 cpu.RegisterAccessFlags(range.StartAddress, range.Size, isIoMemory: true); 1725 } 1726 } 1727 else 1728 { 1729 if(perCore is ICPUWithMappedMemory cpuWithMappedMemory) 1730 { 1731 cpuWithMappedMemory.RegisterAccessFlags(range.StartAddress, range.Size, isIoMemory: true); 1732 } 1733 } 1734 } 1735 } 1736 } 1737 ExchangeRegistrationPointForPeripheral(IPeripheral parent, IPeripheral child, IRegistrationPoint oldPoint, IRegistrationPoint newPoint)1738 public void ExchangeRegistrationPointForPeripheral(IPeripheral parent, IPeripheral child, IRegistrationPoint oldPoint, IRegistrationPoint newPoint) 1739 { 1740 // assert paused state or within per-core context 1741 var exitLock = false; 1742 try 1743 { 1744 if(!SystemBus.TryGetCurrentCPU(out var cpu) || !cpu.OnPossessedThread) 1745 { 1746 Monitor.Enter(pausingSync, ref exitLock); 1747 if(!IsPaused) 1748 { 1749 throw new RecoverableException("Attempted to exchange registration point while not in paused state nor on context's CPU thread"); 1750 } 1751 } 1752 lock(collectionSync) 1753 { 1754 registeredPeripherals.GetNode(parent).ReplaceConnectionWay(oldPoint, newPoint); 1755 var operation = PeripheralsChangedEventArgs.PeripheralChangeType.Moved; 1756 PeripheralsChanged?.Invoke(this, PeripheralsChangedEventArgs.Create(child, operation)); 1757 } 1758 } 1759 finally 1760 { 1761 if(exitLock) 1762 { 1763 Monitor.Exit(pausingSync); 1764 } 1765 } 1766 } 1767 1768 [Constructor] 1769 private Dictionary<int, GdbStub> gdbStubs; 1770 private string userState; 1771 private Action<string> userStateHook; 1772 private bool alreadyDisposed; 1773 private State state; 1774 private PausedState pausedState; 1775 private List<IPeripheral> currentStamp; 1776 private int currentStampLevel; 1777 private Recorder recorder; 1778 private Player player; 1779 private TimeSourceBase localTimeSource; 1780 private RealTimeClockMode realTimeClockMode; 1781 1782 private readonly MultiTree<IPeripheral, IRegistrationPoint> registeredPeripherals; 1783 private readonly Dictionary<IBusPeripheral, BusControllerWrapper> peripheralsBusControllers; 1784 private readonly Dictionary<IPeripheral, string> localNames; 1785 private readonly HashSet<IHasOwnLife> ownLifes; 1786 private readonly object collectionSync; 1787 private readonly object pausingSync; 1788 private readonly object disposedSync; 1789 private readonly DateTime machineCreatedAt; 1790 1791 /* 1792 * Variables used for memory invalidation 1793 */ 1794 private const int InitialDirtyListLength = 1 << 10; 1795 private readonly Dictionary<ICPU, int> firstUnbroadcastedDirtyAddressIndex; 1796 private readonly Dictionary<ICPU, List<long>> invalidatedAddressesByCpu; 1797 private readonly Dictionary<string, List<long>> invalidatedAddressesByArchitecture; 1798 private readonly object invalidatedAddressesLock; 1799 1800 private enum State 1801 { 1802 NotStarted, 1803 Started, 1804 Paused 1805 } 1806 1807 private class BusControllerWrapper : BusControllerProxy 1808 { BusControllerWrapper(IBusController wrappedController)1809 public BusControllerWrapper(IBusController wrappedController) : base(wrappedController) 1810 { 1811 } 1812 ChangeWrapped(IBusController wrappedController)1813 public void ChangeWrapped(IBusController wrappedController) 1814 { 1815 ParentController = wrappedController; 1816 } 1817 } 1818 1819 private sealed class PausedState : IDisposable 1820 { 1821 // PausedState is used only within Machine so we can take Machine as an argument, instead of IMachine. PausedState(Machine machine)1822 public PausedState(Machine machine) 1823 { 1824 this.machine = machine; 1825 sync = machine.pausingSync; 1826 } 1827 Enter()1828 public PausedState Enter() 1829 { 1830 LevelUp(); 1831 return this; 1832 } 1833 Exit()1834 public void Exit() 1835 { 1836 LevelDown(); 1837 } 1838 Dispose()1839 public void Dispose() 1840 { 1841 Exit(); 1842 } 1843 LevelUp()1844 private void LevelUp() 1845 { 1846 lock(sync) 1847 { 1848 if(currentLevel == 0) 1849 { 1850 if(machine.IsPaused) 1851 { 1852 wasPaused = true; 1853 } 1854 else 1855 { 1856 wasPaused = false; 1857 pausedLifes = machine.ownLifes.Where(ownLife => ownLife.IsPaused).ToArray(); 1858 machine.Pause(); 1859 } 1860 } 1861 currentLevel++; 1862 } 1863 } 1864 LevelDown()1865 private void LevelDown() 1866 { 1867 lock(sync) 1868 { 1869 if(currentLevel == 1) 1870 { 1871 if(!wasPaused) 1872 { 1873 machine.Start(ownLife => !pausedLifes.Contains(ownLife)); 1874 } 1875 } 1876 if(currentLevel == 0) 1877 { 1878 throw new InvalidOperationException("LevelDown without prior LevelUp"); 1879 } 1880 currentLevel--; 1881 } 1882 } 1883 1884 // As this mechanism is used to pause the emulation before the serialization - we have to drop value of this field to avoid starting with a non-zero value. 1885 // Otherwise, after the deserialization we won't be able to ever reach the currentLevel == 0 and restart the machine when needed. 1886 [Transient] 1887 private int currentLevel; 1888 private bool wasPaused; 1889 private IHasOwnLife[] pausedLifes; 1890 private readonly Machine machine; 1891 private readonly object sync; 1892 } 1893 1894 private sealed class PeripheralsGroupsManager : IPeripheralsGroupsManager 1895 { PeripheralsGroupsManager(IMachine machine)1896 public PeripheralsGroupsManager(IMachine machine) 1897 { 1898 this.machine = machine; 1899 groups = new List<PeripheralsGroup>(); 1900 } 1901 GetOrCreate(string name, IEnumerable<IPeripheral> peripherals)1902 public IPeripheralsGroup GetOrCreate(string name, IEnumerable<IPeripheral> peripherals) 1903 { 1904 IPeripheralsGroup existingResult = null; 1905 var result = (PeripheralsGroup)existingResult; 1906 if(!TryGetByName(name, out existingResult)) 1907 { 1908 result = new PeripheralsGroup(name, machine); 1909 groups.Add(result); 1910 } 1911 1912 foreach(var p in peripherals) 1913 { 1914 result.Add(p); 1915 } 1916 1917 return result; 1918 } 1919 GetOrCreate(string name)1920 public IPeripheralsGroup GetOrCreate(string name) 1921 { 1922 IPeripheralsGroup result; 1923 if(!TryGetByName(name, out result)) 1924 { 1925 result = new PeripheralsGroup(name, machine); 1926 groups.Add((PeripheralsGroup)result); 1927 } 1928 1929 return result; 1930 } 1931 RemoveFromAllGroups(IPeripheral value)1932 public void RemoveFromAllGroups(IPeripheral value) 1933 { 1934 foreach(var group in ActiveGroups) 1935 { 1936 ((List<IPeripheral>)group.Peripherals).Remove(value); 1937 } 1938 } 1939 TryGetActiveGroupContaining(IPeripheral peripheral, out IPeripheralsGroup group)1940 public bool TryGetActiveGroupContaining(IPeripheral peripheral, out IPeripheralsGroup group) 1941 { 1942 group = ActiveGroups.SingleOrDefault(x => ((PeripheralsGroup)x).Contains(peripheral)); 1943 return group != null; 1944 } 1945 TryGetAnyGroupContaining(IPeripheral peripheral, out IPeripheralsGroup group)1946 public bool TryGetAnyGroupContaining(IPeripheral peripheral, out IPeripheralsGroup group) 1947 { 1948 group = groups.SingleOrDefault(x => x.Contains(peripheral)); 1949 return group != null; 1950 } 1951 TryGetByName(string name, out IPeripheralsGroup group)1952 public bool TryGetByName(string name, out IPeripheralsGroup group) 1953 { 1954 group = ActiveGroups.SingleOrDefault(x => x.Name == name); 1955 return group != null; 1956 } 1957 1958 public IEnumerable<IPeripheralsGroup> ActiveGroups 1959 { 1960 get 1961 { 1962 return groups.Where(x => x.IsActive); 1963 } 1964 } 1965 1966 private readonly List<PeripheralsGroup> groups; 1967 private readonly IMachine machine; 1968 1969 private sealed class PeripheralsGroup : IPeripheralsGroup 1970 { PeripheralsGroup(string name, IMachine machine)1971 public PeripheralsGroup(string name, IMachine machine) 1972 { 1973 Machine = machine; 1974 Name = name; 1975 IsActive = true; 1976 Peripherals = new List<IPeripheral>(); 1977 } 1978 Add(IPeripheral peripheral)1979 public void Add(IPeripheral peripheral) 1980 { 1981 if(!Machine.IsRegistered(peripheral)) 1982 { 1983 throw new RegistrationException("Peripheral must be registered prior to adding to the group"); 1984 } 1985 ((List<IPeripheral>)Peripherals).Add(peripheral); 1986 } 1987 Contains(IPeripheral peripheral)1988 public bool Contains(IPeripheral peripheral) 1989 { 1990 return Peripherals.Contains(peripheral); 1991 } 1992 Remove(IPeripheral peripheral)1993 public void Remove(IPeripheral peripheral) 1994 { 1995 ((List<IPeripheral>)Peripherals).Remove(peripheral); 1996 } 1997 Unregister()1998 public void Unregister() 1999 { 2000 IsActive = false; 2001 using(Machine.ObtainPausedState(true)) 2002 { 2003 foreach(var p in Peripherals.ToList()) 2004 { 2005 Machine.UnregisterFromParent(p); 2006 } 2007 } 2008 ((PeripheralsGroupsManager)Machine.PeripheralsGroups).groups.Remove(this); 2009 } 2010 2011 public string Name { get; private set; } 2012 2013 public bool IsActive { get; private set; } 2014 2015 public IMachine Machine { get; private set; } 2016 2017 public IEnumerable<IPeripheral> Peripherals { get; private set; } 2018 } 2019 } 2020 } 2021 } 2022 2023