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 
9 using System;
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Time;
12 using Antmicro.Renode.Exceptions;
13 using Antmicro.Renode.Peripherals.CPU;
14 
15 namespace Antmicro.Renode.Peripherals.Timers
16 {
17     public class LimitTimer : ITimer, IPeripheral
18     {
LimitTimer(IClockSource clockSource, long frequency, IPeripheral owner, string localName, ulong limit = ulong.MaxValue, Direction direction = Direction.Descending, bool enabled = false, WorkMode workMode = WorkMode.Periodic, bool eventEnabled = false, bool autoUpdate = false, int divider = 1)19         public LimitTimer(IClockSource clockSource, long frequency, IPeripheral owner, string localName, ulong limit = ulong.MaxValue, Direction direction = Direction.Descending, bool enabled = false, WorkMode workMode = WorkMode.Periodic, bool eventEnabled = false, bool autoUpdate = false, int divider = 1)
20         {
21             if(limit <= 0)
22             {
23                 throw new ConstructionException("Limit must be greater than 0");
24             }
25             if(divider <= 0)
26             {
27                 throw new ConstructionException("Divider must be greater than 0");
28             }
29             if(frequency <= 0)
30             {
31                 throw new ConstructionException("Frequency must be greater than 0");
32             }
33 
34             irqSync = new object();
35             this.clockSource = clockSource;
36 
37             initialFrequency = frequency;
38             initialLimit = limit;
39             initialDirection = direction;
40             initialEnabled = enabled;
41             initialWorkMode = workMode;
42             initialEventEnabled = eventEnabled;
43             initialAutoUpdate = autoUpdate;
44             initialDivider = divider;
45             this.owner = this is IPeripheral && owner == null ? this : owner;
46             this.localName = localName;
47             InternalReset();
48         }
49 
LimitTimer(IClockSource clockSource, long frequency, ulong limit = ulong.MaxValue, Direction direction = Direction.Descending, bool enabled = false, WorkMode workMode = WorkMode.Periodic, bool eventEnabled = false, bool autoUpdate = false, int divider = 1)50         protected LimitTimer(IClockSource clockSource, long frequency, ulong limit = ulong.MaxValue, Direction direction = Direction.Descending, bool enabled = false, WorkMode workMode = WorkMode.Periodic, bool eventEnabled = false, bool autoUpdate = false, int divider = 1)
51             : this(clockSource, frequency, null, null, limit, direction, enabled, workMode, eventEnabled, autoUpdate, divider)
52         {
53         }
54 
GetValueAndLimit(out ulong currentLimit)55         public ulong GetValueAndLimit(out ulong currentLimit)
56         {
57             var clockEntry = clockSource.GetClockEntry(OnLimitReached);
58             currentLimit = clockEntry.Period;
59             return clockEntry.Value;
60         }
61 
Increment(ulong incrementBy)62         public uint Increment(ulong incrementBy)
63         {
64             var incValue = Value + incrementBy;
65 
66             Value = incValue % Limit;
67 
68             return (uint)(incValue / Limit);
69         }
70 
Decrement(ulong decrementBy)71         public uint Decrement(ulong decrementBy)
72         {
73             var timesOverflown = (uint)(((Limit - 1 - Value) + decrementBy) / Limit);
74 
75             Value = Value + (Limit * timesOverflown) - decrementBy;
76 
77             return timesOverflown;
78         }
79 
80         public long Frequency
81         {
82             get
83             {
84                 return frequency;
85             }
86             set
87             {
88                 if(value <= 0)
89                 {
90                     throw new ArgumentException("Frequency must be greater than 0");
91                 }
92                 frequency = value;
93                 var effectiveFrequency = frequency / Divider;
94                 clockSource.ExchangeClockEntryWith(OnLimitReached, oldEntry => oldEntry.With(frequency: effectiveFrequency));
95 
96                 RequestReturnOnCurrentCpu();
97             }
98         }
99 
100         public ulong Value
101         {
102             get
103             {
104                 return clockSource.GetClockEntry(OnLimitReached).Value;
105             }
106             set
107             {
108                 if(value > initialLimit)
109                 {
110                     throw new ArgumentException("Value cannot be larger than limit");
111                 }
112 
113                 clockSource.ExchangeClockEntryWith(OnLimitReached, oldEntry => oldEntry.With(value: value));
114 
115                 RequestReturnOnCurrentCpu();
116             }
117         }
118 
119         public WorkMode Mode
120         {
121             get
122             {
123                 return clockSource.GetClockEntry(OnLimitReached).WorkMode;
124             }
125             set
126             {
127                 clockSource.ExchangeClockEntryWith(OnLimitReached, oldEntry => oldEntry.With(workMode: value));
128             }
129         }
130 
131         public int Divider
132         {
133             get
134             {
135                 return divider;
136             }
137             set
138             {
139                 if(value == divider)
140                 {
141                     return;
142                 }
143                 if(value <= 0)
144                 {
145                     throw new ArgumentException("Divider must be greater than 0");
146                 }
147                 divider = value;
148                 var effectiveFrequency = Frequency / divider;
149                 clockSource.ExchangeClockEntryWith(OnLimitReached, oldEntry => oldEntry.With(frequency: effectiveFrequency));
150 
151                 RequestReturnOnCurrentCpu();
152             }
153         }
154 
155         public ulong Limit
156         {
157             get
158             {
159                 return clockSource.GetClockEntry(OnLimitReached).Period;
160             }
161             set
162             {
163                 clockSource.ExchangeClockEntryWith(OnLimitReached, oldEntry =>
164                 {
165                     if(AutoUpdate)
166                     {
167                         return oldEntry.With(period: value, value: oldEntry.Direction == Direction.Ascending ? 0 : value);
168                     }
169 
170                     return oldEntry.With(period: value);
171                 }, () =>
172                 {
173                     throw new InvalidOperationException("Should not reach here.");
174                 });
175 
176                 RequestReturnOnCurrentCpu();
177             }
178         }
179 
180         public bool AutoUpdate { get; set; }
181 
182         public bool Enabled
183         {
184             get
185             {
186                 return clockSource.GetClockEntry(OnLimitReached).Enabled;
187             }
188             set
189             {
190                 clockSource.ExchangeClockEntryWith(OnLimitReached, oldEntry => oldEntry.With(enabled: value),
191                     () => { throw new InvalidOperationException("Should not reach here."); });
192                     // should not reach here - limit should already be set in ctor
193 
194                 RequestReturnOnCurrentCpu();
195             }
196         }
197 
198         public bool EventEnabled
199         {
200             get
201             {
202                 lock(irqSync)
203                 {
204                     return eventEnabled;
205                 }
206             }
207             set
208             {
209                 lock(irqSync)
210                 {
211                     eventEnabled = value;
212                 }
213             }
214         }
215 
216         public bool Interrupt
217         {
218             get
219             {
220                 lock(irqSync)
221                 {
222                     return rawInterrupt && eventEnabled;
223                 }
224             }
225         }
226 
227         public bool RawInterrupt
228         {
229             get
230             {
231                 lock(irqSync)
232                 {
233                     return rawInterrupt;
234                 }
235             }
236         }
237 
ClearInterrupt()238         public void ClearInterrupt()
239         {
240             lock(irqSync)
241             {
242                 rawInterrupt = false;
243             }
244         }
245 
ResetValue()246         public void ResetValue()
247         {
248             clockSource.ExchangeClockEntryWith(OnLimitReached, oldEntry =>
249             {
250                     if(oldEntry.Direction == Direction.Ascending)
251                     {
252                         return oldEntry.With(value: 0);
253                     }
254                     return oldEntry.With(value: oldEntry.Period);
255             });
256 
257             RequestReturnOnCurrentCpu();
258         }
259 
260         public Direction Direction
261         {
262             get
263             {
264                 return clockSource.GetClockEntry(OnLimitReached).Direction;
265             }
266             set
267             {
268                 clockSource.ExchangeClockEntryWith(OnLimitReached, oldEntry => oldEntry.With(direction: value),
269                     () =>
270                     {
271                         throw new InvalidOperationException("Should not reach here.");
272                     });
273 
274                 RequestReturnOnCurrentCpu();
275             }
276         }
277 
Reset()278         public virtual void Reset()
279         {
280             InternalReset();
281         }
282 
283         public event Action LimitReached;
284 
285         // Should be used with caution as that clears all the subscriptions from all the sources.
286         // Consequences within wider context may be difficult to predict
ClearSubscriptions()287         protected void ClearSubscriptions()
288         {
289             LimitReached = null;
290         }
291 
OnLimitReached()292         protected virtual void OnLimitReached()
293         {
294             lock(irqSync)
295             {
296                 rawInterrupt = true;
297 
298                 if (!eventEnabled)
299                 {
300                     return;
301                 }
302 
303                 var alarm = LimitReached;
304                 if(alarm != null)
305                 {
306                     alarm();
307                 }
308             }
309         }
310 
InternalReset()311         private void InternalReset()
312         {
313             frequency = initialFrequency;
314             divider = initialDivider;
315 
316             var clockEntry = new ClockEntry(initialLimit,  frequency / divider, OnLimitReached, owner, localName, initialEnabled, initialDirection, initialWorkMode)
317                 { Value = initialDirection == Direction.Ascending ? 0 : initialLimit };
318 
319             clockSource.ExchangeClockEntryWith(OnLimitReached, x => clockEntry, () => clockEntry);
320             EventEnabled = initialEventEnabled;
321             AutoUpdate = initialAutoUpdate;
322             rawInterrupt = false;
323         }
324 
RequestReturnOnCurrentCpu()325         private void RequestReturnOnCurrentCpu()
326         {
327             if(EmulationManager.Instance.CurrentEmulation.TryGetExecutionContext(out var machine, out var cpu))
328             {
329                 (cpu as IControllableCPU)?.RequestReturn();
330             }
331         }
332 
333         private readonly long initialFrequency;
334         private readonly WorkMode initialWorkMode;
335         private readonly ulong initialLimit;
336         private readonly Direction initialDirection;
337         private readonly bool initialEnabled;
338         private readonly IClockSource clockSource;
339         private readonly object irqSync;
340         private readonly bool initialEventEnabled;
341         private readonly bool initialAutoUpdate;
342         private readonly int initialDivider;
343         private readonly IPeripheral owner;
344         private readonly string localName;
345 
346         private bool eventEnabled;
347         private bool rawInterrupt;
348         private long frequency;
349         private int divider;
350     }
351 }
352 
353