1 // 2 // Copyright (c) 2010-2022 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 using System; 8 using System.Threading; 9 using Antmicro.Migrant; 10 using Antmicro.Renode.Core; 11 using Antmicro.Renode.Logging; 12 using Antmicro.Renode.Utilities; 13 using Antmicro.Renode.Debugging; 14 15 namespace Antmicro.Renode.Time 16 { 17 /// <summary> 18 /// Represents an intermediate entity that distribute interval granted from master time sources to other sinks. 19 /// </summary> 20 public class SlaveTimeSource : TimeSourceBase, ITimeSource, ITimeSink, IDisposable 21 { 22 /// <summary> 23 /// Creates a new instance of <see cref="SlaveTimeSource">. 24 /// </summary> SlaveTimeSource()25 public SlaveTimeSource() 26 { 27 locker = new object(); 28 TimePassed += HandleTimePassed; 29 } 30 31 /// <summary> 32 /// Disposes this instance. 33 /// </summary> Dispose()34 public override void Dispose() 35 { 36 this.Trace("Disposing..."); 37 base.Dispose(); 38 base.Stop(); 39 lock(locker) 40 { 41 TimeHandle?.Dispose(); 42 } 43 StopDispatcher(); 44 this.Trace("Disposed"); 45 } 46 47 /// <summary> 48 /// Pauses the execution of the time source. It can be resumed using <see cref="Resume"> method. 49 /// </summary> Pause()50 public void Pause() 51 { 52 lock(locker) 53 { 54 this.Trace("Pausing..."); 55 if(!isStarted) 56 { 57 this.Trace(); 58 return; 59 } 60 if(isPaused) 61 { 62 this.Trace(); 63 return; 64 } 65 RequestStop(); 66 using(sync.HighPriority) 67 { 68 stopwatch.Stop(); 69 isPaused = true; 70 DeactivateSlavesSourceSide(); 71 } 72 this.Trace("Paused"); 73 } 74 } 75 76 /// <summary> 77 /// Resumes execution of the time source. 78 /// </summary> Resume()79 public void Resume() 80 { 81 this.Trace("Resuming..."); 82 lock(locker) 83 { 84 using(sync.HighPriority) 85 { 86 ActivateSlavesSourceSide(); 87 isPaused = false; 88 stopwatch.Start(); 89 } 90 } 91 this.Trace("Resumed"); 92 } 93 94 /// <see cref="ITimeSource.Domain"> 95 /// <remarks> 96 /// If this slave is not attached to any master time source, the domain is null. 97 /// </remarks> 98 public override ITimeDomain Domain { get { return timeHandle?.TimeSource.Domain; } } 99 100 /// <see cref="ITimeSink.TimeHandle"> 101 /// <remarks> 102 /// If this time source is already connected to a master, old handle is disposed before accepting the new one. 103 /// </remarks> 104 public TimeHandle TimeHandle 105 { 106 get 107 { 108 return timeHandle; 109 } 110 set 111 { 112 lock(locker) 113 { 114 StopDispatcher(); 115 TimeHandle?.Dispose(); 116 this.Trace("About to attach to the new master"); 117 timeHandle = value; 118 timeHandle.PauseRequested += RequestStop; 119 timeHandle.StartRequested += HandleStartRequest; 120 ResetVirtualTime(timeHandle.TotalElapsedTime); 121 StartDispatcher(); 122 } 123 } 124 } 125 HandleStartRequest()126 private void HandleStartRequest() 127 { 128 this.Trace(); 129 lock(locker) 130 { 131 this.Trace(); 132 if(dispatcherThread == null) 133 { 134 this.Trace(); 135 // if the dispatcher is not started yet - start it 136 StartDispatcher(); 137 } 138 else 139 { 140 this.Trace(); 141 // if the dispatcher is already running - set the restart flag 142 dispatcherStartRequested = true; 143 } 144 } 145 } 146 147 /// <summary> 148 /// Provides the implementation of time-distribution among slaves. 149 /// </summary> Dispatch()150 private void Dispatch() 151 { 152 var isLocked = false; 153 try 154 { 155 #if DEBUG 156 using(this.TraceRegion("Dispatcher loop")) 157 #endif 158 using(this.ObtainSourceActiveState()) 159 using(this.ObtainSinkActiveState()) 160 using(TimeDomainsManager.Instance.RegisterCurrentThread(() => new TimeStamp(TimeHandle.TimeSource.NearestSyncPoint, TimeHandle.TimeSource.Domain))) 161 { 162 while(true) 163 { 164 try 165 { 166 while(isStarted) 167 { 168 WaitIfBlocked(); 169 if(!DispatchInner()) 170 { 171 break; 172 } 173 } 174 } 175 catch(Exception e) 176 { 177 this.Trace(LogLevel.Error, $"Got an exception: {e.Message} {e.StackTrace}"); 178 throw; 179 } 180 181 lock(locker) 182 { 183 if(!dispatcherStartRequested) 184 { 185 this.Trace(); 186 // the `locker` is re-acquired here to 187 // make sure that dispose-related code of all usings 188 // is executed before setting `dispatcherThread` to 189 // null (what allows to start new dispatcher thread); 190 // otherwise there could be a race condition when 191 // new thread enters usings (e.g., activates source side) 192 // and then the old one exits them (deactivating source 193 // side as a result) 194 Monitor.Enter(locker, ref isLocked); 195 break; 196 } 197 198 dispatcherStartRequested = false; 199 this.Trace(); 200 } 201 } 202 } 203 } 204 finally 205 { 206 dispatcherThread = null; 207 if(isLocked) 208 { 209 this.Trace(); 210 Monitor.Exit(locker); 211 } 212 this.Trace(); 213 } 214 } 215 DispatchInner()216 private bool DispatchInner() 217 { 218 if(!TimeHandle.RequestTimeInterval(out var intervalGranted)) 219 { 220 this.Trace("Time interval request interrupted"); 221 return false; 222 } 223 224 if(isPaused) 225 { 226 this.Trace("Handle paused"); 227 TimeHandle.ReportBackAndBreak(intervalGranted); 228 return true; 229 } 230 231 var quantum = Quantum; 232 this.Trace($"Current QUANTUM is {quantum.Ticks} ticks"); 233 var timeLeft = intervalGranted; 234 235 while(waitingForSlave || (timeLeft >= quantum && isStarted)) 236 { 237 waitingForSlave = false; 238 var syncPointReached = InnerExecute(out var elapsed); 239 timeLeft -= elapsed; 240 if(!syncPointReached) 241 { 242 // we should not ask for time grant since the current one is not finished yet 243 waitingForSlave = true; 244 TimeHandle.ReportBackAndBreak(timeLeft); 245 return true; 246 } 247 } 248 249 TimeHandle.ReportBackAndContinue(timeLeft); 250 return true; 251 } 252 StartDispatcher()253 private void StartDispatcher() 254 { 255 lock(locker) 256 { 257 if(dispatcherThread != null || TimeHandle == null) 258 { 259 return; 260 } 261 262 isStarted = true; 263 dispatcherThread = new Thread(Dispatch) { IsBackground = true, Name = "SlaveTimeSource Dispatcher Thread" }; 264 dispatcherThread.Start(); 265 } 266 } 267 StopDispatcher()268 private void StopDispatcher() 269 { 270 Thread threadToJoin = null; 271 lock(locker) 272 { 273 isStarted = false; 274 if(dispatcherThread != null && Thread.CurrentThread.ManagedThreadId != dispatcherThread.ManagedThreadId) 275 { 276 threadToJoin = dispatcherThread; 277 } 278 } 279 threadToJoin?.Join(); 280 } 281 HandleTimePassed(TimeInterval diff)282 private void HandleTimePassed(TimeInterval diff) 283 { 284 TimeHandle?.ReportProgress(diff); 285 } 286 287 [Transient] 288 private Thread dispatcherThread; 289 private TimeHandle timeHandle; 290 private bool waitingForSlave; 291 private bool dispatcherStartRequested; 292 private readonly object locker; 293 } 294 } 295