1 //
2 // Copyright (c) 2010-2022 Antmicro
3 // Copyright (c) 2011-2015 Realtime Embedded
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 using System;
9 using System.Collections.Concurrent;
10 using System.Collections.Generic;
11 using System.Diagnostics;
12 using System.IO;
13 using System.Linq;
14 using System.Text;
15 using System.Threading;
16 using Antmicro.Migrant;
17 using Antmicro.Migrant.Hooks;
18 using Antmicro.Renode.Core;
19 using Antmicro.Renode.Logging;
20 using Antmicro.Renode.Peripherals.Bus;
21 using Antmicro.Renode.Utilities;
22 #if !PLATFORM_WINDOWS
23 using Mono.Unix;
24 using Mono.Unix.Native;
25 #endif
26 using Antmicro.Renode.UserInterface;
27 
28 namespace Antmicro.Renode.Peripherals.Miscellaneous
29 {
30     [Icon("controller")]
31     [AllowedTranslations(AllowedTranslation.ByteToDoubleWord)]
32     public class EmulatorController : IDoubleWordPeripheral, IKnownSize
33     {
EmulatorController(IMachine machine)34         public EmulatorController(IMachine machine)
35         {
36             this.machine = machine;
37             stopwatch = new Stopwatch();
38             stringRegister = new byte[StringRegisterSize];
39             fileRegister = new byte[FileRegisterSize];
40             dictionary = new ConcurrentDictionary<string, string>();
41         }
42 
ReadDoubleWord(long offset)43         public uint ReadDoubleWord(long offset)
44         {
45             if((Register)offset == Register.Activate)
46             {
47                 return Magic + Version;
48             }
49             if(activated)
50             {
51                 return HandleRead(offset);
52             }
53             this.Log(LogLevel.Warning, "Trying to read without prior activation, offset {0}.", offset);
54             return 0;
55         }
56 
WriteDoubleWord(long offset, uint value)57         public void WriteDoubleWord(long offset, uint value)
58         {
59             if((Register)offset == Register.Activate)
60             {
61                 TryActivate(value);
62                 return;
63             }
64             if(activated)
65             {
66                 HandleWrite(offset, value);
67             }
68             else
69             {
70                 this.Log(LogLevel.Warning, "Trying to write without prior activation, value {0} @ 0x{1:X}.", value, offset);
71             }
72         }
73 
Reset()74         public void Reset()
75         {
76             activated = false;
77             state = State.Usual;
78         }
79 
80         public long Size
81         {
82             get
83             {
84                 return FileRegisterEnd;
85             }
86         }
87 
88         public string this[string key]
89         {
90             get
91             {
92                 string value;
93                 if(!dictionary.TryGetValue(key, out value))
94                 {
95                     throw new KeyNotFoundException();
96                 }
97                 return value;
98             }
99             set
100             {
101                 dictionary.AddOrUpdate(key, x => value, (x, y) => value);
102             }
103         }
104 
TryActivate(uint value)105         private void TryActivate(uint value)
106         {
107             if(value == Magic + Version)
108             {
109                 activated = true;
110                 this.Log(LogLevel.Info, "Activated.");
111             }
112             else
113             {
114                 this.Log(LogLevel.Warning,
115                     "Write at activate register, but incorrent MAGIC value 0x{0:X}, should be 0x{1:X}.",
116                     value, Magic + Version);
117             }
118         }
119 
HandleRead(long offset)120         private uint HandleRead(long offset)
121         {
122             switch((Register)offset)
123             {
124 #if !PLATFORM_WINDOWS
125             case Register.ReceiveFileFromEmulator:
126                 return HandleReceiveFile();
127 #endif
128             case Register.SendFileToEmulator:
129                 return HandleSendFile();
130             case Register.SendReceiveController:
131                 return HandleReadPacket();
132             case Register.GetOrSet:
133                 return HandleGet();
134             case Register.List:
135                 return HandleList();
136             default:
137                 if(offset >= StringRegisterStart && offset < StringRegisterEnd)
138                 {
139                     return HandleArrayRead(offset - StringRegisterStart, stringRegister);
140                 }
141                 if(offset >= FileRegisterStart && offset < FileRegisterEnd)
142                 {
143                     return HandleArrayRead(offset - FileRegisterStart, fileRegister);
144                 }
145                 this.LogUnhandledRead(offset);
146                 return 0;
147             }
148         }
149 
HandleWrite(long offset, uint value)150         private void HandleWrite(long offset, uint value)
151         {
152             switch((Register)offset)
153             {
154             case Register.Save:
155                 HandleSave(value);
156                 break;
157             case Register.Load:
158                 HandleLoad(value);
159                 break;
160             case Register.SendReceiveController:
161                 HandleWritePacket(value);
162                 break;
163             case Register.Date:
164                 HandleDate();
165                 break;
166             case Register.MeasureTime:
167                 HandleTimeMeasure((TimeMeasurementOperation)value);
168                 break;
169             case Register.GetOrSet:
170                 HandleSet();
171                 break;
172             default:
173                 if(offset >= StringRegisterStart && offset < StringRegisterEnd)
174                 {
175                     HandleArrayWrite(offset - StringRegisterStart, value, stringRegister);
176                     return;
177                 }
178                 if(offset >= FileRegisterStart && offset < FileRegisterEnd)
179                 {
180                     HandleArrayWrite(offset - FileRegisterStart, value, fileRegister);
181                     return;
182                 }
183                 this.LogUnhandledWrite(offset, value);
184                 break;
185             }
186         }
187 
HandleList()188         private uint HandleList()
189         {
190             if(!keyListPosition.HasValue)
191             {
192                 keyListPosition = 0;
193                 keys = dictionary.Keys.ToArray();
194             }
195             if(keyListPosition.Value >= keys.Length)
196             {
197                 keyListPosition = null;
198                 keys = null;
199                 return 0;
200             }
201             SetCurrentStringRegister(keys[keyListPosition.Value]);
202             keyListPosition++;
203             return 1;
204         }
205 
HandleGet()206         private uint HandleGet()
207         {
208             var key = GetCurrentStringRegister();
209             string value;
210             if(!dictionary.TryGetValue(key, out value))
211             {
212                 return 0;
213             }
214             SetCurrentStringRegister(value);
215             return 1;
216         }
217 
HandleSet()218         private void HandleSet()
219         {
220             switch(state)
221             {
222             case State.Usual:
223                 currentKeyToSet = GetCurrentStringRegister();
224                 state = State.SetValue;
225                 break;
226             case State.SetValue:
227                 this[currentKeyToSet] = GetCurrentStringRegister();
228                 state = State.Usual;
229                 break;
230             default:
231                 throw new InvalidOperationException("Improper state while setting a value.");
232             }
233         }
234 
HandleDate()235         private void HandleDate()
236         {
237             SetCurrentStringRegister(string.Format("{0:yyyy.MM.dd-HH:mm:ss}", machine.RealTimeClockDateTime));
238         }
239 
HandleTimeMeasure(TimeMeasurementOperation operation)240         private void HandleTimeMeasure(TimeMeasurementOperation operation)
241         {
242             switch(operation)
243             {
244             case TimeMeasurementOperation.Start:
245                 stopwatch.Start();
246                 this.Log(LogLevel.Info, "Time measurement started.");
247                 break;
248             case TimeMeasurementOperation.Stop:
249                 stopwatch.Stop();
250                 this.Log(LogLevel.Info, "Time measurement finished. Elapsed {0}s = {1}", Misc.NormalizeDecimal(stopwatch.Elapsed.TotalSeconds), stopwatch.Elapsed);
251                 break;
252             case TimeMeasurementOperation.Reset:
253                 if(stopwatch.IsRunning)
254                 {
255                     stopwatch.Restart();
256                 }
257                 else
258                 {
259                     stopwatch.Reset();
260                 }
261                 this.Log(LogLevel.Info, "Time measurement reseted.");
262                 break;
263             default:
264                 this.Log(LogLevel.Warning, "Invalid value written to time measurement register, ignoring.");
265                 break;
266             }
267         }
268 #if !PLATFORM_WINDOWS
HandleReceiveFile()269         private uint HandleReceiveFile()
270         {
271             var transferFileName = GetCurrentStringRegister();
272             try
273             {
274                 OpenStreamForReading(transferFileName);
275                 state = State.FileReceive;
276                 this.Log(LogLevel.Info, "Sending file {0} to emulation started.", transferStream.Name);
277                 var info = new UnixFileInfo(transferStream.Name).FileAccessPermissions;
278                 return (uint)info;
279             }
280             catch(IOException e)
281             {
282                 HandleException(e);
283                 return 0;
284             }
285         }
286 #endif
287 
HandleSendFile()288         private uint HandleSendFile()
289         {
290             var transferFileName = GetCurrentStringRegister();
291             try
292             {
293                 OpenStreamForWriting(transferFileName);
294                 state = State.FileSend;
295                 this.Log(LogLevel.Info, "Receiving file {0} from emulation started.", transferStream.Name);
296                 return 0;
297             }
298             catch(IOException e)
299             {
300                 HandleException(e);
301                 return 1;
302             }
303         }
304 
HandleReadPacket()305         private uint HandleReadPacket()
306         {
307             try
308             {
309                 if(state != State.FileReceive)
310                 {
311                     this.Log(LogLevel.Error, "HandleReadPacket called in an improper state.", state);
312                     return 0;
313                 }
314                 var retValue = (uint)transferStream.Read(fileRegister, 0, (int)(FileRegisterEnd - FileRegisterStart));
315                 if(retValue == 0)
316                 {
317                     state = State.Usual;
318                     this.Log(LogLevel.Info,
319                         "Sending file {0} to emulation ended, {1}B transmitted.",
320                         transferStream.Name, Misc.NormalizeBinary(transferStream.Position));
321                     transferStream.Close();
322                 }
323                 this.NoisyLog("Prepared packet of data to read by guest of size {0}B.",
324                     Misc.NormalizeBinary(retValue));
325                 return retValue;
326 
327             }
328             catch(IOException e)
329             {
330                 HandleException(e);
331                 return 0;
332             }
333         }
334 
HandleWritePacket(uint value)335         private void HandleWritePacket(uint value)
336         {
337             try
338             {
339                 if(state == State.ReceivePermisions)
340                 {
341 #if !PLATFORM_WINDOWS
342                     Syscall.chmod(transferStream.Name, (FilePermissions)value);
343 #else
344                     this.Log(LogLevel.Warning, "Setting file permissions in not supported in Windows.");
345 #endif
346                     state = State.Usual;
347                     return;
348                 }
349                 if(state != State.FileSend)
350                 {
351                     this.Log(LogLevel.Error, "HandleWritePacket called in improper state.",
352                         state);
353                     return;
354                 }
355                 if(value == 0)
356                 {
357                     state = State.ReceivePermisions;
358                     this.Log(LogLevel.Info,
359                         "Receiving file {0} from emulation ended, {1}B transmitted.",
360                         transferStream.Name, Misc.NormalizeBinary(transferStream.Position));
361                     transferStream.Close();
362                     return;
363                 }
364                 this.NoisyLog("Received packet of data to write of size {0}B.", Misc.NormalizeBinary(value));
365                 transferStream.Write(fileRegister, 0, (int)value);
366             }
367             catch(IOException e)
368             {
369                 HandleException(e);
370             }
371         }
372 
GetCurrentStringRegister()373         private string GetCurrentStringRegister()
374         {
375             var count = stringRegister.IndexOf(x => x == 0);
376             if(count == -1)
377             {
378                 count = stringRegister.Length;
379             }
380             return Encoding.ASCII.GetString(stringRegister, 0, count);
381         }
382 
SetCurrentStringRegister(string value)383         private void SetCurrentStringRegister(string value)
384         {
385             var bytes = Encoding.ASCII.GetBytes(value);
386             if(bytes.Length - 1 > StringRegisterEnd - StringRegisterStart)
387             {
388                 throw new ArgumentException(string.Format("String size cannot exceed {0} bytes.", StringRegisterEnd - StringRegisterStart - 1));
389             }
390             bytes.CopyTo(stringRegister, 0);
391             stringRegister[bytes.Length] = 0;
392         }
393 
HandleException(IOException e)394         private void HandleException(IOException e)
395         {
396             this.Log(LogLevel.Error, "IOException: {0}.", e.Message);
397         }
398 
OpenStreamForReading(string transferFileName)399         private void OpenStreamForReading(string transferFileName)
400         {
401             transferStream = new FileStream(transferFileName, FileMode.Open, FileAccess.Read, FileShare.Read);
402         }
403 
OpenStreamForWriting(string transferFileName)404         private void OpenStreamForWriting(string transferFileName)
405         {
406             transferStream = new FileStream(transferFileName, FileMode.Create, FileAccess.ReadWrite);
407         }
408 
HandleArrayWrite(long offset, uint value, byte[] array)409         private static void HandleArrayWrite(long offset, uint value, byte[] array)
410         {
411             var index = (int)(offset);
412             var bytes = BitConverter.GetBytes(value);
413             for(var i = 0; i < 4; i++)
414             {
415                 array[index + i] = bytes[i];
416             }
417         }
418 
HandleArrayRead(long offset, byte[] array)419         private static uint HandleArrayRead(long offset, byte[] array)
420         {
421             var index = (int)(offset);
422             var bytes = new byte[4];
423             for(var i = 0; i < 4; i++)
424             {
425                 bytes[i] = array[index + i];
426             }
427             return BitConverter.ToUInt32(bytes, 0);
428         }
429 
HandleLoad(uint value)430         private static void HandleLoad(uint value)
431         {
432             ThreadPool.QueueUserWorkItem(delegate { EmulationManager.Instance.Load(string.Format(SavepointName, value)); });
433         }
434 
HandleSave(uint value)435         private static void HandleSave(uint value)
436         {
437             ThreadPool.QueueUserWorkItem(delegate { EmulationManager.Instance.Save(string.Format(SavepointName, value)); });
438         }
439 
440         [PostDeserialization]
AfterDeserialization()441         private void AfterDeserialization()
442         {
443             if(state == State.FileSend || state == State.FileReceive)
444             {
445                 // we don't know whether the file is still available etc.
446                 // therefore we go back to the usual state
447                 state = State.Usual;
448             }
449         }
450 
451         private bool activated;
452         private State state;
453 
454         [Transient]
455         private FileStream transferStream;
456         private string[] keys;
457         private string currentKeyToSet;
458         private int? keyListPosition;
459 
460         private readonly IMachine machine;
461         private readonly Stopwatch stopwatch;
462         private readonly byte[] stringRegister;
463         private readonly byte[] fileRegister;
464         private readonly ConcurrentDictionary<string, string> dictionary;
465 
466         private const uint Magic = 0xDEADBEEF;
467         private const uint Version = 3;
468         private const string SavepointName = "ckpt{0}.dat";
469 
470         private enum State
471         {
472             Usual,
473             FileReceive,
474             FileSend,
475             ReceivePermisions,
476             SetValue
477         }
478 
479         private enum Register : uint
480         {
481             Activate = 0x00,
482             Save = 0x04,
483             Load = 0x08,
484             ReceiveFileFromEmulator = 0x14,
485             SendFileToEmulator = 0x18,
486             SendReceiveController = 0x1C,
487             GetOrSet = 0x20,
488             List = 0x24,
489             Date = 0x28,
490             MeasureTime = 0x2C
491         }
492 
493         private enum TimeMeasurementOperation : uint
494         {
495             Start = 0,
496             Stop = 1,
497             Reset = 2
498         }
499 
500         private const uint StringRegisterStart = 0x100;
501         private const uint StringRegisterSize = 0x100;
502         private const uint StringRegisterEnd = StringRegisterStart + StringRegisterSize;
503         private const uint FileRegisterStart = StringRegisterEnd;
504         private const uint FileRegisterSize = 0x10000;
505         private const uint FileRegisterEnd = FileRegisterStart + FileRegisterSize;
506     }
507 }
508 
509