1 // 2 // Copyright (c) 2010-2020 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 8 using System; 9 using System.Collections.Generic; 10 using System.Linq; 11 using System.Threading; 12 13 using Antmicro.Renode.Core; 14 using Antmicro.Renode.Core.USB; 15 using Antmicro.Renode.Core.USB.CDC; 16 using Antmicro.Renode.Exceptions; 17 using Antmicro.Renode.Extensions.Utilities.USBIP; 18 using Antmicro.Renode.Logging; 19 using Antmicro.Renode.Peripherals; 20 using Antmicro.Renode.Peripherals.CPU; 21 using Antmicro.Renode.Utilities; 22 23 namespace Antmicro.Renode.Integrations 24 { 25 public static class ArduinoLoaderExtensions 26 { CreateArduinoLoader(this USBIPServer server, CortexM cpu, ulong binaryLoadAddress = 0x10000, int? port = null, string name = null)27 public static void CreateArduinoLoader(this USBIPServer server, CortexM cpu, ulong binaryLoadAddress = 0x10000, int? port = null, string name = null) 28 { 29 var loader = new ArduinoLoader(cpu, binaryLoadAddress); 30 server.Register(loader, port); 31 32 var emulation = EmulationManager.Instance.CurrentEmulation; 33 emulation.ExternalsManager.AddExternal(loader, name ?? "arduinoLoader"); 34 } 35 } 36 37 public class ArduinoLoader : IUSBDevice, IExternal 38 { ArduinoLoader(CortexM cpu, ulong binaryLoadAddress = 0x10000)39 public ArduinoLoader(CortexM cpu, ulong binaryLoadAddress = 0x10000) 40 { 41 USBEndpoint interruptEndpoint = null; 42 43 this.cpu = cpu; 44 this.machine = cpu.GetMachine(); 45 this.binaryLoadAddress = binaryLoadAddress; 46 47 USBCore = new USBDeviceCore(this, 48 classCode: USBClassCode.CommunicationsCDCControl, 49 maximalPacketSize: PacketSize.Size16, 50 vendorId: 0x2341, 51 productId: 0x805a, 52 deviceReleaseNumber: 0x0100) 53 .WithConfiguration(configure: c => c 54 .WithInterface(new Antmicro.Renode.Core.USB.CDC.Interface(this, 55 identifier: 0, 56 subClassCode: 0x2, 57 protocol: 0x1, 58 descriptors: new [] { 59 new FunctionalDescriptor(CdcFunctionalDescriptorType.Interface, CdcFunctionalDescriptorSubtype.Header, 0x10, 0x01), 60 new FunctionalDescriptor(CdcFunctionalDescriptorType.Interface, CdcFunctionalDescriptorSubtype.CallManagement, 0x01, 0x01), 61 new FunctionalDescriptor(CdcFunctionalDescriptorType.Interface, CdcFunctionalDescriptorSubtype.AbstractControlManagement, 0x02), 62 new FunctionalDescriptor(CdcFunctionalDescriptorType.Interface, CdcFunctionalDescriptorSubtype.Union, 0x00, 0x01) 63 }) 64 .WithEndpoint(Direction.DeviceToHost, 65 EndpointTransferType.Interrupt, 66 maximumPacketSize: 0x08, 67 interval: 0x0a, 68 createdEndpoint: out interruptEndpoint)) 69 .WithInterface(new USBInterface(this, 70 identifier: 1, 71 classCode: USBClassCode.CDCData, 72 subClassCode: 0x0, 73 protocol: 0x0) 74 .WithEndpoint(id: 2, 75 direction: Direction.HostToDevice, 76 transferType: EndpointTransferType.Bulk, 77 maximumPacketSize: 0x20, 78 interval: 0x0, 79 createdEndpoint: out hostToDeviceEndpoint) 80 .WithEndpoint(id: 3, 81 direction: Direction.DeviceToHost, 82 transferType: EndpointTransferType.Bulk, 83 maximumPacketSize: 0x20, 84 interval: 0x0, 85 createdEndpoint: out deviceToHostEndpoint))); 86 87 // when asked, say that nothing interesting happened 88 interruptEndpoint.NonBlocking = true; 89 deviceToHostEndpoint.NonBlocking = true; 90 hostToDeviceEndpoint.DataWritten += HandleData; 91 92 sramBuffer = new byte[BufferSize]; 93 flashBuffer = new byte[BufferSize]; 94 95 binarySync = new AutoResetEvent(false); 96 } 97 WaitForBinary(int timeoutInSeconds, bool autoConnect = false, int port = 0)98 public string WaitForBinary(int timeoutInSeconds, bool autoConnect = false, int port = 0) 99 { 100 if(autoConnect) 101 { 102 SudoTools.EnsureSudoExecute($"usbip attach -r 127.0.0.1 -b 1-{port}"); 103 } 104 105 if(!binarySync.WaitOne(timeoutInSeconds * 1000)) 106 { 107 throw new RecoverableException("Received no binary in the selected time window!"); 108 } 109 110 machine.SystemBus.WriteBytes(flashBuffer, binaryLoadAddress, binaryLength, context: cpu); 111 cpu.VectorTableOffset = (uint)binaryLoadAddress; 112 113 return $"Binary of size {binaryLength} bytes loaded at 0x{binaryLoadAddress:X}"; 114 } 115 Reset()116 public void Reset() 117 { 118 USBCore.Reset(); 119 120 Array.Clear(sramBuffer, 0, sramBuffer.Length); 121 Array.Clear(flashBuffer, 0, flashBuffer.Length); 122 123 flashOffset = 0; 124 sramReadOffset = 0; 125 sramWriteOffset = 0; 126 127 sramBytesLeft = 0; 128 binaryLength = 0; 129 130 binarySync.Reset(); 131 } 132 HandleData(byte[] input)133 private void HandleData(byte[] input) 134 { 135 if(sramBytesLeft > 0) 136 { 137 StoreToSRAMBuffer(input); 138 } 139 else 140 { 141 Decode(input); 142 } 143 } 144 Decode(byte[] d)145 private void Decode(byte[] d) 146 { 147 this.Log(LogLevel.Noisy, "Decoding input: {0}", System.Text.ASCIIEncoding.ASCII.GetString(d)); 148 149 uint value = 0; 150 uint savedValue = 0; 151 var command = Command.None; 152 153 for(var i = 0; i < d.Length; i++) 154 { 155 if(d[i] >= '0' && d[i] <= '9') 156 { 157 AppendNibble(ref value, (byte)(d[i] - '0')); 158 } 159 else if(d[i] >= 'a' && d[i] <= 'f') 160 { 161 AppendNibble(ref value, (byte)(d[i] - 'a')); 162 } 163 else if(d[i] >= 'A' && d[i] <= 'F') 164 { 165 AppendNibble(ref value, (byte)(d[i] - 'A')); 166 } 167 else 168 { 169 switch((char)d[i]) 170 { 171 case '#': // End of command 172 HandleCommand((Command)command, savedValue, value); 173 savedValue = 0; 174 value = 0; 175 break; 176 177 case (char)Command.DumpSRAMBufferToFLASH: 178 case (char)Command.SetSRAMBuffer: 179 case (char)Command.GetHWInfo: 180 case (char)Command.SwitchToNonInteractiveMode: 181 case (char)Command.GetSWVersion: 182 case (char)Command.EraseFlash: 183 case (char)Command.ExecuteLoadedApp: 184 case (char)Command.WriteByte: 185 case (char)Command.WriteWord: 186 case (char)Command.WriteDoubleWord: 187 command = (Command)d[i]; 188 break; 189 190 case ',': 191 savedValue = value; 192 value = 0; 193 break; 194 195 default: 196 this.Log(LogLevel.Warning, "Unknown command {0} (0x{0:X})", (Command)d[i]); 197 return; 198 } 199 } 200 } 201 } 202 HandleCommand(Command c, uint arg0, uint arg1)203 private void HandleCommand(Command c, uint arg0, uint arg1) 204 { 205 this.Log(LogLevel.Noisy, "Handling command {0} (0x{0:X}) with args [0]: 0x{1:X} [1]: 0x{2:X}", c, arg0, arg1); 206 207 switch(c) 208 { 209 case Command.SetSRAMBuffer: 210 { 211 sramWriteOffset = arg0; 212 sramBytesLeft = arg1; 213 214 // no response 215 break; 216 } 217 218 case Command.DumpSRAMBufferToFLASH: 219 { 220 if(arg1 != 0) 221 { 222 CopyFromSRAMToFlash(arg1); 223 } 224 else 225 { 226 sramReadOffset = arg0; 227 } 228 229 SendResponse("Y"); 230 break; 231 } 232 233 case Command.ExecuteLoadedApp: 234 { 235 binarySync.Set(); 236 237 // no response 238 break; 239 } 240 241 case Command.SwitchToNonInteractiveMode: 242 SendResponse(string.Empty); 243 break; 244 245 case Command.GetSWVersion: 246 SendResponse("Arduino Bootloader (SAM-BA extended) 2.0 [Arduino:IKXYZ]"); 247 break; 248 249 case Command.GetHWInfo: 250 SendResponse("nRF52840-QIAA"); 251 break; 252 253 case Command.EraseFlash: 254 Array.Clear(flashBuffer, 0, flashBuffer.Length); 255 SendResponse("X"); 256 break; 257 258 case Command.WriteByte: 259 machine.SystemBus.WriteByte(arg0, (byte)arg1); 260 break; 261 262 case Command.WriteWord: 263 machine.SystemBus.WriteWord(arg0, (ushort)arg1); 264 break; 265 266 case Command.WriteDoubleWord: 267 machine.SystemBus.WriteDoubleWord(arg0, arg1); 268 break; 269 270 default: 271 this.Log(LogLevel.Warning, "Unsupported command {0} (0x{0:X})", c); 272 break; 273 } 274 } 275 SendResponse(string s)276 private void SendResponse(string s) 277 { 278 deviceToHostEndpoint.HandlePacket(System.Text.ASCIIEncoding.ASCII.GetBytes(s + "\n\r")); 279 } 280 AppendNibble(ref uint val, byte b)281 private void AppendNibble(ref uint val, byte b) 282 { 283 val <<= 4; 284 val |= b & 0xFu; 285 } 286 StoreToSRAMBuffer(byte[] d)287 private void StoreToSRAMBuffer(byte[] d) 288 { 289 var len = (uint)d.Length; 290 if(sramWriteOffset + d.Length > sramBuffer.Length) 291 { 292 len = (uint)sramBuffer.Length - sramWriteOffset; 293 this.Log(LogLevel.Warning, "Received {0} bytes of data to store in the SRAM buffer, but there is space only for {1}. Ignoring the rest - it can cause problems!", d.Length, len); 294 } 295 296 for(var i = 0; i < len; i++) 297 { 298 sramBuffer[sramWriteOffset + i] = d[i]; 299 } 300 sramBytesLeft -= len; 301 sramWriteOffset += len; 302 } 303 CopyFromSRAMToFlash(uint len)304 private void CopyFromSRAMToFlash(uint len) 305 { 306 if(flashOffset + len > flashBuffer.Length) 307 { 308 var origLen = len; 309 len = (uint)flashBuffer.Length - flashOffset; 310 this.Log(LogLevel.Warning, "Asked to write {0} bytes to flash buffer, but there is space only for {1}. Ignoring the rest - it can cause problems!", len, origLen); 311 } 312 313 for(var i = 0; i < len; i++) 314 { 315 flashBuffer[flashOffset + i] = sramBuffer[sramReadOffset + i]; 316 } 317 flashOffset += len; 318 binaryLength += len; 319 } 320 321 public USBDeviceCore USBCore { get; } 322 323 private USBEndpoint hostToDeviceEndpoint; 324 private USBEndpoint deviceToHostEndpoint; 325 326 private byte[] sramBuffer; 327 private byte[] flashBuffer; 328 329 private uint flashOffset; 330 private uint sramWriteOffset; 331 private uint sramReadOffset; 332 333 private uint sramBytesLeft; 334 private uint binaryLength; 335 336 private readonly AutoResetEvent binarySync; 337 private readonly CortexM cpu; 338 private readonly IMachine machine; 339 private readonly ulong binaryLoadAddress; 340 341 private const int BufferSize = 0xf0000; 342 343 private enum Command : byte 344 { 345 None = 0, 346 347 DumpSRAMBufferToFLASH = (byte)'Y', 348 SetSRAMBuffer = (byte)'S', 349 GetHWInfo = (byte)'I', 350 SwitchToNonInteractiveMode = (byte)'N', 351 GetSWVersion = (byte)'V', 352 EraseFlash = (byte)'X', 353 ExecuteLoadedApp = (byte)'K', 354 WriteByte = (byte)'O', 355 WriteWord = (byte)'H', 356 WriteDoubleWord = (byte)'W', 357 } 358 } 359 } 360