1 //
2 // Copyright (c) 2010-2024 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.Numerics;
10 using System.Linq;
11 using System.Reflection;
12 using System.Collections.Generic;
13 using Antmicro.Renode.Core;
14 using Antmicro.Renode.Exceptions;
15 using Antmicro.Renode.Logging;
16 using Antmicro.Renode.Utilities;
17 using Antmicro.Renode.Peripherals.Memory;
18 using Antmicro.Renode.Peripherals.CPU;
19 using Antmicro.Renode.Core.Structure.Registers;
20 
21 namespace Antmicro.Renode.Peripherals.Miscellaneous
22 {
23     public interface IOpenTitan_BigNumberAcceleratorCore
24     {
ExecuteInstructions(int instructionCount, out ulong executedInstructions)25         ExecutionResult ExecuteInstructions(int instructionCount, out ulong executedInstructions);
Reset()26         void Reset();
GetRegister(int id)27         RegisterValue GetRegister(int id);
GetWideRegister(int number, bool special)28         BigInteger GetWideRegister(int number, bool special);
29         string FixedRandomPattern { get; set; }
30         string KeyShare0 { get; set; }
31         string KeyShare1 { get; set; }
32         CoreError LastError { get; }
33     }
34 
35     public static class BigIntegerHelpers
36     {
ToByteArray(this BigInteger bi, int width)37         public static byte[] ToByteArray(this BigInteger bi, int width)
38         {
39             var array = bi.ToByteArray();
40             if(array.Length > width)
41             {
42                 array = array.Take(width).ToArray();
43             }
44             else if(array.Length < width)
45             {
46                 var fill = width - array.Length;
47                 var lsb = array[array.Length - 1];
48                 array = array.Concat(Enumerable.Repeat(lsb >= 0x80 ? (byte)0xff : (byte)0, fill)).ToArray();
49             }
50             return array;
51         }
52 
ToLongString(this BigInteger bi, int width)53         public static string ToLongString(this BigInteger bi, int width)
54         {
55             var s = string.Join("", bi.ToByteArray(width).Reverse().Select(x => x.ToString("x2")));
56             return $"0x{s}";
57         }
58     }
59 
60     public class OpenTitan_BigNumberAccelerator : BasicDoubleWordPeripheral, IKnownSize
61     {
OpenTitan_BigNumberAccelerator(IMachine machine)62         public OpenTitan_BigNumberAccelerator(IMachine machine) : base(machine)
63         {
64             ulong instructionMemorySize = sizeof(uint) * DataMemoryWindowsCount;
65             ulong dataMemorySize = sizeof(uint) * InstructionsMemoryWindowsCount;
66 
67             dataMemory = new OpenTitan_ScrambledMemory(machine, (long)dataMemorySize);
68             instructionsMemory = new OpenTitan_ScrambledMemory(machine, (long)instructionMemorySize);
69 
70             DoneIRQ = new GPIO();
71             FatalAlert = new GPIO();
72             RecoverableAlert = new GPIO();
73             core = InitOtbnCore(instructionsMemory, dataMemory);
74 
75             DefineRegisters();
76             Reset();
77         }
78 
LoadELF(string filePath)79         public void LoadELF(string filePath)
80         {
81             using(var elf = ELFUtils.LoadELF(filePath))
82             {
83                 if(elf.TryGetSection(".text", out var iMemSection))
84                 {
85                     instructionsMemory.WriteBytes(0, iMemSection.GetContents());
86                 }
87                 else
88                 {
89                     var availableSections = String.Join(", ", elf.Sections.Select(x => x.Name).ToArray());
90                     throw new RecoverableException($".text section not found. Available sections: {availableSections}");
91                 }
92 
93                 if(elf.TryGetSection(".data", out var dMemSection))
94                 {
95                     dataMemory.WriteBytes(0, dMemSection.GetContents());
96                 }
97                 else
98                 {
99                     var availableSections = String.Join(", ", elf.Sections.Select(x => x.Name).ToArray());
100                     throw new RecoverableException($".data section not found. Available sections: {availableSections}");
101                 }
102             }
103         }
104 
Reset()105         public override void Reset()
106         {
107             dataMemory.ZeroAll();
108             instructionsMemory.ZeroAll();
109             core.Reset();
110 
111             // DoneIRQ is unset via the registers callbacks
112             // Alerts need to be unset manually
113             FatalAlert.Unset();
114             RecoverableAlert.Unset();
115             executedInstructionsTotal = 0;
116 
117             base.Reset();
118         }
119 
GetCoreRegister(int number)120         public ulong GetCoreRegister(int number)
121         {
122             return this.core.GetRegister(number);
123         }
124 
GetWideRegister(int number, bool special)125         public string GetWideRegister(int number, bool special)
126         {
127             return core.GetWideRegister(number, special).ToLongString(32);
128         }
129 
130         public long Size => 0x10000;
131 
132         public GPIO DoneIRQ { get; }
133         public GPIO FatalAlert { get; }
134         public GPIO RecoverableAlert { get; }
135 
136         public bool IsIdle => (status.Value == Status.Idle);
137 
138         public string FixedRandomPattern
139         {
140             get => core.FixedRandomPattern;
141             set
142             {
143                 core.FixedRandomPattern = value;
144             }
145         }
146 
147         public string KeyShare0
148         {
149             get => core.KeyShare0;
150             set
151             {
152                 core.KeyShare0 = value;
153             }
154         }
155 
156         public string KeyShare1
157         {
158             get => core.KeyShare1;
159             set
160             {
161                 core.KeyShare1 = value;
162             }
163         }
164 
DefineRegisters()165         private void DefineRegisters()
166         {
167             Registers.InterruptState.Define(this)
168                 .WithFlag(0, out doneInterruptState, FieldMode.Read | FieldMode.WriteOneToClear, name: "done")
169                 .WithReservedBits(1, 31)
170                 .WithWriteCallback((_, __) => UpdateInterrupts());
171 
172             Registers.InterruptEnable.Define(this)
173                 .WithFlag(0, out doneInterruptEnable, name: "done")
174                 .WithReservedBits(1, 31)
175                 .WithWriteCallback((_, __) => UpdateInterrupts());
176 
177             Registers.InterruptTest.Define(this)
178                 .WithFlag(0, FieldMode.Write, writeCallback: (_, val) => { if(val) doneInterruptState.Value = true; }, name: "done")
179                 .WithReservedBits(1, 31)
180                 .WithWriteCallback((_, val) => { if(val != 0) UpdateInterrupts(); });
181 
182             Registers.AlertTest.Define(this)
183                 .WithFlag(0, FieldMode.Write, writeCallback: (_, val) => { if(val) FatalAlert.Blink(); }, name: "fatal")
184                 .WithFlag(1, FieldMode.Write, writeCallback: (_, val) => { if(val) RecoverableAlert.Blink(); }, name: "recov")
185                 .WithReservedBits(2, 30);
186 
187             Registers.Command.Define(this)
188                 .WithEnumField<DoubleWordRegister, Command>(0, 8, FieldMode.Write, writeCallback: (_, command) =>
189                 {
190                     // enter the busy state now, but execute the command (and clear the busy state later)
191                     // this is required by some tests
192                     EnterState(command);
193                     machine.LocalTimeSource.ExecuteInNearestSyncedState(__ =>
194                     {
195                         HandleCommand(command);
196                     });
197                 }, name: "cmd")
198                 .WithReservedBits(8, 24);
199 
200             IFlagRegisterField softwareErrorsFatal = null;
201             Registers.Control.Define(this)
202                 .WithFlag(0, out softwareErrorsFatal, name: "software_errs_fatal",
203                     writeCallback: (prevVal, newVal) =>
204                     {
205                         var s = status.Value;
206                         if(s != Status.Idle)
207                         {
208                             // according to the documentation: "Writes are ignored if OTBN is not idle."
209                             softwareErrorsFatal.Value = prevVal;
210 
211                             this.Log(LogLevel.Warning, "Ignoring software_errs_fatal write, as the device is in {0} mode.", s);
212                         }
213                     })
214                 .WithReservedBits(1, 31);
215 
216             Registers.Status.Define(this)
217                 .WithEnumField<DoubleWordRegister, Status>(0, 8, out status, FieldMode.Read, name: "status")
218                 .WithReservedBits(8, 24);
219 
220             Registers.OperationResult.Define(this)
221                 .WithFlag(0, out badDataAddressError, name: "bad_data_addr")
222                 .WithFlag(1, out badInstructionAddressError, name: "bad_insn_addr")
223                 .WithFlag(2, out callStackError, name: "call_stack")
224                 .WithFlag(3, out illegalInstructionError, name: "illegal_insn")
225                 .WithFlag(4, out loopError, name: "loop")
226                 .WithTaggedFlag("key_invalid", 5)
227                 .WithTaggedFlag("rnd_rep_chk_fail", 6)
228                 .WithTaggedFlag("rnd_fips_chk_fail", 7)
229                 .WithReservedBits(8, 8)
230                 .WithTaggedFlag("imem_intg_violation", 16)
231                 .WithTaggedFlag("dmem_intg_violation", 17)
232                 .WithTaggedFlag("reg_intg_violation", 18)
233                 .WithTaggedFlag("bus_intg_violation", 19)
234                 .WithTaggedFlag("bad_internal_state", 20)
235                 .WithTaggedFlag("illegal_bus_access", 21)
236                 .WithTaggedFlag("lifecycle_escalation", 22)
237                 .WithTaggedFlag("fatal_software", 23)
238                 .WithReservedBits(24, 8)
239                 .WithWriteCallback((_, __) =>
240                 {
241                     // The host CPU can clear this register when OTBN is not running, by writing any value. Write attempts while OTBN is running are ignored.
242                     if(!IsIdle)
243                     {
244                         badDataAddressError.Value = false;
245                         badInstructionAddressError.Value = false;
246                         callStackError.Value = false;
247                         illegalInstructionError.Value = false;
248                         loopError.Value = false;
249                     }
250                 });
251 
252             Registers.FatalAlertCause.Define(this)
253                 .WithTaggedFlag("imem_intg_violation", 0)
254                 .WithTaggedFlag("dmem_intg_violation", 1)
255                 .WithTaggedFlag("reg_intg_violation", 2)
256                 .WithTaggedFlag("bus_intg_violation", 3)
257                 .WithTaggedFlag("bad_internal_state", 4)
258                 .WithTaggedFlag("illegal_bus_access", 5)
259                 .WithTaggedFlag("lifecycle_escalation", 6)
260                 .WithTaggedFlag("fatal_software", 7)
261                 .WithReservedBits(8, 24);
262 
263             Registers.InstructionCount.Define(this)
264                 .WithValueField(0,32, FieldMode.Read, valueProviderCallback: (_) => (uint)executedInstructionsTotal, name: "insn_cnt");
265 
266             Registers.A32bitCRCchecksumofdatawrittentomemory.Define(this)
267                 .WithTag("checksum", 0, 32);
268 
269             Registers.InstructionMemoryAccess.DefineMany(this, InstructionsMemoryWindowsCount, (register, regId) =>
270             {
271                 register
272                     .WithValueField(0, 32,
273                                     valueProviderCallback: _ => instructionsMemory.ReadDoubleWord(regId * sizeof(uint)),
274                                     writeCallback: (_, val) => instructionsMemory.WriteDoubleWord(regId * sizeof(uint), (uint)val),
275                                     name: $"InstructionMemoryAccess{regId}");
276             });
277 
278             Registers.DataMemoryAccess.DefineMany(this, DataMemoryWindowsCount, (register, regId) =>
279             {
280                 register
281                     .WithValueField(0, 32,
282                                     valueProviderCallback: _ => dataMemory.ReadDoubleWord(regId * sizeof(uint)),
283                                     writeCallback: (_, val) => dataMemory.WriteDoubleWord(regId * sizeof(uint), (uint)val),
284                                     name: $"DataMemoryAccess{regId}");
285             });
286         }
287 
UpdateInterrupts()288         private void UpdateInterrupts()
289         {
290             var done = doneInterruptEnable.Value && doneInterruptState.Value;
291             DoneIRQ.Set(done);
292         }
293 
EnterState(Command command)294         private void EnterState(Command command)
295         {
296             switch(command)
297             {
298                 case Command.Execute:
299                     status.Value = Status.BusyExecute;
300                     break;
301                 case Command.WipeDMem:
302                     status.Value = Status.BusySecureWipeDMem;
303                     break;
304                 case Command.WipeIMem:
305                     status.Value = Status.BusySecureWipeIMem;
306                     break;
307                 default:
308                     this.Log(LogLevel.Error, "Unrecognized command : 0x{0:X}", command);
309                     return;
310             }
311         }
312 
HandleCommand(Command command)313         private void HandleCommand(Command command)
314         {
315             switch(command)
316             {
317                 case Command.Execute:
318                     Execute();
319                     break;
320                 case Command.WipeDMem:
321                     dataMemory.ZeroAll();
322                     break;
323                 case Command.WipeIMem:
324                     instructionsMemory.ZeroAll();
325                     break;
326                 default:
327                     this.Log(LogLevel.Error, "Unrecognized command : 0x{0:X}", command);
328                     return;
329             }
330 
331             doneInterruptState.Value = true;
332             status.Value = Status.Idle;
333             UpdateInterrupts();
334         }
335 
Execute()336         private void Execute()
337         {
338             this.Log(LogLevel.Debug, "Starting execution");
339             var result = ExecutionResult.Ok;
340             executedInstructionsTotal = 0;
341 
342             core.Reset();
343             while(result == ExecutionResult.Ok)
344             {
345                 result = core.ExecuteInstructions(1, out var executedInstructions);
346                 this.Log(LogLevel.Debug, "Core executed {0} instructions and returned {1}", executedInstructions, result);
347 
348                 if(result == ExecutionResult.Aborted)
349                 {
350                     switch((CoreError)core.LastError)
351                     {
352                         case CoreError.BadDataAddress:
353                             badDataAddressError.Value = true;
354                             break;
355 
356                         case CoreError.BadInstructionAddress:
357                             badInstructionAddressError.Value = true;
358                             break;
359 
360                         case CoreError.CallStack:
361                             callStackError.Value = true;
362                             break;
363 
364                         case CoreError.IllegalInstruction:
365                             illegalInstructionError.Value = true;
366                             break;
367 
368                         case CoreError.Loop:
369                             loopError.Value = true;
370                             break;
371                     }
372                     break;
373                 }
374 
375                 executedInstructionsTotal += executedInstructions;
376             }
377 
378             this.Log(LogLevel.Debug, "Execution finished");
379         }
380 
InitOtbnCore(OpenTitan_ScrambledMemory iMem, OpenTitan_ScrambledMemory dMem)381         private IOpenTitan_BigNumberAcceleratorCore InitOtbnCore(OpenTitan_ScrambledMemory iMem, OpenTitan_ScrambledMemory dMem)
382         {
383             // As the binary with needed type is included during the runtime we must use reflection
384             Type coreType = null;
385             foreach(Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
386             {
387                 if(asm.FullName.StartsWith("Infrastructure"))
388                 {
389                     coreType = asm.GetType("Antmicro.Renode.Peripherals.CPU.OpenTitan_BigNumberAcceleratorCore", false);
390                     if(coreType != null)
391                     {
392                         break;
393                     }
394                 }
395             }
396 
397             if(coreType == null)
398             {
399                 throw new ConstructionException($"Couldn't find the OpenTitan_BigNumberAcceleratorCore class. Check your Renode installation");
400             }
401 
402             var constructor = coreType.GetConstructor(new Type[] { typeof(OpenTitan_BigNumberAccelerator), typeof(OpenTitan_ScrambledMemory), typeof(OpenTitan_ScrambledMemory) });
403             return (IOpenTitan_BigNumberAcceleratorCore)constructor.Invoke(new object [] { this, iMem, dMem });
404         }
405 
406         private IFlagRegisterField badDataAddressError;
407         private IFlagRegisterField badInstructionAddressError;
408         private IFlagRegisterField illegalInstructionError;
409         private IFlagRegisterField callStackError;
410         private IFlagRegisterField loopError;
411 
412         private IFlagRegisterField doneInterruptEnable;
413         private IFlagRegisterField doneInterruptState;
414         private IEnumRegisterField<Status> status;
415 
416         private ulong executedInstructionsTotal;
417         private IOpenTitan_BigNumberAcceleratorCore core;
418 
419         private readonly OpenTitan_ScrambledMemory dataMemory;
420         private readonly OpenTitan_ScrambledMemory instructionsMemory;
421 
422         private const int DataMemoryWindowsCount = 1024;
423         private const int InstructionsMemoryWindowsCount = 1024;
424 
425         private enum Command
426         {
427             Execute = 0xd8,
428             WipeDMem = 0xc3,
429             WipeIMem = 0x1e,
430         }
431 
432         private enum Status
433         {
434             Idle = 0x0,
435             BusyExecute = 0x1,
436             BusySecureWipeDMem = 0x2,
437             BusySecureWipeIMem = 0x3,
438             BusySecureWipeInternal = 0x4,
439             Locked = 0xFF,
440         }
441 
442         private enum Registers
443         {
444             InterruptState = 0x0,
445             InterruptEnable = 0x4,
446             InterruptTest = 0x8,
447             AlertTest = 0xc,
448             Command = 0x10,
449             Control = 0x14,
450             Status = 0x18,
451             OperationResult = 0x1c,
452             FatalAlertCause = 0x20,
453             InstructionCount = 0x24,
454             A32bitCRCchecksumofdatawrittentomemory = 0x28,
455             InstructionMemoryAccess = 0x4000,
456             DataMemoryAccess = 0x8000,
457         }
458     }
459 
460     public enum CoreError
461     {
462         None =                  0b00000,
463         BadDataAddress =        0b00001,
464         BadInstructionAddress = 0b00010,
465         CallStack =             0b00100,
466         IllegalInstruction =    0b01000,
467         Loop =                  0b10000,
468         // Other errors not implemented yet
469     }
470 }
471