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.Threading;
9 using System.Runtime.InteropServices;
10 using System.Collections.Concurrent;
11 using Antmicro.Renode.Debugging;
12 using Antmicro.Renode.Logging;
13 using Antmicro.Renode.Exceptions;
14 using Antmicro.Renode.Utilities.Binding;
15 using Antmicro.Renode.Plugins.CoSimulationPlugin.Connection.Protocols;
16 
17 namespace Antmicro.Renode.Plugins.CoSimulationPlugin.Connection
18 {
19     public class LibraryConnection: ICoSimulationConnection, IEmulationElement
20     {
LibraryConnection(IEmulationElement parentElement, int timeout, Action<ProtocolMessage> receiveAction)21         public LibraryConnection(IEmulationElement parentElement, int timeout, Action<ProtocolMessage> receiveAction)
22         {
23             this.parentElement = parentElement;
24             this.timeout = timeout;
25             receivedHandler = receiveAction;
26             mainReceived = new AutoResetEvent(initialState: false);
27             receiveQueue = new BlockingCollection<ProtocolMessage>();
28             senderData = new BlockingCollection<string>();
29             peripheralActive = new CancellationTokenSource();
30             nativeLock = new object();
31         }
32 
Dispose()33         public void Dispose()
34         {
35             Abort();
36             binder?.Dispose();
37             Marshal.FreeHGlobal(mainResponsePointer);
38             mainResponsePointer = IntPtr.Zero;
39             Marshal.FreeHGlobal(senderResponsePointer);
40             senderResponsePointer = IntPtr.Zero;
41         }
42 
TrySendMessage(ProtocolMessage message)43         public bool TrySendMessage(ProtocolMessage message)
44         {
45             lock(nativeLock)
46             {
47                 Marshal.StructureToPtr(message, mainResponsePointer, true);
48                 handleRequest(mainResponsePointer);
49             }
50             return true;
51         }
52 
TryRespond(ProtocolMessage message)53         public bool TryRespond(ProtocolMessage message)
54         {
55             try
56             {
57                 receiveQueue.Add(message, peripheralActive.Token);
58             }
59             catch(OperationCanceledException)
60             {
61                 return false;
62             }
63 
64             return true;
65         }
66 
TryReceiveMessage(out ProtocolMessage message)67         public bool TryReceiveMessage(out ProtocolMessage message)
68         {
69             if(mainReceived.WaitOne(timeout))
70             {
71                 DebugHelper.Assert(receivedMessage.HasValue);
72                 message = receivedMessage.Value;
73                 receivedMessage = null;
74                 return true;
75             }
76 
77             message = default(ProtocolMessage);
78             return false;
79         }
80 
Connect()81         public void Connect()
82         {
83             IsConnected = true;
84         }
85 
HandleMessage()86         public void HandleMessage()
87         {
88             // intentionally left empty
89         }
90 
Abort()91         public void Abort()
92         {
93             peripheralActive.Cancel();
94             IsConnected = false;
95         }
96 
97         [Export]
HandleMainMessage(IntPtr received)98         public void HandleMainMessage(IntPtr received)
99         {
100             // Main is used when Renode initiates communication.
101             DebugHelper.Assert(!receivedMessage.HasValue);
102             receivedMessage = (ProtocolMessage)Marshal.PtrToStructure(received, typeof(ProtocolMessage));
103             mainReceived.Set();
104         }
105 
106         [Export]
HandleSenderMessage(IntPtr received)107         public void HandleSenderMessage(IntPtr received)
108         {
109             // Sender is used when peripheral initiates communication.
110             try
111             {
112                 var message = (ProtocolMessage)Marshal.PtrToStructure(received, typeof(ProtocolMessage));
113                 if(message.ActionId == ActionType.LogMessage && (int)message.Address > 0)
114                 {
115                     // ProtocolMessage doesn't allow for larger then 8 bytes data transfer, so LogMessage is
116                     // treated as a special case, where:
117                     // if Address is 0 then Data caries logLevel
118                     // otherwise Address is a length of a cstring pointed to by Data
119                     senderData.Add(Marshal.PtrToStringAuto((IntPtr)message.Data, (int)message.Address), peripheralActive.Token);
120                     return;
121                 }
122                 HandleReceived(message);
123             }
124             catch(OperationCanceledException)
125             {
126                 return;
127             }
128         }
129 
130         [Export]
Receive(IntPtr messagePtr)131         public void Receive(IntPtr messagePtr)
132         {
133             try
134             {
135                 var message = receiveQueue.Take(peripheralActive.Token);
136                 Marshal.StructureToPtr(message, messagePtr, false);
137             }
138             catch(OperationCanceledException)
139             {
140                 return;
141             }
142         }
143 
144         public bool IsConnected { get; private set; }
145 
146         public string Context
147         {
148             get
149             {
150                 return this.context;
151             }
152             set
153             {
154                 if(IsConnected)
155                 {
156                     throw new RecoverableException("Context cannot be modified while connected");
157                 }
158                 this.context = (value == "") ? null : value;
159             }
160         }
161 
162         public string SimulationFilePath
163         {
164             get
165             {
166                 return simulationFilePath;
167             }
168             set
169             {
170                 if(value == null)
171                 {
172                     throw new ArgumentException($"Cannot find library {value}");
173                 }
174                 lock(nativeLock)
175                 {
176                     try
177                     {
178                         simulationFilePath = value;
179                         binder = new NativeBinder(this, value);
180                         if(this.context != null)
181                         {
182                             IntPtr pContext = Marshal.StringToHGlobalAnsi(this.context);
183                             initializeContext(pContext);
184                             Marshal.FreeHGlobal(pContext);
185                         }
186                         initializeNative();
187                         mainResponsePointer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ProtocolMessage)));
188                         senderResponsePointer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(ProtocolMessage)));
189                         resetPeripheral();
190                     }
191                     catch(Exception e)
192                     {
193                         var info = "Error starting cosimulated peripheral!\n" + e.Message;
194                         parentElement.Log(LogLevel.Error, info);
195                         throw new RecoverableException(info);
196                     }
197                 }
198             }
199         }
200 
HandleReceived(ProtocolMessage message)201         private void HandleReceived(ProtocolMessage message)
202         {
203             switch(message.ActionId)
204             {
205                 case ActionType.LogMessage:
206                     try
207                     {
208                         var logMessage = senderData.Take(peripheralActive.Token);
209                         parentElement.Log((LogLevel)(int)message.Data, logMessage);
210                     }
211                     catch(OperationCanceledException)
212                     {
213                         return;
214                     }
215                     break;
216                 default:
217                     receivedHandler(message);
218                     break;
219             }
220         }
221 
222         private string simulationFilePath;
223         private string context;
224         private NativeBinder binder;
225         private IntPtr mainResponsePointer;
226         private IntPtr senderResponsePointer;
227         private ProtocolMessage? receivedMessage;
228         private IEmulationElement parentElement;
229         private Action<ProtocolMessage> receivedHandler;
230 
231         private readonly AutoResetEvent mainReceived;
232         private readonly CancellationTokenSource peripheralActive;
233         private readonly BlockingCollection<ProtocolMessage> receiveQueue;
234         private readonly BlockingCollection<string> senderData;
235         private readonly int timeout;
236         private readonly object nativeLock;
237 
238 #pragma warning disable 649
239         [Import(UseExceptionWrapper = false)]
240         private Action<IntPtr> handleRequest;
241         [Import(UseExceptionWrapper = false, Optional = true)]
242         private Action<IntPtr> initializeContext;
243         [Import(UseExceptionWrapper = false)]
244         private Action initializeNative;
245         [Import(UseExceptionWrapper = false)]
246         public Action resetPeripheral;
247 #pragma warning restore 649
248     }
249 }
250