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