1 //
2 // Copyright (c) 2010-2022 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.IO;
10 using System.Linq;
11 using System.Text;
12 using Antmicro.Renode.Core;
13 using Antmicro.Renode.Core.Structure.Registers;
14 using Antmicro.Renode.Exceptions;
15 using Antmicro.Renode.Logging;
16 using Antmicro.Renode.Peripherals.Bus;
17 using Antmicro.Renode.Peripherals.Memory;
18 using Antmicro.Renode.Utilities;
19 using Org.BouncyCastle.Crypto.Digests;
20 
21 namespace Antmicro.Renode.Peripherals.MemoryControllers
22 {
23     public class OpenTitan_ROMController: IDoubleWordPeripheral, IKnownSize
24     {
OpenTitan_ROMController(MappedMemory rom, string nonce, string key)25         public OpenTitan_ROMController(MappedMemory rom, string nonce, string key)
26         {
27             this.rom = rom;
28             romLengthInWords = (ulong)rom.Size / 4;
29             romIndexWidth = BitHelper.GetMostSignificantSetBitIndex(romLengthInWords - 1) + 1;
30             if(romLengthInWords <= 8)
31             {
32                 throw new ConstructionException("Provided rom's size has to be greater than 8 words (32 bytes)");
33             }
34             if(rom.Size % 4 != 0)
35             {
36                 throw new ConstructionException("Provided rom's size has to be divisible by word size (4)");
37             }
38 
39             Key = key;
40             Nonce = nonce;
41             FatalAlert = new GPIO();
42 
43             digest = new byte[NumberOfDigestRegisters * 4];
44             expectedDigest = new byte[NumberOfDigestRegisters * 4];
45             registers = new DoubleWordRegisterCollection(this, BuildRegisterMap());
46             Reset();
47         }
48 
ReadDoubleWord(long offset)49         public uint ReadDoubleWord(long offset)
50         {
51             return registers.Read(offset);
52         }
53 
WriteDoubleWord(long offset, uint value)54         public void WriteDoubleWord(long offset, uint value)
55         {
56             registers.Write(offset, value);
57         }
58 
LoadVmem(string fileName)59         public void LoadVmem(string fileName)
60         {
61             var digestData = new ulong[romLengthInWords - 8];
62             try
63             {
64                 var reader = new VmemReader(fileName);
65                 foreach(var touple in reader.GetIndexDataPairs())
66                 {
67                     var scrambledIndex = (ulong)touple.Item1;
68                     var data = touple.Item2;
69                     LoadWord(scrambledIndex, data, digestData);
70                 }
71             }
72             catch(Exception e)
73             {
74                 throw new RecoverableException(string.Format("Exception while loading file {0}: {1}", fileName, e.Message));
75             }
76             CalculateDigest(digestData);
77         }
78 
Reset()79         public void Reset()
80         {
81             // do not clear ROM
82             registers.Reset();
83             CheckDigest();
84             FatalAlert.Unset();
85         }
86 
87         public long Size => 0x1000;
88 
89         public GPIO FatalAlert { get; }
90 
91         public IEnumerable<byte> Digest => digest;
92 
93         public string Nonce
94         {
95             set
96             {
97                 ulong[] temp;
98                 if(!Misc.TryParseHexString(value, out temp, sizeof(ulong), endiannessSwap: true))
99                 {
100                     throw new RecoverableException("Unable to parse value. Incorrect Length");
101                 }
102 
103                 addressKey = temp[0] >> (64 - romIndexWidth);
104                 dataNonce = temp[0] << romIndexWidth;
105             }
106             get
107             {
108                 return "{0:X16}".FormatWith((dataNonce >> romIndexWidth) | (addressKey << (64 - romIndexWidth)));
109             }
110         }
111 
112         public string Key
113         {
114             set
115             {
116                 if(!Misc.TryParseHexString(value, out key, sizeof(ulong), endiannessSwap: true))
117                 {
118                     throw new RecoverableException("Unable to parse value. Incorrect Length");
119                 }
120             }
121             get
122             {
123                 return key.Select(x => "{0:X16}".FormatWith(x)).Stringify();
124             }
125         }
126 
BuildRegisterMap()127         private Dictionary<long, DoubleWordRegister> BuildRegisterMap()
128         {
129             var registersDictionary = new Dictionary<long, DoubleWordRegister>
130             {
131                 {(long)Registers.AlertTest, new DoubleWordRegister(this)
132                     .WithFlag(0, FieldMode.Write, writeCallback: (_, value) =>
133                         {
134                             checkerError.Value |= value;
135                             integrityError.Value |= value;
136                             if(value)
137                             {
138                                 FatalAlert.Blink();
139                             }
140                         }, name: "fatal")
141                     .WithReservedBits(1, 31)
142                 },
143                 {(long)Registers.FatalAlertCause, new DoubleWordRegister(this)
144                     .WithFlag(0, out checkerError, FieldMode.Read, name: "checker_error")
145                     .WithFlag(1, out integrityError, FieldMode.Read, name: "integrity_error")
146                     .WithReservedBits(2, 30)
147                 },
148             };
149 
150             for(var i = 0; i < NumberOfDigestRegisters; ++i)
151             {
152                 registersDictionary.Add((long)Registers.Digest0 + i * 4, new DoubleWordRegister(this)
153                     .WithValueField(0, 32, FieldMode.Read, valueProviderCallback: _ => (uint)BitConverter.ToInt32(digest, i * 4), name: $"DIGEST_{i}"));
154                 registersDictionary.Add((long)Registers.ExpectedDigest0 + i * 4, new DoubleWordRegister(this)
155                     .WithValueField(0, 32, FieldMode.Read, valueProviderCallback: _ => (uint)BitConverter.ToInt32(expectedDigest, i * 4), name: $"EXP_DIGEST_{i}"));
156             }
157 
158             return registersDictionary;
159         }
160 
LoadWord(ulong scrambledIndex, ulong data, ulong[] digestData)161         private void LoadWord(ulong scrambledIndex, ulong data, ulong[] digestData)
162         {
163             var index = PRESENTCipher.Descramble(scrambledIndex, addressKey, romIndexWidth, NumberOfScramblingRounds);
164             if(index >= romLengthInWords)
165             {
166                 throw new RecoverableException($"Error while loading: decoded offset (0x{(index * 4):X}) is out of bounds [0;0x{rom.Size:X})");
167             }
168             if(index >= romLengthInWords - 8)
169             {
170                 // top 8 (by logical addressing) words are 256-bit expected hash, stored not scrambled.
171                 expectedDigest.SetBytesFromValue((uint)data, (int)(index + 8 - romLengthInWords) * 4);
172             }
173             else
174             {
175                 digestData[index] = data;
176             }
177 
178             // data's width is 32 bits of proper data and 7 bits of ECC
179             var dataPresent = PRESENTCipher.Descramble(data, 0, 32 + 7, NumberOfScramblingRounds);
180             var dataPrince = PRINCECipher.Scramble(index | dataNonce, key[1], key[0], rounds: 6);
181             var descrabled = (dataPresent ^ dataPrince) & ((1UL << 39) - 1);
182             if(index < romLengthInWords - 8 && !ECCHsiao.CheckECC(descrabled))
183             {
184                 this.Log(LogLevel.Warning, "ECC error at logical index 0x{0:X}", index);
185             }
186             rom.WriteDoubleWord((long)index * 4, (uint)descrabled);
187         }
188 
CalculateDigest(ulong[] words)189         private void CalculateDigest(ulong[] words)
190         {
191             var hasher = new CShakeDigest(256, null, Encoding.ASCII.GetBytes("ROM_CTRL"));
192             foreach(var word in words)
193             {
194                 hasher.BlockUpdate(BitConverter.GetBytes(word), 0, 8);
195             }
196 
197             hasher.DoFinal(digest, 0, digest.Length);
198             CheckDigest();
199         }
200 
CheckDigest()201         private void CheckDigest()
202         {
203             integrityError.Value = expectedDigest.Zip(digest, (b0, b1) => b0 != b1).Any(b => b);
204         }
205 
206         private ulong addressKey;
207         private ulong dataNonce;
208         private readonly ulong romLengthInWords;
209         private readonly int romIndexWidth;
210         private readonly MappedMemory rom;
211         private readonly DoubleWordRegisterCollection registers;
212 
213         private readonly byte[] digest;
214         private readonly byte[] expectedDigest;
215         private ulong[] key;
216 
217         private IFlagRegisterField checkerError;
218         private IFlagRegisterField integrityError;
219 
220         private const int NumberOfDigestRegisters = 8;
221         private const int NumberOfScramblingRounds = 2;
222 
223         private class ECCHsiao
224         {
225             // A variation of/optimization over Hamming Code (39,32)
226             // For explenation on parity-check matrix construction see:
227             // https://www.ysu.am/files/11-1549527438-.pdf
AddECC(uint word)228             public static ulong AddECC(uint word)
229             {
230                 var code = 0ul;
231                 for(byte i = 0; i < 7; ++i)
232                 {
233                     var inverted = i % 2 == 1;
234                     var bit = BitHelper.CalculateParity(word & bitmask[i]);
235                     BitHelper.SetBit(ref code, i, bit ^ inverted);
236                 }
237                 return (ulong)word | (code << 32);
238             }
239 
CheckECC(ulong word)240             public static bool CheckECC(ulong word)
241             {
242                 return word == AddECC((uint)word);
243             }
244 
245             // generated with opentitan's secded_gen.py
246             private static readonly uint[] bitmask = new uint[]
247             {
248                 0x2606bd25, 0xdeba8050, 0x413d89aa, 0x31234ed1,
249                 0xc2c1323b, 0x2dcc624c, 0x98505586
250             };
251         }
252         #pragma warning disable format
253         private enum Registers: long
254         {
255             AlertTest       = 0x00,
256             FatalAlertCause = 0x04,
257             Digest0         = 0x08,
258             Digest1         = 0x0C,
259             Digest2         = 0x10,
260             Digest3         = 0x14,
261             Digest4         = 0x18,
262             Digest5         = 0x1C,
263             Digest6         = 0x20,
264             Digest7         = 0x24,
265             ExpectedDigest0 = 0x28,
266             ExpectedDigest1 = 0x2C,
267             ExpectedDigest2 = 0x30,
268             ExpectedDigest3 = 0x34,
269             ExpectedDigest4 = 0x38,
270             ExpectedDigest5 = 0x3C,
271             ExpectedDigest6 = 0x40,
272             ExpectedDigest7 = 0x44,
273         }
274         #pragma warning restore format
275     }
276 }
277