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