1 //
2 // Copyright (c) 2010-2018 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.Collections.Generic;
9 using System.Dynamic;
10 using System.IO;
11 using System.Linq;
12 using Antmicro.Renode.Core;
13 using Antmicro.Renode.Core.Structure;
14 using Antmicro.Renode.Core.USB;
15 using Antmicro.Renode.Core.USB.MSC;
16 using Antmicro.Renode.Core.USB.MSC.BOT;
17 using Antmicro.Renode.Exceptions;
18 using Antmicro.Renode.Extensions.Utilities.USBIP;
19 using Antmicro.Renode.Logging;
20 using Antmicro.Renode.Storage;
21 using Antmicro.Renode.Storage.SCSI;
22 using Antmicro.Renode.Storage.SCSI.Commands;
23 using Antmicro.Renode.Utilities;
24 using Antmicro.Renode.Utilities.Packets;
25 
26 namespace Antmicro.Renode.Peripherals.USB
27 {
28     public static class USBPendriveExtensions
29     {
PendriveFromFile(this IMachine machine, string file, string name, IPeripheralRegister<IUSBDevice, NumberRegistrationPoint<int>> attachTo, int port, bool persistent = true)30         public static void PendriveFromFile(this IMachine machine, string file, string name, IPeripheralRegister<IUSBDevice, NumberRegistrationPoint<int>> attachTo, int port, bool persistent = true)
31         {
32             var pendrive = new USBPendrive(file, persistent: persistent);
33             attachTo.Register(pendrive, new NumberRegistrationPoint<int>(port));
34             machine.SetLocalName(pendrive, name);
35         }
36 
PendriveFromFile(this USBIPServer usbController, string file, bool persistent = true, int? port = null)37         public static void PendriveFromFile(this USBIPServer usbController, string file, bool persistent = true, int? port = null)
38         {
39             var pendrive = new USBPendrive(file, persistent: persistent);
40             usbController.Register(pendrive, port);
41         }
42     }
43 
44     public class USBPendrive : IUSBDevice, IDisposable
45     {
USBPendrive(string imageFile, long? size = null, bool persistent = false, uint blockSize = 512)46         public USBPendrive(string imageFile, long? size = null, bool persistent = false, uint blockSize = 512)
47         {
48             BlockSize = blockSize;
49             dataBackend = DataStorage.Create(imageFile, size, persistent);
50 
51             var sizeMisalignment = dataBackend.Length % blockSize;
52             if(sizeMisalignment != 0)
53             {
54                 dataBackend.SetLength(dataBackend.Length + (blockSize - sizeMisalignment));
55                 this.Log(LogLevel.Warning, "Underlying data size extended by {0} bytes to align it to the block size ({1} bytes)", blockSize - sizeMisalignment, blockSize);
56             }
57 
58             USBCore = new USBDeviceCore(this)
59                 .WithConfiguration(configure: c => c.WithInterface<Core.USB.MSC.Interface>(
60                     (byte)Core.USB.MSC.Subclass.ScsiTransparentCommandSet,
61                     (byte)Core.USB.MSC.Protocol.BulkOnlyTransport,
62                     configure: x =>
63                         x.WithEndpoint(
64                             Direction.HostToDevice,
65                             EndpointTransferType.Bulk,
66                             MaximumPacketSize,
67                             0x10,
68                             out hostToDeviceEndpoint)
69                         .WithEndpoint(
70                             Direction.DeviceToHost,
71                             EndpointTransferType.Bulk,
72                             MaximumPacketSize,
73                             0x10,
74                             out deviceToHostEndpoint))
75                 );
76 
77                 hostToDeviceEndpoint.DataWritten += HandleInput;
78         }
79 
Dispose()80         public void Dispose()
81         {
82             dataBackend.Dispose();
83         }
84 
HandleInput(byte[] packet)85         public void HandleInput(byte[] packet)
86         {
87             this.Log(LogLevel.Debug, "Received a packet of {0} bytes in {1} mode.", packet.Length, mode);
88             switch(mode)
89             {
90                 case Mode.Command:
91                     HandleCommand(packet);
92                     break;
93 
94                 case Mode.Data:
95                     HandleData(packet);
96                     break;
97 
98                 default:
99                     throw new ArgumentException($"Unexpected mode: {mode}");
100             }
101         }
102 
Reset()103         public void Reset()
104         {
105             USBCore.Reset();
106             mode = Mode.Command;
107             dataBackend.Position = 0;
108             bytesToWrite = 0;
109             writeCommandDescriptor = null;
110         }
111 
112         public USBDeviceCore USBCore { get; }
113         public uint BlockSize { get; }
114 
SendResult(BulkOnlyTransportCommandBlockWrapper commandBlockWrapper, CommandStatus status = CommandStatus.Success, uint dataResidue = 0)115         private void SendResult(BulkOnlyTransportCommandBlockWrapper commandBlockWrapper, CommandStatus status = CommandStatus.Success, uint dataResidue = 0)
116         {
117             var response = new CommandStatusWrapper(commandBlockWrapper.Tag, dataResidue, status);
118             this.Log(LogLevel.Debug, "Sending result: {0}", response);
119             deviceToHostEndpoint.HandlePacket(Packet.Encode(response));
120         }
121 
SendData(byte[] data)122         private void SendData(byte[] data)
123         {
124             this.Log(LogLevel.Debug, "Sending data of length {0}.", data.Length);
125             deviceToHostEndpoint.HandlePacket(data);
126         }
127 
HandleData(byte[] packet)128         private void HandleData(byte[] packet)
129         {
130             if(packet.Length > bytesToWrite)
131             {
132                 this.Log(LogLevel.Warning, "Received more data ({0} bytes) than expected ({1} bytes). Aborting the operation", packet.Length, bytesToWrite);
133                 SendResult(writeCommandWrapper, CommandStatus.Failure);
134                 return;
135             }
136 
137             this.Log(LogLevel.Noisy, "Writing {0} bytes of data at address 0x{1:x}", packet.Length, dataBackend.Position);
138             dataBackend.Write(packet, 0, packet.Length);
139             bytesToWrite -= (uint)packet.Length;
140             if(bytesToWrite == 0)
141             {
142                 SendResult(writeCommandWrapper);
143                 this.Log(LogLevel.Noisy, "All data written, switching to Command mode");
144                 mode = Mode.Command;
145             }
146         }
147 
HandleCommand(byte[] packet)148         private void HandleCommand(byte[] packet)
149         {
150             if(!BulkOnlyTransportCommandBlockWrapper.TryParse(packet, out var commandBlockWrapper))
151             {
152                 this.Log(LogLevel.Warning, "Broken SCSI command block wrapper detected. Ignoring it.");
153                 return;
154             }
155 
156             this.Log(LogLevel.Noisy, "Parsed command block wrapper: {0}", commandBlockWrapper);
157             var command = SCSICommandDescriptorBlock.DecodeCommand(packet, BulkOnlyTransportCommandBlockWrapper.CommandOffset);
158             this.Log(LogLevel.Noisy, "Decoded command: {0}", command);
159             switch(command)
160             {
161                 case SCSICommand.TestUnitReady:
162                     SendResult(commandBlockWrapper);
163                     break;
164                 case SCSICommand.Inquiry:
165                     // this is just an empty stub
166                     SendData(new byte[36]);
167                     SendResult(commandBlockWrapper);
168                     break;
169                 case SCSICommand.ReadCapacity:
170                     var result = new ReadCapcity10Result
171                     {
172                         BlockLengthInBytes = BlockSize,
173                         ReturnedLogicalBlockAddress = (uint)(dataBackend.Length / BlockSize - 1)
174                     };
175                     SendData(Packet.Encode(result));
176                     SendResult(commandBlockWrapper);
177                     break;
178                 case SCSICommand.Read10:
179                     var cmd = Packet.DecodeDynamic<IReadWrite10Command>(packet, BulkOnlyTransportCommandBlockWrapper.CommandOffset);
180                     this.Log(LogLevel.Noisy, "Command args: LogicalBlockAddress: 0x{0:x}, TransferLength: {1}", (uint)cmd.LogicalBlockAddress, (ushort)cmd.TransferLength);
181                     var bytesCount = (int)(cmd.TransferLength * BlockSize);
182                     var readPosition = (long)cmd.LogicalBlockAddress * BlockSize;
183                     dataBackend.Position = readPosition;
184                     var data = dataBackend.ReadBytes(bytesCount);
185                     this.Log(LogLevel.Noisy, "Reading {0} bytes from address 0x{1:x}", bytesCount, readPosition);
186                     SendData(data);
187                     SendResult(commandBlockWrapper, CommandStatus.Success, (uint)(commandBlockWrapper.DataTransferLength - data.Length));
188                     break;
189                 case SCSICommand.Write10:
190                     // the actual write will be triggered after receiving the next packet with data
191                     // we should not send result now
192                     writeCommandWrapper = commandBlockWrapper;
193                     writeCommandDescriptor = Packet.DecodeDynamic<IReadWrite10Command>(packet, BulkOnlyTransportCommandBlockWrapper.CommandOffset);
194                     var position = (long)((dynamic)writeCommandDescriptor).LogicalBlockAddress * BlockSize;
195                     dataBackend.Position = position;
196                     bytesToWrite = (uint)((dynamic)writeCommandDescriptor).TransferLength * BlockSize;
197                     this.Log(LogLevel.Noisy, "Preparing to write {1} bytes of data at address: 0x{0:x}", dataBackend.Position, bytesToWrite);
198                     mode = Mode.Data;
199                     break;
200                 case SCSICommand.ModeSense6:
201                     // this is just an empty stub
202                     SendData(new byte[192]);
203                     SendResult(commandBlockWrapper);
204                     break;
205                 case SCSICommand.RequestSense:
206                     // this is just an empty stub
207                     SendData(new byte[commandBlockWrapper.DataTransferLength]);
208                     SendResult(commandBlockWrapper);
209                     break;
210                 default:
211                     this.Log(LogLevel.Warning, "Unsupported SCSI command: {0}", command);
212                     SendResult(commandBlockWrapper, CommandStatus.Failure, commandBlockWrapper.DataTransferLength);
213                     break;
214             }
215         }
216 
217         private uint bytesToWrite;
218         private Mode mode;
219         private USBEndpoint hostToDeviceEndpoint;
220         private USBEndpoint deviceToHostEndpoint;
221         private BulkOnlyTransportCommandBlockWrapper writeCommandWrapper;
222         private object writeCommandDescriptor;
223         private readonly Stream dataBackend;
224 
225         // 64 is a maximum value for USB 2.0 low/full-speed devices;
226         // 512 is allowed only for high-speed devices that
227         // might not be supported by all USB host controllers (i.e., MAX3421E)
228         private const int MaximumPacketSize = 64;
229 
230         private enum Mode
231         {
232             Command,
233             Data
234         }
235     }
236 }
237