1 //
2 // Copyright (c) 2010-2025 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.Collections.Generic;
10 using System.Linq;
11 using Antmicro.Renode.Core;
12 using Antmicro.Renode.Core.Structure.Registers;
13 using Antmicro.Renode.Exceptions;
14 using Antmicro.Renode.Logging;
15 using Antmicro.Renode.Utilities;
16 
17 namespace Antmicro.Renode.Peripherals.Miscellaneous
18 {
19     public class SAM4S_EEFC : BasicDoubleWordPeripheral, IKnownSize
20     {
SAM4S_EEFC(IMachine machine, IMemory underlyingMemory, uint flashIdentifier = DefaultFlashIdentifier, int pageSize = 512, int sectorSize = 64000, int lockRegionSize = 8000)21         public SAM4S_EEFC(IMachine machine, IMemory underlyingMemory, uint flashIdentifier = DefaultFlashIdentifier,
22             int pageSize = 512, int sectorSize = 64000, int lockRegionSize = 8000) : base(machine)
23         {
24             AssertPositive(nameof(pageSize), pageSize);
25             AssertPositive(nameof(sectorSize), sectorSize);
26             AssertPositive(nameof(lockRegionSize), lockRegionSize);
27 
28             if(underlyingMemory == null)
29             {
30                 throw new ConstructionException($"'{nameof(underlyingMemory)}' can't be null");
31             }
32 
33             if(underlyingMemory.Size % pageSize > 0)
34             {
35                 throw new ConstructionException($"Size of '{nameof(underlyingMemory)}' is not divisible by page size ({pageSize})");
36             }
37 
38             if(sectorSize % pageSize > 0)
39             {
40                 throw new ConstructionException($"Sector size ({sectorSize}) is not divisible by page size ({pageSize})");
41             }
42 
43             if(sectorSize % lockRegionSize > 0)
44             {
45                 throw new ConstructionException($"Sector size ({sectorSize}) is not divisible by lock region size ({lockRegionSize})");
46             }
47 
48             this.underlyingMemory = underlyingMemory;
49             this.flashIdentifier = flashIdentifier;
50             this.pageSize = pageSize;
51             this.sectorSize = sectorSize;
52             this.lockRegionSize = lockRegionSize;
53             this.lockBits = new bool[NumberOfLockRegions];
54 
55             DefineRegisters();
56         }
57 
EraseAll()58         public void EraseAll()
59         {
60             for(var i = 0; i < NumberOfPages; ++i)
61             {
62                 ErasePage(i);
63             }
64         }
65 
ErasePage(int page)66         public void ErasePage(int page)
67         {
68             if(page < 0 || page > NumberOfPages)
69             {
70                 throw new RecoverableException($"'{nameof(page)}' should be between 0 and {NumberOfPages - 1}");
71             }
72 
73             if(IsPageLocked(page))
74             {
75                 triedWriteLocked.Value = true;
76                 this.Log(LogLevel.Debug, "Tried to erase page #{0} which is currently locked", page);
77                 return;
78             }
79 
80             underlyingMemory.WriteBytes(page * pageSize, Enumerable.Repeat((byte)0xFF, pageSize).ToArray(), 0, pageSize);
81         }
82 
83         public long Size => 0x200;
84 
85         public GPIO IRQ { get; } = new GPIO();
86 
87         public int NumberOfPages => (int)underlyingMemory.Size / pageSize;
88 
89         public int PagesInSector => sectorSize / pageSize;
90 
91         public int NumberOfLockRegions => (int)underlyingMemory.Size / lockRegionSize;
92 
PageToLockRegionIndex(int page)93         private int PageToLockRegionIndex(int page)
94         {
95             return page * pageSize / lockRegionSize;
96         }
97 
IsPageLocked(int page)98         private bool IsPageLocked(int page)
99         {
100             return lockBits[PageToLockRegionIndex(page)];
101         }
102 
ExecuteFlashCommand(Commands command, int argument)103         private void ExecuteFlashCommand(Commands command, int argument)
104         {
105             this.Log(LogLevel.Debug, "Executing command {0}, argument 0x{1:X}", command, argument);
106 
107             // NOTE: We're busy, unset IRQ
108             IRQ.Unset();
109 
110             switch(command)
111             {
112                 case Commands.GetFlashDescriptor:
113                     resultQueue.EnqueueRange(new uint[]
114                     {
115                         /* Flash interface description */ flashIdentifier,
116                         /*         Flash size in bytes */ (uint)underlyingMemory.Size,
117                         /*          Page size in bytes */ (uint)pageSize,
118                         /*            Number of planes */ 1,
119                         /* Number of bytes in plane #0 */ (uint)underlyingMemory.Size,
120                         /*         Number of lock bits */ (uint)NumberOfLockRegions
121                     });
122                     // Number of bytes in (each) lock region
123                     resultQueue.EnqueueRange(Enumerable.Repeat((uint)lockRegionSize, NumberOfLockRegions));
124                     break;
125 
126                 case Commands.WritePage:
127                     // NOTE: This command does nothing, as changes are committed immediately to underlying memory
128                     break;
129 
130                 case Commands.WritePageAndLock:
131                     // NOTE: This command effectively locks single page; see comment above
132                     goto case Commands.SetLockBit;
133 
134                 case Commands.ErasePageAndWritePage:
135                     if(argument >= NumberOfPages)
136                     {
137                         commandError.Value = true;
138                         return;
139                     }
140                     ErasePage(argument);
141                     break;
142 
143                 case Commands.ErasePageAndWritePageThenLock:
144                     if(argument >= NumberOfPages)
145                     {
146                         commandError.Value = true;
147                         return;
148                     }
149                     ErasePage(argument);
150                     goto case Commands.SetLockBit;
151 
152                 case Commands.EraseAll:
153                     EraseAll();
154                     break;
155 
156                 case Commands.ErasePages:
157                     var arg0 = argument & 0x3;
158                     var arg1 = argument >> (2 + arg0);
159                     var numberOfPages = 4 << arg0;
160                     var pageStart = numberOfPages * arg1;
161 
162                     this.Log(LogLevel.Debug, "Erasing {0} pages starting from {1}", numberOfPages, pageStart);
163                     for(var i = 0; i < numberOfPages && i + pageStart < NumberOfPages; ++i)
164                     {
165                         ErasePage(pageStart + i);
166                     }
167                     break;
168 
169                 case Commands.SetLockBit:
170                     if(argument >= NumberOfPages)
171                     {
172                         commandError.Value = true;
173                         return;
174                     }
175                     lockBits[PageToLockRegionIndex(argument)] = true;
176                     this.Log(LogLevel.Debug, "Locked region #{0}", PageToLockRegionIndex(argument));
177                     break;
178 
179                 case Commands.ClearLockBit:
180                     if(argument >= NumberOfPages)
181                     {
182                         commandError.Value = true;
183                         return;
184                     }
185                     lockBits[PageToLockRegionIndex(argument)] = false;
186                     this.Log(LogLevel.Debug, "Unlocked region #{0}", PageToLockRegionIndex(argument));
187                     break;
188 
189                 case Commands.GetLockBit:
190                     if(argument >= NumberOfPages)
191                     {
192                         commandError.Value = true;
193                         return;
194                     }
195                     resultQueue.Enqueue(lockBits[PageToLockRegionIndex(argument)] ? 1U : 0U);
196                     break;
197 
198                 case Commands.EraseSector:
199                     if(argument >= NumberOfLockRegions)
200                     {
201                         commandError.Value = true;
202                         return;
203                     }
204 
205                     if(lockBits[argument])
206                     {
207                         triedWriteLocked.Value = true;
208                         this.Log(LogLevel.Debug, "Tried to erase sector #{0} which is currently locked", argument);
209                     }
210 
211                     var firstPage = argument * PagesInSector;
212                     for(var i = 0; i < PagesInSector; ++i)
213                     {
214                         ErasePage(firstPage + i);
215                     }
216                     break;
217 
218                 case Commands.SetGPNVM:
219                 case Commands.ClearGPNVM:
220                 case Commands.GetGPNVM:
221                 case Commands.StartReadUniqueIdentifier:
222                 case Commands.StopReadUniqueIdentifier:
223                 case Commands.GetCALIB:
224                 case Commands.WriteUserSignature:
225                 case Commands.EraseUserSignature:
226                 case Commands.StartReadUserSignature:
227                 case Commands.StopReadUserSignature:
228                     this.Log(LogLevel.Warning, "{0} command is not supported", command);
229                     break;
230 
231                 default:
232                     throw new Exception("unreachable");
233             }
234         }
235 
AssertPositive(string argument, int value)236         private void AssertPositive(string argument, int value)
237         {
238             if(value <= 0)
239             {
240                 throw new ConstructionException($"'{argument}' should be greater than zero");
241             }
242         }
243 
DefineRegisters()244         private void DefineRegisters()
245         {
246             Registers.Mode.Define(this)
247                 .WithFlag(0, out generateInterrupt, name: "FRDY",
248                     changeCallback: (_, __) => IRQ.Set(generateInterrupt.Value))
249                 .WithTag("FWS", 8, 4)
250                 .WithTaggedFlag("SCOD", 16)
251                 .WithTaggedFlag("FAM", 24)
252                 .WithTaggedFlag("CLOE", 26)
253             ;
254 
255             Registers.Command.Define(this)
256                 .WithValueField(0, 8, out var command, name: "FCMD")
257                 .WithValueField(8, 16, out var argument, name: "FARG")
258                 .WithValueField(24, 8, out var key, name: "FKEY")
259                 .WithWriteCallback((_, __) =>
260                 {
261                     if(key.Value != Password || !Enum.IsDefined(typeof(Commands), (int)command.Value))
262                     {
263                         // NOTE: Invalid password or command; ignore
264                         commandError.Value = true;
265                         return;
266                     }
267 
268                     ExecuteFlashCommand((Commands)command.Value, (int)argument.Value);
269                     IRQ.Set(generateInterrupt.Value);
270                 })
271             ;
272 
273             Registers.Status.Define(this)
274                 .WithFlag(0, FieldMode.Read, name: "FRDY",
275                     valueProviderCallback: _ => true)
276                 .WithTaggedFlag("FCMDE", 1)
277                 .WithFlag(2, out triedWriteLocked, FieldMode.ReadToClear, name: "FLOCKE")
278                 .WithFlag(3, out commandError, FieldMode.ReadToClear, name: "FLERR")
279                 .WithReservedBits(4, 28)
280             ;
281 
282             Registers.Result.Define(this)
283                 .WithValueField(0, 32, FieldMode.Read, name: "FRR",
284                     valueProviderCallback: _ => resultQueue.TryDequeue(out var result) ? result : 0)
285             ;
286         }
287 
288         private IFlagRegisterField generateInterrupt;
289         private IFlagRegisterField triedWriteLocked;
290         private IFlagRegisterField commandError;
291 
292         private readonly IMemory underlyingMemory;
293         private readonly uint flashIdentifier;
294         private readonly int pageSize;
295         private readonly int sectorSize;
296         private readonly int lockRegionSize;
297         private readonly bool[] lockBits; // NOTE: Initialized in constructor
298         private readonly Queue<uint> resultQueue = new Queue<uint>();
299 
300         private const uint Password = 0x5A;
301         private const uint DefaultFlashIdentifier = 0x00112233;
302 
303         private enum Commands
304         {
305             GetFlashDescriptor = 0x00,
306             WritePage = 0x01,
307             WritePageAndLock = 0x02,
308             ErasePageAndWritePage = 0x03,
309             ErasePageAndWritePageThenLock = 0x04,
310             EraseAll = 0x05,
311             ErasePages = 0x07,
312             SetLockBit = 0x08,
313             ClearLockBit = 0x09,
314             GetLockBit = 0x0A,
315             SetGPNVM = 0x0B,
316             ClearGPNVM = 0x0C,
317             GetGPNVM = 0x0D,
318             StartReadUniqueIdentifier = 0x0E,
319             StopReadUniqueIdentifier = 0x0F,
320             GetCALIB = 0x10,
321             EraseSector = 0x11,
322             WriteUserSignature = 0x12,
323             EraseUserSignature = 0x13,
324             StartReadUserSignature = 0x14,
325             StopReadUserSignature = 0x15,
326         }
327 
328         private enum Registers
329         {
330             Mode = 0x00,
331             Command = 0x04,
332             Status = 0x08,
333             Result = 0x0C,
334         }
335     }
336 }
337