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