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