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