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