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.Collections.ObjectModel;
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Core.Structure;
12 using Antmicro.Renode.Core.Structure.Registers;
13 using Antmicro.Renode.Logging;
14 using Antmicro.Renode.Peripherals.IRQControllers;
15 using Antmicro.Renode.Peripherals.Memory;
16 using Antmicro.Renode.Exceptions;
17 
18 namespace Antmicro.Renode.Peripherals.Miscellaneous
19 {
20     public class ZynqMP_IPI : BasicDoubleWordPeripheral, IKnownSize, INumberedGPIOOutput, IPeripheralRegister<ZynqMP_PlatformManagementUnit, NullRegistrationPoint>
21     {
GetRegisterOffset(ChannelId channelId, RegisterOffset registerOffset)22         public static long GetRegisterOffset(ChannelId channelId, RegisterOffset registerOffset)
23         {
24             var channelOffset = GetChannelOffsetFromId(channelId);
25             return channelOffset + (long)registerOffset;
26         }
27 
GetMailboxOffset(ChannelId channelId)28         public static long GetMailboxOffset(ChannelId channelId)
29         {
30             switch(channelId)
31             {
32                 case ChannelId.Channel0:
33                     // Mailbox for channel 0 should start at offset 0x400 according
34                     // to documetation, but DTS uses this address instead.
35                     return 0x5c0;
36                 case ChannelId.Channel1:
37                     return 0x0;
38                 case ChannelId.Channel2:
39                     return 0x200;
40                 // All PMU channels have the same mailbox
41                 case ChannelId.Channel3:
42                 case ChannelId.Channel4:
43                 case ChannelId.Channel5:
44                 case ChannelId.Channel6:
45                     return 0xe00;
46                 case ChannelId.Channel7:
47                     return 0x600;
48                 case ChannelId.Channel8:
49                     return 0x800;
50                 case ChannelId.Channel9:
51                     return 0xa00;
52                 case ChannelId.Channel10:
53                     return 0xc00;
54                 default:
55                     throw new ArgumentOutOfRangeException("channelId");
56             }
57         }
58 
ZynqMP_IPI(IMachine machine, IMemory mailbox)59         public ZynqMP_IPI(IMachine machine, IMemory mailbox) : base(machine)
60         {
61             this.mailbox = mailbox;
62             var innerConnections = new Dictionary<int, IGPIO>();
63             for(var channelIdx = 0; channelIdx < NrOfChannels; ++channelIdx)
64             {
65                 innerConnections[channelIdx] = new GPIO();
66             }
67             Connections = new ReadOnlyDictionary<int, IGPIO>(innerConnections);
68 
69             channels = new Channel[] {
70                 new Channel(this, ChannelId.Channel0, Connections[0]),
71                 new Channel(this, ChannelId.Channel1, Connections[1]),
72                 new Channel(this, ChannelId.Channel2, Connections[2]),
73                 new Channel(this, ChannelId.Channel3, Connections[3]),
74                 new Channel(this, ChannelId.Channel4, Connections[4]),
75                 new Channel(this, ChannelId.Channel5, Connections[5]),
76                 new Channel(this, ChannelId.Channel6, Connections[6]),
77                 new Channel(this, ChannelId.Channel7, Connections[7]),
78                 new Channel(this, ChannelId.Channel8, Connections[8]),
79                 new Channel(this, ChannelId.Channel9, Connections[9]),
80                 new Channel(this, ChannelId.Channel10, Connections[10])
81             };
82         }
83 
Reset()84         public override void Reset()
85         {
86             base.Reset();
87 
88             foreach(var gpio in Connections)
89             {
90                 gpio.Value.Unset();
91             }
92         }
93 
Register(ZynqMP_PlatformManagementUnit peripheral, NullRegistrationPoint registrationPoint)94         public void Register(ZynqMP_PlatformManagementUnit peripheral, NullRegistrationPoint registrationPoint)
95         {
96             if(pmu != null)
97             {
98                 throw new RegistrationException("A PMU is already registered.");
99             }
100             pmu = peripheral;
101             pmu.RegisterIPI(this);
102             machine.RegisterAsAChildOf(this, peripheral, registrationPoint);
103         }
104 
Unregister(ZynqMP_PlatformManagementUnit peripheral)105         public void Unregister(ZynqMP_PlatformManagementUnit peripheral)
106         {
107             pmu = null;
108             machine.UnregisterAsAChildOf(this, peripheral);
109         }
110 
111         public long Size => 0x80000;
112 
113         public IReadOnlyDictionary<int, IGPIO> Connections { get; }
114 
115         public readonly IMemory mailbox;
116 
GetRegisterFromChannelIdAndRegisterOffset(ChannelId channelId, RegisterOffset registerOffset)117         private static Registers GetRegisterFromChannelIdAndRegisterOffset(ChannelId channelId, RegisterOffset registerOffset)
118         {
119             // This function is used only in channel construction and should receive hardcoded arguments.
120             // Hence we don't want to catch exeception as it may indicate error in setup code.
121             var channelOffset = GetChannelOffsetFromId(channelId);
122             return (Registers)(channelOffset + (long)registerOffset);
123         }
124 
GetChannelOffsetFromId(ChannelId channelId)125         private static long GetChannelOffsetFromId(ChannelId channelId)
126         {
127             switch(channelId)
128             {
129                 case ChannelId.Channel0:
130                     return 0x00000;
131                 case ChannelId.Channel1:
132                     return 0x10000;
133                 case ChannelId.Channel2:
134                     return 0x20000;
135                 case ChannelId.Channel3:
136                     return 0x30000;
137                 case ChannelId.Channel4:
138                     return 0x31000;
139                 case ChannelId.Channel5:
140                     return 0x32000;
141                 case ChannelId.Channel6:
142                     return 0x33000;
143                 case ChannelId.Channel7:
144                     return 0x40000;
145                 case ChannelId.Channel8:
146                     return 0x50000;
147                 case ChannelId.Channel9:
148                     return 0x60000;
149                 case ChannelId.Channel10:
150                     return 0x70000;
151                 default:
152                     throw new ArgumentOutOfRangeException("channelId");
153             }
154         }
155 
GetChannelFromId(ChannelId channelId)156         private Channel GetChannelFromId(ChannelId channelId)
157         {
158             switch(channelId)
159             {
160                 case ChannelId.Channel0:
161                     return channels[0];
162                 case ChannelId.Channel1:
163                     return channels[1];
164                 case ChannelId.Channel2:
165                     return channels[2];
166                 case ChannelId.Channel3:
167                     return channels[3];
168                 case ChannelId.Channel4:
169                     return channels[4];
170                 case ChannelId.Channel5:
171                     return channels[5];
172                 case ChannelId.Channel6:
173                     return channels[6];
174                 case ChannelId.Channel7:
175                     return channels[7];
176                 case ChannelId.Channel8:
177                     return channels[8];
178                 case ChannelId.Channel9:
179                     return channels[9];
180                 case ChannelId.Channel10:
181                     return channels[10];
182                 default:
183                     throw new ArgumentOutOfRangeException("channelId");
184             }
185         }
186 
TryTriggerInterrupt(ChannelId targetChannelId, ChannelId sourceChannelId)187         private void TryTriggerInterrupt(ChannelId targetChannelId, ChannelId sourceChannelId)
188         {
189             try
190             {
191                 var sourceChannel = GetChannelFromId(sourceChannelId);
192                 var targetChannel = GetChannelFromId(targetChannelId);
193 
194                 // We set channels in Observation and StatusAndClear even if interrupt wasn't triggered
195                 sourceChannel.SetChannelInObservation(targetChannelId);
196                 targetChannel.SetChannelInStatusAndClear(sourceChannelId);
197 
198                 if(targetChannel.CanBeTriggeredBy(sourceChannelId))
199                 {
200                     targetChannel.TriggerInterrupt();
201                 }
202             }
203             catch(ArgumentOutOfRangeException)
204             {
205                 // We assume that sourceChannelId is correct. It is set and checked in channel initialization.
206                 this.Log(LogLevel.Warning, "Trying to trigger interrupt on non existing channel with id 0x{0:X}. The interrupt won't be triggered.", (uint)targetChannelId);
207             }
208         }
209 
ClearInterrupt(ChannelId targetChannelId, ChannelId sourceChannelId)210         private void ClearInterrupt(ChannelId targetChannelId, ChannelId sourceChannelId)
211         {
212             try
213             {
214                 var sourceChannel = GetChannelFromId(sourceChannelId);
215                 var targetChannel = GetChannelFromId(targetChannelId);
216 
217                 sourceChannel.ClearChannelInObservation(targetChannelId);
218                 targetChannel.ClearChannelInStatusAndClear(sourceChannelId);
219             }
220             catch(ArgumentOutOfRangeException)
221             {
222                 // We assume that sourceChannelId is correct. It is set and checked in channel initialization.
223                 this.Log(LogLevel.Warning, "Trying to clear interrupt from non existing channel with id 0x{0:X}. No interrupt will be cleared.", (uint)targetChannelId);
224             }
225         }
226 
227         private readonly Channel[] channels;
228 
229         private ZynqMP_PlatformManagementUnit pmu;
230 
231         private const int NrOfChannels = 11;
232 
233         // Each IPI register has it's unique offset within channel.
234         // We use this offset to recognize registers.
235         public enum RegisterOffset
236         {
237             Trigger         = Registers.Channel0Trigger,        // TRIG
238             Observation     = Registers.Channel0Observation,    // OBS
239             StatusAndClear  = Registers.Channel0StatusAndClear, // ISR
240             Mask            = Registers.Channel0Mask,           // IMR
241             EnableMask      = Registers.Channel0EnableMask,     // IER
242             DisableMask     = Registers.Channel0DisableMask     // IDR
243         }
244 
245         // Each channel has corresponding bit in interrupt value.
246         // We use this bit as an id of a channel.
247         // None channel is for compatibility with Flags.
248         [Flags]
249         public enum ChannelId : uint
250         {
251             None        = 0,
252             Channel0    = 1,
253             Channel1    = 1 << 8,
254             Channel2    = 1 << 9,
255             Channel3    = 1 << 16,
256             Channel4    = 1 << 17,
257             Channel5    = 1 << 18,
258             Channel6    = 1 << 19,
259             Channel7    = 1 << 24,
260             Channel8    = 1 << 25,
261             Channel9    = 1 << 26,
262             Channel10   = 1 << 27
263         }
264 
265         private class Channel
266         {
Channel(ZynqMP_IPI ipi, ChannelId id, IGPIO IRQ)267             public Channel(ZynqMP_IPI ipi, ChannelId id, IGPIO IRQ)
268             {
269                 this.ipi = ipi;
270                 this.id = id;
271                 this.IRQ = IRQ;
272 
273                 GetRegisterFromChannelIdAndRegisterOffset(id, RegisterOffset.Trigger).Define(ipi)
274                     .WithEnumField<DoubleWordRegister, ChannelId>(0, 32, FieldMode.Write,
275                             writeCallback: (_, val) => HandleWriteToTrigger(val));
276 
277                 GetRegisterFromChannelIdAndRegisterOffset(id, RegisterOffset.Observation).Define(ipi)
278                     .WithEnumField<DoubleWordRegister, ChannelId>(0, 32, out observationField, FieldMode.Read);
279 
280                 GetRegisterFromChannelIdAndRegisterOffset(id, RegisterOffset.StatusAndClear).Define(ipi)
281                     .WithEnumField<DoubleWordRegister, ChannelId>(0, 32, out statusAndClearField,
282                             writeCallback: (_, val) => HandleWriteToStatusAndClear(val));
283 
284                 GetRegisterFromChannelIdAndRegisterOffset(id, RegisterOffset.Mask).Define(ipi)
285                     .WithEnumField<DoubleWordRegister, ChannelId>(0, 32, out maskField, FieldMode.Read);
286 
287                 GetRegisterFromChannelIdAndRegisterOffset(id, RegisterOffset.EnableMask).Define(ipi)
288                     .WithEnumField<DoubleWordRegister, ChannelId>(0, 32, FieldMode.Write,
289                             writeCallback: (_, val) => HandleWriteToEnableMask(val));
290 
291                 GetRegisterFromChannelIdAndRegisterOffset(id, RegisterOffset.DisableMask).Define(ipi)
292                     .WithEnumField<DoubleWordRegister, ChannelId>(0, 32, FieldMode.Write,
293                             writeCallback: (_, val) => HandleWriteToDisableMask(val));
294             }
295 
CanBeTriggeredBy(ChannelId sourceChannelId)296             public bool CanBeTriggeredBy(ChannelId sourceChannelId)
297             {
298                 return !maskField.Value.HasFlag(sourceChannelId);
299             }
300 
TriggerInterrupt()301             public void TriggerInterrupt()
302             {
303                 ipi.Log(LogLevel.Noisy, "Interrupt triggered on: {0}", id);
304                 IRQ.Set(true);
305             }
306 
SetChannelInStatusAndClear(ChannelId channelId)307             public void SetChannelInStatusAndClear(ChannelId channelId)
308             {
309                 statusAndClearField.Value |= channelId;
310             }
311 
ClearChannelInStatusAndClear(ChannelId channelId)312             public void ClearChannelInStatusAndClear(ChannelId channelId)
313             {
314                 statusAndClearField.Value &= ~channelId;
315                 if (statusAndClearField.Value == ChannelId.None)
316                 {
317                     IRQ.Unset();
318                 }
319             }
320 
SetChannelInObservation(ChannelId channelId)321             public void SetChannelInObservation(ChannelId channelId)
322             {
323                 observationField.Value |= channelId;
324             }
325 
ClearChannelInObservation(ChannelId channelId)326             public void ClearChannelInObservation(ChannelId channelId)
327             {
328                 observationField.Value &= ~channelId;
329             }
330 
HandleWriteToTrigger(ChannelId channelId)331             private void HandleWriteToTrigger(ChannelId channelId)
332             {
333                 ipi.TryTriggerInterrupt(channelId, this.id);
334             }
335 
HandleWriteToStatusAndClear(ChannelId channelId)336             private void HandleWriteToStatusAndClear(ChannelId channelId)
337             {
338                 ipi.ClearInterrupt(id, channelId);
339             }
340 
HandleWriteToEnableMask(ChannelId channelId)341             private void HandleWriteToEnableMask(ChannelId channelId)
342             {
343                 maskField.Value &= ~channelId;
344             }
345 
HandleWriteToDisableMask(ChannelId channelId)346             private void HandleWriteToDisableMask(ChannelId channelId)
347             {
348                 maskField.Value |= channelId;
349                 // It's not a documented behavior, but Linux running OpenAMP depends on it
350                 ipi.ClearInterrupt(id, channelId);
351             }
352 
353             private readonly ZynqMP_IPI ipi;
354             private readonly ChannelId id;
355             private readonly IGPIO IRQ;
356             private readonly IEnumRegisterField<ChannelId> observationField;
357             private readonly IEnumRegisterField<ChannelId> statusAndClearField;
358             private readonly IEnumRegisterField<ChannelId> maskField;
359         }
360 
361         private enum Registers
362         {
363            Channel0Trigger          = 0x00000,
364            Channel0Observation      = 0x00004,
365            Channel0StatusAndClear   = 0x00010,
366            Channel0Mask             = 0x00014,
367            Channel0EnableMask       = 0x00018,
368            Channel0DisableMask      = 0x0001c,
369 
370            Channel1Trigger          = 0x10000,
371            Channel1Observation      = 0x10004,
372            Channel1StatusAndClear   = 0x10010,
373            Channel1Mask             = 0x10014,
374            Channel1EnableMask       = 0x10018,
375            Channel1DisableMask      = 0x1001c,
376 
377            Channel2Trigger          = 0x20000,
378            Channel2Observation      = 0x20004,
379            Channel2StatusAndClear   = 0x20010,
380            Channel2Mask             = 0x20014,
381            Channel2EnableMask       = 0x20018,
382            Channel2DisableMask      = 0x2001c,
383 
384            Channel3Trigger          = 0x30000,
385            Channel3Observation      = 0x30004,
386            Channel3StatusAndClear   = 0x30010,
387            Channel3Mask             = 0x30014,
388            Channel3EnableMask       = 0x30018,
389            Channel3DisableMask      = 0x3001c,
390 
391            Channel4Trigger          = 0x31000,
392            Channel4Observation      = 0x31004,
393            Channel4StatusAndClear   = 0x31010,
394            Channel4Mask             = 0x31014,
395            Channel4EnableMask       = 0x31018,
396            Channel4DisableMask      = 0x3101c,
397 
398            Channel5Trigger          = 0x32000,
399            Channel5Observation      = 0x32004,
400            Channel5StatusAndClear   = 0x32010,
401            Channel5Mask             = 0x32014,
402            Channel5EnableMask       = 0x32018,
403            Channel5DisableMask      = 0x3201c,
404 
405            Channel6Trigger          = 0x33000,
406            Channel6Observation      = 0x33004,
407            Channel6StatusAndClear   = 0x33010,
408            Channel6Mask             = 0x33014,
409            Channel6EnableMask       = 0x33018,
410            Channel6DisableMask      = 0x3301c,
411 
412            Channel7Trigger          = 0x40000,
413            Channel7Observation      = 0x40004,
414            Channel7StatusAndClear   = 0x40010,
415            Channel7Mask             = 0x40014,
416            Channel7EnableMask       = 0x40018,
417            Channel7DisableMask      = 0x4001c,
418 
419            Channel8Trigger          = 0x50000,
420            Channel8Observation      = 0x50004,
421            Channel8StatusAndClear   = 0x50010,
422            Channel8Mask             = 0x50014,
423            Channel8EnableMask       = 0x50018,
424            Channel8DisableMask      = 0x5001c,
425 
426            Channel9Trigger          = 0x60000,
427            Channel9Observation      = 0x60004,
428            Channel9StatusAndClear   = 0x60010,
429            Channel9Mask             = 0x60014,
430            Channel9EnableMask       = 0x60018,
431            Channel9DisableMask      = 0x6001c,
432 
433            Channel10Trigger         = 0x70000,
434            Channel10Observation     = 0x70004,
435            Channel10StatusAndClear  = 0x70010,
436            Channel10Mask            = 0x70014,
437            Channel10EnableMask      = 0x70018,
438            Channel10DisableMask     = 0x7001c,
439         }
440     }
441 }
442