From d9c0f0f07c490353d33a978b2c76db7d17629e74 Mon Sep 17 00:00:00 2001 From: liuwb Date: Mon, 27 Apr 2026 09:08:34 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B9=B6=E5=8F=A3flash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- YB29LV160Flash.cs | 318 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 YB29LV160Flash.cs diff --git a/YB29LV160Flash.cs b/YB29LV160Flash.cs new file mode 100644 index 0000000..0601176 --- /dev/null +++ b/YB29LV160Flash.cs @@ -0,0 +1,318 @@ +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; + } +}