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 #if PLATFORM_LINUX 8 using System; 9 using System.Collections.Generic; 10 using System.Runtime.InteropServices; 11 using System.Threading; 12 using Antmicro.Migrant; 13 using Antmicro.Renode.Core; 14 using Antmicro.Renode.Core.CAN; 15 using Antmicro.Renode.Core.Structure; 16 using Antmicro.Renode.Exceptions; 17 using Antmicro.Renode.Logging; 18 using Antmicro.Renode.Utilities; 19 using Antmicro.Renode.Utilities.Packets; 20 21 namespace Antmicro.Renode.Peripherals.CAN 22 { 23 public static class SocketCANBridgeExtensions 24 { CreateSocketCANBridge(this IMachine machine, string name, string canInterfaceName = R, bool ensureFdFrames = false, bool ensureXlFrames = false)25 public static void CreateSocketCANBridge(this IMachine machine, string name, string canInterfaceName = "vcan0", bool ensureFdFrames = false, bool ensureXlFrames = false) 26 { 27 var bridge = new SocketCANBridge(canInterfaceName, ensureFdFrames, ensureXlFrames); 28 machine.RegisterAsAChildOf(machine.SystemBus, bridge, NullRegistrationPoint.Instance); 29 machine.SetLocalName(bridge, name); 30 } 31 } 32 33 public class SocketCANBridge : ICAN 34 { SocketCANBridge(string canInterfaceName = R, bool ensureFdFrames = false, bool ensureXlFrames = false)35 public SocketCANBridge(string canInterfaceName = "vcan0", bool ensureFdFrames = false, bool ensureXlFrames = false) 36 { 37 if(canInterfaceName.Length >= InterfaceRequest.InterfaceNameSize) 38 { 39 throw new ConstructionException($"Parameter '{nameof(canInterfaceName)}' is too long, name of CAN device \"{canInterfaceName}\" exceeds {InterfaceRequest.InterfaceNameSize - 1} bytes"); 40 } 41 42 canSocket = LibCWrapper.Socket(ProtocolFamilyCan, SocketTypeRaw, ProtocolFamilyCanRaw); 43 if(canSocket == -1) 44 { 45 throw new ConstructionException($"Could not create a socket: {LibCWrapper.GetLastError()}"); 46 } 47 maximumTransmissionUnit = ClassicalSocketCANFrame.Size; 48 49 if(TryEnableSocketOption(ensureFdFrames, SocketOptionFdFrames, "FD CAN frames")) 50 { 51 maximumTransmissionUnit = FlexibleSocketCANFrame.Size; 52 } 53 54 if(TryEnableSocketOption(ensureXlFrames, SocketOptionXlFrames, "XL CAN frames")) 55 { 56 maximumTransmissionUnit = XLSocketCANFrame.Size; 57 } 58 59 var request = new InterfaceRequest(canInterfaceName); 60 if(LibCWrapper.Ioctl(canSocket, SocketConfigurationControlFindIndex, ref request) == -1) 61 { 62 throw new ConstructionException($"Could not get the \"{canInterfaceName}\" interface index: {LibCWrapper.GetLastError()}"); 63 } 64 65 var address = new SocketAddressCan(request.InterfaceIndex); 66 if(LibCWrapper.Bind(canSocket, address, Marshal.SizeOf(typeof(SocketAddressCan))) == -1) 67 { 68 throw new ConstructionException($"Binding the CAN socket to the interface failed: {LibCWrapper.GetLastError()}"); 69 } 70 71 StartTransmitThread(); 72 } 73 Reset()74 public void Reset() 75 { 76 // intentionally left empty 77 } 78 OnFrameReceived(CANMessageFrame message)79 public void OnFrameReceived(CANMessageFrame message) 80 { 81 this.Log(LogLevel.Debug, "Received {0}", message); 82 83 byte[] frame; 84 try 85 { 86 frame = message.ToSocketCAN(true); 87 } 88 catch(RecoverableException e) 89 { 90 this.Log(LogLevel.Warning, "Failed to create SocketCAN from {0}: {1}", message, e.Message); 91 return; 92 } 93 94 var handle = GCHandle.Alloc(frame, GCHandleType.Pinned); 95 try 96 { 97 if(!LibCWrapper.Write(canSocket, handle.AddrOfPinnedObject(), frame.Length)) 98 { 99 this.Log(LogLevel.Error, "Encountered an error while writing to the socket: {0}", LibCWrapper.GetLastError()); 100 } 101 } 102 finally 103 { 104 handle.Free(); 105 } 106 } 107 108 public event Action<CANMessageFrame> FrameSent; 109 TryEnableSocketOption(bool ensure, int option, string optionName)110 private bool TryEnableSocketOption(bool ensure, int option, string optionName) 111 { 112 var optionValue = 1; 113 if(LibCWrapper.SetSocketOption(canSocket, SocketOptionLevelCanRaw, option, ref optionValue) != -1) 114 { 115 return true; 116 } 117 118 var error = Marshal.GetLastWin32Error(); 119 if(ensure || error != ProtocolNotAvailableError) 120 { 121 throw new ConstructionException($"Could not enable {optionName} on the socket: {LibCWrapper.Strerror(error)}"); 122 } 123 124 this.Log(LogLevel.Info, "Attempted to enable {0}, but it's not supported by this host", optionName); 125 return false; 126 } 127 StartTransmitThread()128 private void StartTransmitThread() 129 { 130 cancellationTokenSource = new CancellationTokenSource(); 131 thread = new Thread(() => TransmitLoop(cancellationTokenSource.Token)) 132 { 133 Name = $"{nameof(SocketCANBridge)} transmit thread", 134 IsBackground = true 135 }; 136 thread.Start(); 137 } 138 TransmitLoop(CancellationToken token)139 private void TransmitLoop(CancellationToken token) 140 { 141 Func<bool> isCancellationRequested = () => token.IsCancellationRequested; 142 var buffer = new List<byte>(); 143 while(true) 144 { 145 if(maximumTransmissionUnit - buffer.Count <= 0) 146 { 147 throw new Exception("Unreachable"); 148 } 149 150 var data = LibCWrapper.Read(canSocket, maximumTransmissionUnit - buffer.Count, ReadSocketTimeout, isCancellationRequested); 151 if(token.IsCancellationRequested) 152 { 153 return; 154 } 155 if(data == null) 156 { 157 continue; 158 } 159 160 buffer.AddRange(data); 161 162 if(!buffer.TryDecodeAsSocketCANFrame(out var frame, false)) 163 { 164 // not enough bytes 165 continue; 166 } 167 buffer.RemoveRange(0, frame.Size); 168 this.Log(LogLevel.Noisy, "Frame read from socket: {0}", frame); 169 170 if(!CANMessageFrame.TryFromSocketCAN(frame, out var message)) 171 { 172 this.Log(LogLevel.Warning, "Failed to convert SocketCAN frame to CANMessageFrame"); 173 continue; 174 } 175 176 this.Log(LogLevel.Debug, "Transmitting {0}", message); 177 FrameSent?.Invoke(message); 178 } 179 } 180 181 private int canSocket; 182 [Transient] 183 private CancellationTokenSource cancellationTokenSource; 184 [Transient] 185 private Thread thread; 186 187 private int maximumTransmissionUnit; 188 189 // PF_CAN 190 private const int ProtocolFamilyCan = 29; 191 // SOCK_RAW 192 private const int SocketTypeRaw = 3; 193 // CAN_RAW 194 private const int ProtocolFamilyCanRaw = 1; 195 // SIOCGIFINDEX 196 private const int SocketConfigurationControlFindIndex = 0x8933; 197 // SOL_CAN_RAW 198 private const int SocketOptionLevelCanRaw = 100 + ProtocolFamilyCanRaw; 199 // CAN_RAW_FD_FRAMES 200 private const int SocketOptionFdFrames = 5; 201 // CAN_RAW_XL_FRAMES 202 private const int SocketOptionXlFrames = 7; 203 // ENOPROTOOPT 204 private const int ProtocolNotAvailableError = 92; 205 private const int ReadSocketTimeout = 1000; 206 } 207 } 208 #endif 209