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