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