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