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