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