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;
8 using System.Collections.Generic;
9 using System.Linq;
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Core.Structure.Registers;
12 using Antmicro.Renode.Exceptions;
13 using Antmicro.Renode.Logging;
14 using Antmicro.Renode.Peripherals.Bus;
15 using Antmicro.Renode.Utilities;
16 using Antmicro.Renode.Peripherals.CPU;
17 using Antmicro.Renode.Peripherals.IRQControllers.PLIC;
18 
19 namespace Antmicro.Renode.Peripherals.IRQControllers.PLIC
20 {
21     public abstract class PlatformLevelInterruptControllerBase : IPlatformLevelInterruptController, IDoubleWordPeripheral, IIRQController, INumberedGPIOOutput
22     {
PlatformLevelInterruptControllerBase(int numberOfSources, int numberOfContexts, bool prioritiesEnabled, uint extraInterrupts = 0)23         public PlatformLevelInterruptControllerBase(int numberOfSources, int numberOfContexts, bool prioritiesEnabled, uint extraInterrupts = 0)
24         {
25             var connections = new Dictionary<int, IGPIO>();
26             for(var i = 0; i < numberOfContexts + extraInterrupts; i++)
27             {
28                 connections[i] = new GPIO();
29             }
30             Connections = connections;
31 
32             this.prioritiesEnabled = prioritiesEnabled;
33 
34             irqSources = new IrqSource[numberOfSources];
35             for(var i = 0u; i < numberOfSources; i++)
36             {
37                 irqSources[i] = new IrqSource(i, this);
38             }
39 
40             irqContexts = new IrqContext[numberOfContexts];
41             for(var i = 0u; i < irqContexts.Length; i++)
42             {
43                 irqContexts[i] = new IrqContext(i, this);
44             }
45         }
46 
ReadDoubleWord(long offset)47         public uint ReadDoubleWord(long offset)
48         {
49             return registers.Read(offset);
50         }
51 
Reset()52         public void Reset()
53         {
54             this.Log(LogLevel.Noisy, "Resetting peripheral state");
55 
56             registers.Reset();
57             foreach(var irqSource in irqSources)
58             {
59                 irqSource.Reset();
60             }
61             foreach(var irqContext in irqContexts)
62             {
63                 irqContext.Reset();
64             }
65             RefreshInterrupts();
66         }
67 
WriteDoubleWord(long offset, uint value)68         public void WriteDoubleWord(long offset, uint value)
69         {
70             registers.Write(offset, value);
71         }
72 
OnGPIO(int number, bool value)73         public void OnGPIO(int number, bool value)
74         {
75             if(!IsIrqSourceAvailable(number))
76             {
77                 this.Log(LogLevel.Error, "Wrong gpio source: {0}", number);
78                 return;
79             }
80             lock(irqSources)
81             {
82                 this.Log(LogLevel.Noisy, "Setting GPIO number #{0} to value {1}", number, value);
83                 var irq = irqSources[number];
84                 irq.State = value;
85 
86                 if(value)
87                 {
88                     foreach(var irqContext in irqContexts)
89                     {
90                         irqContext.MarkSourceAsPending(irq);
91                     }
92                 }
93 
94                 RefreshInterrupts();
95             }
96         }
97 
98         public IReadOnlyDictionary<int, IGPIO> Connections { get; }
99 
100         /// <summary>
101         /// Setting this property to a value different than -1 causes all interrupts to be reported to a context with a given id.
102         ///
103         /// This is mostly for debugging purposes.
104         /// It allows to designate a single core (in a multi-core setup) to handle external interrupts making it easier to debug trap handlers.
105         /// </summary>
106         public int ForcedContext { get; set; } = -1;
107 
RefreshInterrupts()108         protected void RefreshInterrupts()
109         {
110             lock(irqSources)
111             {
112                 foreach(var context in irqContexts)
113                 {
114                     context.RefreshInterrupt();
115                 }
116             }
117         }
118 
AddContextClaimCompleteRegister(Dictionary<long, DoubleWordRegister> registersMap, long offset, uint contextId)119         protected void AddContextClaimCompleteRegister(Dictionary<long, DoubleWordRegister> registersMap, long offset, uint contextId)
120         {
121             registersMap.Add(offset, new DoubleWordRegister(this).WithValueField(0, 32, valueProviderCallback: _ =>
122             {
123                 lock(irqSources)
124                 {
125                     return irqContexts[contextId].AcknowledgePendingInterrupt();
126                 }
127             },
128             writeCallback: (_, value) =>
129             {
130                 if(!IsIrqSourceAvailable((int)value))
131                 {
132                     this.Log(LogLevel.Error, "Trying to complete handling of non-existing interrupt source {0}", value);
133                     return;
134                 }
135                 lock(irqSources)
136                 {
137                     irqContexts[contextId].CompleteHandlingInterrupt(irqSources[value]);
138                 }
139             }));
140         }
141 
AddContextEnablesRegister(Dictionary<long, DoubleWordRegister> registersMap, long address, uint contextId, int numberOfSources)142         protected void AddContextEnablesRegister(Dictionary<long, DoubleWordRegister> registersMap, long address, uint contextId, int numberOfSources)
143         {
144             var maximumSourceDoubleWords = (int)Math.Ceiling((numberOfSources + 1) / 32.0) * 4;
145 
146             for(var offset = 0u; offset < maximumSourceDoubleWords; offset += 4)
147             {
148                 var lOffset = offset;
149                 registersMap.Add(address + offset, new DoubleWordRegister(this).WithValueField(0, 32, writeCallback: (_, value) =>
150                 {
151                     lock(irqSources)
152                     {
153                         // Each source is represented by one bit. offset and lOffset indicate the offset in double words from ContextXEnables,
154                         // `bit` is the bit number in the given double word,
155                         // and `sourceIdBase + bit` indicate the source number.
156                         var sourceIdBase = lOffset * 8;
157                         var bits = BitHelper.GetBits((uint)value);
158                         for(var bit = 0u; bit < bits.Length; bit++)
159                         {
160                             var sourceNumber = sourceIdBase + bit;
161                             if(!IsIrqSourceAvailable((int)sourceNumber))
162                             {
163                                 if(bits[bit])
164                                 {
165                                     this.Log(LogLevel.Warning, "Trying to enable non-existing source: {0}", sourceNumber);
166                                 }
167                                 continue;
168                             }
169 
170                             irqContexts[contextId].EnableSource(irqSources[sourceNumber], bits[bit]);
171                         }
172                         RefreshInterrupts();
173                     }
174                 }));
175             }
176         }
177 
AddContextPriorityThresholdRegister(Dictionary<long, DoubleWordRegister> registersMap, long offset, uint hartId)178         protected void AddContextPriorityThresholdRegister(Dictionary<long, DoubleWordRegister> registersMap, long offset, uint hartId)
179         {
180             this.Log(LogLevel.Noisy, "Adding Context {0} threshold priority register address 0x{1:X}", hartId, offset);
181             registersMap.Add(offset, new DoubleWordRegister(this)
182                 .WithValueField(0, 32,
183                     valueProviderCallback: _ =>
184                     {
185                         // TODO: implement
186                         return 0;
187                     },
188                     writeCallback: (_, value) =>
189                     {
190                         // TODO: implement
191                         // Only log a warning if the software attempts to set a nonzero threshold as the model behaves as if it is 0
192                         this.Log(prioritiesEnabled && value != 0 ? LogLevel.Warning : LogLevel.Noisy, "Setting priority threshold for Context {0} not supported", hartId);
193                     }));
194         }
195 
IsIrqSourceAvailable(int number)196         protected virtual bool IsIrqSourceAvailable(int number)
197         {
198             return number >= 0 && number < irqSources.Length;
199         }
200 
201         protected DoubleWordRegisterCollection registers;
202 
203         protected readonly bool prioritiesEnabled;
204 
205         protected readonly IrqSource[] irqSources;
206         protected readonly IrqContext[] irqContexts;
207     }
208 }
209