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.Linq; 10 using Antmicro.Renode.Logging; 11 using Antmicro.Renode.Utilities; 12 13 namespace Antmicro.Renode.Core.USB 14 { 15 public class USBEndpoint : DescriptorProvider 16 { USBEndpoint(IUSBDevice device, byte identifier, Direction direction, EndpointTransferType transferType, short maximumPacketSize, byte interval)17 public USBEndpoint(IUSBDevice device, 18 byte identifier, 19 Direction direction, 20 EndpointTransferType transferType, 21 short maximumPacketSize, 22 byte interval) : base(7, (byte)DescriptorType.Endpoint) 23 { 24 this.device = device; 25 26 Identifier = identifier; 27 Direction = direction; 28 TransferType = transferType; 29 MaximumPacketSize = maximumPacketSize; 30 Interval = interval; 31 32 buffer = new Queue<IEnumerable<byte>>(); 33 packetCreator = new PacketCreator(HandlePacket); 34 } 35 36 public event Action<byte[]> DataWritten 37 { 38 add 39 { 40 if(Direction != Direction.HostToDevice) 41 { 42 throw new ArgumentException("Reading from this descriptor is not supported"); 43 } 44 dataWritten += value; 45 } 46 remove 47 { 48 dataWritten -= value; 49 } 50 } 51 Reset()52 public void Reset() 53 { 54 lock(buffer) 55 { 56 buffer.Clear(); 57 } 58 } 59 WriteData(byte[] packet)60 public void WriteData(byte[] packet) 61 { 62 if(Direction != Direction.HostToDevice) 63 { 64 device.Log(LogLevel.Warning, "Trying to write to a Read-Only endpoint"); 65 return; 66 } 67 68 device.Log(LogLevel.Noisy, "Writing {0} bytes of data", packet.Length); 69 #if DEBUG_PACKETS 70 device.Log(LogLevel.Noisy, Misc.PrettyPrintCollectionHex(packet)); 71 #endif 72 73 var dw = dataWritten; 74 if(dw == null) 75 { 76 device.Log(LogLevel.Warning, "There is no data handler currently registered. Ignoring the written data!"); 77 return; 78 } 79 80 dw(packet); 81 } 82 PreparePacket()83 public PacketCreator PreparePacket() 84 { 85 if(Direction != Direction.DeviceToHost) 86 { 87 throw new ArgumentException("Writing to this descriptor is not supported"); 88 } 89 90 return packetCreator; 91 } 92 SetDataReadCallbackOneShot(Action<USBEndpoint, IEnumerable<byte>> callback)93 public void SetDataReadCallbackOneShot(Action<USBEndpoint, IEnumerable<byte>> callback) 94 { 95 lock(buffer) 96 { 97 device.Log(LogLevel.Noisy, "Data read callback set"); 98 if(buffer.Count > 0) 99 { 100 device.Log(LogLevel.Noisy, "Data read callback fired"); 101 #if DEBUG_PACKETS 102 device.Log(LogLevel.Noisy, "Sending back {0} bytes: {1}", buffer.Peek().Count(), Misc.PrettyPrintCollectionHex(buffer.Peek())); 103 #endif 104 callback(this, buffer.Dequeue()); 105 } 106 else 107 { 108 if(NonBlocking) 109 { 110 device.Log(LogLevel.Noisy, "No data to read in non-blocking mode - returning an empty buffer"); 111 callback(this, new byte[0]); 112 } 113 else 114 { 115 dataCallback = callback; 116 } 117 } 118 } 119 } 120 Read(uint limit, System.Threading.CancellationToken cancellationToken)121 public byte[] Read(uint limit, System.Threading.CancellationToken cancellationToken) 122 { 123 var result = Enumerable.Empty<byte>(); 124 125 var endOfPacketDetected = false; 126 while(!endOfPacketDetected 127 && (!cancellationToken.IsCancellationRequested) 128 && (limit == 0 || result.Count() < limit)) 129 { 130 var mre = new System.Threading.ManualResetEvent(false); 131 SetDataReadCallbackOneShot((e, bytes) => 132 { 133 var arr = bytes.ToArray(); 134 result = result.Concat(arr); 135 if(arr.Length < MaximumPacketSize) 136 { 137 endOfPacketDetected = true; 138 } 139 mre.Set(); 140 }); 141 142 System.Threading.WaitHandle.WaitAny(new System.Threading.WaitHandle[] { cancellationToken.WaitHandle, mre }); 143 } 144 145 if(result.Count() > limit) 146 { 147 Logger.Log(LogLevel.Warning, "Read more data from the USB endpoint ({0}) than limit ({1}). Some bytes will be dropped, expect problems!", result.Count(), limit); 148 result = result.Take((int)limit); 149 } 150 151 return result.ToArray(); 152 } 153 ToString()154 public override string ToString() 155 { 156 return $"[EP: id={Identifier}, dir={Direction}, type={TransferType}, mps={MaximumPacketSize}, int={Interval}]"; 157 } 158 159 public byte Identifier { get; } 160 public Direction Direction { get; } 161 public EndpointTransferType TransferType { get; } 162 public short MaximumPacketSize { get; } 163 public byte Interval { get; } 164 165 public bool NonBlocking { get; set; } 166 FillDescriptor(BitStream buffer)167 protected override void FillDescriptor(BitStream buffer) 168 { 169 buffer 170 .Append((byte)(((int)Direction << 7) | Identifier)) 171 /* TODO: here we ignore isochornous fields */ 172 .Append((byte)TransferType) 173 .Append(MaximumPacketSize) 174 .Append(Interval); 175 } 176 HandlePacket(ICollection<byte> data)177 public void HandlePacket(ICollection<byte> data) 178 { 179 lock(buffer) 180 { 181 device.Log(LogLevel.Noisy, "Handling data packet of size: {0}", data.Count); 182 #if DEBUG_PACKETS 183 device.Log(LogLevel.Noisy, Misc.PrettyPrintCollectionHex(data)); 184 #endif 185 186 // split packet into chunks of size not exceeding `MaximumPacketSize` 187 var offset = 0; 188 while(offset < data.Count) 189 { 190 var toTake = Math.Min(MaximumPacketSize, data.Count - offset); 191 var chunk = data.Skip(offset).Take(toTake); 192 offset += toTake; 193 buffer.Enqueue(chunk); 194 #if DEBUG_PACKETS 195 device.Log(LogLevel.Noisy, "Enqueuing chunk of {0} bytes: {1}", chunk.Count(), Misc.PrettyPrintCollectionHex(chunk)); 196 #endif 197 198 if(offset == data.Count && toTake == MaximumPacketSize) 199 { 200 // in order to indicate the end of a packet 201 // the chunk should be shorter than `MaximumPacketSize`; 202 // in case there is no data to send, empty chunk 203 // is generated 204 buffer.Enqueue(new byte[0]); 205 device.Log(LogLevel.Noisy, "Enqueuing end of packet marker"); 206 } 207 } 208 209 if(dataCallback != null) 210 { 211 dataCallback(this, buffer.Dequeue()); 212 dataCallback = null; 213 } 214 } 215 } 216 217 private event Action<byte[]> dataWritten; 218 private Action<USBEndpoint, IEnumerable<byte>> dataCallback; 219 220 private readonly Queue<IEnumerable<byte>> buffer; 221 private readonly PacketCreator packetCreator; 222 private readonly IUSBDevice device; 223 224 public class PacketCreator : IDisposable 225 { PacketCreator(Action<ICollection<byte>> dataReadyCallback)226 public PacketCreator(Action<ICollection<byte>> dataReadyCallback) 227 { 228 this.dataReadyCallback = dataReadyCallback; 229 localBuffer = new List<byte>(); 230 } 231 Add(byte b)232 public void Add(byte b) 233 { 234 localBuffer.Add(b); 235 } 236 Add(uint u)237 public void Add(uint u) 238 { 239 foreach(var b in BitConverter.GetBytes(u).Reverse()) 240 { 241 localBuffer.Add(b); 242 } 243 } 244 Dispose()245 public void Dispose() 246 { 247 dataReadyCallback(localBuffer); 248 localBuffer = new List<byte>(); 249 } 250 251 private List<byte> localBuffer; 252 private readonly Action<ICollection<byte>> dataReadyCallback; 253 } 254 255 public enum EndpointSynchronizationType 256 { 257 NoSynchronization = 0, 258 Asynchronous = 1, 259 Adaptive = 2, 260 Synchronous = 3 261 } 262 263 public enum EndpointIsoModeType 264 { 265 DataEndpoint = 0, 266 FeedbackEndpoint = 1, 267 ExplicitFeedbackEndpoint = 2 268 } 269 } 270 } 271