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