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 System.Linq; 11 using System.Threading; 12 using Antmicro.Migrant; 13 using Antmicro.Renode.Utilities; 14 15 namespace Antmicro.Renode.Time 16 { 17 public class BaseClockSource : IClockSource 18 { BaseClockSource(bool skipAdvancesHigherThanNearestLimit = false)19 public BaseClockSource(bool skipAdvancesHigherThanNearestLimit = false) 20 { 21 this.skipAdvancesHigherThanNearestLimit = skipAdvancesHigherThanNearestLimit; 22 clockEntries = new List<ClockEntry>(); 23 clockEntriesUpdateHandlers = new List<UpdateHandlerDelegate>(); 24 unaccountedTimes = new List<TimeInterval>(); 25 toNotify = new List<Action>(); 26 nearestLimitIn = TimeInterval.Maximal; 27 sync = new object(); 28 reupdateNeeded = new ThreadLocal<bool>(); 29 updateAlreadyInProgress = new ThreadLocal<bool>(); 30 } 31 32 public TimeInterval NearestLimitIn 33 { 34 get 35 { 36 lock(sync) 37 { 38 return nearestLimitIn; 39 } 40 } 41 } 42 Advance(TimeInterval time, bool immediately = false)43 public void Advance(TimeInterval time, bool immediately = false) 44 { 45 lock(sync) 46 { 47 if(time > nearestLimitIn && !skipAdvancesHigherThanNearestLimit) 48 { 49 var left = time; 50 while(left.Ticks > 0) 51 { 52 var thisTurn = TimeInterval.Min(nearestLimitIn, left); 53 left -= thisTurn; 54 AdvanceInner(thisTurn, immediately); 55 } 56 } 57 else 58 { 59 AdvanceInner(time, immediately); 60 } 61 } 62 } 63 ExecuteInLock(Action action)64 public virtual void ExecuteInLock(Action action) 65 { 66 lock(sync) 67 { 68 action(); 69 } 70 } 71 AddClockEntry(ClockEntry entry)72 public virtual void AddClockEntry(ClockEntry entry) 73 { 74 lock(sync) 75 { 76 if(clockEntries.FindIndex(x => x.Handler == entry.Handler) != -1) 77 { 78 throw new ArgumentException("A clock entry with given handler already exists in the clock source."); 79 } 80 UpdateLimits(); 81 clockEntries.Add(entry); 82 clockEntriesUpdateHandlers.Add(null); 83 unaccountedTimes.Add(TimeInterval.Empty); 84 UpdateUpdateHandler(clockEntries.Count - 1); 85 UpdateLimits(); 86 } 87 NotifyNumberOfEntriesChanged(clockEntries.Count - 1, clockEntries.Count); 88 } 89 ExchangeClockEntryWith(Action handler, Func<ClockEntry, ClockEntry> visitor, Func<ClockEntry> factoryIfNonExistent)90 public virtual void ExchangeClockEntryWith(Action handler, Func<ClockEntry, ClockEntry> visitor, 91 Func<ClockEntry> factoryIfNonExistent) 92 { 93 lock(sync) 94 { 95 UpdateLimits(); 96 var indexOfEntry = clockEntries.FindIndex(x => x.Handler == handler); 97 98 if(indexOfEntry == -1) 99 { 100 if(factoryIfNonExistent != null) 101 { 102 clockEntries.Add(factoryIfNonExistent()); 103 clockEntriesUpdateHandlers.Add(null); 104 unaccountedTimes.Add(TimeInterval.Empty); 105 UpdateUpdateHandler(clockEntries.Count - 1); 106 } 107 else 108 { 109 throw new KeyNotFoundException(); 110 } 111 } 112 else 113 { 114 clockEntries[indexOfEntry] = visitor(clockEntries[indexOfEntry]); 115 UpdateUpdateHandler(indexOfEntry); 116 } 117 UpdateLimits(); 118 } 119 } 120 GetClockEntry(Action handler)121 public virtual ClockEntry GetClockEntry(Action handler) 122 { 123 lock(sync) 124 { 125 var i = clockEntries.IndexOf(x => x.Handler == handler); 126 if(i == -1) 127 { 128 throw new KeyNotFoundException(); 129 } 130 var result = clockEntries[i]; 131 132 // Perform a full update of the clock entry we're getting 133 if(!result.Enabled) 134 { 135 return result; 136 } 137 if(updateAlreadyInProgress.Value) 138 { 139 return result; 140 } 141 updateAlreadyInProgress.Value = true; 142 try 143 { 144 var updateHandler = clockEntriesUpdateHandlers[i]; 145 if(updateHandler(ref result, elapsed + unaccountedTimes[i], ref nearestLimitIn)) 146 { 147 result.Handler(); 148 } 149 // This elapsed time is now accounted for this entry so clear it 150 unaccountedTimes[i] = TimeInterval.Empty; 151 } 152 finally 153 { 154 updateAlreadyInProgress.Value = false; 155 } 156 clockEntries[i] = result; 157 158 // Clear elapsed and deposit it as unaccounted time on all other enabled clock entries 159 var triggerFullUpdate = false; 160 for(int j = 0; j < clockEntries.Count; ++j) 161 { 162 if(i != j && clockEntries[j].Enabled) 163 { 164 unaccountedTimes[j] += elapsed; 165 triggerFullUpdate |= unaccountedTimes[j] >= nearestLimitIn; 166 } 167 } 168 elapsed = TimeInterval.Empty; 169 170 if(triggerFullUpdate) 171 { 172 UpdateLimits(); 173 // unaccountedTimes cleared by Update 174 result = clockEntries[i]; 175 } 176 177 return result; 178 } 179 } 180 GetClockEntryInLockContext(Action handler, Action<ClockEntry> visitor)181 public virtual void GetClockEntryInLockContext(Action handler, Action<ClockEntry> visitor) 182 { 183 lock(sync) 184 { 185 UpdateLimits(); 186 var result = clockEntries.FirstOrDefault(x => x.Handler == handler); 187 if(result.Handler == null) 188 { 189 throw new KeyNotFoundException(); 190 } 191 visitor(result); 192 } 193 } 194 GetAllClockEntries()195 public IEnumerable<ClockEntry> GetAllClockEntries() 196 { 197 lock(sync) 198 { 199 UpdateLimits(); 200 return clockEntries.ToList(); 201 } 202 } 203 TryRemoveClockEntry(Action handler)204 public virtual bool TryRemoveClockEntry(Action handler) 205 { 206 int oldCount; 207 lock(sync) 208 { 209 oldCount = clockEntries.Count; 210 var indexToRemove = clockEntries.FindIndex(x => x.Handler == handler); 211 if(indexToRemove == -1) 212 { 213 return false; 214 } 215 UpdateLimits(); 216 clockEntries.RemoveAt(indexToRemove); 217 clockEntriesUpdateHandlers.RemoveAt(indexToRemove); 218 unaccountedTimes.RemoveAt(indexToRemove); 219 UpdateLimits(); 220 } 221 NotifyNumberOfEntriesChanged(oldCount, clockEntries.Count); 222 return true; 223 } 224 225 public virtual TimeInterval CurrentValue 226 { 227 get 228 { 229 return totalElapsed; 230 } 231 } 232 EjectClockEntries()233 public virtual IEnumerable<ClockEntry> EjectClockEntries() 234 { 235 int oldCount; 236 IEnumerable<ClockEntry> result; 237 lock(sync) 238 { 239 oldCount = clockEntries.Count; 240 result = clockEntries.ToArray(); 241 clockEntries.Clear(); 242 clockEntriesUpdateHandlers.Clear(); 243 unaccountedTimes.Clear(); 244 } 245 NotifyNumberOfEntriesChanged(oldCount, 0); 246 return result; 247 } 248 AddClockEntries(IEnumerable<ClockEntry> entries)249 public void AddClockEntries(IEnumerable<ClockEntry> entries) 250 { 251 lock(sync) 252 { 253 foreach(var entry in entries) 254 { 255 AddClockEntry(entry); 256 } 257 } 258 } 259 260 public bool HasEntries 261 { 262 get 263 { 264 lock(sync) 265 { 266 return clockEntries.Count > 0; 267 } 268 } 269 } 270 271 public event Action<int, int> NumberOfEntriesChanged; 272 HandleDirectionDescendingPositiveRatio(ref ClockEntry entry, TimeInterval time, ref TimeInterval nearestTickIn)273 private static bool HandleDirectionDescendingPositiveRatio(ref ClockEntry entry, TimeInterval time, ref TimeInterval nearestTickIn) 274 { 275 var emulatorTicks = time.Ticks; 276 var entryTicks = emulatorTicks * entry.Ratio + entry.ValueResiduum; 277 var isReached = entryTicks.Integer >= entry.Value; 278 entry.ValueResiduum = entryTicks.Fractional; 279 if(isReached) 280 { 281 entry.Value = entry.Period; 282 entry.ValueResiduum = Fraction.Zero; 283 entry = entry.With(enabled: entry.Enabled & (entry.WorkMode != WorkMode.OneShot)); 284 } 285 else 286 { 287 entry.Value -= entryTicks.Integer; 288 } 289 290 var emulatorTicksToLimit = (entry.Value - entry.ValueResiduum) / entry.Ratio; 291 var wholeTicksToLimit = emulatorTicksToLimit.Integer; 292 if(wholeTicksToLimit < uint.MaxValue && emulatorTicksToLimit.Fractional.Numerator != 0) 293 { 294 wholeTicksToLimit += 1; 295 } 296 nearestTickIn = nearestTickIn.WithTicksMin(wholeTicksToLimit); 297 return isReached; 298 } 299 HandleDirectionAscendingPositiveRatio(ref ClockEntry entry, TimeInterval time, ref TimeInterval nearestTickIn)300 private static bool HandleDirectionAscendingPositiveRatio(ref ClockEntry entry, TimeInterval time, ref TimeInterval nearestTickIn) 301 { 302 var emulatorTicks = time.Ticks; 303 var entryTicks = emulatorTicks * entry.Ratio + entry.ValueResiduum; 304 var isReached = false; 305 entry.Value += entryTicks.Integer; 306 entry.ValueResiduum = entryTicks.Fractional; 307 308 if(entry.Value >= entry.Period) 309 { 310 isReached = true; 311 entry.Value = 0; 312 entry.ValueResiduum = Fraction.Zero; 313 entry = entry.With(enabled: entry.Enabled & (entry.WorkMode != WorkMode.OneShot)); 314 } 315 316 var emulatorTicksToLimit = (entry.Period - entry.Value - entry.ValueResiduum) / entry.Ratio; 317 var wholeTicksToLimit = emulatorTicksToLimit.Integer; 318 if(wholeTicksToLimit < uint.MaxValue && emulatorTicksToLimit.Fractional.Numerator != 0) 319 { 320 wholeTicksToLimit += 1; 321 } 322 nearestTickIn = nearestTickIn.WithTicksMin(wholeTicksToLimit); 323 return isReached; 324 } 325 AdvanceInner(TimeInterval time, bool immediately)326 private void AdvanceInner(TimeInterval time, bool immediately) 327 { 328 lock(sync) 329 { 330 #if DEBUG 331 if(time > nearestLimitIn && !skipAdvancesHigherThanNearestLimit) 332 { 333 throw new InvalidOperationException("Should not reach here."); 334 } 335 #endif 336 elapsed += time; 337 totalElapsed += time; 338 if(nearestLimitIn > time && !immediately) 339 { 340 // nothing happens 341 nearestLimitIn -= time; 342 return; 343 } 344 345 if(updateAlreadyInProgress.Value) 346 { 347 reupdateNeeded.Value = true; 348 } 349 else 350 { 351 var alreadyRunHandlers = new List<Action>(); 352 Update(elapsed, ref alreadyRunHandlers); 353 // Check if another update was attempted in the meantime, e.g., a clock entry was updated within the handlers. 354 while(reupdateNeeded.Value) 355 { 356 reupdateNeeded.Value = false; 357 Update(TimeInterval.Empty, ref alreadyRunHandlers); 358 } 359 } 360 361 elapsed = TimeInterval.Empty; 362 } 363 } 364 NotifyNumberOfEntriesChanged(int oldValue, int newValue)365 private void NotifyNumberOfEntriesChanged(int oldValue, int newValue) 366 { 367 var numberOfEntriesChanged = NumberOfEntriesChanged; 368 if(numberOfEntriesChanged != null) 369 { 370 numberOfEntriesChanged(oldValue, newValue); 371 } 372 } 373 UpdateLimits()374 private void UpdateLimits() 375 { 376 AdvanceInner(TimeInterval.Empty, true); 377 } 378 Update(TimeInterval time, ref List<Action> alreadyRunHandlers)379 private void Update(TimeInterval time, ref List<Action> alreadyRunHandlers) 380 { 381 if(updateAlreadyInProgress.Value) 382 { 383 return; 384 } 385 try 386 { 387 updateAlreadyInProgress.Value = true; 388 lock(sync) 389 { 390 nearestLimitIn = TimeInterval.Maximal; 391 for(var i = 0; i < clockEntries.Count; i++) 392 { 393 var clockEntry = clockEntries[i]; 394 var updateHandler = clockEntriesUpdateHandlers[i]; 395 if(!clockEntry.Enabled) 396 { 397 continue; 398 } 399 if(updateHandler(ref clockEntry, time + unaccountedTimes[i], ref nearestLimitIn) && !alreadyRunHandlers.Contains(clockEntry.Handler)) 400 { 401 toNotify.Add(clockEntry.Handler); 402 } 403 clockEntries[i] = clockEntry; 404 unaccountedTimes[i] = TimeInterval.Empty; 405 } 406 } 407 try 408 { 409 foreach(var action in toNotify) 410 { 411 action(); 412 alreadyRunHandlers.Add(action); 413 } 414 } 415 finally 416 { 417 toNotify.Clear(); 418 } 419 } 420 finally 421 { 422 updateAlreadyInProgress.Value = false; 423 } 424 } 425 UpdateUpdateHandler(int clockEntryIndex)426 private void UpdateUpdateHandler(int clockEntryIndex) 427 { 428 if(clockEntries[clockEntryIndex].Direction == Direction.Descending) 429 { 430 clockEntriesUpdateHandlers[clockEntryIndex] = HandleDirectionDescendingPositiveRatio; 431 } 432 else 433 { 434 clockEntriesUpdateHandlers[clockEntryIndex] = HandleDirectionAscendingPositiveRatio; 435 } 436 } 437 438 [Constructor] 439 private ThreadLocal<bool> reupdateNeeded; 440 [Constructor] 441 private ThreadLocal<bool> updateAlreadyInProgress; 442 443 private TimeInterval nearestLimitIn; 444 private TimeInterval elapsed; 445 private TimeInterval totalElapsed; 446 private readonly bool skipAdvancesHigherThanNearestLimit; 447 private readonly List<Action> toNotify; 448 private readonly List<ClockEntry> clockEntries; 449 private readonly List<UpdateHandlerDelegate> clockEntriesUpdateHandlers; 450 private readonly List<TimeInterval> unaccountedTimes; 451 private readonly object sync; 452 UpdateHandlerDelegate(ref ClockEntry entry, TimeInterval time, ref TimeInterval nearestTickIn)453 private delegate bool UpdateHandlerDelegate(ref ClockEntry entry, TimeInterval time, ref TimeInterval nearestTickIn); 454 } 455 } 456 457