1 // 2 // Copyright (c) 2010-2024 Antmicro 3 // Copyright (c) 2011-2015 Realtime Embedded 4 // 5 // This file is licensed under the MIT License. 6 // Full license text is available in 'licenses/MIT.txt'. 7 // 8 using System; 9 using Antmicro.Renode.Peripherals.Bus; 10 using Antmicro.Renode.Core; 11 using Antmicro.Renode.Logging; 12 using Antmicro.Renode.Core.Extensions; 13 14 namespace Antmicro.Renode.Peripherals.Miscellaneous 15 { 16 public sealed class SEMA4 : IBytePeripheral, IKnownSize, IDoubleWordPeripheral 17 { SEMA4(IMachine machine)18 public SEMA4(IMachine machine) 19 { 20 sysbus = machine.GetSystemBus(this); 21 irqLock = new object(); 22 locks = new Lock[NumberOfEntries]; 23 for(var i = 0; i < locks.Length; i++) 24 { 25 locks[i] = new Lock(this, i); 26 } 27 CPU0 = new GPIO(); 28 CPU1 = new GPIO(); 29 } 30 31 public GPIO CPU0 { get; private set; } 32 33 public GPIO CPU1 { get; private set; } 34 ReadByte(long offset)35 public byte ReadByte(long offset) 36 { 37 if(offset < 16) 38 { 39 return locks[(int)offset].Read(); 40 } 41 return this.ReadByteUsingDoubleWord(offset); 42 } 43 WriteByte(long offset, byte value)44 public void WriteByte(long offset, byte value) 45 { 46 if(offset < 16) 47 { 48 locks[(int)offset].Write(value); 49 } 50 else 51 { 52 this.WriteByteUsingDoubleWord(offset, value); 53 } 54 } 55 ReadDoubleWord(long offset)56 public uint ReadDoubleWord(long offset) 57 { 58 lock(irqLock) 59 { 60 switch((Register)offset) 61 { 62 case Register.InterruptNotificationEnable1: 63 return (uint)(ApplyVybridErrata(interruptEnabled0) << 16); 64 case Register.InterruptNotificationEnable2: 65 return (uint)(ApplyVybridErrata(interruptEnabled0) << 16); 66 case Register.InterruptNotificationStatus1: 67 return (uint)(ApplyVybridErrata(notifyCPU0) << 16); 68 case Register.InterruptNotificationStatus2: 69 return (uint)(ApplyVybridErrata(notifyCPU1) << 16); 70 default: 71 if(offset < 16) 72 { 73 return this.ReadDoubleWordUsingByte(offset); 74 } 75 this.LogUnhandledRead(offset); 76 return 0; 77 } 78 } 79 } 80 WriteDoubleWord(long offset, uint value)81 public void WriteDoubleWord(long offset, uint value) 82 { 83 lock(irqLock) 84 { 85 ushort valueToWrite; 86 switch((Register)offset) 87 { 88 case Register.InterruptNotificationEnable1: 89 valueToWrite = ApplyVybridErrata((ushort)(value >> 16)); 90 interruptEnabled0 = valueToWrite; 91 this.NoisyLog("Interrupt enabled for CPU0 set to 0x{0:X}.", valueToWrite); 92 break; 93 case Register.InterruptNotificationEnable2: 94 valueToWrite = ApplyVybridErrata((ushort)(value >> 16)); 95 interruptEnabled1 = valueToWrite; 96 this.NoisyLog("Interrupt enabled for CPU1 set to 0x{0:X}.", valueToWrite); 97 break; 98 default: 99 this.WriteDoubleWordUsingByte(offset, value); 100 break; 101 } 102 } 103 } 104 Reset()105 public void Reset() 106 { 107 foreach(var @lock in locks) 108 { 109 @lock.Reset(); 110 } 111 lock(irqLock) 112 { 113 interruptEnabled0 = 0; 114 interruptEnabled1 = 0; 115 notifyCPU0 = 0; 116 notifyCPU1 = 0; 117 RefreshInterrupts(); 118 } 119 } 120 121 public long Size 122 { 123 get 124 { 125 return 0x108; 126 } 127 } 128 RefreshInterrupts()129 private void RefreshInterrupts() 130 { 131 var effectiveNotifyCPU0 = notifyCPU0 & interruptEnabled0; 132 var effectiveNotifyCPU1 = notifyCPU1 & interruptEnabled1; 133 this.NoisyLog("Effective interrupt state: CPU0: 0x{0:X} (0x{1:X} & 0x{2:X}), CPU1: 0x{3:X} (0x{4:X} & 0x{5:X}).", effectiveNotifyCPU0, notifyCPU0, 134 interruptEnabled0, effectiveNotifyCPU1, notifyCPU1, interruptEnabled1); 135 CPU0.Set(effectiveNotifyCPU0 != 0); 136 CPU1.Set(effectiveNotifyCPU1 != 0); 137 } 138 ApplyVybridErrata(ushort value)139 private static ushort ApplyVybridErrata(ushort value) 140 { 141 var result = 0; 142 for(var i = 0; i < 16; i++) 143 { 144 result |= (value & (1 << VybridErrataIndices[i])) != 0 ? (1 << i) : 0; 145 } 146 return (ushort)result; 147 } 148 149 private readonly Lock[] locks; 150 private ushort interruptEnabled0; 151 private ushort interruptEnabled1; 152 private ushort notifyCPU0; 153 private ushort notifyCPU1; 154 private readonly IBusController sysbus; 155 private readonly object irqLock; 156 private const int NumberOfEntries = 16; 157 private static readonly int[] VybridErrataIndices = { 12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3 }; 158 159 private enum Register 160 { 161 InterruptNotificationEnable1 = 0x40, 162 InterruptNotificationEnable2 = 0x48, 163 InterruptNotificationStatus1 = 0x80, 164 InterruptNotificationStatus2 = 0x88, 165 } 166 167 private class Lock 168 { Lock(SEMA4 sema, int number)169 public Lock(SEMA4 sema, int number) 170 { 171 this.sema = sema; 172 this.number = number; 173 } 174 Read()175 public byte Read() 176 { 177 lock(this) 178 { 179 return (byte)currentValue; 180 } 181 } 182 Write(byte value)183 public void Write(byte value) 184 { 185 if(value > 2) 186 { 187 sema.Log(LogLevel.Warning, "Gate {0}: unsupported value {1} written to lock.", number, value); 188 return; 189 // ignore such writes 190 } 191 if(!sema.sysbus.TryGetCurrentCPU(out var cpu)) 192 { 193 sema.Log(LogLevel.Warning, "Gate {0}: write outside the CPU thread.", number); 194 return; 195 // write that did not come from CPU 196 } 197 int id = (int)cpu.MultiprocessingId + 1; 198 lock(this) 199 { 200 if(value == 0) 201 { 202 // unlock 203 if(currentValue == id) 204 { 205 currentValue = 0; 206 sema.DebugLog("Gate {0}: unlock by CPU {1}.", number, id - 1); 207 lock(sema.irqLock) 208 { 209 sema.NoisyLog("CPU waiting for lock: {0}.", cpuWaitingForLock); 210 switch(cpuWaitingForLock) 211 { 212 case 1: 213 sema.notifyCPU0 |= (ushort)(1 << number); 214 break; 215 case 2: 216 sema.notifyCPU1 |= (ushort)(1 << number); 217 break; 218 } 219 sema.RefreshInterrupts(); 220 } 221 } 222 else 223 { 224 // unlock failed 225 sema.Log(LogLevel.Warning, "Gate {0}: unsuccesful unlock try by CPU {1}.", number, id - 1); 226 } 227 } 228 else if(value == id && (currentValue == 0 || currentValue == id)) 229 { 230 // lock 231 currentValue = value; 232 sema.DebugLog("Gate {0}: lock by CPU {1}.", number, id - 1); 233 if(cpuWaitingForLock != 0) 234 { 235 lock(sema.irqLock) 236 { 237 // interrupt is always deasserted in that case 238 sema.notifyCPU0 &= (ushort)~(1 << number); 239 sema.notifyCPU1 &= (ushort)~(1 << number); 240 sema.RefreshInterrupts(); 241 } 242 if(cpuWaitingForLock == id) 243 { 244 cpuWaitingForLock = 0; 245 } 246 } 247 } 248 else 249 { 250 // lock failed 251 sema.DebugLog("Gate {0}: unsuccessful lock try by CPU {1}.", number, id - 1); 252 cpuWaitingForLock = id; 253 } 254 } 255 } 256 Reset()257 public void Reset() 258 { 259 lock(this) 260 { 261 currentValue = 0; 262 } 263 } 264 265 public int CPUWaitingForLock 266 { 267 get 268 { 269 return cpuWaitingForLock; 270 } 271 } 272 273 public bool InterruptActive { get; private set; } 274 275 private uint currentValue; 276 private int cpuWaitingForLock; 277 private readonly SEMA4 sema; 278 private readonly int number; 279 } 280 } 281 } 282 283