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 Antmicro.Renode.Core;
9 using Antmicro.Renode.Core.Extensions;
10 using Antmicro.Renode.Exceptions;
11 using Antmicro.Renode.Logging;
12 using Antmicro.Renode.Peripherals.Bus;
13 using Antmicro.Renode.Peripherals.Memory;
14 using Antmicro.Renode.Utilities;
15 using System;
16 using System.Linq;
17 using System.Collections.Generic;
18 using Endianess = ELFSharp.ELF.Endianess;
19 using static Antmicro.Renode.Utilities.BitHelper;
20 using System.IO;
21 
22 namespace Antmicro.Renode.Peripherals.MTD
23 {
24     public sealed class AMDCFIFlash : IBytePeripheral, IWordPeripheral, IDoubleWordPeripheral, IKnownSize, IDisposable
25     {
AMDCFIFlash(IMachine machine, MappedMemory underlyingMemory, int interleave = 4, ushort manufacturerId = 2, ushort alternateManufacturerId = 0, uint deviceId = 0x1234)26         public AMDCFIFlash(IMachine machine, MappedMemory underlyingMemory, int interleave = 4,
27             ushort manufacturerId = 2, ushort alternateManufacturerId = 0, uint deviceId = 0x1234)
28         {
29             var sysbus = machine.GetSystemBus(this);
30             // We need to consider the bus endianness for the reads that we pass through to the memory peripheral.
31             if(sysbus.Endianess == Endianess.BigEndian)
32             {
33                 readMemoryWord = underlyingMemory.ReadWordBigEndian;
34                 readMemoryDoubleWord = underlyingMemory.ReadDoubleWordBigEndian;
35                 writeMemoryWord = underlyingMemory.WriteWordBigEndian;
36                 writeMemoryDoubleWord = underlyingMemory.WriteDoubleWordBigEndian;
37             }
38             else
39             {
40                 readMemoryWord = underlyingMemory.ReadWordBigEndian;
41                 readMemoryDoubleWord = underlyingMemory.ReadDoubleWordBigEndian;
42                 writeMemoryWord = underlyingMemory.WriteWordBigEndian;
43                 writeMemoryDoubleWord = underlyingMemory.WriteDoubleWordBigEndian;
44             }
45             this.underlyingMemory = underlyingMemory;
46             this.interleave = interleave;
47             this.manufacturerId = manufacturerId;
48             this.alternateManufacturerId = alternateManufacturerId;
49             this.deviceId = deviceId;
50             bankSectorSizes = new List<List<long>>
51             {
52                 Enumerable.Repeat(0x2000L, 8).Concat(Enumerable.Repeat(0x10000L, 15)).ToList(),
53                 Enumerable.Repeat(0x10000L, 48).ToList(),
54                 Enumerable.Repeat(0x10000L, 48).ToList(),
55                 Enumerable.Repeat(0x10000L, 15).Concat(Enumerable.Repeat(0x2000L, 8)).ToList(),
56             };
57             RecalculateSizes();
58             Reset();
59         }
60 
61         // Loading happens immediately, saving at dispose time.
AddBackingFile(ReadFilePath path, long offset, int size, bool read = true, bool write = false)62         public void AddBackingFile(ReadFilePath path, long offset, int size, bool read = true, bool write = false)
63         {
64             if(read)
65             {
66                 try
67                 {
68                     if(size + offset > Size)
69                     {
70                         throw new ArgumentException($"Loaded size ({size} bytes) plus offset ({offset}) exceeds memory size ({Size} bytes)");
71                     }
72                     this.DebugLog("Loading {0} bytes at 0x{1:x} from {2}", size, offset, path);
73                     byte[] bytes;
74                     using(var stream = File.OpenRead(path))
75                     {
76                         using(var reader = new BinaryReader(stream))
77                         {
78                             bytes = reader.ReadBytes(size);
79                         }
80                     }
81                     if(bytes.Length < size)
82                     {
83                         this.WarningLog("Wanted to read {0} bytes from '{1}', but got only {2}", size, path, bytes.Length);
84                     }
85                     underlyingMemory.WriteBytes(offset, bytes);
86                 }
87                 catch(Exception e)
88                 {
89                     throw new RecoverableException($"Failed to load data from file: {e.Message}", e);
90                 }
91             }
92 
93             if(write)
94             {
95                 backingFiles.Add(offset, new BackingFile(size, path));
96             }
97         }
98 
99         // Used to define a custom flash layout in the repl/resc. The default one is equivalent to:
100         // ClearSectorSizes
101         // AddSectorSizes 0x2000 8 bank=0
102         // AddSectorSizes 0x10000 15 bank=0
103         // AddSectorSizes 0x10000 48 bank=1
104         // AddSectorSizes 0x10000 48 bank=2
105         // AddSectorSizes 0x10000 15 bank=3
106         // AddSectorSizes 0x2000 8 bank=3
107         // The banks must be in sequential order, and ClearSectorSizes should be used to clear the old layout first.
ClearSectorSizes()108         public void ClearSectorSizes()
109         {
110             bankSectorSizes = new List<List<long>>();
111         }
112 
AddSectorSizes(long size, int repeat, int bank)113         public void AddSectorSizes(long size, int repeat, int bank)
114         {
115             var lastBank = bankSectorSizes.Count - 1;
116             var entry = Enumerable.Repeat(size, repeat);
117             if(bank != lastBank)
118             {
119                 if(bank != lastBank + 1)
120                 {
121                     throw new RecoverableException($"Banks must be added in sequential order (expected {lastBank + 1})");
122                 }
123                 bankSectorSizes.Add(entry.ToList());
124             }
125             else
126             {
127                 bankSectorSizes[bank].AddRange(entry);
128             }
129 
130             RecalculateSizes();
131         }
132 
Reset()133         public void Reset()
134         {
135             state = State.ReadArray;
136             unlockCycle = 0;
137         }
138 
Dispose()139         public void Dispose()
140         {
141             foreach(var fileOffset in backingFiles)
142             {
143                 var offset = fileOffset.Key;
144                 var file = fileOffset.Value;
145                 this.DebugLog("Saving {0} bytes at 0x{1:x} to {2}", file.size, offset, file.path);
146                 try
147                 {
148                     File.WriteAllBytes(file.path, underlyingMemory.ReadBytes(offset, file.size));
149                 }
150                 catch(Exception e)
151                 {
152                     throw new RecoverableException($"Failed to save data to file: {e.Message}", e);
153                 }
154             }
155         }
156 
ReadByte(long offset)157         public byte ReadByte(long offset)
158         {
159             if(state == State.ReadArray)
160             {
161                 return underlyingMemory.ReadByte(offset);
162             }
163             if(state == State.ReadQuery)
164             {
165                 // No alignment requirement, in fact for example for interleave = 4 it's expected that
166                 // ReadByte(0x10..0x13) gives the same result - all the chips get the same value on their
167                 // address lines and so the CFI space looks like every byte is repeated 4 times
168                 return HandleQuery(offset / interleave);
169             }
170             if(state == State.ReadAutoselect)
171             {
172                 // The same applies for the autoselect data
173                 return HandleAutoselect(offset / interleave);
174             }
175 
176             this.WarningLog("ReadByte in unexpected state {0}", state);
177             return 0;
178         }
179 
WriteByte(long offset, byte value)180         public void WriteByte(long offset, byte value)
181         {
182             if(state == State.ProgramWord && Unlocked)
183             {
184                 underlyingMemory.WriteByte(offset, value);
185                 state = State.ReadArray;
186                 unlockCycle = 0;
187                 return;
188             }
189             if(state == State.SectorEraseSetup && Unlocked)
190             {
191                 var sectorIdx = Array.BinarySearch(sectorStarts, offset);
192                 if(sectorIdx < 0)
193                 {
194                     this.WarningLog("Not erasing at unaligned address 0x{0:x}", offset);
195                 }
196                 else if(value != (byte)Command.SectorErase)
197                 {
198                     this.WarningLog("Unexpected byte written in ready-to-erase state: 0x{0:x2}", value);
199                 }
200                 else
201                 {
202                     var sectorSize = sectorSizes[sectorIdx];
203                     underlyingMemory.SetRange(offset, sectorSize, ErasedValue);
204                     this.DebugLog("Erased {0}-byte sector at 0x{1:x}", sectorSize, offset);
205                 }
206                 state = State.ReadArray;
207                 unlockCycle = 0;
208                 return;
209             }
210 
211             this.DebugLog("Handling command {0} (0x{1:x})", (Command)value, value);
212             switch((Command)value)
213             {
214                 case Command.ReadQuery:
215                     state = State.ReadQuery;
216                     break;
217                 case Command.ReadAutoselect:
218                     state = State.ReadAutoselect;
219                     break;
220                 case Command.Reset:
221                 case Command.ReadArray:
222                     state = State.ReadArray;
223                     break;
224                 case Command.UnlockCycle1:
225                     unlockCycle = unlockCycle == 0 ? 1 : 0;
226                     break;
227                 case Command.UnlockCycle2:
228                     unlockCycle = unlockCycle == 1 ? 2 : 0;
229                     break;
230                 case Command.ProgramWord:
231                     state = Unlocked ? State.ProgramWord : State.ReadArray;
232                     break;
233                 case Command.SectorEraseSetup:
234                     // To allow setting up an erase operation, the device needs to be unlocked,
235                     if(Unlocked)
236                     {
237                         state = State.SectorEraseSetup;
238                         // and another unlock sequence will be required to actually perform the erase.
239                         unlockCycle = 0;
240                     }
241                     break;
242                 default:
243                     this.WarningLog("Unknown command 0x{0:x}", value);
244                     break;
245             }
246         }
247 
ReadWord(long offset)248         public ushort ReadWord(long offset)
249         {
250             if(state == State.ReadArray)
251             {
252                 return readMemoryWord(offset);
253             }
254             return this.ReadWordUsingByte(offset);
255         }
256 
WriteWord(long offset, ushort value)257         public void WriteWord(long offset, ushort value)
258         {
259             if(state == State.ProgramWord && Unlocked)
260             {
261                 writeMemoryWord(offset, value);
262                 state = State.ReadArray;
263                 unlockCycle = 0;
264                 return;
265             }
266             this.WriteWordUsingByte(offset, value);
267         }
268 
ReadDoubleWord(long offset)269         public uint ReadDoubleWord(long offset)
270         {
271             if(state == State.ReadArray)
272             {
273                 return readMemoryDoubleWord(offset);
274             }
275             return this.ReadDoubleWordUsingByte(offset);
276         }
277 
WriteDoubleWord(long offset, uint value)278         public void WriteDoubleWord(long offset, uint value)
279         {
280             if(state == State.ProgramWord && Unlocked)
281             {
282                 writeMemoryDoubleWord(offset, value);
283                 state = State.ReadArray;
284                 unlockCycle = 0;
285                 return;
286             }
287             this.WriteDoubleWordUsingByte(offset, value);
288         }
289 
290         public long Size => underlyingMemory.Size;
291 
PrepareCfiData()292         private byte[] PrepareCfiData()
293         {
294             return new VariableLengthValue(0x5c * 8)
295                 .DefineFragment(0x10 * 8, 8, (byte)'Q')
296                 .DefineFragment(0x11 * 8, 8, (byte)'R')
297                 .DefineFragment(0x12 * 8, 8, (byte)'Y')
298                 .DefineFragment(0x13 * 8, 16, manufacturerId, name: "Manufacturer ID")
299                 .DefineFragment(0x15 * 8, 16, 0x0040, name: "Starting Address for the Primary Vendor-Specific Extended Query Table")
300                 .DefineFragment(0x17 * 8, 16, alternateManufacturerId, name: "Alternate manufacturer ID")
301                 .DefineFragment(0x19 * 8, 16, 0x0000, name: "Starting Address for the Alternate Vendor-Specific Extended Query Table")
302                 .DefineFragment(0x1B * 8, 8, 0x27, name: "V_cc lower limit")
303                 .DefineFragment(0x1C * 8, 8, 0x36, name: "V_cc upper limit")
304                 .DefineFragment(0x1D * 8, 8, 0x00, name: "V_pp lower limit")
305                 .DefineFragment(0x1E * 8, 8, 0x00, name: "V_pp upper limit")
306                 .DefineFragment(0x1F * 8, 8, 0x03, name: "Typical word programming time")
307                 .DefineFragment(0x20 * 8, 8, 0x00, name: "Typical buffer programming time")
308                 .DefineFragment(0x21 * 8, 8, 0x09, name: "Typical sector erase time")
309                 .DefineFragment(0x22 * 8, 8, 0x0F, name: "Typical chip erase time")
310                 .DefineFragment(0x23 * 8, 8, 0x04, name: "Maximum word programming time")
311                 .DefineFragment(0x24 * 8, 8, 0x00, name: "Maximum buffer programming time")
312                 .DefineFragment(0x25 * 8, 8, 0x04, name: "Maximum sector erase time")
313                 .DefineFragment(0x26 * 8, 8, 0x00, name: "Maximum chip erase time")
314                 .DefineFragment(0x27 * 8, 8, (ulong)Math.Log(Size, 2), name: "Base-2 logarithm of device size")
315                 .DefineFragment(0x28 * 8, 8, 0x0002, name: "Flash Device Interface Code description")
316                 .DefineFragment(0x2A * 8, 16, 0x0000, name: "Base-2 logarithm of the maximum number of bytes in a multi-byte program")
317                 .DefineFragment(0x2C * 8, 8, (ulong)sectorRegions.Length, name: "Number of erase sector regions within device")
318                 .DefineFragment(0x2D * 8, 16, (sectorRegions.ElementAtOrDefault(0)?.count ?? 1) - 1, name: "Number of sectors in region 1")
319                 .DefineFragment(0x2F * 8, 16, (sectorRegions.ElementAtOrDefault(0)?.size ?? 0UL) / 256, name: "Size of sector in region 1")
320                 .DefineFragment(0x31 * 8, 16, (sectorRegions.ElementAtOrDefault(1)?.count ?? 1) - 1, name: "Number of sectors in region 2")
321                 .DefineFragment(0x33 * 8, 16, (sectorRegions.ElementAtOrDefault(1)?.size ?? 0UL) / 256, name: "Size of sector in region 2")
322                 .DefineFragment(0x35 * 8, 16, (sectorRegions.ElementAtOrDefault(2)?.count ?? 1) - 1, name: "Number of sectors in region 3")
323                 .DefineFragment(0x37 * 8, 16, (sectorRegions.ElementAtOrDefault(2)?.size ?? 0UL) / 256, name: "Size of sector in region 3")
324                 .DefineFragment(0x39 * 8, 16, (sectorRegions.ElementAtOrDefault(3)?.count ?? 1) - 1, name: "Number of sectors in region 4")
325                 .DefineFragment(0x3B * 8, 16, (sectorRegions.ElementAtOrDefault(3)?.size ?? 0UL) / 256, name: "Size of sector in region 4")
326                 .DefineFragment(0x40 * 8, 8, (byte)'P')
327                 .DefineFragment(0x41 * 8, 8, (byte)'R')
328                 .DefineFragment(0x42 * 8, 8, (byte)'I')
329                 .DefineFragment(0x43 * 8, 8, (byte)'1', name: "Major version number, ASCII (reflects modifications to the silicon)")
330                 .DefineFragment(0x44 * 8, 8, (byte)'3', name: "Major version number, ASCII (reflects modifications to the CFI table)")
331                 .DefineFragment(0x45 * 8, 2, 0b00, name: "Address sensitive unlock")
332                 .DefineFragment(0x45 * 8 + 2, 6, 0b110000, name: "Process technology")
333                 .DefineFragment(0x46 * 8, 8, 0x02, name: "Erase suspend")
334                 .DefineFragment(0x47 * 8, 8, 0x01, name: "Sector protect")
335                 .DefineFragment(0x48 * 8, 8, 0x01, name: "Temporary sector unprotect")
336                 .DefineFragment(0x49 * 8, 8, 0x04, name: "Sector protection scheme")
337                 .DefineFragment(0x4A * 8, 8, 0x07, name: "Simultaneous operation")
338                 .DefineFragment(0x4B * 8, 8, 0x00, name: "Burst mode type")
339                 .DefineFragment(0x4C * 8, 8, 0x00, name: "Page mode type")
340                 .DefineFragment(0x4D * 8, 8, 0x00, name: "Acceleration power supply voltage lower limit")
341                 .DefineFragment(0x4E * 8, 8, 0x00, name: "Acceleration power supply voltage lower limit")
342                 .DefineFragment(0x4F * 8, 8, 0x01, name: "Sector layout (uniform / 8x8 KiB top/bottom)")
343                 .DefineFragment(0x50 * 8, 8, 0x00, name: "Program suspend")
344                 .DefineFragment(0x57 * 8, 8, (ulong)bankSectorSizes.Count, name: "Number of banks")
345                 .DefineFragment(0x58 * 8, 8, (ulong)(bankSectorSizes.ElementAtOrDefault(0)?.Count ?? 0), name: "Number of sectors in bank 1")
346                 .DefineFragment(0x59 * 8, 8, (ulong)(bankSectorSizes.ElementAtOrDefault(1)?.Count ?? 0), name: "Number of sectors in bank 2")
347                 .DefineFragment(0x5A * 8, 8, (ulong)(bankSectorSizes.ElementAtOrDefault(2)?.Count ?? 0), name: "Number of sectors in bank 3")
348                 .DefineFragment(0x5B * 8, 8, (ulong)(bankSectorSizes.ElementAtOrDefault(3)?.Count ?? 0), name: "Number of sectors in bank 4")
349                 .Bits.AsByteArray();
350         }
351 
PrepareAutoselectData()352         private byte[] PrepareAutoselectData()
353         {
354             return new VariableLengthValue(0x1f * 8)
355                 .DefineFragment(0x00 * 8, 8, (ulong)(manufacturerId & 0xff), name: "Manufacturer ID")
356                 .DefineFragment(0x02 * 8, 8, (deviceId >> 16) & 0xff, name: "Device ID 1")
357                 .DefineFragment(0x1c * 8, 8, (deviceId >> 8) & 0xff, name: "Device ID 2")
358                 .DefineFragment(0x1e * 8, 8, deviceId & 0xff, name: "Device ID 3")
359                 .Bits.AsByteArray();
360         }
361 
HandleQuery(long offset)362         private byte HandleQuery(long offset)
363         {
364             // This division is to use sequential (byte mode) offsets in the data definition
365             var byteOffset = offset / 2;
366             if(byteOffset >= cfiData.Length)
367             {
368                 this.WarningLog("Unhandled query read at offset 0x{0:x}, returning 0", offset);
369                 return 0;
370             }
371             return cfiData[byteOffset];
372         }
373 
HandleAutoselect(long offset)374         private byte HandleAutoselect(long offset)
375         {
376             if(offset >= autoselectData.Length)
377             {
378                 this.WarningLog("Unhandled autoselect read at offset 0x{0:x}, returning 0", offset);
379                 return 0;
380             }
381             return cfiData[offset];
382         }
383 
RecalculateSizes()384         private void RecalculateSizes()
385         {
386             sectorSizes = bankSectorSizes.SelectMany(bankSizes => bankSizes.Select(s => s * interleave)).ToArray();
387             sectorStarts = Enumerable.Concat(new [] { 0L }, Misc.Prefix(sectorSizes, (a, b) => a + b)).ToArray();
388             // A sector region is a run of contiguous sectors with the same size, including across banks.
389             // We divide the sizes in here by the number of chips in parallel as they are used in CFI data, and that of
390             // course needs to contain the block size for *one* chip.
391             sectorRegions = sectorSizes.Aggregate(new List<SectorRegion>(), (regions, size) =>
392             {
393                 if(regions.LastOrDefault()?.size == (ulong)(size / interleave))
394                 {
395                     regions.Last().count++;
396                 }
397                 else
398                 {
399                     regions.Add(new SectorRegion((ulong)(size / interleave), 1));
400                 }
401                 return regions;
402             }).ToArray();
403 
404             cfiData = PrepareCfiData();
405             autoselectData = PrepareAutoselectData();
406         }
407 
408         private bool Unlocked => unlockCycle == 2;
409 
410         private List<List<long>> bankSectorSizes; // For one chip
411         private long[] sectorSizes; // For all chips in parallel
412         private long[] sectorStarts; // For all chips in parallel
413         private SectorRegion[] sectorRegions; // For one chip
414         private byte[] cfiData;
415         private byte[] autoselectData;
416         private State state;
417         private int unlockCycle;
418 
419         private readonly MappedMemory underlyingMemory;
420         private readonly int interleave;
421         private readonly ushort manufacturerId;
422         private readonly ushort alternateManufacturerId;
423         private readonly uint deviceId;
424         private readonly Func<long, ushort> readMemoryWord;
425         private readonly Func<long, uint> readMemoryDoubleWord;
426         private readonly Action<long, ushort> writeMemoryWord;
427         private readonly Action<long, uint> writeMemoryDoubleWord;
428         private readonly Dictionary<long, BackingFile> backingFiles = new Dictionary<long, BackingFile>();
429 
430         private const byte ErasedValue = 0xFF;
431 
432         private class SectorRegion
433         {
SectorRegion(ulong size, ulong count)434             public SectorRegion(ulong size, ulong count)
435             {
436                 this.size = size;
437                 this.count = count;
438             }
439 
440             public readonly ulong size;
441             public ulong count;
442         }
443 
444         private class BackingFile
445         {
BackingFile(int size, FilePath path)446             public BackingFile(int size, FilePath path)
447             {
448                 this.size = size;
449                 this.path = path;
450             }
451 
452             public readonly int size;
453             public readonly FilePath path;
454         }
455 
456         private enum Command : byte
457         {
458             Reset = 0xF0,
459             ReadArray = 0xFF,
460             ReadQuery = 0x98,
461             ReadAutoselect = 0x90,
462             UnlockCycle1 = 0xAA,
463             UnlockCycle2 = 0x55,
464             SectorEraseSetup = 0x80,
465             SectorErase = 0x30,
466             ProgramWord = 0xA0,
467         }
468 
469         // Not currently used as the unlock mechanism is not address sensitive
470         private enum Registers : long
471         {
472             UnlockCycle1 = 0xAAA,
473             UnlockCycle2 = 0x555,
474         }
475 
476         private enum State
477         {
478             ReadArray,
479             ReadQuery,
480             ReadAutoselect,
481             ProgramWord,
482             SectorEraseSetup,
483         }
484     }
485 }
486 
487