1 //
2 // Copyright (c) 2010-2024 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 Antmicro.Renode.Logging;
10 using Antmicro.Renode.Peripherals.Bus;
11 using Antmicro.Renode.Storage;
12 using Antmicro.Renode.Utilities;
13 using Antmicro.Renode.Exceptions;
14 using System.IO;
15 using Antmicro.Renode.Core;
16 using Antmicro.Renode.UserInterface;
17 
18 namespace Antmicro.Renode.Peripherals.MTD
19 {
20     [Icon("sd")]
21     public sealed class CFIFlash : IBytePeripheral, IWordPeripheral, IDoubleWordPeripheral, IKnownSize, IDisposable
22     {
CFIFlash(string fileName, int? size = null, SysbusAccessWidth bits = SysbusAccessWidth.DoubleWord, bool nonPersistent = false)23         public CFIFlash(string fileName, int? size = null, SysbusAccessWidth bits = SysbusAccessWidth.DoubleWord, bool nonPersistent = false)
24         {
25             switch(bits)
26             {
27             case SysbusAccessWidth.Byte:
28                 busWidth = 0;
29                 break;
30             case SysbusAccessWidth.Word:
31                 busWidth = 1;
32                 break;
33             case SysbusAccessWidth.DoubleWord:
34                 busWidth = 2;
35                 break;
36             default:
37                 throw new ArgumentOutOfRangeException();
38             }
39             Init(fileName, size, nonPersistent);
40             CheckBuffer(0);
41         }
42 
43         public long Size
44         {
45             get
46             {
47                 return size;
48             }
49         }
50 
51         /// <summary>
52         /// Gets or sets the size of the erase block.
53         /// </summary>
54         /// <value>
55         /// The size of the erase block in bytes.
56         /// </value>
57         /// <exception cref='ArgumentException'>
58         /// Thrown when erase block size is not divisible by 256 or greater than 16776960 or it
59         /// does not divide whole flash size.
60         /// </exception>
61         public int EraseBlockSize
62         {
63             get
64             {
65                 return 256 * eraseBlockSizeDivided;
66             }
67             set
68             {
69                 if(value % 256 != 0)
70                 {
71                     throw new ArgumentException("Erase block size has to be divisible by 256.");
72                 }
73                 if(size % value != 0)
74                 {
75                     throw new ArgumentException(string.Format(
76                         "Erase block has to divide flash size, which is {0}B.", Misc.NormalizeBinary(size))
77                     );
78                 }
79                 if(size / value > ushort.MaxValue + 1)
80                 {
81                     throw new ArgumentException(string.Format(
82                         "Erase block cannot be smaller than {0}B for given flash size {1}B.",
83                         Misc.NormalizeBinary(size / (ushort.MaxValue + 1)),
84                         Misc.NormalizeBinary(Size))
85                     );
86                 }
87                 if(value / 256 > ushort.MaxValue)
88                 {
89                     throw new ArgumentException(string.Format(
90                         "Erase block cannot be larger than {0}B ({2}B was given) for given flash size {1}B.",
91                         256 * ushort.MaxValue,
92                         Misc.NormalizeBinary(Size),
93                         value)
94                     );
95                 }
96                 eraseBlockSizeDivided = (ushort)(value / 256);
97                 eraseBlockCountMinusOne = (ushort)(size / value - 1);
98             }
99         }
100 
ReadByte(long offset)101         public byte ReadByte(long offset)
102         {
103             switch(state)
104             {
105             case State.ReadArray:
106                 return (byte)HandleRead(offset, 0);
107             case State.ReadQuery:
108                 return HandleQuery(offset);
109             case State.ActionDone:
110                 return (byte)statusRegister;
111             case State.WaitingForElementCount:
112                 return (byte)statusRegister;
113             case State.ReadJEDECId:
114                 return HandleReadJEDEC(offset);
115             }
116             this.Log(LogLevel.Warning, string.Format(
117                 "Read @ unknown state {1}, offset 0x{0:X}", offset, state)
118             );
119             return 0;
120         }
121 
ReadWord(long offset)122         public ushort ReadWord(long offset)
123         {
124             if(state == State.ReadArray)
125             {
126                 return (ushort)HandleRead(offset, 1);
127             }
128             offset >>= (int)busWidth;
129             return ReadByte(offset);
130         }
131 
ReadDoubleWord(long offset)132         public uint ReadDoubleWord(long offset)
133         {
134             if(state == State.ReadArray)
135             {
136                 return HandleRead(offset, 2);
137             }
138             offset >>= (int)busWidth;
139             return ReadByte(offset);
140         }
141 
WriteByte(long offset, byte value)142         public void WriteByte(long offset, byte value)
143         {
144             switch(state)
145             {
146             case State.ProgramWish:
147                 HandleProgramByte(offset, value, 0);
148                 return;
149             case State.WaitingForElementCount:
150                 SetupWriteBuffer(offset, value);
151                 return;
152             case State.WaitingForBufferData:
153                 HandleMultiByteWrite(offset, value, 0);
154                 return;
155             case State.WaitingForWriteConfirm:
156                 HandleWriteConfirm(offset, value);
157                 return;
158             }
159             switch(value)
160             {
161             case Command.ReadArray:
162                 state = State.ReadArray;
163                 return;
164             case Command.ReadQuery:
165                 state = State.ReadQuery;
166                 return;
167             case Command.EraseBlock:
168                 state = State.EraseWish;
169                 return;
170             case Command.ProgramByte:
171                 state = State.ProgramWish;
172                 return;
173             case Command.ReadJEDECId: // TODO
174                 state = State.ReadJEDECId;
175                 return;
176             case Command.ReadStatus:
177                 state = State.ActionDone;
178                 return;
179             case Command.SetupWriteBuffer:
180                 state = State.WaitingForElementCount;
181                 statusRegister |= StatusRegister.ActionDone;
182                 return;
183             case Command.Confirm:
184                 switch(state)
185                 {
186                 case State.EraseWish:
187                     HandleErase(offset);
188                     state = State.ActionDone;
189                     return;
190                 default:
191                     this.Log(LogLevel.Warning,
192                         "Confirm command sent in improper state {0}.", state);
193                     break;
194                 }
195                 return;
196             case Command.ClearStatus:
197                 statusRegister = 0;
198                 this.NoisyLog("Status register cleared.", offset, EraseBlockSize);
199                 return;
200             }
201             this.Log(LogLevel.Warning,
202                 "Unknown command written @ offset 0x{0:X}, value 0x{1:X}.", offset, value);
203         }
204 
WriteWord(long offset, ushort value)205         public void WriteWord(long offset, ushort value)
206         {
207             switch(state)
208             {
209             case State.ProgramWish:
210                 HandleProgramByte(offset, value, 1);
211                 break;
212             case State.WaitingForBufferData:
213                 HandleMultiByteWrite(offset, value, 1);
214                 break;
215             default:
216                 WriteByte(offset, (byte)value);
217                 break;
218             }
219         }
220 
WriteDoubleWord(long offset, uint value)221         public void WriteDoubleWord(long offset, uint value)
222         {
223             switch(state)
224             {
225             case State.ProgramWish:
226                 HandleProgramByte(offset, value, 2);
227                 break;
228             case State.WaitingForBufferData:
229                 HandleMultiByteWrite(offset, value, 2);
230                 break;
231             default:
232                 WriteByte(offset, (byte)value);
233                 break;
234             }
235         }
236 
Reset()237         public void Reset()
238         {
239             FlushBuffer();
240             writeBuffer = null;
241             buffer = new byte[DesiredBufferSize];
242             currentBufferSize = 0;
243             currentBufferStart = 0;
244             state = State.ReadArray;
245             CheckBuffer(0);
246         }
247 
Dispose()248         public void Dispose()
249         {
250             this.NoisyLog("Dispose: flushing buffer and closing underlying stream.");
251             FlushBuffer();
252             stream.Dispose();
253         }
254 
Init(string fileName, int? requestedSize, bool nonPersistent)255         private void Init(string fileName, int? requestedSize, bool nonPersistent)
256         {
257             if(nonPersistent)
258             {
259                 var tempFile = TemporaryFilesManager.Instance.GetTemporaryFile();
260                 FileCopier.Copy(fileName, tempFile, true);
261                 fileName = tempFile;
262             }
263             // if `requestedSize` is `null`, the file lenght will be used
264             stream = new SerializableStreamView(new FileStream(fileName, FileMode.OpenOrCreate), requestedSize, 0xFF);
265             size = (int)stream.Length;
266             CheckSize(size, requestedSize);
267             size2n = (byte)Misc.Logarithm2(size);
268             buffer = new byte[DesiredBufferSize];
269             // default erase block is whole flash or 256KB
270             EraseBlockSize = Math.Min(size, DefaultEraseBlockSize);
271         }
272 
HandleRead(long offset, int width)273         private uint HandleRead(long offset, int width)
274         {
275             CheckBuffer(offset);
276             var localOffset = offset - currentBufferStart;
277             var returnValue = (uint)buffer[localOffset];
278             if(width > 0)
279             {
280                 returnValue |= ((uint)buffer[localOffset + 1] << 8);
281             }
282             if(width > 1)
283             {
284                 returnValue |= ((uint)buffer[localOffset + 2] << 16);
285                 returnValue |= ((uint)buffer[localOffset + 3] << 24);
286             }
287             return returnValue;
288         }
289 
HandleReadJEDEC(long offset)290         private byte HandleReadJEDEC(long offset)
291         {
292             switch(offset)
293             {
294             case 0:
295                 return 0x89;
296             case 1:
297                 return 0x18;
298             default:
299                 this.Log(LogLevel.Warning, "Read @ unsupported offset 0x{0:X} while in state {1}.", offset, state);
300                 return 0;
301             }
302         }
303 
HandleQuery(long offset)304         private byte HandleQuery(long offset)
305         {
306             //TODO: enum/const!!!
307             this.NoisyLog("Query at 0x{0:X}.", offset);
308             switch(offset)
309             {
310             case 0x10: //Q
311                 return 0x51;
312             case 0x11: //R
313                 return 0x52;
314             case 0x12: //Y
315                 return 0x59;
316             case 0x13:
317                 return 0x03; // Intel command set
318             case 0x15:
319                 return 0x31;
320             case 0x1B:
321                 return 0x45;
322             case 0x1C:
323                 return 0x55;
324             case 0x1F:
325                 return 0x07; // timeout
326             case 0x20:
327                 return 0x07;
328             case 0x21:
329                 return 0x0A;
330             case 0x23:
331                 return 0x04; // timeout 2
332             case 0x24:
333                 return 0x04;
334             case 0x25:
335                 return 0x04;
336             case 0x27:
337                 return size2n; // size
338             case 0x28:
339                 return 0x02;
340             case 0x2A:
341                 return 8; // write buffer size
342             case 0x2C:
343                 return 0x01; // no of erase block regions, currently one
344             case 0x2D:
345                 return unchecked((byte)(eraseBlockCountMinusOne));
346             case 0x2E:
347                 return unchecked((byte)((eraseBlockCountMinusOne) >> 8));
348             case 0x2F:
349                 return unchecked((byte)eraseBlockSizeDivided);
350             case 0x30:
351                 return unchecked((byte)(eraseBlockSizeDivided >> 8));
352             case 0x31:
353                 return 0x50;
354             case 0x32:
355                 return 0x52;
356             case 0x33:
357                 return 0x49;
358             case 0x34:
359                 return 0x31;
360             case 0x35:
361                 return 0x31;
362             default:
363                 return 0x00;
364             }
365         }
366 
HandleErase(long offset)367         private void HandleErase(long offset)
368         {
369             if(!IsBlockAddress(offset))
370             {
371                 this.Log(LogLevel.Error,
372                     "Block address given to erase is not block address; given was 0x{0:X}. Cancelling erase.", offset);
373                 statusRegister |= StatusRegister.EraseOrClearLockError;
374                 return;
375             }
376             var inBuffer = false;
377             if(currentBufferStart <= offset && currentBufferStart + currentBufferSize >= offset + EraseBlockSize)
378             {
379                 // we can handle erase in the current buffer
380                 inBuffer = true;
381                 for(var i = 0; i < EraseBlockSize; i++)
382                 {
383                     buffer[offset - currentBufferStart + i] = 0xFF;
384                 }
385             }
386             else
387             {
388                 FlushBuffer();
389                 DiscardBuffer();
390                 stream.Seek(offset, SeekOrigin.Begin);
391                 var a = new byte[EraseBlockSize];
392                 for(var i = 0; i < a.Length; i++)
393                 {
394                     a[i] = 0xFF;
395                 }
396                 stream.Write(a, 0, EraseBlockSize);
397                 CheckBuffer(offset);
398             }
399             statusRegister |= StatusRegister.ActionDone;
400             this.NoisyLog(
401                 "Erased block @ offset 0x{0:X}, size 0x{1:X} ({2}).", offset, EraseBlockSize, inBuffer ? "in buffer" : "in stream");
402         }
403 
HandleProgramByte(long offset, uint value, int width)404         private void HandleProgramByte(long offset, uint value, int width)
405         {
406             CheckBuffer(offset);
407             dirty = true;
408             var index = offset - currentBufferStart;
409             WriteLikeFlash(ref buffer[index], (byte)value);
410             if(width > 0)
411             {
412                 WriteLikeFlash(ref buffer[index + 1], (byte)(value >> 8));
413             }
414             if(width > 1)
415             {
416                 WriteLikeFlash(ref buffer[index + 2], (byte)(value >> 16));
417                 WriteLikeFlash(ref buffer[index + 3], (byte)(value >> 24));
418             }
419             state = State.ActionDone;
420             statusRegister |= StatusRegister.ActionDone;
421             this.NoisyLog("Programmed byte @ offset 0x{0:X}, value 0x{1:X}.", offset, value);
422         }
423 
HandleWriteConfirm(long offset, byte value)424         private void HandleWriteConfirm(long offset, byte value)
425         {
426             if(value != Command.Confirm)
427             {
428                 // discarding data
429                 this.NoisyLog("Discarded buffer of size {0}B.", Misc.NormalizeBinary(writeBuffer.Length));
430                 writeBuffer = null;
431                 state = State.ReadArray;
432                 return;
433             }
434             if(offset >= currentBufferStart &&
435                 writeBufferStart - currentBufferStart + writeBuffer.Length <= currentBufferSize)
436             {
437                 writeBuffer.CopyTo(buffer, writeBufferStart - currentBufferStart);
438                 dirty = true;
439                 this.NoisyLog("Programmed buffer (with delayed write) of {0}B at 0x{1:X}.",
440                     Misc.NormalizeBinary(writeBuffer.Length), writeBufferStart);
441             }
442             else
443             {
444                 FlushBuffer();
445                 DiscardBuffer(); // to assure consistency
446                 stream.Seek(writeBufferStart, SeekOrigin.Begin);
447                 stream.Write(writeBuffer, 0, writeBuffer.Length);
448                 this.NoisyLog("Programmed buffer (with direct write) of {0}B at 0x{1:X}.",
449                     Misc.NormalizeBinary(writeBuffer.Length), writeBufferStart);
450             }
451             writeBuffer = null;
452             statusRegister |= StatusRegister.ActionDone;
453             state = State.ActionDone;
454             writtenCount = 0;
455         }
456 
HandleMultiByteWrite(long offset, uint value, int width)457         private void HandleMultiByteWrite(long offset, uint value, int width)
458         {
459             if(writtenCount == 0)
460             {
461                 writeBufferStart = offset;
462                 if(likeFlashProgramming)
463                 {
464                     // maybe we can read from buffer
465                     if(currentBufferStart <= offset && currentBufferStart + currentBufferSize >= writeBuffer.Length + offset)
466                     {
467                         for(var i = 0; i < writeBuffer.Length; i++)
468                         {
469                             writeBuffer[i] = buffer[offset - currentBufferStart + i];
470                         }
471                         this.NoisyLog("Write buffer filled from buffer.");
472                     }
473                     else
474                     {
475                         FlushBuffer();
476                         stream.Seek(offset, SeekOrigin.Begin);
477                         var read = stream.Read(writeBuffer, 0, writeBuffer.Length);
478                         if(read != writeBuffer.Length)
479                         {
480                             this.Log(LogLevel.Error,
481                                 "Error while reading data to fill write buffer. Read {0}, but requested {1}.",
482                                 read, writeBuffer.Length);
483                         }
484                         this.NoisyLog("Write buffer filled from stream.");
485                     }
486                 }
487             }
488 #if DEBUG
489             if(offset != (writeBufferStart + writtenCount))
490             {
491                 this.Log(LogLevel.Error,
492                     "Non continous write using write buffer, offset 0x{0:X} but buffer start 0x{1:X} and written count 0x{2:X}." +
493                     "Possibility of data corruption.", offset, writeBufferStart, writtenCount);
494             }
495 #endif
496             WriteLikeFlash(ref writeBuffer[writtenCount], (byte)value);
497             writtenCount++;
498             if(width > 0)
499             {
500                 WriteLikeFlash(ref writeBuffer[writtenCount], (byte)(value >> 8));
501                 writtenCount++;
502             }
503             if(width > 1)
504             {
505                 WriteLikeFlash(ref writeBuffer[writtenCount], (byte)(value >> 16));
506                 WriteLikeFlash(ref writeBuffer[writtenCount + 1], (byte)(value >> 24));
507                 writtenCount += 2;
508             }
509             if(writtenCount == writeBuffer.Length)
510             {
511                 state = State.WaitingForWriteConfirm;
512             }
513         }
514 
SetupWriteBuffer(long offset, byte value)515         private void SetupWriteBuffer(long offset, byte value)
516         {
517             var size = ((value + 1) << (int)busWidth);
518             this.NoisyLog("Setting up write buffer of size {0}.", size);
519             writeBuffer = new byte[size];
520             state = State.WaitingForBufferData;
521             writeBufferStart = offset;
522         }
523 
IsBlockAddress(long offset)524         private bool IsBlockAddress(long offset)
525         {
526             return offset % EraseBlockSize == 0;
527         }
528 
CheckSize(int sizeToCheck, int? requestedSize)529         private void CheckSize(int sizeToCheck, int? requestedSize)
530         {
531             if(sizeToCheck == 0 && !requestedSize.HasValue)
532             {
533                 // most probably we've just created an empty file
534                 throw new ConstructionException("Size must be provided when creating a new backend file");
535             }
536             if(sizeToCheck < 256)
537             {
538                 throw new ConstructionException("Size cannot be less than 256B.");
539             }
540             if((sizeToCheck & (sizeToCheck - 1)) != 0)
541             {
542                 throw new ConstructionException("Size has to be power of two.");
543             }
544         }
545 
CheckBuffer(long offset)546         private void CheckBuffer(long offset)
547         {
548             if(offset >= currentBufferStart && offset < currentBufferStart + currentBufferSize)
549             {
550                 return;
551             }
552             FlushBuffer();
553             var alignedAddress = offset & (~3);
554             this.NoisyLog("Reloading buffer at 0x{0:X}.", alignedAddress);
555             stream.Seek(alignedAddress, SeekOrigin.Begin);
556             currentBufferStart = alignedAddress;
557             currentBufferSize = unchecked((int)Math.Min(DesiredBufferSize, size - alignedAddress));
558             var read = stream.Read(buffer, 0, currentBufferSize);
559             if(read != currentBufferSize)
560             {
561                 this.Log(LogLevel.Error, "Error while reading from file: read {0}B, but {1}B requested.",
562                     read, currentBufferSize);
563             }
564         }
565 
FlushBuffer()566         private void FlushBuffer()
567         {
568             if(buffer == null || !dirty)
569             {
570                 return;
571             }
572             this.NoisyLog("Buffer flushed.");
573             stream.Seek(currentBufferStart, SeekOrigin.Begin);
574             stream.Write(buffer, 0, currentBufferSize);
575             dirty = false;
576         }
577 
DiscardBuffer()578         private void DiscardBuffer()
579         {
580             this.NoisyLog("Buffer discarded.");
581             currentBufferStart = 0;
582             currentBufferSize = 0;
583             dirty = false;
584         }
585 
WriteLikeFlash(ref byte where, byte what)586         private void WriteLikeFlash(ref byte where, byte what)
587         {
588             if(likeFlashProgramming)
589             {
590                 where &= what;
591             }
592             else
593             {
594                 where = what;
595             }
596         }
597 
598         private class Command
599         {
600             public const byte ReadArray = 0xFF;
601             public const byte ReadQuery = 0x98;
602             public const byte ReadJEDECId = 0x90;
603             public const byte ReadStatus = 0x70;
604             public const byte ClearStatus = 0x50;
605             public const byte EraseBlock = 0x20;
606             public const byte Confirm = 0xD0;
607             public const byte ProgramByte = 0x40;
608             public const byte PageLock = 0x60;
609             public const byte PageUnlock = 0x60;
610             public const byte SetupWriteBuffer = 0xE8;
611         }
612 
613         private enum State : int
614         {
615             ReadArray = 0,
616             ReadStatus,
617             ReadQuery,
618             ReadJEDECId,
619             EraseWish,
620             ProgramWish,
621             WaitingForElementCount,
622             WaitingForBufferData,
623             WaitingForWriteConfirm,
624             ActionDone
625         }
626 
627         [Flags]
628         private enum StatusRegister : byte
629         {
630             Protected = 2,
631             WriteOrSetLockError = 16,
632             EraseOrClearLockError = 32,
633             ActionDone = 128
634         }
635 
636         private const int DesiredBufferSize = 100 * 1024;
637         private const int DefaultEraseBlockSize = 256 * 1024;
638         private const int CopyBufferSize = 256 * 1024;
639         private long currentBufferStart;
640         private int currentBufferSize;
641         private byte[] buffer;
642         private bool dirty;
643         private byte[] writeBuffer;
644         private long writeBufferStart;
645         private int writtenCount;
646         private ushort eraseBlockSizeDivided;
647         private ushort eraseBlockCountMinusOne;
648         private StatusRegister statusRegister;
649         private byte size2n;
650         private int size;
651         private State state;
652         private SerializableStreamView stream;
653         private readonly int busWidth;
654         private static readonly bool likeFlashProgramming = true;
655     }
656 }
657 
658