319 lines
9.8 KiB
C#
319 lines
9.8 KiB
C#
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;
|
|
}
|
|
}
|