1 // 2 // Copyright (c) 2010-2023 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.Collections.Generic; 9 using System.Collections.ObjectModel; 10 using System.Linq; 11 using Antmicro.Renode.Core; 12 using Antmicro.Renode.Core.Structure.Registers; 13 using Antmicro.Renode.Exceptions; 14 using Antmicro.Renode.Peripherals.CPU; 15 using Antmicro.Renode.Peripherals.IRQControllers; 16 using Antmicro.Renode.Time; 17 using Antmicro.Renode.Utilities; 18 19 namespace Antmicro.Renode.Peripherals.Timers 20 { 21 public class ARM_GlobalTimer : BasicDoubleWordPeripheral, INumberedGPIOOutput, IKnownSize 22 { ARM_GlobalTimer(IMachine machine, long frequency, IARMCPUsConnectionsProvider irqController)23 public ARM_GlobalTimer(IMachine machine, long frequency, IARMCPUsConnectionsProvider irqController) 24 : base(machine) 25 { 26 BuildRegisters(); 27 globalTimer = new LimitTimer(machine.ClockSource, frequency, this, "coreTimer", direction: Direction.Ascending); 28 connections = new Dictionary<int, IGPIO>(); 29 comparators = new Dictionary<ICPU, PrivateComparator>(); 30 lock(locker) 31 { 32 irqController.CPUAttached += AddCPU; 33 foreach(var cpu in irqController.AttachedCPUs) 34 { 35 AddCPU(cpu); 36 } 37 } 38 } 39 40 public long Size => 0x100; 41 42 public IReadOnlyDictionary<int, IGPIO> Connections 43 { 44 get 45 { 46 lock(locker) 47 { 48 connectionsLocked = true; 49 return connections; 50 } 51 } 52 } 53 Reset()54 public override void Reset() 55 { 56 lock(locker) 57 { 58 base.Reset(); 59 foreach(var cmp in comparators.Values) 60 { 61 cmp.Reset(); 62 } 63 } 64 } 65 ReadDoubleWord(long offset)66 public override uint ReadDoubleWord(long offset) 67 { 68 lock(locker) 69 { 70 return base.ReadDoubleWord(offset); 71 } 72 } 73 WriteDoubleWord(long offset, uint value)74 public override void WriteDoubleWord(long offset, uint value) 75 { 76 lock(locker) 77 { 78 base.WriteDoubleWord(offset, value); 79 } 80 } 81 ReadDoubleWord(long offset, ICPU cpu)82 public uint ReadDoubleWord(long offset, ICPU cpu) 83 { 84 lock(locker) 85 { 86 providedCpu = cpu; 87 var value = base.ReadDoubleWord(offset); 88 providedCpu = null; 89 return value; 90 } 91 } 92 WriteDoubleWord(long offset, uint value, ICPU cpu)93 public void WriteDoubleWord(long offset, uint value, ICPU cpu) 94 { 95 lock(locker) 96 { 97 providedCpu = cpu; 98 base.WriteDoubleWord(offset, value); 99 providedCpu = null; 100 } 101 } 102 BuildRegisters()103 private void BuildRegisters() 104 { 105 Registers.CounterLow.Define(this) 106 .WithValueField(0, 32, name: "Counter [0:31]", 107 writeCallback: (_, value) => CounterLow = value, 108 valueProviderCallback: (_) => CounterLow 109 ) 110 ; 111 Registers.CounterHigh.Define(this) 112 .WithValueField(0, 32, name: "Counter [32:63]", 113 writeCallback: (_, value) => CounterHigh = value, 114 valueProviderCallback: (_) => CounterHigh 115 ) 116 ; 117 Registers.Control.Define(this) 118 .WithFlag(0, name: "Timer enable", 119 writeCallback: (_, value) => Enabled = value, 120 valueProviderCallback: (_) => Enabled 121 ) 122 .WithFlag(1, name: "Comp enable", 123 writeCallback: (_, value) => GetComparator().ComparatorEnabled = value, 124 valueProviderCallback: (_) => GetComparator().ComparatorEnabled 125 ) 126 .WithFlag(2, name: "IRQ enable", 127 writeCallback: (_, value) => GetComparator().IrqEnabled = value, 128 valueProviderCallback: (_) => GetComparator().IrqEnabled 129 ) 130 .WithFlag(3, name: "Auto-increment", 131 writeCallback: (_, value) => GetComparator().AutoIncrementEnabled = value, 132 valueProviderCallback: (_) => GetComparator().AutoIncrementEnabled 133 ) 134 .WithReservedBits(4, 4) 135 .WithValueField(8, 8, name: "Prescaler", 136 writeCallback: (_, value) => Prescaler = value, 137 valueProviderCallback: (_) => Prescaler 138 ) 139 .WithReservedBits(16, 16) 140 ; 141 Registers.InterruptStatus.Define(this) 142 .WithFlag(0, name: "Event", 143 writeCallback: (_, value) => GetComparator().EventFlag = value, 144 valueProviderCallback: (_) => GetComparator().EventFlag 145 ) 146 .WithReservedBits(1, 31) 147 ; 148 Registers.CompareValueLow.Define(this) 149 .WithValueField(0, 32, name: "Comparator Value [0:31]", 150 writeCallback: (_, value) => GetComparator().CompareValueLow = value, 151 valueProviderCallback: (_) => GetComparator().CompareValueLow 152 ) 153 ; 154 Registers.CompareValueHigh.Define(this) 155 .WithValueField(0, 32, name: "Comparator Value [32:63]", 156 writeCallback: (_, value) => GetComparator().CompareValueHigh = value, 157 valueProviderCallback: (_) => GetComparator().CompareValueHigh 158 ) 159 ; 160 Registers.AutoIncrement.Define(this) 161 .WithValueField(0, 32, name: "Auto-increment", 162 writeCallback: (_, value) => GetComparator().AutoIncrement = value, 163 valueProviderCallback: (_) => GetComparator().AutoIncrement 164 ) 165 ; 166 } 167 AddCPU(ICPU cpu)168 private void AddCPU(ICPU cpu) 169 { 170 lock(locker) 171 { 172 if(connectionsLocked) 173 { 174 throw new RecoverableException($"CPU (connection #{cpu.MultiprocessingId}) attached to IRQ Controller after Global Timer's GPIO initialization"); 175 } 176 var comparator = new PrivateComparator(machine.ClockSource, globalTimer, this, $"{cpu.MultiprocessingId}"); 177 connections.Add((int)cpu.MultiprocessingId, comparator.IRQ); 178 comparators.Add(cpu, comparator); 179 } 180 } 181 GetComparator(ICPU cpu = null)182 private PrivateComparator GetComparator(ICPU cpu = null) 183 { 184 cpu = cpu ?? providedCpu; 185 if(cpu == null && !sysbus.TryGetCurrentCPU(out cpu)) 186 { 187 throw new RecoverableException("Attempted to access a core specific feature, but no CPU is selected nor detected"); 188 } 189 if(comparators.TryGetValue(cpu, out var cmp)) 190 { 191 return cmp; 192 } 193 throw new RecoverableException($"Detected CPU {machine.GetLocalName(cpu)} is not connected to this peripheral"); 194 } 195 196 private ulong CounterLow 197 { 198 get => (uint)Counter; 199 set => Counter = BitHelper.SetMaskedValue(Counter, value, 0, 32); 200 } 201 202 private ulong CounterHigh 203 { 204 get => (uint)(Counter >> 32); 205 set => Counter = BitHelper.SetMaskedValue(Counter, value, 32, 32); 206 } 207 208 private ulong Counter 209 { 210 get 211 { 212 var cpu = providedCpu; 213 if(cpu != null || sysbus.TryGetCurrentCPU(out cpu)) 214 { 215 cpu.SyncTime(); 216 } 217 return globalTimer.Value; 218 } 219 set 220 { 221 if(globalTimer.Value == value) 222 { 223 return; 224 } 225 globalTimer.Value = value; 226 foreach(var cmp in comparators.Values) 227 { 228 cmp.Value = value; 229 } 230 } 231 } 232 233 private bool Enabled 234 { 235 get => globalTimer.Enabled; 236 set 237 { 238 if(globalTimer.Enabled == value) 239 { 240 return; 241 } 242 globalTimer.Enabled = value; 243 foreach(var cmp in comparators.Values) 244 { 245 cmp.Enabled = value; 246 } 247 } 248 } 249 250 private ulong Prescaler 251 { 252 get => (ulong)globalTimer.Divider; 253 set 254 { 255 if((ulong)globalTimer.Divider == value + 1) 256 { 257 return; 258 } 259 globalTimer.Divider = (int)value + 1; 260 foreach(var cmp in comparators.Values) 261 { 262 cmp.Divider = (uint)value + 1; 263 } 264 } 265 } 266 267 private bool connectionsLocked; 268 private ICPU providedCpu; 269 private readonly LimitTimer globalTimer; 270 private readonly Dictionary<ICPU, PrivateComparator> comparators; 271 private readonly Dictionary<int, IGPIO> connections; 272 private readonly object locker = new Object(); 273 274 public enum Registers 275 { 276 CounterLow = 0x00, 277 CounterHigh = 0x04, 278 Control = 0x08, 279 InterruptStatus = 0x0C, 280 CompareValueLow = 0x10, 281 CompareValueHigh = 0x14, 282 AutoIncrement = 0x18, 283 } 284 285 private class PrivateComparator 286 { PrivateComparator(IClockSource clockSource, LimitTimer coreTimer, IPeripheral owner, string coreName)287 public PrivateComparator(IClockSource clockSource, LimitTimer coreTimer, IPeripheral owner, string coreName) 288 { 289 innerTimer = new ComparingTimer(clockSource, coreTimer.Frequency, owner, $"compareTimer-{coreName}", direction: Direction.Ascending, compare: 0, workMode: WorkMode.Periodic); 290 innerTimer.Value = coreTimer.Value; 291 innerTimer.Enabled = coreTimer.Enabled; 292 innerTimer.Divider = (uint)coreTimer.Divider; 293 innerTimer.CompareReached += HandleCompareEvent; 294 IRQ = new GPIO(); 295 } 296 Reset()297 public void Reset() 298 { 299 innerTimer.Reset(); 300 eventFlag = false; 301 irqEnabled = false; 302 UpdateInterrupt(); 303 } 304 305 public IGPIO IRQ { get; } 306 307 public ulong CompareValueLow 308 { 309 get => (uint)innerTimer.Compare; 310 set 311 { 312 innerTimer.Compare = BitHelper.SetMaskedValue(innerTimer.Compare, value, 0, 32); 313 UpdateEventFlag(); 314 UpdateInterrupt(); 315 } 316 } 317 318 public ulong CompareValueHigh 319 { 320 get => (uint)(innerTimer.Compare >> 32); 321 set 322 { 323 innerTimer.Compare = BitHelper.SetMaskedValue(innerTimer.Compare, value, 32, 32); 324 UpdateEventFlag(); 325 UpdateInterrupt(); 326 } 327 } 328 329 public ulong AutoIncrement { get; set; } 330 331 public bool EventFlag 332 { 333 get => eventFlag; 334 set 335 { 336 if(!value) 337 { 338 return; 339 } 340 eventFlag = false; 341 UpdateEventFlag(); 342 UpdateInterrupt(); 343 } 344 } 345 346 public bool Enabled 347 { 348 set => innerTimer.Enabled = value; 349 } 350 351 public bool ComparatorEnabled 352 { 353 get => innerTimer.EventEnabled; 354 set 355 { 356 innerTimer.EventEnabled = value; 357 UpdateEventFlag(); 358 UpdateInterrupt(); 359 } 360 } 361 362 public bool IrqEnabled 363 { 364 get => irqEnabled; 365 set 366 { 367 if(irqEnabled == value) 368 { 369 return; 370 } 371 irqEnabled = value; 372 UpdateInterrupt(); 373 } 374 } 375 376 public bool AutoIncrementEnabled { get; set; } 377 378 public ulong Value 379 { 380 set 381 { 382 innerTimer.Value = value; 383 UpdateEventFlag(); 384 UpdateInterrupt(); 385 } 386 } 387 388 public uint Divider 389 { 390 set => innerTimer.Divider = value; 391 } 392 HandleCompareEvent()393 private void HandleCompareEvent() 394 { 395 eventFlag = true; 396 if(AutoIncrementEnabled) 397 { 398 innerTimer.Compare += AutoIncrement; 399 } 400 UpdateInterrupt(); 401 } 402 UpdateEventFlag()403 private void UpdateEventFlag() 404 { 405 eventFlag |= ComparatorEnabled && innerTimer.Value >= innerTimer.Compare; 406 } 407 UpdateInterrupt()408 private void UpdateInterrupt() 409 { 410 IRQ.Set(IrqEnabled && EventFlag); 411 } 412 413 private bool eventFlag; 414 private bool irqEnabled; 415 private readonly ComparingTimer innerTimer; 416 } 417 } 418 } 419