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