1 // 2 // Copyright (c) 2010-2024 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.Collections.Generic; 8 using System.Linq; 9 using Antmicro.Renode.Core; 10 using Antmicro.Renode.Core.Structure.Registers; 11 using Antmicro.Renode.Peripherals.CPU; 12 using Antmicro.Renode.Peripherals.Bus; 13 using Antmicro.Renode.Utilities; 14 using Antmicro.Renode.Peripherals.Timers; 15 using Antmicro.Renode.Time; 16 using Antmicro.Renode.Logging; 17 using System; 18 19 //TODO: Priorities are handled not as in the docs, higher vector wins. 20 namespace Antmicro.Renode.Peripherals.IRQControllers 21 { 22 public sealed class LAPIC : IDoubleWordPeripheral, IIRQController, IKnownSize 23 { LAPIC(IMachine machine, int id = 0)24 public LAPIC(IMachine machine, int id = 0) 25 { 26 // frequency guessed from driver and zephyr code 27 localTimer = new LimitTimer(machine.ClockSource, 32000000, this, nameof(localTimer), direction: Direction.Descending, workMode: WorkMode.OneShot, eventEnabled: true, divider: 2); 28 localTimer.LimitReached += () => 29 { 30 if(localTimerMasked.Value || !lapicEnabled.Value) 31 { 32 return; 33 } 34 lock(sync) 35 { 36 interrupts[(int)localTimerVector.Value] |= IRQState.Pending; 37 FindPendingInterrupt(); 38 } 39 }; 40 IRQ = new GPIO(); 41 ID = id; 42 DefineRegisters(); 43 Reset(); 44 this.machine = machine; 45 } 46 OnGPIO(int number, bool value)47 public void OnGPIO(int number, bool value) 48 { 49 lock(sync) 50 { 51 if(value) 52 { 53 if(lapicEnabled.Value) //according to 10.4.7.2 54 { 55 this.Log(LogLevel.Noisy, "Received an interrupt vector {0}.", number); 56 // It is possible to have the same vector active and pending. We latch it whenever there is a change in Running. 57 if((interrupts[number] & IRQState.Running) == 0) 58 { 59 interrupts[number] |= IRQState.Pending; 60 } 61 } 62 interrupts[number] |= IRQState.Running; 63 } 64 else 65 { 66 interrupts[number] &= ~IRQState.Running; 67 } 68 FindPendingInterrupt(); 69 } 70 } 71 ReadDoubleWord(long offset)72 public uint ReadDoubleWord(long offset) 73 { 74 lock(sync) 75 { 76 int regNumber; 77 if(offset >= 0x100 && offset < 0x280) 78 { 79 IRQState flagToCheck; 80 if(offset < 0x180) //InService 81 { 82 offset -= 0x100; 83 flagToCheck = IRQState.Active; 84 } 85 else if(offset < 0x200) //TriggerMode 86 { 87 offset -= 0x180; 88 flagToCheck = IRQState.TriggerModeIndicator; 89 } 90 else //InterruptRequest 91 { 92 offset -= 0x200; 93 flagToCheck = IRQState.Pending; 94 } 95 regNumber = (int)offset / 0x10; 96 return BitHelper.GetValueFromBitsArray(interrupts.Skip(regNumber * 32).Take(32).Select(x => (x & flagToCheck) != 0)); 97 } 98 return registers.Read(offset); 99 } 100 } 101 WriteDoubleWord(long offset, uint value)102 public void WriteDoubleWord(long offset, uint value) 103 { 104 lock(sync) 105 { 106 registers.Write(offset, value); 107 } 108 } 109 Reset()110 public void Reset() 111 { 112 localTimer.Reset(); 113 registers.Reset(); 114 interrupts = new IRQState[availableVectors]; 115 activeIrqs.Clear(); 116 } 117 GetPendingInterrupt()118 public int GetPendingInterrupt() 119 { 120 lock(sync) 121 { 122 var result = FindPendingInterrupt(); 123 if(result != -1) 124 { 125 interrupts[result] |= IRQState.Active; 126 interrupts[result] &= ~IRQState.Pending; 127 this.NoisyLog("Acknowledged IRQ {0}.", result); 128 activeIrqs.Push(result); 129 IRQ.Unset(); 130 return result; 131 } 132 this.Log(LogLevel.Warning, "Trying to acknowledge an interrupt, but there is nothing to acknowledge!"); 133 // We should probably handle spurious vector here 134 return 0; 135 } 136 } 137 138 public long Size 139 { 140 get 141 { 142 return 1.KB(); 143 } 144 } 145 146 public GPIO IRQ { get; private set; } 147 148 public int ID { get; } 149 DefineRegisters()150 private void DefineRegisters() 151 { 152 var addresses = new Dictionary<long, DoubleWordRegister> 153 { 154 {(long)Registers.LocalAPICId, new DoubleWordRegister(this) 155 .WithReservedBits(0, 24) 156 .WithValueField(24, 8, FieldMode.Read, valueProviderCallback: _ => (ulong)this.ID) 157 }, 158 {(long)Registers.LocalAPICVersion, new DoubleWordRegister(this, Version + (MaxLVTEntry << 16)) 159 .WithValueField(0, 8, FieldMode.Read, valueProviderCallback: _ => Version) 160 .WithValueField(16, 8, FieldMode.Read, valueProviderCallback: _ => MaxLVTEntry) 161 }, 162 {(long)Registers.LocalVectorTableThermal, new DoubleWordRegister(this, 0x10000) 163 .WithTag("Vector", 0, 8) 164 .WithTag("Delivery Mode", 8, 3) 165 .WithTag("Delivery status", 12, 1) 166 .WithTag("Masked", 16, 1) 167 }, 168 {(long)Registers.EndOfInterrupt, new DoubleWordRegister(this).WithWriteCallback((_,__) => EndOfInterrupt()) 169 }, 170 {(long)Registers.SpuriousInterrupt, new DoubleWordRegister(this, 0xFF) 171 .WithTag("Spurious Vector, older bits", 4, 4) 172 .WithFlag(8, out lapicEnabled, changeCallback: ApicEnabledChanged, name: "APIC S/W enable/disable") 173 }, 174 {(long)Registers.InterruptCommandLo, new DoubleWordRegister(this) 175 .WithTag("Vector", 0, 8) 176 .WithValueField(8, 3, out var deliveryMode, name: "Delivery Mode") 177 .WithTaggedFlag("Destination Mode", 11) 178 .WithReservedBits(12, 2) 179 .WithTaggedFlag("Level", 14) 180 .WithTaggedFlag("Trigger Mode", 15) 181 .WithReservedBits(16, 2) 182 .WithTag("Destination Shorthand", 18, 2) 183 .WithReservedBits(20, 12) 184 .WithWriteCallback((_,__) => 185 { 186 switch((LocalVectorTableDeliveryMode)deliveryMode.Value) 187 { 188 case LocalVectorTableDeliveryMode.SIPI: 189 // Find the CPU of LAPIC with specified ID 190 var cpu = machine.SystemBus.GetCPUs().OfType<BaseX86>() 191 .FirstOrDefault(x => (ulong)x.Lapic.ID == destination.Value); 192 193 if(cpu == null) 194 { 195 this.WarningLog("There is no cpu having LAPIC with id {0}. Not sending IPI", destination.Value); 196 } 197 else 198 { 199 this.InfoLog("Unhalting cpu having LAPIC with id {0}", destination.Value); 200 cpu.IsHalted = false; 201 } 202 break; 203 204 default: 205 this.WarningLog("Received unsupported delivery mode value: {0}", deliveryMode.Value); 206 break; 207 } 208 }) 209 }, 210 {(long)Registers.InterruptCommandHi, new DoubleWordRegister(this) 211 .WithValueField(0, 32, out destination, name: "Destination Field") 212 }, 213 {(long)Registers.LocalVectorTablePerformanceMonitorCounters, new DoubleWordRegister(this, 0x10000) 214 .WithTag("Vector", 0, 8) 215 .WithTag("Delivery Mode", 8, 3) 216 .WithTag("Delivery status", 12, 1) 217 .WithTag("Masked", 16, 1) 218 }, 219 {(long)Registers.LocalVectorTableTimer, new DoubleWordRegister(this, 0x10000) 220 .WithValueField(0, 8, out localTimerVector, name: "Vector") 221 .WithTag("Delivery status", 12, 1) // Read-only. This should not be needed, as it is set before writing to IRR. We do not support 222 // "rejecting" of interrupts, so everything is automatically accepted. 223 .WithFlag(16, out localTimerMasked, name: "Masked") 224 .WithFlag(17, name: "Periodic", changeCallback: (_, v) => 225 { 226 localTimer.Mode = v ? WorkMode.Periodic : WorkMode.OneShot; 227 if(v) 228 { 229 localTimer.Enabled = true; 230 } 231 this.Log(LogLevel.Info, "Local timer mode set to {0}", localTimer.Mode); 232 }) 233 }, 234 //These two registers are not supported despite being written to, I think they are not relevant in our setup. 235 {(long)Registers.LocalVectorTableLINT0, new DoubleWordRegister(this, 0x10000) 236 .WithTag("Vector", 0, 8) 237 .WithTag("Delivery mode", 8, 3) 238 .WithTag("Delivery status", 12, 1) //Read-only 239 .WithTag("Interrupt Input Pin Polarity", 13, 1) 240 .WithTag("Remote IRR", 14, 1) //Read-only 241 .WithTag("Level triggered", 15, 1) 242 .WithTag("Masked", 16, 1) 243 }, 244 {(long)Registers.LocalVectorTableLINT1, new DoubleWordRegister(this, 0x10000) 245 .WithTag("Vector", 0, 8) 246 .WithTag("Delivery mode", 8, 3) 247 .WithTag("Delivery status", 12, 1) //Read-only 248 .WithTag("Interrupt Input Pin Polarity", 13, 1) 249 .WithTag("Remote IRR", 14, 1) //Read-only 250 .WithTag("Level triggered", 15, 1) 251 .WithTag("Masked", 16, 1) 252 }, 253 {(long)Registers.LocalVectorTableError, new DoubleWordRegister(this, 0x10000) 254 .WithTag("Vector", 0, 8) 255 .WithTag("Delivery status", 12, 1) 256 .WithTag("Masked", 16, 1) 257 }, 258 {(long)Registers.LocalVectorTableTimerInitialCount, new DoubleWordRegister(this) 259 .WithValueField(0, 32, name: "Initial Count Value", writeCallback: (_, val) => 260 { 261 this.Log(LogLevel.Info, "Setting local timer initial value to {0}", val); 262 localTimer.Limit = val; 263 localTimer.ResetValue(); 264 localTimer.Enabled = true; 265 }) 266 }, 267 {(long)Registers.LocalVectorTableTimerCurrentCount, new DoubleWordRegister(this) 268 .WithValueField(0, 32, FieldMode.Read, name: "Current Count Value", valueProviderCallback: _ => (uint)localTimer.Value) 269 }, 270 {(long)Registers.LocalVectorTableTimerDivideConfig, new DoubleWordRegister(this) 271 .WithValueField(0, 4, name: "Divide Value", writeCallback: (_, val) => 272 { 273 switch(val) 274 { 275 case 0x0: 276 localTimer.Divider = 2; 277 break; 278 case 0x1: 279 localTimer.Divider = 4; 280 break; 281 case 0x2: 282 localTimer.Divider = 8; 283 break; 284 case 0x3: 285 localTimer.Divider = 16; 286 break; 287 case 0x8: 288 localTimer.Divider = 32; 289 break; 290 case 0x9: 291 localTimer.Divider = 64; 292 break; 293 case 0xA: 294 localTimer.Divider = 128; 295 break; 296 case 0xB: 297 localTimer.Divider = 1; 298 break; 299 default: 300 this.Log(LogLevel.Warning, "Setting unsupported divider value: 0x{0:x}", val); 301 return; 302 } 303 304 this.Log(LogLevel.Info, "Divider set to {0}", localTimer.Divider); 305 }) 306 } 307 }; 308 registers = new DoubleWordRegisterCollection(this, addresses); 309 } 310 EndOfInterrupt()311 public void EndOfInterrupt() 312 { 313 lock(sync) 314 { 315 if(activeIrqs.Count() == 0) 316 { 317 this.DebugLog("Trying to end and interrupt, but no interrupt was acknowledged."); 318 } 319 else 320 { 321 var activeIRQ = activeIrqs.Pop(); 322 interrupts[activeIRQ] &= ~IRQState.Active; 323 if((interrupts[activeIRQ] & IRQState.Running) > 0) 324 { 325 this.NoisyLog("Completed IRQ {0} active -> pending.", activeIRQ); 326 interrupts[activeIRQ] |= IRQState.Pending; 327 } 328 else 329 { 330 this.NoisyLog("Completed IRQ {0} active -> inactive.", activeIRQ); 331 } 332 } 333 FindPendingInterrupt(); 334 } 335 } 336 337 public uint InternalTimerVector { get => (uint)localTimerVector.Value; } 338 FindPendingInterrupt()339 private int FindPendingInterrupt() 340 { 341 var result = -1; 342 var preemptionNeeded = activeIrqs.Count != 0; 343 344 for(var i = interrupts.Length - 1; i >= 0; i--) 345 { 346 if((interrupts[i] & IRQState.Pending) != 0) 347 { 348 result = i; 349 break; 350 } 351 } 352 if(result != -1 && (!preemptionNeeded || activeIrqs.Peek() < result)) 353 { 354 IRQ.Set(); 355 } 356 return result; 357 } 358 ApicEnabledChanged(bool oldValue, bool newValue)359 private void ApicEnabledChanged(bool oldValue, bool newValue) 360 { 361 if(!newValue) 362 { 363 localTimerMasked.Value = true; 364 } // not enabling otherwise, as we don't reenable timer just because we started LAPIC 365 } 366 367 private LimitTimer localTimer; 368 369 private DoubleWordRegisterCollection registers; 370 private IFlagRegisterField localTimerMasked; 371 private IValueRegisterField localTimerVector; 372 private IValueRegisterField destination; 373 private IFlagRegisterField lapicEnabled; 374 375 private readonly object sync = new object(); 376 private readonly IMachine machine; 377 378 private IRQState[] interrupts = new IRQState[availableVectors]; 379 private Stack<int> activeIrqs = new Stack<int>(); 380 381 private const int availableVectors = 256; 382 private const uint Version = 0x10; //1x means local apic, x is model specific 383 private const uint MaxLVTEntry = 3; //lowest possible? for pentium 384 385 [Flags] 386 private enum IRQState 387 { 388 Running = 1, 389 Pending = 2, 390 Active = 4, 391 TriggerModeIndicator = 8, //currently unused 392 } 393 394 private enum LocalVectorTableDeliveryMode 395 { 396 Fixed = 0, 397 SMI = 2, 398 NMI = 4, 399 INIT = 5, 400 SIPI = 6, 401 ExtINT = 7 402 //other values are reserved 403 } 404 405 public enum Registers 406 { 407 LocalAPICId = 0x20, 408 LocalAPICVersion = 0x30, 409 TaskPriority = 0x80, 410 ArbitrationPriority = 0x90, 411 ProcessorPriority = 0xa0, 412 EndOfInterrupt = 0xb0, 413 RemoteRead = 0xc0, 414 LogicalDestination = 0xd0, 415 DestinationFormat = 0xe0, 416 SpuriousInterrupt = 0xf0, 417 InService = 0x100, 418 TriggerMode = 0x180, 419 InterruptRequest = 0x200, 420 ErrorStatus = 0x280, 421 LocalVectorTableCMCI = 0x2F0, 422 InterruptCommandLo = 0x300, 423 InterruptCommandHi = 0x310, 424 LocalVectorTableTimer = 0x320, 425 LocalVectorTableThermal = 0x330, 426 LocalVectorTablePerformanceMonitorCounters = 0x340, 427 LocalVectorTableLINT0 = 0x350, 428 LocalVectorTableLINT1 = 0x360, 429 LocalVectorTableError = 0x370, 430 LocalVectorTableTimerInitialCount = 0x380, 431 LocalVectorTableTimerCurrentCount = 0x390, 432 LocalVectorTableTimerDivideConfig = 0x3e0 433 } 434 } 435 } 436