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 Antmicro.Renode.Exceptions;
10 using Antmicro.Renode.Core;
11 using Antmicro.Renode.Peripherals.CPU;
12 using Antmicro.Renode.Logging;
13 
14 namespace Antmicro.Renode.Peripherals.Miscellaneous
15 {
16     public class ZynqMP_PlatformManagementUnit : IGPIOReceiver
17     {
ZynqMP_PlatformManagementUnit(ICPU apu0, ICPU apu1, ICPU apu2, ICPU apu3, ICPU rpu0, ICPU rpu1)18         public ZynqMP_PlatformManagementUnit(ICPU apu0, ICPU apu1, ICPU apu2, ICPU apu3, ICPU rpu0, ICPU rpu1)
19         {
20             this.apu0 = apu0;
21             this.apu1 = apu1;
22             this.apu2 = apu2;
23             this.apu3 = apu3;
24             this.rpu0 = rpu0;
25             this.rpu1 = rpu1;
26             registeredPeripherals[apu0] = new HashSet<IPeripheral>();
27             registeredPeripherals[apu1] = new HashSet<IPeripheral>();
28             registeredPeripherals[apu2] = new HashSet<IPeripheral>();
29             registeredPeripherals[apu3] = new HashSet<IPeripheral>();
30             registeredPeripherals[rpu0] = new HashSet<IPeripheral>();
31             registeredPeripherals[rpu1] = new HashSet<IPeripheral>();
32             powerManagement = new PowerManagementModule(this);
33         }
34 
OnGPIO(int number, bool value)35         public void OnGPIO(int number, bool value)
36         {
37             if(value)
38             {
39                 HandleInterruptOnIpi((PmuIpiChannel)number);
40             }
41         }
42 
Reset()43         public void Reset()
44         {
45             powerManagement.Reset();
46         }
47 
RegisterIPI(ZynqMP_IPI ipi)48         public void RegisterIPI(ZynqMP_IPI ipi)
49         {
50             this.ipi = ipi;
51         }
52 
RegisterPeripheral(ICPU cpu, IPeripheral peripheral)53         public void RegisterPeripheral(ICPU cpu, IPeripheral peripheral)
54         {
55             if(registeredPeripherals.ContainsKey(cpu))
56             {
57                 registeredPeripherals[cpu].Add(peripheral);
58             }
59             else
60             {
61                 throw new ConstructionException("Trying to register peripheral on invalid CPU.");
62             }
63         }
64 
HandleInterruptOnIpi(PmuIpiChannel channel)65         private void HandleInterruptOnIpi(PmuIpiChannel channel)
66         {
67             // PMU is hardwired to channels 3-6
68             switch(channel)
69             {
70                 case PmuIpiChannel.PMU0:
71                     ProcessInterruptsOnIpiChannel(ZynqMP_IPI.ChannelId.Channel3);
72                     break;
73                 case PmuIpiChannel.PMU1:
74                     ProcessInterruptsOnIpiChannel(ZynqMP_IPI.ChannelId.Channel4);
75                     break;
76                 case PmuIpiChannel.PMU2:
77                     ProcessInterruptsOnIpiChannel(ZynqMP_IPI.ChannelId.Channel5);
78                     break;
79                 case PmuIpiChannel.PMU3:
80                     ProcessInterruptsOnIpiChannel(ZynqMP_IPI.ChannelId.Channel6);
81                     break;
82                 default:
83                     this.Log(LogLevel.Error, "Trying to signal GPIO {0} which is out of range.", (int)channel);
84                     break;
85             }
86         }
87 
ProcessInterruptsOnIpiChannel(ZynqMP_IPI.ChannelId targetChannelId)88         private void ProcessInterruptsOnIpiChannel(ZynqMP_IPI.ChannelId targetChannelId)
89         {
90             var interrupts = GetInterruptsSourcesAndClearMaskedChannels(targetChannelId);
91             foreach(ZynqMP_IPI.ChannelId sourceChannelId in Enum.GetValues(typeof(ZynqMP_IPI.ChannelId)))
92             {
93                 if(sourceChannelId != ZynqMP_IPI.ChannelId.None && interrupts.HasFlag(sourceChannelId))
94                 {
95                     ProcessSingleInterruptOnChannel(targetChannelId, sourceChannelId);
96                 }
97             }
98         }
99 
GetInterruptsSourcesAndClearMaskedChannels(ZynqMP_IPI.ChannelId channelId)100         private ZynqMP_IPI.ChannelId GetInterruptsSourcesAndClearMaskedChannels(ZynqMP_IPI.ChannelId channelId)
101         {
102             // We don't need to handle exceptions as we use hardcoded values.
103             // Exception would indicate error in PMU code rather than in emulated software.
104             var imrOffset = ZynqMP_IPI.GetRegisterOffset(channelId, ZynqMP_IPI.RegisterOffset.Mask);
105             var isrOffset = ZynqMP_IPI.GetRegisterOffset(channelId, ZynqMP_IPI.RegisterOffset.StatusAndClear);
106 
107             var imr = ipi.ReadDoubleWord(imrOffset);
108             var isr = ipi.ReadDoubleWord(isrOffset);
109 
110             // We still should clear unhandled interrupts
111             var maskedInterrupts = (ZynqMP_IPI.ChannelId)(imr & isr);
112             ClearInterruptsOnChannel(channelId, maskedInterrupts);
113 
114             return (ZynqMP_IPI.ChannelId)(~imr & isr);
115         }
116 
ClearInterruptsOnChannel(ZynqMP_IPI.ChannelId channelId, ZynqMP_IPI.ChannelId interrupts)117         private void ClearInterruptsOnChannel(ZynqMP_IPI.ChannelId channelId, ZynqMP_IPI.ChannelId interrupts)
118         {
119             // We don't need to handle exceptions as we use hardcoded values.
120             // Exception would indicate error in PMU code rather than in emulated software.
121             var isrOffset = ZynqMP_IPI.GetRegisterOffset(channelId, ZynqMP_IPI.RegisterOffset.StatusAndClear);
122             foreach(ZynqMP_IPI.ChannelId sourceChannelId in Enum.GetValues(typeof(ZynqMP_IPI.ChannelId)))
123             {
124                 if(sourceChannelId != ZynqMP_IPI.ChannelId.None && interrupts.HasFlag(sourceChannelId))
125                 {
126                     ipi.WriteDoubleWord(isrOffset, (uint)sourceChannelId);
127                 }
128             }
129         }
130 
ProcessSingleInterruptOnChannel(ZynqMP_IPI.ChannelId targetChannelId, ZynqMP_IPI.ChannelId sourceChannelId)131         private void ProcessSingleInterruptOnChannel(ZynqMP_IPI.ChannelId targetChannelId, ZynqMP_IPI.ChannelId sourceChannelId)
132         {
133             IpiMessage message = ReadMessageFromMailbox(sourceChannelId);
134             IpiMessage response = ProcessIpiMessage(message);
135             WriteMessageToMailbox(sourceChannelId, response);
136 
137             // We clear interrupt to notify source
138             var isrOffset = ZynqMP_IPI.GetRegisterOffset(targetChannelId, ZynqMP_IPI.RegisterOffset.StatusAndClear);
139             ipi.WriteDoubleWord(isrOffset, (uint)sourceChannelId);
140         }
141 
ProcessIpiMessage(IpiMessage message)142         private IpiMessage ProcessIpiMessage(IpiMessage message)
143         {
144             // 8 most significant bits of header indicate which PMU module should handle message
145             this.Log(LogLevel.Debug, "Processing message with Header = 0x{0:X}", message.Header);
146             var moduleId = (PmuModule)(message.Header >> 16);
147             switch(moduleId)
148             {
149                 case PmuModule.PowerManagement:
150                     return powerManagement.HandleMessage(message);
151                 default:
152                     this.Log(LogLevel.Warning, "Received call for PMU module with ID {0} which is not implemented.", (uint)moduleId);
153                     // PMU don't handle messages with wrong module id, so we return empty message
154                     return new IpiMessage();
155             }
156         }
157 
ReadMessageFromMailbox(ZynqMP_IPI.ChannelId sourceId)158         private IpiMessage ReadMessageFromMailbox(ZynqMP_IPI.ChannelId sourceId)
159         {
160             // We don't need to handle exceptions as we iterate over valid channel ids.
161             // Exception would indicate error in PMU code rather than in emulated software.
162             var sourceMailboxAddress = ZynqMP_IPI.GetMailboxOffset(sourceId);
163 
164             var message = new IpiMessage();
165             message.Header = ipi.mailbox.ReadDoubleWord(sourceMailboxAddress + IpiMessage.HeaderOffset);
166             for(var payloadIdx = 0; payloadIdx < IpiMessage.PayloadLen; ++payloadIdx)
167             {
168                 var payloadAddress = sourceMailboxAddress + IpiMessage.PayloadOffset + IpiMessage.FieldSize * payloadIdx;
169                 message.Payload[payloadIdx] = ipi.mailbox.ReadDoubleWord(payloadAddress);
170             }
171             message.Reserved = ipi.mailbox.ReadDoubleWord(sourceMailboxAddress + IpiMessage.ReservedOffset);
172             message.Checksum =  ipi.mailbox.ReadDoubleWord(sourceMailboxAddress + IpiMessage.ChecksumOffset);
173 
174             return message;
175         }
176 
WriteMessageToMailbox(ZynqMP_IPI.ChannelId targetId, IpiMessage message)177         private void WriteMessageToMailbox(ZynqMP_IPI.ChannelId targetId, IpiMessage message)
178         {
179             // We don't need to handle exceptions as we iterate over valid channel ids.
180             // Exception would indicate error in PMU code rather than in emulated software.
181             var targetMailboxOffset = ZynqMP_IPI.GetMailboxOffset(targetId);
182             targetMailboxOffset += MailboxLocalResponseOffset;
183 
184             ipi.mailbox.WriteDoubleWord(targetMailboxOffset + IpiMessage.HeaderOffset, message.Header);
185             for(var payloadIdx = 0; payloadIdx < IpiMessage.PayloadLen; ++payloadIdx)
186             {
187                 var payloadAddress = targetMailboxOffset + IpiMessage.PayloadOffset + IpiMessage.FieldSize * payloadIdx;
188                 ipi.mailbox.WriteDoubleWord(payloadAddress, message.Payload[payloadIdx]);
189             }
190             ipi.mailbox.WriteDoubleWord(targetMailboxOffset + IpiMessage.ReservedOffset, message.Reserved);
191             ipi.mailbox.WriteDoubleWord(targetMailboxOffset + IpiMessage.ChecksumOffset, message.Checksum);
192         }
193 
194         private readonly PowerManagementModule powerManagement;
195         private readonly Dictionary<ICPU, ISet<IPeripheral>> registeredPeripherals = new Dictionary<ICPU, ISet<IPeripheral>>();
196 
197         private ZynqMP_IPI ipi;
198         private ICPU apu0;
199         private ICPU apu1;
200         private ICPU apu2;
201         private ICPU apu3;
202         private ICPU rpu0;
203         private ICPU rpu1;
204 
205         private const long MailboxLocalResponseOffset = 0x20;
206 
207         private class IpiMessage
208         {
CreateSuccessResponse()209             public static IpiMessage CreateSuccessResponse()
210             {
211                 var message = new IpiMessage();
212                 message.Header = (uint)IpiResponseHeader.Success;
213                 return message;
214             }
215 
CreateInvalidParamResponse()216             public static IpiMessage CreateInvalidParamResponse()
217             {
218                 var response = new IpiMessage();
219                 response.Header = (uint)IpiResponseHeader.InvalidParam;
220                 return response;
221             }
222 
223             public uint Header = 0;
224             public uint[] Payload = new uint[PayloadLen];
225             public uint Reserved = 0;
226             public uint Checksum = 0;
227 
228             public const long HeaderOffset = 0x0;
229             public const long PayloadOffset = 0x4;
230             public const long ReservedOffset = 0x18;
231             public const long ChecksumOffset = 0x1c;
232             public const long FieldSize = 0x4;
233             public const uint PayloadLen = 5;
234 
235             private enum IpiResponseHeader
236             {
237                 Success = 0x0,
238                 InvalidParam = 0xf
239             }
240         }
241 
242         private class PowerManagementModule
243         {
PowerManagementModule(ZynqMP_PlatformManagementUnit pmu)244             public PowerManagementModule(ZynqMP_PlatformManagementUnit pmu)
245             {
246                 this.pmu = pmu;
247                 resetStatus = new Dictionary<uint, uint>();
248             }
249 
Reset()250             public void Reset()
251             {
252                 resetStatus.Clear();
253             }
254 
HandleMessage(IpiMessage message)255             public IpiMessage HandleMessage(IpiMessage message)
256             {
257                 var apiId = (PmApi)message.Header;
258                 switch(apiId)
259                 {
260                     case PmApi.GetApiVersion:
261                         return HandleGetApiVersion();
262                     case PmApi.ForcePowerdown:
263                         return HandleForcePowerdown(message);
264                     case PmApi.RequestWakeup:
265                         return HandleRequestWakeup(message);
266                     case PmApi.ResetAssert:
267                         return HandleResetAssert(message);
268                     case PmApi.ResetGetStatus:
269                         return HandleResetGetStatus(message);
270                     case PmApi.ClockGetDivider:
271                         return HandleClockGetDivider(message);
272                     case PmApi.PllGetParameter:
273                         return HandlePllGetParameter(message);
274                     default:
275                         return HandleDefault();
276                 }
277             }
278 
HandleGetApiVersion()279             private IpiMessage HandleGetApiVersion()
280             {
281                 var response = IpiMessage.CreateSuccessResponse();
282                 response.Payload[0] = ApiVersion;
283                 return response;
284             }
285 
HandleForcePowerdown(IpiMessage message)286             private IpiMessage HandleForcePowerdown(IpiMessage message)
287             {
288                 var node = (Node)message.Payload[0];
289                 var ack = (RequestAck)message.Payload[1];
290                 var response = IpiMessage.CreateSuccessResponse();
291                 var cpu = GetCpuFromNode(node);
292                 if (cpu != null)
293                 {
294                     cpu.IsHalted = true;
295                     cpu.Reset();
296                     foreach(var peripheral in pmu.registeredPeripherals[cpu])
297                     {
298                         peripheral.Reset();
299                     }
300 
301                     try
302                     {
303                         return CreateAckResponse(response, ack);
304                     }
305                     catch (ArgumentOutOfRangeException)
306                     {
307                         pmu.Log(LogLevel.Warning, "Received invalid ack request with value {0}.", ack);
308                         return new IpiMessage();
309                     }
310                 }
311 
312                 pmu.Log(LogLevel.Warning, "Received shutdown request for node with id {0} which is not implemented.", (uint)node);
313                 return IpiMessage.CreateInvalidParamResponse();
314             }
315 
CreateAckResponse(IpiMessage response, RequestAck ack)316             private IpiMessage CreateAckResponse(IpiMessage response, RequestAck ack)
317             {
318                 switch(ack)
319                 {
320                     case RequestAck.AckNo:
321                         // AckNo means we shouldn't do anything so we return empty message
322                         return new IpiMessage();
323                     case RequestAck.AckBlocking:
324                         return response;
325                     case RequestAck.AckNonBlocking:
326                         pmu.Log(LogLevel.Warning, "Requested non blocking ACK which is not implemented.");
327                         return new IpiMessage();
328                     default:
329                         throw new ArgumentOutOfRangeException("RequestAck");
330                 }
331             }
332 
HandleRequestWakeup(IpiMessage message)333             private IpiMessage HandleRequestWakeup(IpiMessage message)
334             {
335                 var node = (Node)message.Payload[0];
336                 var pc = ((ulong)message.Payload[2] << 32) | (ulong)message.Payload[1];
337                 var cpu = GetCpuFromNode(node);
338                 if (cpu != null)
339                 {
340                     // First bit of new PC indicated whether we update PC
341                     if ((pc & 1) == 1)
342                     {
343                         pc = pc & ~1UL;
344                         pmu.Log(LogLevel.Debug, "Set PC of node {0} to 0x{1:X}.", node, pc);
345                         cpu.PC = pc;
346                     }
347                     pmu.Log(LogLevel.Debug, "Unhalt node {0}.", node);
348                     cpu.IsHalted = false;
349 
350                     return IpiMessage.CreateSuccessResponse();
351                 }
352 
353                 pmu.Log(LogLevel.Warning, "Received wakeup request for node with id {0} which is not implemented.", (uint)node);
354                 return IpiMessage.CreateInvalidParamResponse();
355             }
356 
GetCpuFromNode(Node node)357             private ICPU GetCpuFromNode(Node node)
358             {
359                 switch(node)
360                 {
361                     case Node.APU0:
362                         return pmu.apu0;
363                     case Node.APU1:
364                         return pmu.apu1;
365                     case Node.APU2:
366                         return pmu.apu2;
367                     case Node.APU3:
368                         return pmu.apu3;
369                     case Node.RPU0:
370                         return pmu.rpu0;
371                     case Node.RPU1:
372                         return pmu.rpu1;
373                     default:
374                         return null;
375                 }
376             }
377 
HandleResetAssert(IpiMessage message)378             private IpiMessage HandleResetAssert(IpiMessage message)
379             {
380                 var key = message.Payload[0];
381                 var val = message.Payload[1];
382                 resetStatus[key] = val;
383                 return IpiMessage.CreateSuccessResponse();
384             }
385 
HandleResetGetStatus(IpiMessage message)386             private IpiMessage HandleResetGetStatus(IpiMessage message)
387             {
388                 var key = message.Payload[0];
389                 var response = IpiMessage.CreateSuccessResponse();
390                 response.Payload[0] = 0;
391                 resetStatus.TryGetValue(key, out response.Payload[0]);
392                 return response;
393             }
394 
HandleClockGetDivider(IpiMessage message)395             private IpiMessage HandleClockGetDivider(IpiMessage message)
396             {
397                 var clock = (Clock)message.Payload[0];
398                 var divider = (ClockDivider)message.Payload[1];
399 
400                 var response = IpiMessage.CreateSuccessResponse();
401 
402                 if(clock == Clock.Uart1Ref)
403                 {
404                     if(divider == ClockDivider.Div0)
405                     {
406                         response.Payload[0] = (CrlApbUart1RefCtrl >> ClockDivider0Shift) & ClockDividerMask;
407                     }
408                     else if(divider == ClockDivider.Div1)
409                     {
410                         response.Payload[0] = (CrlApbUart1RefCtrl >> ClockDivider1Shift) & ClockDividerMask;
411                     }
412                     else
413                     {
414                         return IpiMessage.CreateInvalidParamResponse();
415                     }
416                 }
417                 return response;
418             }
419 
HandlePllGetParameter(IpiMessage message)420             private IpiMessage HandlePllGetParameter(IpiMessage message)
421             {
422                 var pllNode = (Node)message.Payload[0];
423                 var pllParam = (PllParameter)message.Payload[1];
424 
425                 var resetValue = GetResetValueFromPllNode(pllNode);
426                 var shift = GetShiftFromPllParam(pllParam);
427                 var mask = GetMaskFromPllParam(pllParam);
428 
429                 var response = IpiMessage.CreateSuccessResponse();
430                 response.Payload[0] = (resetValue >> shift) & mask;
431                 return response;
432             }
433 
GetResetValueFromPllNode(Node pllNode)434             private uint GetResetValueFromPllNode(Node pllNode)
435             {
436                switch(pllNode)
437                {
438                     case Node.APll:
439                         return 0x00012c09;
440                     case Node.DPll:
441                         return 0x00012809;
442                     case Node.RPll:
443                         return 0x00002c09;
444                     case Node.VPll:
445                         return 0x00012c09;
446                     case Node.IOPll:
447                         return 0x00012c09;
448                     default:
449                         return 0x0;
450                }
451             }
452 
GetShiftFromPllParam(PllParameter param)453             private int GetShiftFromPllParam(PllParameter param)
454             {
455                 switch(param)
456                 {
457                     case PllParameter.Div2:
458                         return 16;
459                     case PllParameter.FbDiv:
460                         return 8;
461                     case PllParameter.PreSrc:
462                         return 20;
463                     case PllParameter.PostSrc:
464                         return 24;
465                     default:
466                         return 0;
467                 }
468             }
469 
GetMaskFromPllParam(PllParameter param)470             private uint GetMaskFromPllParam(PllParameter param)
471             {
472                 switch(param)
473                 {
474                     case PllParameter.Div2:
475                         return 0x1;
476                     case PllParameter.FbDiv:
477                         return 0x7f;
478                     case PllParameter.PreSrc:
479                         return 0x7;
480                     case PllParameter.PostSrc:
481                         return 0x7;
482                     default:
483                         return 0x0;
484                 }
485             }
486 
HandleDefault()487             private IpiMessage HandleDefault()
488             {
489                 return IpiMessage.CreateSuccessResponse();
490             }
491 
492             private readonly ZynqMP_PlatformManagementUnit pmu;
493             private readonly Dictionary<uint, uint> resetStatus;
494 
495             private const uint ApiVersion = 0x10001;
496             private const uint ClockDividerMask = 0x3f;
497             private const int ClockDivider0Shift = 8;
498             private const int ClockDivider1Shift = 16;
499             private const uint CrlApbUart1RefCtrl = 0x1001800;
500 
501             // We only list clocks that we need.
502             // This enum corresponds to XPmClock enum in PMU FW source code.
503             private enum Clock
504             {
505                 Uart1Ref = 0x39
506             }
507 
508             private enum ClockDivider
509             {
510                 Div0 = 0,
511                 Div1 = 1
512             }
513 
514             // We only list nodes that we need.
515             // This enum corresponds to XPmNodeId enum in PMU FW source code.
516             private enum Node
517             {
518                 APU0    = 0x2,
519                 APU1    = 0x3,
520                 APU2    = 0x4,
521                 APU3    = 0x5,
522                 RPU0    = 0x7,
523                 RPU1    = 0x8,
524                 APll    = 0x32,
525                 VPll    = 0x33,
526                 DPll    = 0x34,
527                 RPll    = 0x35,
528                 IOPll   = 0x36
529             }
530 
531             // We only list PLL params that we need.
532             // This enum corresponds to XPmPllParam enum in PMU FW source code.
533             private enum PllParameter
534             {
535                 Div2    = 0x0,
536                 FbDiv   = 0x1,
537                 PreSrc  = 0x3,
538                 PostSrc = 0x4
539             }
540 
541             private enum RequestAck
542             {
543                 AckNo = 1,
544                 AckBlocking = 2,
545                 AckNonBlocking = 3
546             }
547 
548             // We only list API ids that we need.
549             // This enum corresponds to XPm_ApiId enum in PMU FW source code.
550             private enum PmApi
551             {
552                 ApiMin          = 0x0,
553                 GetApiVersion   = 0x1,
554                 ForcePowerdown  = 0x8,
555                 RequestWakeup   = 0xa,
556                 ResetAssert     = 0x11,
557                 ResetGetStatus  = 0x12,
558                 ClockGetDivider = 0x28,
559                 PllGetParameter = 0x31,
560                 ApiMax          = 0x4a
561             }
562         };
563 
564         private enum PmuIpiChannel
565         {
566             PMU0 = 0,
567             PMU1 = 1,
568             PMU2 = 2,
569             PMU3 = 3
570         }
571 
572         // Right now we only need to handle calls to PM module
573         private enum PmuModule
574         {
575             PowerManagement = 0x0
576         }
577     }
578 }
579