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