using System.Net; using DotNext; namespace RemoteUpdateClient; static class RemoteUpdaterAddr { public const UInt32 Base = 0x20_00_00_00; /// /// ADDR: 0X00: 写Flash-读写地址——控制位
/// [31:16]: wr_sector_num
/// [15: 0]: {flash_wr_en,-,-,-, start_wr_sector}
///
public const UInt32 WriteCtrl = Base + 0x00; /// /// ADDR: 0X01: 写Flash-只写地址——FIFO入口
/// [31:0]: 写比特流数据入口
///
public const UInt32 WriteFIFO = Base + 0x01; /// /// ADDR: 0X02: 写Flash-只读地址——标志位
/// [31:24]: {-, -, -, -, -, -, -, wr_fifo_full}
/// [23:16]: {-, -, -, -, -, -, -, wr_fifo_empty}
/// [15: 8]: {-, -, -, -, -, -, -, flash_wr_done}
/// [ 7: 0]: {-, -, -, -, -, -, -, flash_clear_done}
///
public const UInt32 WriteSign = Base + 0x02; /// /// ADDR: 0X03: 读Flash-读写地址——控制位1
/// [31:16]: rd_sector_num
/// [15: 0]: {flash_rd_en,-,-,-, start_rd_sub_sector}
///
public const UInt32 ReadCtrl1 = Base + 0x03; /// /// ADDR: 0X04: 读Flash-读写地址——控制位2
/// [31:24]: { }
/// [23:16]: {-, -, -, -, -, -,{ bs_crc32_ok }}
/// [15: 8]: {-, -, -, -, -, -, -, crc_check_en}
/// [ 7: 0]: {-, -, -, -, -, -, -, bitstream_up2cpu_en}
///
public const UInt32 ReadCtrl2 = Base + 0x04; /// /// ADDR: 0X05: 读Flash-只读地址——FIFO出口
/// [31:0]: 读比特流数据出口
///
public const UInt32 ReadFIFO = Base + 0x05; /// /// ADDR: 0X06: 读Flash-只读地址——CRC校验值
/// [31:0]: CRC校验值 bs_readback_crc
///
public const UInt32 ReadCRC = Base + 0x06; /// /// ADDR: 0X07: 读Flash-只读地址——标志位
/// [31:24]: {-, -, -, -, -, -, -, rd_fifo_afull}
/// [23:16]: {-, -, -, -, -, -, -, rd_fifo_empty}
/// [15: 8]: {-, -, -, -, -, -, -, flash_rd_done}
/// [ 7: 0]: {-, -, -, -, -, -, -, bs_readback_crc_valid}
///
public const UInt32 ReadSign = Base + 0x07; /// /// ADDR: 0X08: 热启动开关-读写地址——控制位
/// [31: 8]: hotreset_addr
/// [ 7: 0]: {-, -, -, -, -, -, -, hotreset_en}
///
public const UInt32 HotResetCtrl = Base + 0x08; /// /// ADDR: 0X09: 只读地址 版本号
/// [31: 0]: FPGA_VERSION[31:0]
///
public const UInt32 Version = Base + 0x09; } static class FlashAddr { public static readonly UInt32[] Switch = { 0xFFFF, 0x0000, 0x2000, 0x4000 }; public static readonly UInt32[] Jump = { 0xFFFF, 0x1000, 0x3000, 0x5000 }; public static readonly UInt32[] Bitstream = { 0x006000, 0x406000, 0x806000, 0xC06000 }; } /// /// [TODO:description] /// public class RemoteUpdater { private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); const int FLASH_SECTOR_LENGTH = 4 * 1024; readonly int timeout = 2000; readonly int timeoutForWait = 60 * 1000; readonly int port; readonly string address; private IPEndPoint ep; /// /// [TODO:description] /// /// [TODO:parameter] /// [TODO:parameter] /// [TODO:parameter] /// [TODO:parameter] /// [TODO:return] public RemoteUpdater(string address, int port, int timeout = 2000, int timeoutForWait = 60 * 1000) { if (timeout < 0) throw new ArgumentException("Timeout couldn't be negative", nameof(timeout)); this.address = address; this.port = port; this.ep = new IPEndPoint(IPAddress.Parse(address), port); this.timeout = timeout; this.timeoutForWait = timeoutForWait; } /// /// [TODO:description] /// /// [TODO:parameter] /// [TODO:parameter] /// [TODO:parameter] /// [TODO:return] private async ValueTask> WriteFlash(UInt32 flashAddr, int writeSectorNum, byte[] bytesData) { // Assert if (writeSectorNum <= 0 || writeSectorNum >= FLASH_SECTOR_LENGTH) return new(new ArgumentException( $"Write sector num should be 1 ~ 4096, but given {writeSectorNum}", nameof(writeSectorNum))); if (bytesData.Length % (4 * 1024) != 0) return new(new ArgumentException( $"The length of data should be divided by 4096, bug given {bytesData.Length}", nameof(bytesData))); { var ret = await UDPClientPool.WriteAddr( this.ep, RemoteUpdaterAddr.WriteCtrl, Convert.ToUInt32((writeSectorNum << 16) | (1 << 15) | Convert.ToInt32(flashAddr / 4096)), this.timeout); if (!ret.IsSuccessful) return new(ret.Error); if (!ret.Value) return new(new Exception("Enable write flash failed")); } { var ret = await UDPClientPool.ReadAddrWithWait( this.ep, RemoteUpdaterAddr.WriteSign, 0x00_00_00_01, 0x00_00_00_01, this.timeoutForWait); if (!ret.IsSuccessful) return new(ret.Error); if (!ret.Value) return new(new Exception( $"Flash clear failed after {this.timeoutForWait} milliseconds")); } { var ret = await UDPClientPool.WriteAddr(this.ep, RemoteUpdaterAddr.WriteFIFO, bytesData, this.timeout); if (!ret.IsSuccessful) return new(ret.Error); if (!ret.Value) return new(new Exception("Send data to flash failed")); } { var ret = await UDPClientPool.ReadAddrWithWait( this.ep, RemoteUpdaterAddr.WriteSign, 0x00_00_01_00, 0x00_00_01_00, this.timeoutForWait); if (!ret.IsSuccessful) return new(ret.Error); return ret.Value; } } /// /// [TODO:description] /// /// [TODO:parameter] /// [TODO:return] private async ValueTask> EnableBitstream(int bitstreamNum) { // Assert if (bitstreamNum <= 0 || bitstreamNum > 3) return new(new ArgumentException( $"Bitsteam num should be 1 ~ 3 for EnableBitstream, but given {bitstreamNum}", nameof(bitstreamNum))); var bytesData = new byte[FLASH_SECTOR_LENGTH]; Array.Fill(bytesData, 0xFF); bytesData[FLASH_SECTOR_LENGTH - 1] = 0x01; bytesData[FLASH_SECTOR_LENGTH - 2] = 0x33; bytesData[FLASH_SECTOR_LENGTH - 3] = 0x2D; bytesData[FLASH_SECTOR_LENGTH - 4] = 0x94; var ret = await WriteFlash(FlashAddr.Switch[bitstreamNum], 1, bytesData); if (!ret.IsSuccessful) return new(ret.Error); return ret.Value; } /// /// [TODO:description] /// /// [TODO:parameter] /// [TODO:return] private async ValueTask> DisableBitstream(int bitstreamNum) { // Assert if (bitstreamNum <= 0 || bitstreamNum > 3) return new(new ArgumentException( $"Bitsteam num should be 1 ~ 3 for DisableBitstream, but given {bitstreamNum}", nameof(bitstreamNum))); var bytesData = new byte[FLASH_SECTOR_LENGTH]; Array.Fill(bytesData, 0xFF); var ret = await WriteFlash(FlashAddr.Switch[bitstreamNum], 1, bytesData); if (!ret.IsSuccessful) return new(ret.Error); return ret.Value; } /// /// [TODO:description] /// /// [TODO:parameter] /// [TODO:return] private async ValueTask> WriteJumpCmd(int bitstreamNum) { // Assert if (bitstreamNum <= 0 || bitstreamNum > 3) return new(new ArgumentException( $"Bitsteam num should be 1 ~ 3 for WriteJumpCmd, but given {bitstreamNum}", nameof(bitstreamNum))); // Init data var bytesData = new byte[FLASH_SECTOR_LENGTH]; for (int i = 3; i < FLASH_SECTOR_LENGTH; i += 4) bytesData[i] = (byte)0xA0; { var bytesSrc = new byte[] { 0x01, 0x00, 0x00, 0xAB }; Buffer.BlockCopy(bytesSrc, 0, bytesData, 0x04, 4); } { var bytesSrc = new byte[] { 0x0B, 0x00, 0x00, 0x00 }; Buffer.BlockCopy(bytesSrc, 0, bytesData, 0x08, 4); } { var bytesSrc = new byte[] { 0x01, 0x00, 0xC0, 0xAB }; Buffer.BlockCopy(bytesSrc, 0, bytesData, 0x34, 4); } { var bytesSrc = new byte[] { 0x00, 0x00, 0x00, 0x00 }; Buffer.BlockCopy(bytesSrc, 0, bytesData, 0x38, 4); } { var bytesSrc = new byte[] { 0x01, 0x00, 0x00, 0xAC }; Buffer.BlockCopy(bytesSrc, 0, bytesData, 0x3C, 4); } { var bytesSrc = Common.Number.NumberToBytes(FlashAddr.Bitstream[bitstreamNum], 4).Value; Buffer.BlockCopy(Common.Number.ReverseBytes(bytesSrc, 4).Value, 0, bytesData, 0x40, 4); } { var bytesSrc = new byte[] { 0x01, 0x00, 0x80, 0xA8 }; Buffer.BlockCopy(bytesSrc, 0, bytesData, 0x44, 4); } { var bytesSrc = new byte[] { 0x0F, 0x00, 0x00, 0x00 }; Buffer.BlockCopy(bytesSrc, 0, bytesData, 0x48, 4); } var ret = await WriteFlash(FlashAddr.Jump[bitstreamNum], 1, bytesData); if (!ret.IsSuccessful) return new(ret.Error); return ret.Value; } /// /// [TODO:description] /// /// [TODO:parameter] /// [TODO:return] private async ValueTask> InitRemoteUpdate(int bitstreamNum) { // Assert if (bitstreamNum < 0 || bitstreamNum > 3) return new(new ArgumentException( $"Bitsteam num should be 0 ~ 3 for InitRemoteUpdate, but given {bitstreamNum}", nameof(bitstreamNum))); for (int i = 1; i <= 3; i++) { { var ret = (i == bitstreamNum) ? await EnableBitstream(i) : await DisableBitstream(i); if (!ret.IsSuccessful) return new(ret.Error); if (!ret.Value) return new(new Exception($"Write switch {i} failed")); } { var ret = await WriteJumpCmd(i); if (!ret.IsSuccessful) return new(ret.Error); if (!ret.Value) return new(new Exception($"Write jump {i} failed")); } } return true; } /// /// [TODO:description] /// /// [TODO:parameter] /// [TODO:parameter] /// [TODO:parameter] /// [TODO:return] private async ValueTask> CheckBitstreamCRC(int bitstreamNum, int bitstreamLen, UInt32 checkSum) { { var ret = await UDPClientPool.WriteAddr(this.ep, RemoteUpdaterAddr.ReadCtrl2, 0x00_00_00_00, this.timeout); if (!ret.IsSuccessful) return new(ret.Error); if (!ret.Value) return new(new Exception("Write read control 2 failed")); } { var ret = await UDPClientPool.WriteAddr( this.ep, RemoteUpdaterAddr.ReadCtrl1, Convert.ToUInt32((bitstreamLen << 16) | (1 << 15) | Convert.ToInt32(FlashAddr.Bitstream[bitstreamNum] / 4096)), this.timeout); if (!ret.IsSuccessful) return new(ret.Error); if (!ret.Value) return new(new Exception("Write read control 1 failed")); } { var ret = await UDPClientPool.ReadAddrWithWait( this.ep, RemoteUpdaterAddr.ReadSign, 0x00_00_01_00, 0x00_00_01_00, this.timeoutForWait); if (!ret.IsSuccessful) return new(ret.Error); if (!ret.Value) return new(new Exception( $"Read bitstream failed after {this.timeoutForWait} milliseconds")); } { var ret = await UDPClientPool.ReadAddr(this.ep, RemoteUpdaterAddr.ReadCRC, this.timeout); if (!ret.IsSuccessful) return new(ret.Error); var bytes = ret.Value.Options.Data; if (bytes is null) return new(new Exception("CRC is null")); var remoteCRC = Common.Number.BytesToUInt32(bytes); if (!remoteCRC.IsSuccessful) return new(remoteCRC.Error); logger.Debug($"Bitstream {bitstreamNum} Expected CRC: \t 0x{Convert.ToString(checkSum, 16)}"); logger.Debug($"Bitstream {bitstreamNum} Received CRC: \t 0x{Convert.ToString(remoteCRC.Value, 16)}"); return remoteCRC.Value == checkSum; } } /// /// [TODO:description] /// /// [TODO:parameter] /// [TODO:return] private async ValueTask> HotReset(int bitstreamNum) { // Assert if (bitstreamNum < 0 || bitstreamNum > 3) return new(new ArgumentException( $"Bitsteam num should be 0 ~ 3 for HotRest, but given {bitstreamNum}", nameof(bitstreamNum))); var ret = await UDPClientPool.WriteAddr( this.ep, RemoteUpdaterAddr.HotResetCtrl, ((FlashAddr.Bitstream[bitstreamNum] << 8) | 1), this.timeout); if (!ret.IsSuccessful) return new(ret.Error); return ret.Value; } /// /// [TODO:description] /// /// [TODO:parameter] /// [TODO:return] public async ValueTask> HotResetBitstream(int bitstreamNum) { await MsgBus.UDPServer.ClearUDPData(this.address); logger.Trace("Clear udp data finished"); { var ret = await InitRemoteUpdate(bitstreamNum); if (!ret.IsSuccessful) return new(ret.Error); if (!ret.Value) return new(new Exception("Init remote update failed")); } { var ret = await HotReset(bitstreamNum); if (!ret.IsSuccessful) return new(ret.Error); return ret.Value; } } /// /// [TODO:description] /// /// [TODO:parameter] /// [TODO:parameter] /// [TODO:parameter] /// [TODO:parameter] /// [TODO:return] public async ValueTask> UploadBitstreams( byte[]? goldenBitream, byte[]? bitstream1, byte[]? bitstream2, byte[]? bitstream3) { await MsgBus.UDPServer.ClearUDPData(this.address); logger.Trace("Clear udp data finished"); for (int bitstreamNum = 0; bitstreamNum < 4; bitstreamNum++) { byte[] bytesData; if (bitstreamNum == 0 && goldenBitream is not null) bytesData = goldenBitream; else if (bitstreamNum == 1 && bitstream1 is not null) bytesData = bitstream1; else if (bitstreamNum == 2 && bitstream2 is not null) bytesData = bitstream2; else if (bitstreamNum == 3 && bitstream3 is not null) bytesData = bitstream3; else continue; var bitstreamBlockNum = bytesData.Length / (4 * 1024); { var ret = await WriteFlash(FlashAddr.Bitstream[bitstreamNum], bitstreamBlockNum, bytesData); if (!ret.IsSuccessful) return new(ret.Error); if (!ret.Value) return new(new Exception("Write bitstream failed")); } { var crc = Honoo.IO.Hashing.Crc.Create(Honoo.IO.Hashing.CrcName.CRC32_MPEG_2); var checkSum = crc.ComputeFinal(bytesData); var ret = await CheckBitstreamCRC(bitstreamNum, bitstreamBlockNum, checkSum.ToUInt32()); if (!ret.IsSuccessful) return new(ret.Error); if (!ret.Value) logger.Warn($"Bitstream {bitstreamNum} CRC32 not correct!"); else logger.Info($"Bitstream {bitstreamNum} CRC32 calibration passed"); } } return true; } /// /// [TODO:description] /// /// [TODO:parameter] /// [TODO:parameter] /// [TODO:return] public async ValueTask> UpdateBitstream(int bitstreamNum, byte[] bytesData) { if (bytesData.Length % (4 * 1024) != 0) return new(new ArgumentException( $"The length of data should be divided by 4096, bug given {bytesData.Length}", nameof(bytesData))); var bitstreamBlockNum = bytesData.Length / (4 * 1024); await MsgBus.UDPServer.ClearUDPData(this.address); logger.Trace("Clear udp data finished"); { var ret = await InitRemoteUpdate(bitstreamNum); if (!ret.IsSuccessful) return new(ret.Error); if (!ret.Value) return new(new Exception("Init remote update failed")); } { var ret = await WriteFlash(FlashAddr.Bitstream[bitstreamNum], bitstreamBlockNum, bytesData); if (!ret.IsSuccessful) return new(ret.Error); if (!ret.Value) return new(new Exception("Write bitstream failed")); } { var crc = Honoo.IO.Hashing.Crc.Create(Honoo.IO.Hashing.CrcName.CRC32_MPEG_2); var checkSum = crc.ComputeFinal(bytesData); var ret = await CheckBitstreamCRC(bitstreamNum, bitstreamBlockNum, checkSum.ToUInt32()); if (!ret.IsSuccessful) return new(ret.Error); if (!ret.Value) logger.Warn("CRC32 not correct!"); else logger.Info("CRC32 calibration passed"); } { var ret = await HotReset(bitstreamNum); if (!ret.IsSuccessful) return new(ret.Error); return ret.Value; } } /// /// [TODO:description] /// /// [TODO:parameter] /// [TODO:parameter] /// [TODO:parameter] /// [TODO:parameter] /// [TODO:parameter] /// [TODO:return] public async ValueTask> UpdateBitstreams( int enableBitstreamNum, byte[]? goldenBitream, byte[]? bitstream1, byte[]? bitstream2, byte[]? bitstream3) { // Assert if (goldenBitream is null && bitstream1 is null && bitstream2 is null && bitstream3 is null) return new(new ArgumentException( $"At least one bitstream should not be empty")); if ((enableBitstreamNum == 0 && goldenBitream is null) || (enableBitstreamNum == 1 && bitstream1 is null) || (enableBitstreamNum == 2 && bitstream2 is null) || (enableBitstreamNum == 3 && bitstream3 is null)) return new(new ArgumentException($"Bitstream {enableBitstreamNum} shouldn't be empty")); { var ret = await UploadBitstreams(goldenBitream, bitstream1, bitstream2, bitstream3); if (!ret.IsSuccessful) return new(ret.Error); if (!ret.Value) return false; } { var ret = await HotResetBitstream(enableBitstreamNum); if (!ret.IsSuccessful) return new(ret.Error); return ret.Value; } } /// /// [TODO:description] /// /// [TODO:return] public async ValueTask> GetVersion() { await MsgBus.UDPServer.ClearUDPData(this.address); logger.Trace("Clear udp data finished"); { var ret = await UDPClientPool.ReadAddr(this.ep, RemoteUpdaterAddr.Version, this.timeout); if (!ret.IsSuccessful) return new(ret.Error); var retData = ret.Value.Options.Data; if (retData is null || retData.Length != 4) return new(new Exception("Failed to read remote update firmware version")); var version = Common.Number.BytesToUInt32(retData); return version; } } }