using System; using Antmicro.Renode.Core; using Antmicro.Renode.Logging; using Antmicro.Renode.Peripherals.Bus; namespace Antmicro.Renode.Peripherals.CustomPeripherals { public class YB29LV160Flash : IDoubleWordPeripheral, IWordPeripheral, IBytePeripheral, IKnownSize, IGPIOReceiver { public YB29LV160Flash(IMachine machine, long size = DefaultFlashSize) { if(size <= 0 || size > int.MaxValue) { throw new ArgumentException($"Invalid flash size: {size}", nameof(size)); } Size = size; storage = new byte[(int)size]; Ready = new GPIO(); Array.Fill(storage, ErasedValue); Reset(); } public byte ReadByte(long offset) { if(!TryValidateRange(offset, 1)) { return 0xFF; } return storage[(int)offset]; } public ushort ReadWord(long offset) { if(!TryValidateRange(offset, 2)) { return 0xFFFF; } var index = (int)offset; return (ushort)(storage[index] | (storage[index + 1] << 8)); } public uint ReadDoubleWord(long offset) { if(!TryValidateRange(offset, 4)) { return 0xFFFFFFFF; } var index = (int)offset; return (uint)( storage[index] | (storage[index + 1] << 8) | (storage[index + 2] << 16) | (storage[index + 3] << 24) ); } public void WriteByte(long offset, byte value) { this.Log(LogLevel.Warning, "Unhandled byte write at offset 0x{0:X}: 0x{1:X2}; firmware is expected to use 32-bit accesses", offset, value); commandState = FlashCommandState.ReadArray; } public void WriteWord(long offset, ushort value) { this.Log(LogLevel.Warning, "Unhandled word write at offset 0x{0:X}: 0x{1:X4}; firmware is expected to use 32-bit accesses", offset, value); commandState = FlashCommandState.ReadArray; } public void WriteDoubleWord(long offset, uint value) { if(!TryValidateRange(offset, 4)) { commandState = FlashCommandState.ReadArray; return; } this.Log(LogLevel.Noisy, "Flash write offset 0x{0:X}, value 0x{1:X8}, state {2}", offset, value, commandState); switch(commandState) { case FlashCommandState.ReadArray: if(IsUnlock1(offset, value)) { commandState = FlashCommandState.GotAA; return; } break; case FlashCommandState.GotAA: if(IsUnlock2(offset, value)) { commandState = FlashCommandState.GotAA55; return; } break; case FlashCommandState.GotAA55: if(IsProgramSetup(offset, value)) { commandState = FlashCommandState.ProgramSetup; return; } if(IsEraseSetup(offset, value)) { commandState = FlashCommandState.EraseSetup; return; } break; case FlashCommandState.ProgramSetup: ProgramDoubleWord(offset, value); commandState = FlashCommandState.ReadArray; return; case FlashCommandState.EraseSetup: if(IsUnlock1(offset, value)) { commandState = FlashCommandState.EraseGotAA; return; } break; case FlashCommandState.EraseGotAA: if(IsUnlock2(offset, value)) { commandState = FlashCommandState.EraseGotAA55; return; } break; case FlashCommandState.EraseGotAA55: if(value == SectorEraseConfirm) { EraseSectorContaining(offset); commandState = FlashCommandState.ReadArray; return; } break; } this.Log(LogLevel.Warning, "Unexpected flash command write at offset 0x{0:X}: 0x{1:X8} in state {2}", offset, value, commandState); commandState = FlashCommandState.ReadArray; } public void OnGPIO(int number, bool value) { switch(number) { case WriteProtectInput: // The board-level WP pin is active low in the C driver. writeProtected = !value; this.Log(LogLevel.Debug, "Hardware write protect {0}", writeProtected ? "enabled" : "disabled"); break; default: this.Log(LogLevel.Warning, "Unhandled GPIO #{0} value {1}", number, value); break; } } public void Reset() { commandState = FlashCommandState.ReadArray; writeProtected = true; SetReady(true); } public void SetWriteProtect(bool enabled) { writeProtected = enabled; } public GPIO Ready { get; } public long Size { get; } private void ProgramDoubleWord(long offset, uint value) { if(writeProtected) { this.Log(LogLevel.Warning, "Ignoring program command at 0x{0:X}; hardware write protect is enabled", offset); return; } // NOR programming can only drive bits from 1 to 0. var index = (int)offset; storage[index] = (byte)(storage[index] & (value & 0xFF)); storage[index + 1] = (byte)(storage[index + 1] & ((value >> 8) & 0xFF)); storage[index + 2] = (byte)(storage[index + 2] & ((value >> 16) & 0xFF)); storage[index + 3] = (byte)(storage[index + 3] & ((value >> 24) & 0xFF)); this.Log(LogLevel.Debug, "Programmed flash at 0x{0:X} with 0x{1:X8}", offset, value); } private void EraseSectorContaining(long offset) { if(writeProtected) { this.Log(LogLevel.Warning, "Ignoring sector erase at 0x{0:X}; hardware write protect is enabled", offset); return; } var sectorStart = GetSectorStart(offset, out var sectorSize); if(sectorSize == 0) { this.Log(LogLevel.Warning, "Cannot erase sector for offset 0x{0:X}", offset); return; } for(var i = 0; i < sectorSize; i++) { storage[sectorStart + i] = ErasedValue; } this.Log(LogLevel.Debug, "Erased sector at 0x{0:X}, size 0x{1:X}", sectorStart, sectorSize); } private void SetReady(bool value) { Ready.Set(value); } private int GetSectorStart(long offset, out int sectorSize) { if(offset < 0 || offset >= Size) { sectorSize = 0; return 0; } // Mirror the firmware's get_flash_sector_addr_size() layout. if(offset >= 0x3F8000) { sectorSize = 0x8000; return 0x3F8000; } if(offset >= 0x3F4000) { sectorSize = 0x4000; return 0x3F4000; } if(offset >= 0x3F0000) { sectorSize = 0x4000; return 0x3F0000; } if(offset >= 0x3E0000) { sectorSize = 0x10000; return 0x3E0000; } sectorSize = 0x20000; return (int)(offset / sectorSize) * sectorSize; } private bool TryValidateRange(long offset, int width) { if(offset < 0 || offset + width > Size) { this.Log(LogLevel.Warning, "Out-of-range flash access at offset 0x{0:X}, width {1}", offset, width); return false; } return true; } private static bool IsUnlock1(long offset, uint value) { return offset == UnlockAddress1 && value == UnlockData1; } private static bool IsUnlock2(long offset, uint value) { return offset == UnlockAddress2 && value == UnlockData2; } private static bool IsProgramSetup(long offset, uint value) { return offset == UnlockAddress1 && value == ProgramSetupData; } private static bool IsEraseSetup(long offset, uint value) { return offset == UnlockAddress1 && value == EraseSetupData; } private readonly byte[] storage; private FlashCommandState commandState; private bool writeProtected; private enum FlashCommandState { ReadArray, GotAA, GotAA55, ProgramSetup, EraseSetup, EraseGotAA, EraseGotAA55 } private const long DefaultFlashSize = 0x400000; private const byte ErasedValue = 0xFF; private const int WriteProtectInput = 0; private const long UnlockAddress1 = 0x1554; private const long UnlockAddress2 = 0x0AA8; private const uint UnlockData1 = 0x00AA00AA; private const uint UnlockData2 = 0x00550055; private const uint ProgramSetupData = 0x00A000A0; private const uint EraseSetupData = 0x00800080; private const uint SectorEraseConfirm = 0x00300030; } }