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 
8 // uncomment the line below to get more detailed logs
9 // note: dumping packets may severely lower performance
10 // #define DEBUG_PACKETS
11 
12 using System;
13 using System.Collections.Generic;
14 using System.Linq;
15 using System.Text.RegularExpressions;
16 using System.Threading;
17 using Antmicro.Renode.Core;
18 using Antmicro.Renode.Core.Structure;
19 using Antmicro.Renode.Core.USB;
20 using Antmicro.Renode.Debugging;
21 using Antmicro.Renode.Logging;
22 using Antmicro.Renode.Utilities;
23 using Antmicro.Renode.Utilities.Packets;
24 
25 namespace Antmicro.Renode.Extensions.Utilities.USBIP
26 {
27     public static class USBIPServerExtensions
28     {
CreateUSBIPServer(this Emulation emulation, int port = 3240, string address = R, string name = R)29         public static void CreateUSBIPServer(this Emulation emulation, int port = 3240, string address = "127.0.0.1", string name = "usb")
30         {
31             var server = new USBIPServer(address, port);
32             server.Run();
33 
34             emulation.HostMachine.AddHostMachineElement(server, name);
35         }
36 
37         // this is just a simple wrapper method allowing to register devices from monitor
Register(this USBIPServer @this, IUSBDevice device, int? port = null)38         public static void Register(this USBIPServer @this, IUSBDevice device, int? port = null)
39         {
40             if(!port.HasValue)
41             {
42                 port = @this.Children.Any()
43                     ? @this.Children.Max(x => x.RegistrationPoint.Address) + 1
44                     : 0;
45             }
46 
47             @this.Register(device, new NumberRegistrationPoint<int>(port.Value));
48         }
49     }
50 
51     public class USBIPServer : SimpleContainerBase<IUSBDevice>, IHostMachineElement, IDisposable
52     {
USBIPServer(string address, int port)53         public USBIPServer(string address, int port)
54         {
55             this.port = port;
56 
57             server = new SocketServerProvider(false, serverName: "USBIP");
58             server.DataReceived += HandleIncomingData;
59             server.ConnectionClosed += Reset;
60 
61             // setting initial size is just an optimization
62             buffer = new List<byte>(Packet.CalculateLength<URBRequest>());
63             cancellationToken = new CancellationTokenSource();
64         }
65 
Dispose()66         public override void Dispose()
67         {
68             base.Dispose();
69             Shutdown();
70         }
71 
Run()72         public void Run()
73         {
74             server.Start(port);
75         }
76 
Reset()77         public void Reset()
78         {
79             state = State.WaitForCommand;
80             urbHeader = default(URBHeader);
81             additionalDataCount = 0;
82 
83             buffer.Clear();
84             cancellationToken = new CancellationTokenSource();
85         }
86 
SendResponse(IEnumerable<byte> bytes)87         private void SendResponse(IEnumerable<byte> bytes)
88         {
89             server.Send(bytes);
90 
91 #if DEBUG_PACKETS
92             this.Log(LogLevel.Noisy, "Count {0}: {1}", bytes.Count(), Misc.PrettyPrintCollectionHex(bytes));
93 #endif
94         }
95 
Shutdown()96         private void Shutdown()
97         {
98             this.Log(LogLevel.Info, "Shutting down the server...");
99 
100             cancellationToken.Cancel();
101             server.Stop();
102             buffer.Clear();
103         }
104 
HandleIncomingData(int b)105         private void HandleIncomingData(int b)
106         {
107 #if DEBUG_PACKETS
108             this.Log(LogLevel.Noisy, "Incoming byte: 0x{0:X}; state = {1}; buffer size = {2}", b, state, buffer.Count);
109 #endif
110 
111             if(b < 0)
112             {
113                 // the current connection is closed - reset the state and prepare for the next one
114                 Reset();
115                 return;
116             }
117 
118             buffer.Add((byte)b);
119 
120             switch(state)
121             {
122                 case State.WaitForCommand:
123                 {
124                     DebugHelper.Assert(buffer.Count <= Packet.CalculateLength<USBIP.Header>());
125                     if(buffer.Count == Packet.CalculateLength<USBIP.Header>())
126                     {
127                         var header = Packet.Decode<USBIP.Header>(buffer);
128                         buffer.Clear();
129                         this.Log(LogLevel.Debug, "Received USB/IP header: {0}", header.ToString());
130 
131                         switch(header.Command)
132                         {
133                             case USBIP.Command.ListDevices:
134                             {
135                                 SendResponse(HandleListDevicesCommand());
136 
137                                 state = State.WaitForCommand;
138                                 break;
139                             }
140 
141                             case USBIP.Command.AttachDevice:
142                             {
143                                 state = State.WaitForBusId;
144                                 break;
145                             }
146 
147                             default:
148                             {
149                                 this.Log(LogLevel.Error, "Unexpected packet command: 0x{0:X}", header.Command);
150                                 Shutdown();
151                                 break;
152                             }
153                         }
154                     }
155                     break;
156                 }
157 
158                 case State.WaitForBusId:
159                 {
160                     DebugHelper.Assert(buffer.Count <= Packet.CalculateLength<USBIP.AttachDeviceCommandDescriptor>());
161                     if(buffer.Count == Packet.CalculateLength<USBIP.AttachDeviceCommandDescriptor>())
162                     {
163                         var busId = System.Text.Encoding.ASCII.GetString(buffer.ToArray());
164                         buffer.Clear();
165 
166                         state = TryHandleDeviceAttachCommand(busId, out var response)
167                             ? State.WaitForURBHeader
168                             : State.WaitForCommand;
169 
170                         SendResponse(response);
171                     }
172                     break;
173                 }
174 
175                 case State.WaitForURBHeader:
176                 {
177                     DebugHelper.Assert(buffer.Count <= Packet.CalculateLength<URBHeader>());
178                     if(buffer.Count == Packet.CalculateLength<URBHeader>())
179                     {
180                         urbHeader = Packet.Decode<URBHeader>(buffer);
181                         this.Log(LogLevel.Debug, "Received URB header: {0}", urbHeader.ToString());
182                         buffer.Clear();
183 
184                         switch(urbHeader.Command)
185                         {
186                             case URBCommand.URBRequest:
187                                 state = State.WaitForURBRequest;
188                                 break;
189                             case URBCommand.Unlink:
190                                 state = State.HandleUnlinkCommand;
191                                 break;
192                             default:
193                                 this.Log(LogLevel.Error, "Unexpected URB command: 0x{0:X}", urbHeader.Command);
194                                 Shutdown();
195                                 break;
196                         }
197                     }
198                     break;
199                 }
200 
201                 case State.WaitForURBRequest:
202                 {
203                     if(buffer.Count < Packet.CalculateLength<URBRequest>() + additionalDataCount)
204                     {
205                         break;
206                     }
207 
208                     var packet = Packet.Decode<URBRequest>(buffer);
209                     if(additionalDataCount == 0)
210                     {
211                         this.Log(LogLevel.Debug, "Received URB request: {0}", packet.ToString());
212 
213                         if(urbHeader.Direction == URBDirection.Out && packet.TransferBufferLength > 0)
214                         {
215                             additionalDataCount = (int)packet.TransferBufferLength;
216                             this.Log(LogLevel.Debug, "Packet comes with {0} bytes of additional data. Waiting for it", additionalDataCount);
217                             break;
218                         }
219                     }
220                     else
221                     {
222                         if(urbHeader.Direction == URBDirection.Out && packet.TransferBufferLength > 0)
223                         {
224                             this.Log(LogLevel.Debug, "Collected {0} bytes of additional data - moving forward", additionalDataCount);
225                         }
226                     }
227 
228                     if(urbHeader.BusId != ExportedBusId)
229                     {
230                         this.Log(LogLevel.Warning, "URB command directed to a non-existing bus 0x{0:X}", urbHeader.BusId);
231                     }
232                     else if(!TryGetByAddress((int)urbHeader.DeviceId, out var device))
233                     {
234                         this.Log(LogLevel.Warning, "URB command directed to a non-existing device 0x{0:X}", urbHeader.DeviceId);
235                     }
236                     else if(urbHeader.EndpointNumber == 0)
237                     {
238                         // setup packet is passed in URB Request in a single `ulong` field
239                         var setupPacket = Packet.Decode<SetupPacket>(buffer, Packet.CalculateOffset<URBRequest>(nameof(URBRequest.Setup)));
240                         var additionalData = (additionalDataCount > 0)
241                             ? buffer.Skip(buffer.Count - additionalDataCount).Take(additionalDataCount).ToArray()
242                             : null;
243                         var replyHeader = urbHeader;
244                         device.USBCore.HandleSetupPacket(setupPacket, additionalData: additionalData, resultCallback: response =>
245                         {
246                             SendResponse(GenerateURBReply(replyHeader, packet, response));
247                         });
248                     }
249                     else
250                     {
251                         var ep = device.USBCore.GetEndpoint((int)urbHeader.EndpointNumber, urbHeader.Direction == URBDirection.Out ? Direction.HostToDevice : Direction.DeviceToHost);
252                         if(ep == null)
253                         {
254                             this.Log(LogLevel.Warning, "URB command directed to a non-existing endpoint 0x{0:X}", urbHeader.EndpointNumber);
255                         }
256                         else if(ep.Direction == Direction.DeviceToHost)
257                         {
258                             this.Log(LogLevel.Noisy, "Reading from endpoint #{0}", ep.Identifier);
259                             var response = ep.Read(packet.TransferBufferLength, cancellationToken.Token);
260 #if DEBUG_PACKETS
261                             this.Log(LogLevel.Noisy, "Count {0}: {1}", response.Length, Misc.PrettyPrintCollectionHex(response));
262 #endif
263                             SendResponse(GenerateURBReply(urbHeader, packet, response));
264                         }
265                         else
266                         {
267                             var additionalData = buffer.Skip(buffer.Count - additionalDataCount).Take(additionalDataCount).ToArray();
268 
269                             ep.WriteData(additionalData);
270                             SendResponse(GenerateURBReply(urbHeader, packet));
271                         }
272                     }
273 
274                     additionalDataCount = 0;
275                     state = State.WaitForURBHeader;
276                     buffer.Clear();
277 
278                     break;
279                 }
280 
281                 case State.HandleUnlinkCommand:
282                 {
283                     DebugHelper.Assert(buffer.Count <= Packet.CalculateLength<URBRequest>());
284                     if(buffer.Count == Packet.CalculateLength<URBRequest>())
285                     {
286                         // this command is practically ignored
287                         buffer.Clear();
288                         state = State.WaitForURBHeader;
289                     }
290                     break;
291                 }
292 
293                 default:
294                     throw new ArgumentException(string.Format("Unexpected state: {0}", state));
295             }
296         }
297 
GenerateURBReply(URBHeader hdr, URBRequest req, IEnumerable<byte> data = null)298         private IEnumerable<byte> GenerateURBReply(URBHeader hdr, URBRequest req, IEnumerable<byte> data = null)
299         {
300             var header = new URBHeader
301             {
302                 Command = URBCommand.URBReply,
303                 SequenceNumber = hdr.SequenceNumber,
304                 BusId = hdr.BusId,
305                 DeviceId = hdr.DeviceId,
306                 Direction = hdr.Direction,
307                 EndpointNumber = hdr.EndpointNumber,
308             };
309 
310             var reply = new URBReply
311             {
312                 // this is intentional:
313                 // - report size of TransferBufferLength when returning no additional data,
314                 // - report additional data size (and not TransferBufferLenght) otherwise
315                 ActualLength = (data == null)
316                     ? req.TransferBufferLength
317                     : (uint)data.Count()
318             };
319 
320             var result = Packet.Encode(header).Concat(Packet.Encode(reply)).AsEnumerable();
321             if(data != null)
322             {
323                 result = result.Concat(data);
324             }
325 
326             return result;
327         }
328 
329         // using this blocking helper method simplifies the logic of other methods
330         // + it seems to be harmless, as this logic is not executed as a result of
331         // intra-emulation communication (where it could lead to deadlocks)
HandleSetupPacketSync(IUSBDevice device, SetupPacket setupPacket)332         private byte[] HandleSetupPacketSync(IUSBDevice device, SetupPacket setupPacket)
333         {
334             byte[] result = null;
335 
336             var mre = new ManualResetEvent(false);
337             device.USBCore.HandleSetupPacket(setupPacket, received =>
338             {
339                 result = received;
340                 mre.Set();
341             });
342 
343             mre.WaitOne();
344             return result;
345         }
346 
ReadDeviceDescriptor(IUSBDevice device)347         private USB.DeviceDescriptor ReadDeviceDescriptor(IUSBDevice device)
348         {
349             var setupPacket = new SetupPacket
350             {
351                 Recipient = PacketRecipient.Device,
352                 Type = PacketType.Standard,
353                 Direction = Direction.DeviceToHost,
354                 Request = (byte)StandardRequest.GetDescriptor,
355                 Value = ((int)DescriptorType.Device << 8),
356                 Count = (ushort)Packet.CalculateLength<USB.DeviceDescriptor>()
357             };
358 
359             return Packet.Decode<USB.DeviceDescriptor>(HandleSetupPacketSync(device, setupPacket));
360         }
361 
ReadConfigurationDescriptor(IUSBDevice device, byte configurationId, out USB.InterfaceDescriptor[] interfaceDescriptors)362         private USB.ConfigurationDescriptor ReadConfigurationDescriptor(IUSBDevice device, byte configurationId, out USB.InterfaceDescriptor[] interfaceDescriptors)
363         {
364             var setupPacket = new SetupPacket
365             {
366                 Recipient = PacketRecipient.Device,
367                 Type = PacketType.Standard,
368                 Direction = Direction.DeviceToHost,
369                 Request = (byte)StandardRequest.GetDescriptor,
370                 Value = (ushort)(((int)DescriptorType.Configuration << 8) | configurationId),
371                 Count = (ushort)Packet.CalculateLength<USB.ConfigurationDescriptor>()
372             };
373             // first ask for the configuration descriptor non-recursively ...
374             var configurationDescriptorBytes = HandleSetupPacketSync(device, setupPacket);
375             var result = Packet.Decode<USB.ConfigurationDescriptor>(configurationDescriptorBytes);
376 
377             interfaceDescriptors = new USB.InterfaceDescriptor[result.NumberOfInterfaces];
378             // ... read the total length of a recursive structure ...
379             setupPacket.Count = result.TotalLength;
380             // ... and only then read the whole structure again.
381             var recursiveBytes = HandleSetupPacketSync(device, setupPacket);
382 
383             var currentOffset = Packet.CalculateLength<USB.ConfigurationDescriptor>();
384             for(var i = 0; i < interfaceDescriptors.Length; i++)
385             {
386                 // the second byte of each descriptor contains the type
387                 while(recursiveBytes[currentOffset + 1] != (byte)DescriptorType.Interface)
388                 {
389                     // the first byte of each descriptor contains the length in bytes
390                     currentOffset += recursiveBytes[currentOffset];
391                 }
392 
393                 interfaceDescriptors[i] = Packet.Decode<USB.InterfaceDescriptor>(recursiveBytes, currentOffset);
394                 // skip until the next interface descriptor
395                 currentOffset += Packet.CalculateLength<USB.InterfaceDescriptor>();
396             }
397 
398             return result;
399         }
400 
GenerateDeviceDescriptor(IUSBDevice device, uint deviceNumber, bool includeInterfaces)401         private IEnumerable<byte> GenerateDeviceDescriptor(IUSBDevice device, uint deviceNumber, bool includeInterfaces)
402         {
403             var deviceDescriptor = ReadDeviceDescriptor(device);
404             var interfaceDescriptors = new List<USB.InterfaceDescriptor>();
405 
406             if(includeInterfaces)
407             {
408                 for(var i = 0; i < deviceDescriptor.NumberOfConfigurations; i++)
409                 {
410                     ReadConfigurationDescriptor(device, (byte)i, out var ifaces);
411                     interfaceDescriptors.AddRange(ifaces);
412                 }
413             }
414 
415             var devDescriptor = new USBIP.DeviceDescriptor
416             {
417                 Path = new byte[256],
418                 BusId = new byte[32],
419 
420                 BusNumber = ExportedBusId,
421                 DeviceNumber = deviceNumber,
422                 Speed = (int)USBSpeed.High, // this is hardcoded, but I don't know if that's good
423 
424                 IdVendor = deviceDescriptor.VendorId,
425                 IdProduct = deviceDescriptor.ProductId,
426 
427                 DeviceClass = deviceDescriptor.Class,
428                 DeviceSubClass = deviceDescriptor.Subclass,
429                 DeviceProtocol = deviceDescriptor.Protocol,
430 
431                 NumberOfConfigurations = deviceDescriptor.NumberOfConfigurations,
432                 NumberOfInterfaces = (byte)interfaceDescriptors.Count
433             };
434 
435             SetText(devDescriptor.BusId, "{0}-{1}", ExportedBusId, deviceNumber);
436             SetText(devDescriptor.Path, "/renode/virtual/{0}-{1}", ExportedBusId, deviceNumber);
437 
438             var result = Packet.Encode(devDescriptor).AsEnumerable();
439 
440             foreach(var iface in interfaceDescriptors)
441             {
442                 var intDescriptor = new USBIP.InterfaceDescriptor
443                 {
444                     InterfaceClass = iface.Class,
445                     InterfaceSubClass = iface.Subclass,
446                     InterfaceProtocol = iface.Protocol
447                 };
448 
449                 result = result.Concat(Packet.Encode(intDescriptor));
450             }
451 
452             return result;
453         }
454 
SetText(byte[] destination, string format, params object[] param)455         private void SetText(byte[] destination, string format, params object[] param)
456         {
457             var textBuffer = System.Text.Encoding.ASCII.GetBytes(string.Format(format, param));
458             Array.Copy(textBuffer, destination, textBuffer.Length);
459         }
460 
HandleListDevicesCommand()461         private IEnumerable<byte> HandleListDevicesCommand()
462         {
463             var header = new USBIP.Header
464             {
465                 Version = ProtocolVersion,
466                 Command = USBIP.Command.ListDevicesReply,
467             };
468 
469             var regCount = new USBIP.DeviceListCount
470             {
471                 NumberOfExportedDevices = (uint)ChildCollection.Count
472             };
473 
474             var result = Packet.Encode(header).Concat(Packet.Encode(regCount));
475 
476             foreach(var child in ChildCollection)
477             {
478                 result = result.Concat(GenerateDeviceDescriptor(child.Value, (uint)child.Key, true));
479             }
480 
481             return result;
482         }
483 
TryHandleDeviceAttachCommand(string deviceIdString, out IEnumerable<byte> response)484         private bool TryHandleDeviceAttachCommand(string deviceIdString, out IEnumerable<byte> response)
485         {
486             var success = true;
487             var deviceId = 0;
488             IUSBDevice device = null;
489 
490             var m = Regex.Match(deviceIdString, "([0-9]+)-([0-9]+)");
491             if(m == null)
492             {
493                 this.Log(LogLevel.Warning, "Unexpected device string when handling attach command: {0}. It should be in format '1-4'", deviceIdString);
494                 success = false;
495             }
496             else
497             {
498                 var busId = int.Parse(m.Groups[1].Value);
499                 deviceId = int.Parse(m.Groups[2].Value);
500 
501                 if(busId != ExportedBusId)
502                 {
503                     this.Log(LogLevel.Warning, "Trying to attach to a non-existing bus 0x{0:X}", busId);
504                     success = false;
505                 }
506                 else if(!TryGetByAddress(deviceId, out device))
507                 {
508                     this.Log(LogLevel.Warning, "Trying to attach to a non-existing device 0x{0:X}", deviceId);
509                     success = false;
510                 }
511             }
512 
513             var header = new USBIP.Header
514             {
515                 Version = ProtocolVersion,
516                 Command = USBIP.Command.AttachDeviceReply,
517                 Status = success ? 0 : 1u
518             };
519 
520             response = Packet.Encode(header).AsEnumerable();
521             if(success)
522             {
523                 response = response.Concat(GenerateDeviceDescriptor(device, (uint)deviceId, false));
524             }
525 
526             return success;
527         }
528 
529         private State state;
530         private URBHeader urbHeader;
531         private int additionalDataCount;
532         private CancellationTokenSource cancellationToken;
533 
534         private readonly int port;
535         private readonly List<byte> buffer;
536         private readonly SocketServerProvider server;
537 
538         private const uint ExportedBusId = 1;
539         private const ushort ProtocolVersion = 0x0111;
540 
541         private enum USBSpeed
542         {
543             Unknown = 0,
544             Low = 1,
545             Full = 2,
546             High = 3,
547             Wireless = 4,
548             Super = 5,
549             SuperPlus = 6,
550         }
551 
552         private enum State
553         {
554             WaitForCommand,
555             WaitForBusId,
556             WaitForURBHeader,
557             HandleUnlinkCommand,
558             WaitForURBRequest,
559         }
560     }
561 }
562