diff --git a/src/Emulator/Peripherals/Peripherals/GPIOPort/Aspeed_GPIO.cs b/src/Emulator/Peripherals/Peripherals/GPIOPort/Aspeed_GPIO.cs new file mode 100644 index 000000000..e4c536503 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/GPIOPort/Aspeed_GPIO.cs @@ -0,0 +1,154 @@ +// +// Copyright (c) 2026 Microsoft +// Licensed under the MIT License. +// + +using System; +using System.Collections.Generic; +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.GPIOPort +{ + // Aspeed AST2600 GPIO Controller + // Reference: QEMU hw/gpio/aspeed_gpio.c + // + // 3.3V controller (0x1E780000): 7 sets (ABCD..YZAAAB), 208 pins, IRQ 40 + // 1.8V controller (0x1E780800): 2 sets (18ABCD, 18E), 36 pins, IRQ 11 + // + // R/W register file. INT_STATUS registers are W1C (write-1-to-clear). + // DATA_READ registers return corresponding DATA_VALUE. + [AllowedTranslations(AllowedTranslation.ByteToDoubleWord | AllowedTranslation.WordToDoubleWord)] + public sealed class Aspeed_GPIO : IDoubleWordPeripheral, IKnownSize, IGPIOSender + { + public Aspeed_GPIO(int numberOfSets = 7) + { + this.numberOfSets = numberOfSets; + storage = new uint[RegisterSpaceSize / 4]; + + // Build lookup tables for special registers + intStatusOffsets = new HashSet(); + dataReadMap = new Dictionary(); + + if(numberOfSets >= 7) + { + // 3.3V GPIO: 7 sets + BuildSetMappings_3_3V(); + } + else + { + // 1.8V GPIO: 2 sets + BuildSetMappings_1_8V(); + } + + Reset(); + } + + public long Size => RegisterSpaceSize; + + public GPIO IRQ { get; } = new GPIO(); + + public void Reset() + { + Array.Clear(storage, 0, storage.Length); + } + + public uint ReadDoubleWord(long offset) + { + if(offset < 0 || offset >= RegisterSpaceSize) + { + return 0; + } + + var byteOff = (uint)offset; + + // DATA_READ registers return the corresponding DATA_VALUE + if(dataReadMap.TryGetValue(byteOff, out var dataValueOff)) + { + return storage[dataValueOff / 4]; + } + + return storage[byteOff / 4]; + } + + public void WriteDoubleWord(long offset, uint value) + { + if(offset < 0 || offset >= RegisterSpaceSize) + { + return; + } + + var byteOff = (uint)offset; + var reg = byteOff / 4; + + // DATA_READ registers are read-only + if(dataReadMap.ContainsKey(byteOff)) + { + return; + } + + // INT_STATUS registers are W1C + if(intStatusOffsets.Contains(byteOff)) + { + storage[reg] &= ~value; + UpdateIRQ(); + return; + } + + storage[reg] = value; + } + + private void UpdateIRQ() + { + bool anyPending = false; + foreach(var off in intStatusOffsets) + { + var enableOff = off - 0x10; // INT_ENABLE is 0x10 before INT_STATUS in each set's block + if(storage[off / 4] != 0) + { + anyPending = true; + break; + } + } + IRQ.Set(anyPending); + } + + private void BuildSetMappings_3_3V() + { + // INT_STATUS offsets (byte addresses) — W1C + uint[] intStatOffsets = { 0x018, 0x038, 0x0A8, 0x0F8, 0x128, 0x158, 0x188 }; + foreach(var off in intStatOffsets) + { + intStatusOffsets.Add(off); + } + + // DATA_READ → DATA_VALUE mappings + dataReadMap[0x0C0] = 0x000; // ABCD + dataReadMap[0x0C4] = 0x020; // EFGH + dataReadMap[0x0C8] = 0x070; // IJKL + dataReadMap[0x0CC] = 0x078; // MNOP + dataReadMap[0x0D0] = 0x080; // QRST + dataReadMap[0x0D4] = 0x088; // UVWX + dataReadMap[0x0D8] = 0x1E0; // YZAAAB + } + + private void BuildSetMappings_1_8V() + { + // 1.8V has same layout as first 2 sets of 3.3V + intStatusOffsets.Add(0x018); // 18ABCD + intStatusOffsets.Add(0x038); // 18E + + dataReadMap[0x0C0] = 0x000; // 18ABCD + dataReadMap[0x0C4] = 0x020; // 18E + } + + private readonly int numberOfSets; + private readonly uint[] storage; + private readonly HashSet intStatusOffsets; + private readonly Dictionary dataReadMap; + + private const int RegisterSpaceSize = 0x800; + } +} diff --git a/src/Emulator/Peripherals/Peripherals/I2C/Aspeed_I2C.cs b/src/Emulator/Peripherals/Peripherals/I2C/Aspeed_I2C.cs new file mode 100644 index 000000000..bb81da363 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/I2C/Aspeed_I2C.cs @@ -0,0 +1,204 @@ +// +// Copyright (c) 2026 Microsoft +// Licensed under the MIT License. +// + +using System; +using System.Collections.Generic; +using Antmicro.Renode.Core; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.I2C +{ + // Aspeed AST2600 I2C Controller — 16 buses + // Reference: QEMU hw/i2c/aspeed_i2c.c (AST2600 "new mode") + // + // Memory map (0x1000 total): + // 0x000-0x00F: Global control + // 0x080+N*0x80: Bus N registers (N=0..15) + // 0xC00-0xDFF: Shared buffer pool + // + // Stub behavior: empty buses return TX_NAK on START+TX commands. + // Linux aspeed-i2c driver probes successfully with NACKs. + [AllowedTranslations(AllowedTranslation.ByteToDoubleWord | AllowedTranslation.WordToDoubleWord)] + public sealed class Aspeed_I2C : IDoubleWordPeripheral, IKnownSize, INumberedGPIOOutput + { + public Aspeed_I2C() + { + storage = new uint[RegisterSpaceSize / 4]; + var gpios = new Dictionary(); + for(int i = 0; i < NumBuses; i++) + { + gpios[i] = new GPIO(); + } + Connections = gpios; + Reset(); + } + + public long Size => RegisterSpaceSize; + + public IReadOnlyDictionary Connections { get; } + + public void Reset() + { + Array.Clear(storage, 0, storage.Length); + } + + public uint ReadDoubleWord(long offset) + { + if(offset < 0 || offset >= RegisterSpaceSize) + { + return 0; + } + return storage[(uint)offset / 4]; + } + + public void WriteDoubleWord(long offset, uint value) + { + if(offset < 0 || offset >= RegisterSpaceSize) + { + return; + } + + var reg = (uint)offset / 4; + + // Determine if this is a bus register + int busIndex = GetBusIndex((uint)offset); + if(busIndex >= 0) + { + uint busLocalOffset = ((uint)offset - BusBase) % BusSpacing; + HandleBusWrite(busIndex, busLocalOffset, value); + return; + } + + // Global registers and pool: plain R/W + storage[reg] = value; + } + + private void HandleBusWrite(int bus, uint localOffset, uint value) + { + uint absOffset = BusBase + (uint)bus * BusSpacing + localOffset; + uint reg = absOffset / 4; + + switch(localOffset) + { + case MasterIntrSts: // I2CM_INTR_STS — W1C + storage[reg] &= ~(value & 0xF007F07F); + UpdateBusIRQ(bus); + return; + + case SlaveIntrSts: // I2CS_INTR_STS — W1C + storage[reg] &= ~value; + UpdateBusIRQ(bus); + return; + + case MasterCmd: // I2CM_CMD — command execution + storage[reg] = value; + HandleMasterCommand(bus, value); + return; + + case AcTiming: + storage[reg] = value & 0x1FFFF0FF; + return; + + case MasterIntrCtrl: + storage[reg] = value & 0x0007F07F; + return; + + default: + storage[reg] = value; + return; + } + } + + private void HandleMasterCommand(int bus, uint cmd) + { + uint absOffset = BusBase + (uint)bus * BusSpacing; + uint intrStsReg = (absOffset + MasterIntrSts) / 4; + + bool startCmd = (cmd & CmdStartBit) != 0; + bool txCmd = (cmd & CmdTxBit) != 0; + bool rxCmd = (cmd & CmdRxBit) != 0; + bool stopCmd = (cmd & CmdStopBit) != 0; + + if(startCmd && txCmd) + { + // START + TX: send address byte → NACK (no devices on stub bus) + storage[intrStsReg] |= IntrTxNak; + } + else if(txCmd) + { + // Data TX → NACK + storage[intrStsReg] |= IntrTxNak; + } + else if(rxCmd) + { + // RX → NACK (no device to receive from) + storage[intrStsReg] |= IntrTxNak; + } + + if(stopCmd) + { + storage[intrStsReg] |= IntrNormalStop; + } + + // Clear the command bits (command consumed) + storage[(absOffset + MasterCmd) / 4] &= ~(CmdStartBit | CmdTxBit | CmdRxBit | CmdStopBit); + + UpdateBusIRQ(bus); + } + + private void UpdateBusIRQ(int bus) + { + uint absOffset = BusBase + (uint)bus * BusSpacing; + uint intrSts = storage[(absOffset + MasterIntrSts) / 4]; + uint intrCtrl = storage[(absOffset + MasterIntrCtrl) / 4]; + + bool pending = (intrSts & intrCtrl) != 0; + ((GPIO)Connections[bus]).Set(pending); + } + + private int GetBusIndex(uint offset) + { + if(offset < BusBase || offset >= BusBase + NumBuses * BusSpacing) + { + return -1; + } + return (int)((offset - BusBase) / BusSpacing); + } + + private readonly uint[] storage; + + // Layout + private const uint BusBase = 0x80; + private const uint BusSpacing = 0x80; + private const int NumBuses = 16; + + // Per-bus register offsets (relative to bus base) + private const uint FunCtrl = 0x00; + private const uint AcTiming = 0x04; + private const uint TxRxByteBuf = 0x08; + private const uint PoolCtrl = 0x0C; + private const uint MasterIntrCtrl = 0x10; + private const uint MasterIntrSts = 0x14; + private const uint MasterCmd = 0x18; + private const uint SlaveIntrCtrl = 0x20; + private const uint SlaveIntrSts = 0x24; + + // Command bits + private const uint CmdStartBit = 1u << 0; + private const uint CmdTxBit = 1u << 1; + private const uint CmdRxBit = 1u << 3; + private const uint CmdStopBit = 1u << 5; + + // Interrupt status bits + private const uint IntrTxAck = 1u << 0; + private const uint IntrTxNak = 1u << 1; + private const uint IntrRxDone = 1u << 2; + private const uint IntrNormalStop = 1u << 4; + private const uint IntrAbnormal = 1u << 5; + + private const int RegisterSpaceSize = 0x1000; + } +} diff --git a/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_ADC.cs b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_ADC.cs new file mode 100644 index 000000000..0f57ba07c --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_ADC.cs @@ -0,0 +1,256 @@ +// +// Copyright (c) 2026 Microsoft +// Licensed under the MIT License. +// +using System; +using Antmicro.Renode.Core; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.Miscellaneous +{ + // Aspeed AST2600 ADC Controller — Dual-engine, 16-channel, 10-bit ADC + // Reference: QEMU hw/adc/aspeed_adc.c + // + // Two independent engines (0 and 1), each with 8 channels. + // Data registers pack two channels per word with 10-bit values. + // Reading a data register auto-increments channel values (simulated sampling). + // Threshold bounds checking triggers per-channel interrupt flags. + [AllowedTranslations(AllowedTranslation.ByteToDoubleWord | AllowedTranslation.WordToDoubleWord)] + public sealed class Aspeed_ADC : IDoubleWordPeripheral, IKnownSize, IGPIOSender + { + public Aspeed_ADC() + { + IRQ = new GPIO(); + engines = new EngineState[NR_ENGINES]; + for(int i = 0; i < NR_ENGINES; i++) + { + engines[i] = new EngineState(); + } + Reset(); + } + + public long Size => REGISTER_SPACE_SIZE; + public GPIO IRQ { get; } + + public void Reset() + { + for(int i = 0; i < NR_ENGINES; i++) + { + Array.Clear(engines[i].Regs, 0, engines[i].Regs.Length); + engines[i].Regs[R_VGA_DETECT_CTRL] = 0x0000000F; + engines[i].Regs[R_CLOCK_CTRL] = 0x0000000F; + } + IRQ.Unset(); + } + + public uint ReadDoubleWord(long offset) + { + if(offset < 0 || offset >= REGISTER_SPACE_SIZE) + { + return 0; + } + + int engineIdx = (int)(offset / ENGINE_SIZE); + if(engineIdx >= NR_ENGINES) + { + return 0; + } + + var eng = engines[engineIdx]; + uint localOffset = (uint)(offset % ENGINE_SIZE); + uint reg = localOffset / 4; + + if(reg >= NR_REGS) + { + return 0; + } + + // Data registers: reading triggers auto-increment and threshold check + if(reg >= R_DATA_CH1_CH0 && reg <= R_DATA_CH7_CH6) + { + uint val = eng.Regs[reg]; + // Auto-increment: lower channel += 7, upper channel += 5 + uint lower = val & ADC_L_MASK; + uint upper = (val >> 16) & ADC_L_MASK; + lower = (lower + 7) & ADC_L_MASK; + upper = (upper + 5) & ADC_L_MASK; + eng.Regs[reg] = lower | (upper << 16); + + // Check thresholds for both channels + uint dataIdx = reg - R_DATA_CH1_CH0; + uint lowerCh = dataIdx * 2; + uint upperCh = dataIdx * 2 + 1; + CheckThreshold(eng, lowerCh, lower); + CheckThreshold(eng, upperCh, upper); + + return val; + } + + return eng.Regs[reg]; + } + + public void WriteDoubleWord(long offset, uint value) + { + if(offset < 0 || offset >= REGISTER_SPACE_SIZE) + { + return; + } + + int engineIdx = (int)(offset / ENGINE_SIZE); + if(engineIdx >= NR_ENGINES) + { + return; + } + + var eng = engines[engineIdx]; + uint localOffset = (uint)(offset % ENGINE_SIZE); + uint reg = localOffset / 4; + + if(reg >= NR_REGS) + { + return; + } + + switch(reg) + { + case R_ENGINE_CTRL: + // AUTO_COMP (bit 5) is always cleared; INIT (bit 8) is set when EN (bit 0) = 1 + value &= ~AUTO_COMP; + if((value & EN) != 0) + { + value |= INIT; + } + eng.Regs[reg] = value; + return; + + case R_INT_CTRL: + eng.Regs[reg] = value & 0xFF; + UpdateIRQ(); + return; + + case R_VGA_DETECT_CTRL: + case R_CLOCK_CTRL: + eng.Regs[reg] = value; + return; + + case R_INT_SOURCE: + // W1C — write-1-to-clear + eng.Regs[reg] &= ~(value & 0xFFFF); + UpdateIRQ(); + return; + + case R_COMPENSATING: + eng.Regs[reg] = value & 0x0F; + return; + + default: + if(reg >= R_DATA_CH1_CH0 && reg <= R_DATA_CH7_CH6) + { + eng.Regs[reg] = value & ADC_LH_MASK; + return; + } + if(reg >= R_BOUNDS_CH0 && reg <= R_BOUNDS_CH7) + { + eng.Regs[reg] = value & ADC_LH_MASK; + return; + } + if(reg >= R_HYST_CH0 && reg <= R_HYST_CH7) + { + eng.Regs[reg] = value & HYST_MASK; + return; + } + eng.Regs[reg] = value; + return; + } + } + + private void CheckThreshold(EngineState eng, uint channel, uint value) + { + if(channel >= NR_CHANNELS_PER_ENGINE) + { + return; + } + uint boundsReg = R_BOUNDS_CH0 + channel; + uint lower = eng.Regs[boundsReg] & ADC_L_MASK; + uint upper = (eng.Regs[boundsReg] >> 16) & ADC_L_MASK; + + // If bounds are both 0, skip threshold check (default) + if(lower == 0 && upper == 0) + { + return; + } + + if(value < lower || value > upper) + { + eng.Regs[R_INT_SOURCE] |= (uint)(1 << (int)channel); + UpdateIRQ(); + } + } + + private void UpdateIRQ() + { + // Aggregate: check if any engine has (INT_SOURCE & INT_CTRL) non-zero + bool pending = false; + for(int i = 0; i < NR_ENGINES; i++) + { + if((engines[i].Regs[R_INT_SOURCE] & engines[i].Regs[R_INT_CTRL]) != 0) + { + pending = true; + break; + } + } + IRQ.Set(pending); + } + + private sealed class EngineState + { + public readonly uint[] Regs = new uint[NR_REGS]; + } + + private readonly EngineState[] engines; + + // Constants + private const int NR_ENGINES = 2; + private const int NR_CHANNELS_PER_ENGINE = 8; + private const int ENGINE_SIZE = 0x100; + private const int NR_REGS = ENGINE_SIZE / 4; // 64 regs per engine + private const int REGISTER_SPACE_SIZE = 0x1000; + + // Register offsets (word index within engine) + private const uint R_ENGINE_CTRL = 0x00 / 4; + private const uint R_INT_CTRL = 0x04 / 4; + private const uint R_VGA_DETECT_CTRL = 0x08 / 4; + private const uint R_CLOCK_CTRL = 0x0C / 4; + private const uint R_DATA_CH1_CH0 = 0x10 / 4; + private const uint R_DATA_CH3_CH2 = 0x14 / 4; + private const uint R_DATA_CH5_CH4 = 0x18 / 4; + private const uint R_DATA_CH7_CH6 = 0x1C / 4; + private const uint R_BOUNDS_CH0 = 0x20 / 4; + private const uint R_BOUNDS_CH1 = 0x24 / 4; + private const uint R_BOUNDS_CH2 = 0x28 / 4; + private const uint R_BOUNDS_CH3 = 0x2C / 4; + private const uint R_BOUNDS_CH4 = 0x30 / 4; + private const uint R_BOUNDS_CH5 = 0x34 / 4; + private const uint R_BOUNDS_CH6 = 0x38 / 4; + private const uint R_BOUNDS_CH7 = 0x3C / 4; + private const uint R_HYST_CH0 = 0x40 / 4; + private const uint R_HYST_CH1 = 0x44 / 4; + private const uint R_HYST_CH2 = 0x48 / 4; + private const uint R_HYST_CH3 = 0x4C / 4; + private const uint R_HYST_CH4 = 0x50 / 4; + private const uint R_HYST_CH5 = 0x54 / 4; + private const uint R_HYST_CH6 = 0x58 / 4; + private const uint R_HYST_CH7 = 0x5C / 4; + private const uint R_INT_SOURCE = 0x60 / 4; + private const uint R_COMPENSATING = 0x64 / 4; + + // Bit masks + private const uint ADC_L_MASK = 0x3FF; + private const uint ADC_LH_MASK = 0x03FF03FF; + private const uint HYST_MASK = 0x83FF3FFF; + private const uint EN = 0x01; + private const uint AUTO_COMP = 0x20; + private const uint INIT = 0x100; + } +} diff --git a/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_HACE.cs b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_HACE.cs new file mode 100644 index 000000000..03fd67864 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_HACE.cs @@ -0,0 +1,377 @@ +// +// Copyright (c) 2026 Microsoft +// Licensed under the MIT License. +// +using System; +using System.Security.Cryptography; +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.Miscellaneous +{ + // Aspeed AST2600 HACE — Hash and Crypto Engine + // Reference: QEMU hw/misc/aspeed_hace.c + // + // Implements SHA-256/SHA-384/SHA-512/SHA-1/MD5 hashing with + // direct and scatter-gather DMA modes. Reads source data from + // system bus (DRAM) and writes digest output back. + [AllowedTranslations(AllowedTranslation.ByteToDoubleWord | AllowedTranslation.WordToDoubleWord)] + public sealed class Aspeed_HACE : IDoubleWordPeripheral, IKnownSize, IGPIOSender + { + public Aspeed_HACE(IMachine machine) + { + this.machine = machine; + IRQ = new GPIO(); + storage = new uint[RegisterSpaceSize / 4]; + Reset(); + } + + public long Size => RegisterSpaceSize; + public GPIO IRQ { get; } + + public void Reset() + { + Array.Clear(storage, 0, storage.Length); + IRQ.Unset(); + totalReqLen = 0; + accumulationCtx = null; + } + + public uint ReadDoubleWord(long offset) + { + if(offset >= 0 && offset < RegisterSpaceSize) + { + return storage[(uint)offset / 4]; + } + return 0; + } + + public void WriteDoubleWord(long offset, uint value) + { + if(offset < 0 || offset >= RegisterSpaceSize) + { + return; + } + + var reg = (uint)offset / 4; + + switch(offset) + { + case StatusOffset: + // W1C for HASH_IRQ (bit 9) + if((value & HashIrqBit) != 0) + { + storage[reg] &= ~HashIrqBit; + IRQ.Unset(); + } + break; + + case HashSrcOffset: + storage[reg] = value & SrcMask; + break; + + case HashDigestOffset: + storage[reg] = value & DestMask; + break; + + case HashKeyBuffOffset: + storage[reg] = value & KeyMask; + break; + + case HashSrcLenOffset: + storage[reg] = value & LenMask; + break; + + case HashCmdOffset: + storage[reg] = value & HashCmdMask; + ExecuteHash(value & HashCmdMask); + break; + + case CryptCmdOffset: + storage[reg] = value; + this.Log(LogLevel.Warning, "HACE: Crypt commands not implemented"); + break; + + default: + storage[reg] = value; + break; + } + } + + private void ExecuteHash(uint cmd) + { + var algo = GetHashAlgorithm(cmd); + if(!algo.HasValue) + { + this.Log(LogLevel.Error, "HACE: Invalid hash algorithm selection 0x{0:X}", cmd); + return; + } + + var hmacMode = (cmd >> 7) & 0x3; + if(hmacMode == 1 || hmacMode == 3) + { + this.Log(LogLevel.Warning, "HACE: HMAC mode not implemented"); + } + + var cascadedMode = cmd & 0x3; + if(cascadedMode != 0) + { + this.Log(LogLevel.Warning, "HACE: Cascaded mode not implemented"); + } + + bool sgMode = (cmd & SgEnBit) != 0; + bool accumMode = hmacMode == 2; + + var srcAddr = storage[HashSrcOffset / 4]; + var digestAddr = storage[HashDigestOffset / 4]; + var srcLen = storage[HashSrcLenOffset / 4]; + + byte[] sourceData; + + if(sgMode) + { + sourceData = ReadScatterGather(srcAddr, accumMode); + } + else + { + sourceData = ReadFromBus(srcAddr, (int)srcLen); + } + + if(sourceData == null || sourceData.Length == 0) + { + this.Log(LogLevel.Warning, "HACE: No source data to hash"); + SetHashComplete(cmd); + return; + } + + byte[] digest; + + if(accumMode) + { + digest = ExecuteAccumulation(algo.Value, sourceData, sgMode); + } + else + { + digest = ComputeHash(algo.Value, sourceData); + } + + if(digest != null && digest.Length > 0) + { + WriteToBus(digestAddr, digest); + } + + SetHashComplete(cmd); + } + + private byte[] ExecuteAccumulation(HashAlgorithmName algo, byte[] data, bool sgMode) + { + // In accumulation mode, detect the final request by checking for + // SHA padding (0x80 byte followed by zeros and 8-byte big-endian + // bit length). QEMU checks padding in both SG and direct modes. + bool isFinal = false; + int dataLen = data.Length; + + if(accumulationCtx == null) + { + accumulationCtx = IncrementalHash.CreateHash(algo); + totalReqLen = 0; + } + + // Update total_req_len BEFORE padding check (matches QEMU behavior) + totalReqLen += dataLen; + + if(dataLen >= 9) + { + // Read 8-byte big-endian bit-length from end of data + ulong bitLen = 0; + for(int i = dataLen - 8; i < dataLen; i++) + { + bitLen = (bitLen << 8) | data[i]; + } + ulong totalMsgLen = bitLen / 8; + if(totalMsgLen <= (ulong)totalReqLen) + { + uint paddingSize = (uint)((ulong)totalReqLen - totalMsgLen); + if(paddingSize > 0 && paddingSize <= (uint)dataLen) + { + int padOffset = dataLen - (int)paddingSize; + if(data[padOffset] == 0x80) + { + isFinal = true; + dataLen = padOffset; + } + } + } + } + + accumulationCtx.AppendData(data, 0, dataLen); + + if(isFinal) + { + var digest = accumulationCtx.GetHashAndReset(); + accumulationCtx.Dispose(); + accumulationCtx = null; + totalReqLen = 0; + return digest; + } + + return null; + } + + private static byte[] ComputeHash(HashAlgorithmName algo, byte[] data) + { + using(var hasher = IncrementalHash.CreateHash(algo)) + { + hasher.AppendData(data); + return hasher.GetHashAndReset(); + } + } + + private byte[] ReadScatterGather(uint sgListAddr, bool accumMode) + { + var result = new System.IO.MemoryStream(); + const int maxEntries = 256; + + for(int i = 0; i < maxEntries; i++) + { + uint lenWord = ReadUInt32FromBus(sgListAddr + (uint)(i * 8)); + uint addrWord = ReadUInt32FromBus(sgListAddr + (uint)(i * 8) + 4); + + uint dataLen = lenWord & SgListLenMask; + uint dataAddr = addrWord & SgListAddrMask; + bool isLast = (lenWord & SgListLastBit) != 0; + + if(dataLen > 0) + { + var chunk = ReadFromBus(dataAddr, (int)dataLen); + if(chunk != null) + { + result.Write(chunk, 0, chunk.Length); + } + } + + if(isLast) + { + break; + } + } + + return result.ToArray(); + } + + private byte[] ReadFromBus(uint address, int length) + { + if(length <= 0 || length > MaxDmaLength) + { + return new byte[0]; + } + + var data = new byte[length]; + var sysbus = machine.SystemBus; + for(int i = 0; i < length; i++) + { + data[i] = sysbus.ReadByte(address + (uint)i); + } + return data; + } + + private uint ReadUInt32FromBus(uint address) + { + var sysbus = machine.SystemBus; + return (uint)(sysbus.ReadByte(address) + | (sysbus.ReadByte(address + 1) << 8) + | (sysbus.ReadByte(address + 2) << 16) + | (sysbus.ReadByte(address + 3) << 24)); + } + + private void WriteToBus(uint address, byte[] data) + { + var sysbus = machine.SystemBus; + for(int i = 0; i < data.Length; i++) + { + sysbus.WriteByte(address + (uint)i, data[i]); + } + } + + private void SetHashComplete(uint cmd) + { + storage[StatusOffset / 4] |= HashIrqBit; + + if((cmd & HashIrqEnBit) != 0) + { + IRQ.Set(true); + } + } + + private static HashAlgorithmName? GetHashAlgorithm(uint cmd) + { + // Bits [6:4] encode the algorithm: + // 0b000 (0) = MD5 → cmd & 0x070 = 0x000 + // 0b010 (2) = SHA1 → cmd & 0x070 = 0x020 + // 0b100 (4) = SHA224 → cmd & 0x070 = 0x040 + // 0b101 (5) = SHA256 → cmd & 0x070 = 0x050 + // 0b110 (6) = SHA512 series → cmd & 0x070 = 0x060 + uint algoBits = (cmd >> 4) & 0x7; + + switch(algoBits) + { + case 0: // MD5 + return HashAlgorithmName.MD5; + case 2: // SHA1 + return HashAlgorithmName.SHA1; + case 4: // SHA224 — .NET lacks native SHA224, use SHA256 + return HashAlgorithmName.SHA256; + case 5: // SHA256 + return HashAlgorithmName.SHA256; + case 6: // SHA512 series — bits [12:10] select variant + uint sha512Variant = (cmd >> 10) & 0x7; + switch(sha512Variant) + { + case 0: return HashAlgorithmName.SHA512; + case 1: return HashAlgorithmName.SHA384; + case 2: return HashAlgorithmName.SHA256; + case 3: return HashAlgorithmName.SHA256; // SHA224 truncated + default: return (HashAlgorithmName?)null; + } + default: + return (HashAlgorithmName?)null; + } + } + + private readonly IMachine machine; + private readonly uint[] storage; + private IncrementalHash accumulationCtx; + private int totalReqLen; + + private const int RegisterSpaceSize = 0x1000; + private const int MaxDmaLength = 0x10000000; // 256 MB + + // Register offsets + private const long CryptCmdOffset = 0x10; + private const long StatusOffset = 0x1C; + private const long HashSrcOffset = 0x20; + private const long HashDigestOffset = 0x24; + private const long HashKeyBuffOffset = 0x28; + private const long HashSrcLenOffset = 0x2C; + private const long HashCmdOffset = 0x30; + + // Bit definitions + private const uint HashIrqBit = 1u << 9; + private const uint HashIrqEnBit = 1u << 9; + private const uint SgEnBit = 1u << 18; + + // AST2600 masks + private const uint SrcMask = 0xFFFFFFFF; + private const uint DestMask = 0xFFFFFFF8; + private const uint KeyMask = 0xFFFFFFF8; + private const uint LenMask = 0x0FFFFFFF; + private const uint HashCmdMask = 0x00147FFF; + + // SG list parsing + private const uint SgListLenMask = 0x0FFFFFFF; + private const uint SgListAddrMask = 0xFFFFFFFF; + private const uint SgListLastBit = 0x80000000; + } +} diff --git a/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_LPC.cs b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_LPC.cs new file mode 100644 index 000000000..0f46e2432 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_LPC.cs @@ -0,0 +1,273 @@ +// Copyright (c) 2026 Microsoft +// Licensed under the MIT license. +// +// Aspeed AST2600 LPC Controller with 4 KCS channels +// Ported from QEMU hw/misc/aspeed_lpc.c + +using System; +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.Miscellaneous +{ + [AllowedTranslations(AllowedTranslation.ByteToDoubleWord | AllowedTranslation.WordToDoubleWord)] + public class Aspeed_LPC : IDoubleWordPeripheral, IKnownSize, IGPIOSender + { + public Aspeed_LPC() + { + registers = new uint[RegisterSpaceSize / 4]; + IRQ = new GPIO(); + Reset(); + } + + public uint ReadDoubleWord(long offset) + { + if (offset < 0 || offset >= RegisterSpaceSize) + { + this.Log(LogLevel.Warning, "LPC read out of range: 0x{0:X}", offset); + return 0; + } + + int idx = (int)(offset / 4); + uint val = registers[idx]; + + // IDR read side-effects: clear IBF, lower IRQ + switch (offset) + { + case IDR1: + ClearIBF(0, STR1); + break; + case IDR2: + ClearIBF(1, STR2); + break; + case IDR3: + ClearIBF(2, STR3); + break; + case IDR4: + ClearIBF(3, STR4); + break; + } + + return val; + } + + public void WriteDoubleWord(long offset, uint value) + { + if (offset < 0 || offset >= RegisterSpaceSize) + { + this.Log(LogLevel.Warning, "LPC write out of range: 0x{0:X}", offset); + return; + } + + int idx = (int)(offset / 4); + + switch (offset) + { + // IDR write: store data, set IBF, raise IRQ if enabled + case IDR1: + registers[idx] = value & 0xFF; + SetIBF(0, STR1); + break; + case IDR2: + registers[idx] = value & 0xFF; + SetIBF(1, STR2); + break; + case IDR3: + registers[idx] = value & 0xFF; + SetIBF(2, STR3); + break; + case IDR4: + registers[idx] = value & 0xFF; + SetIBF(3, STR4); + break; + + // ODR write: store data, set OBF + case ODR1: + registers[idx] = value & 0xFF; + registers[STR1 / 4] |= STR_OBF; + break; + case ODR2: + registers[idx] = value & 0xFF; + registers[STR2 / 4] |= STR_OBF; + break; + case ODR3: + registers[idx] = value & 0xFF; + registers[STR3 / 4] |= STR_OBF; + break; + case ODR4: + registers[idx] = value & 0xFF; + registers[STR4 / 4] |= STR_OBF; + break; + + // STR: R/W for all bits + case STR1: + case STR2: + case STR3: + case STR4: + registers[idx] = value & 0xFF; + break; + + // HICR0: channel enables — may affect IRQ state + case HICR0: + registers[idx] = value; + UpdateIRQ(); + break; + + // HICR2: IBF IRQ enables + case HICR2: + registers[idx] = value; + UpdateIRQ(); + break; + + // HICR4: KCS3 enable bit + case HICR4: + registers[idx] = value; + UpdateIRQ(); + break; + + // HICRB: KCS4 enable + IBF IRQ enable + case HICRB: + registers[idx] = value; + UpdateIRQ(); + break; + + default: + registers[idx] = value; + break; + } + } + + public void Reset() + { + Array.Clear(registers, 0, registers.Length); + registers[HICR7 / 4] = Hicr7ResetValue; + subdeviceIrqsPending = 0; + IRQ.Unset(); + } + + public long Size => RegisterSpaceSize; + + public GPIO IRQ { get; } + + // Configurable HICR7 (chip ID) — survives reset + public uint Hicr7ResetValue { get; set; } = 0; + + // --- Private helpers --- + + private bool IsChannelEnabled(int ch) + { + switch (ch) + { + case 0: return (registers[HICR0 / 4] & HICR0_LPC1E) != 0; + case 1: return (registers[HICR0 / 4] & HICR0_LPC2E) != 0; + case 2: return (registers[HICR0 / 4] & HICR0_LPC3E) != 0 + && (registers[HICR4 / 4] & HICR4_KCSENBL) != 0; + case 3: return (registers[HICRB / 4] & HICRB_LPC4E) != 0; + default: return false; + } + } + + private bool IsIBFIRQEnabled(int ch) + { + if (!IsChannelEnabled(ch)) + return false; + + switch (ch) + { + case 0: return (registers[HICR2 / 4] & HICR2_IBFIE1) != 0; + case 1: return (registers[HICR2 / 4] & HICR2_IBFIE2) != 0; + case 2: return (registers[HICR2 / 4] & HICR2_IBFIE3) != 0; + case 3: return (registers[HICRB / 4] & HICRB_IBFIE4) != 0; + default: return false; + } + } + + private void SetIBF(int ch, long strOffset) + { + registers[strOffset / 4] |= STR_IBF; + + if (IsIBFIRQEnabled(ch)) + { + subdeviceIrqsPending |= (1u << ch); + UpdateIRQ(); + } + } + + private void ClearIBF(int ch, long strOffset) + { + bool wasSet = (registers[strOffset / 4] & STR_IBF) != 0; + registers[strOffset / 4] &= ~STR_IBF; + + if (wasSet) + { + subdeviceIrqsPending &= ~(1u << ch); + UpdateIRQ(); + } + } + + private void UpdateIRQ() + { + if (subdeviceIrqsPending != 0) + IRQ.Set(true); + else + IRQ.Set(false); + } + + private uint[] registers; + private uint subdeviceIrqsPending; + + // Register space + private const int RegisterSpaceSize = 0x1000; + + // HICR registers + private const long HICR0 = 0x00; + private const long HICR1 = 0x04; + private const long HICR2 = 0x08; + private const long HICR3 = 0x0C; + private const long HICR4 = 0x10; + private const long HICR5 = 0x80; + private const long HICR6 = 0x84; + private const long HICR7 = 0x88; + private const long HICR8 = 0x8C; + private const long HICRB = 0x100; + + // KCS Channel 1-3 registers + private const long IDR1 = 0x24; + private const long IDR2 = 0x28; + private const long IDR3 = 0x2C; + private const long ODR1 = 0x30; + private const long ODR2 = 0x34; + private const long ODR3 = 0x38; + private const long STR1 = 0x3C; + private const long STR2 = 0x40; + private const long STR3 = 0x44; + + // KCS Channel 4 registers + private const long IDR4 = 0x114; + private const long ODR4 = 0x118; + private const long STR4 = 0x11C; + + // HICR0 bits + private const uint HICR0_LPC1E = (1u << 5); + private const uint HICR0_LPC2E = (1u << 6); + private const uint HICR0_LPC3E = (1u << 7); + + // HICR2 bits + private const uint HICR2_IBFIE1 = (1u << 1); + private const uint HICR2_IBFIE2 = (1u << 2); + private const uint HICR2_IBFIE3 = (1u << 3); + + // HICR4 bits + private const uint HICR4_KCSENBL = (1u << 2); + + // HICRB bits + private const uint HICRB_LPC4E = (1u << 0); + private const uint HICRB_IBFIE4 = (1u << 1); + + // STR bits + private const uint STR_OBF = (1u << 0); + private const uint STR_IBF = (1u << 1); + } +} diff --git a/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_PECI.cs b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_PECI.cs new file mode 100644 index 000000000..7f2ae2bf5 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_PECI.cs @@ -0,0 +1,101 @@ +// +// Copyright (c) 2026 Microsoft +// Licensed under the MIT License. +// +using System; +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.Miscellaneous +{ + // Aspeed AST2600 PECI (Platform Environment Control Interface) Controller + // Reference: QEMU hw/misc/aspeed_peci.c + // + // Stub peripheral: command FIRE bit auto-completes with success code. + // No actual PECI bus transactions. + [AllowedTranslations(AllowedTranslation.ByteToDoubleWord | AllowedTranslation.WordToDoubleWord)] + public sealed class Aspeed_PECI : IDoubleWordPeripheral, IKnownSize, IGPIOSender + { + public Aspeed_PECI() + { + IRQ = new GPIO(); + storage = new uint[RegisterSpaceSize / 4]; + Reset(); + } + + public long Size => RegisterSpaceSize; + public GPIO IRQ { get; } + + public void Reset() + { + Array.Clear(storage, 0, storage.Length); + IRQ.Unset(); + } + + public uint ReadDoubleWord(long offset) + { + if(offset >= 0 && offset < RegisterSpaceSize) + { + return storage[(uint)offset / 4]; + } + return 0; + } + + public void WriteDoubleWord(long offset, uint value) + { + if(offset < 0 || offset >= RegisterSpaceSize) + { + return; + } + + var reg = (uint)offset / 4; + + switch(reg) + { + case R_CMD: + storage[reg] = value; + if((value & CMD_FIRE) != 0) + { + // Auto-complete: clear FIRE, set CMD_DONE in INT_STS + storage[R_CMD] = value & ~CMD_FIRE; + storage[R_INT_STS] |= INT_CMD_DONE; + // Write success code (0x40) into read data buffer 0 + storage[R_RD_DATA0] = PECI_CC_RSP_SUCCESS; + UpdateIRQ(); + } + return; + + case R_INT_STS: + // W1C (write-1-to-clear) + storage[reg] &= ~value; + UpdateIRQ(); + return; + + default: + storage[reg] = value; + return; + } + } + + private void UpdateIRQ() + { + bool pending = (storage[R_INT_STS] & storage[R_INT_CTRL]) != 0; + IRQ.Set(pending); + } + + private readonly uint[] storage; + + private const uint R_CMD = 0x08 / 4; + private const uint R_INT_CTRL = 0x18 / 4; + private const uint R_INT_STS = 0x1C / 4; + private const uint R_RD_DATA0 = 0x30 / 4; + + private const uint CMD_FIRE = 1u << 0; + private const uint INT_CMD_DONE = 1u << 0; + private const uint PECI_CC_RSP_SUCCESS = 0x40u; + + private const int RegisterSpaceSize = 0x1000; + } +} diff --git a/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_PWM.cs b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_PWM.cs new file mode 100644 index 000000000..9da7f0b44 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_PWM.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) 2026 Microsoft +// Licensed under the MIT License. +// +using System; +using Antmicro.Renode.Core; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.Miscellaneous +{ + // Aspeed AST2600 PWM/Fan Tachometer Controller + // Reference: QEMU hw/misc/aspeed_pwm.c + // + // Stub peripheral: simple R/W register storage. + // No actual PWM output or tachometer input. + [AllowedTranslations(AllowedTranslation.ByteToDoubleWord | AllowedTranslation.WordToDoubleWord)] + public sealed class Aspeed_PWM : IDoubleWordPeripheral, IKnownSize, IGPIOSender + { + public Aspeed_PWM() + { + IRQ = new GPIO(); + storage = new uint[RegisterSpaceSize / 4]; + Reset(); + } + + public long Size => RegisterSpaceSize; + public GPIO IRQ { get; } + + public void Reset() + { + Array.Clear(storage, 0, storage.Length); + IRQ.Unset(); + } + + public uint ReadDoubleWord(long offset) + { + if(offset >= 0 && offset < RegisterSpaceSize) + { + return storage[(uint)offset / 4]; + } + return 0; + } + + public void WriteDoubleWord(long offset, uint value) + { + if(offset >= 0 && offset < RegisterSpaceSize) + { + storage[(uint)offset / 4] = value; + } + } + + private readonly uint[] storage; + private const int RegisterSpaceSize = 0x1000; + } +} diff --git a/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_RTC.cs b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_RTC.cs new file mode 100644 index 000000000..5df2bbfb2 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_RTC.cs @@ -0,0 +1,112 @@ +// +// Copyright (c) 2026 Microsoft +// Licensed under the MIT License. +// +using System; +using Antmicro.Renode.Core; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.Miscellaneous +{ + // Aspeed AST2600 Real-Time Clock + // Reference: QEMU hw/rtc/aspeed_rtc.c + // + // Stub peripheral: returns fixed time (2025-01-01 00:00:00). + // Supports enable/unlock via CONTROL register. + [AllowedTranslations(AllowedTranslation.ByteToDoubleWord | AllowedTranslation.WordToDoubleWord)] + public sealed class Aspeed_RTC : IDoubleWordPeripheral, IKnownSize, IGPIOSender + { + public Aspeed_RTC() + { + IRQ = new GPIO(); + storage = new uint[RegisterSpaceSize / 4]; + Reset(); + } + + public long Size => RegisterSpaceSize; + public GPIO IRQ { get; } + + public void Reset() + { + Array.Clear(storage, 0, storage.Length); + IRQ.Unset(); + } + + public uint ReadDoubleWord(long offset) + { + if(offset < 0 || offset >= RegisterSpaceSize) + { + return 0; + } + + var reg = (uint)offset / 4; + + switch(reg) + { + case R_COUNTER1: + if((storage[R_CONTROL] & RTC_ENABLED) != 0) + { + // Return a fixed time: day=1, hour=0, min=0, sec=0 + return (1u << 24); + } + return storage[reg]; + + case R_COUNTER2: + if((storage[R_CONTROL] & RTC_ENABLED) != 0) + { + // Return a fixed date: century=20, year=25, month=1 + return (20u << 16) | (25u << 8) | 1u; + } + return storage[reg]; + + default: + return storage[reg]; + } + } + + public void WriteDoubleWord(long offset, uint value) + { + if(offset < 0 || offset >= RegisterSpaceSize) + { + return; + } + + var reg = (uint)offset / 4; + + switch(reg) + { + case R_COUNTER1: + case R_COUNTER2: + // Only writable when unlocked + if((storage[R_CONTROL] & RTC_UNLOCKED) != 0) + { + storage[reg] = value; + } + return; + + case R_ALARM_STATUS: + // W1C + storage[reg] &= ~value; + return; + + default: + storage[reg] = value; + return; + } + } + + private readonly uint[] storage; + + private const uint R_COUNTER1 = 0x00 / 4; + private const uint R_COUNTER2 = 0x04 / 4; + private const uint R_ALARM = 0x08 / 4; + private const uint R_CONTROL = 0x10 / 4; + private const uint R_ALARM_STATUS = 0x14 / 4; + + private const uint RTC_ENABLED = 1u << 0; + private const uint RTC_UNLOCKED = 1u << 1; + + private const int RegisterSpaceSize = 0x18; + } +} diff --git a/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_SBC.cs b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_SBC.cs new file mode 100644 index 000000000..b99ebc735 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_SBC.cs @@ -0,0 +1,90 @@ +// +// Copyright (c) 2026 Microsoft +// Licensed under the MIT License. +// + +using System; +using Antmicro.Renode.Core; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.Miscellaneous +{ + // Aspeed AST2600 Secure Boot Controller (SBC) / OTP Controller + // Reference: QEMU hw/misc/aspeed_sbc.c + // + // Stub peripheral: reports "not secured" (normal boot). + // OTP commands accepted but return idle status immediately. + [AllowedTranslations(AllowedTranslation.ByteToDoubleWord | AllowedTranslation.WordToDoubleWord)] + public sealed class Aspeed_SBC : IDoubleWordPeripheral, IKnownSize + { + public Aspeed_SBC() + { + storage = new uint[RegisterSpaceSize / 4]; + Reset(); + } + + public long Size => RegisterSpaceSize; + + public void Reset() + { + Array.Clear(storage, 0, storage.Length); + // OTP idle, not secured, not in UART boot mode + storage[R_STATUS] = OTP_IDLE | OTP_MEM_IDLE; + } + + public uint ReadDoubleWord(long offset) + { + if(offset >= 0 && offset < RegisterSpaceSize) + { + return storage[(uint)offset / 4]; + } + return 0; + } + + public void WriteDoubleWord(long offset, uint value) + { + if(offset < 0 || offset >= RegisterSpaceSize) + { + return; + } + + var reg = (uint)offset / 4; + + switch(reg) + { + case R_STATUS: + case R_QSR: + // Read-only registers + return; + + case R_CMD: + // Accept OTP commands but just return to idle immediately + storage[R_STATUS] |= OTP_IDLE | OTP_MEM_IDLE; + storage[reg] = value; + return; + + default: + storage[reg] = value; + return; + } + } + + private readonly uint[] storage; + + // Register offsets (word index) + private const uint R_PROT = 0x000 / 4; + private const uint R_CMD = 0x004 / 4; + private const uint R_ADDR = 0x010 / 4; + private const uint R_STATUS = 0x014 / 4; + private const uint R_CAMP1 = 0x020 / 4; + private const uint R_CAMP2 = 0x024 / 4; + private const uint R_QSR = 0x040 / 4; + + // Status bits + private const uint OTP_MEM_IDLE = 1u << 1; + private const uint OTP_IDLE = 1u << 2; + + private const int RegisterSpaceSize = 0x1000; + } +} diff --git a/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_XDMA.cs b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_XDMA.cs new file mode 100644 index 000000000..d0bb04076 --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_XDMA.cs @@ -0,0 +1,95 @@ +// +// Copyright (c) 2026 Microsoft +// Licensed under the MIT License. +// +using System; +using Antmicro.Renode.Core; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.Miscellaneous +{ + // Aspeed AST2600 XDMA Engine (Cross-Domain DMA) + // Reference: QEMU hw/misc/aspeed_xdma.c + // + // Stub peripheral: R/W register storage with W1C interrupt status. + // No actual DMA transfers. + [AllowedTranslations(AllowedTranslation.ByteToDoubleWord | AllowedTranslation.WordToDoubleWord)] + public sealed class Aspeed_XDMA : IDoubleWordPeripheral, IKnownSize, IGPIOSender + { + public Aspeed_XDMA() + { + IRQ = new GPIO(); + storage = new uint[RegisterSpaceSize / 4]; + Reset(); + } + + public long Size => RegisterSpaceSize; + public GPIO IRQ { get; } + + public void Reset() + { + Array.Clear(storage, 0, storage.Length); + // QEMU resets IRQ_STATUS to 0xF8000000 + storage[R_IRQ_STATUS] = IRQ_STATUS_RESET; + IRQ.Unset(); + } + + public uint ReadDoubleWord(long offset) + { + if(offset >= 0 && offset < RegisterSpaceSize) + { + return storage[(uint)offset / 4]; + } + return 0; + } + + public void WriteDoubleWord(long offset, uint value) + { + if(offset < 0 || offset >= RegisterSpaceSize) + { + return; + } + + var reg = (uint)offset / 4; + + switch(reg) + { + case R_IRQ_CTRL: + storage[reg] = value & IRQ_CTRL_W_MASK; + return; + + case R_IRQ_STATUS: + // W1C (write-1-to-clear) + storage[reg] &= ~value; + UpdateIRQ(); + return; + + default: + storage[reg] = value; + return; + } + } + + private void UpdateIRQ() + { + bool pending = (storage[R_IRQ_STATUS] & storage[R_IRQ_CTRL]) != 0; + IRQ.Set(pending); + } + + private readonly uint[] storage; + + // AST2600 register offsets (word index) + private const uint R_BMC_CMDQ_ADDR = 0x14 / 4; + private const uint R_BMC_CMDQ_ENDP = 0x18 / 4; + private const uint R_BMC_CMDQ_WRP = 0x1C / 4; + private const uint R_BMC_CMDQ_RDP = 0x20 / 4; + private const uint R_IRQ_CTRL = 0x38 / 4; + private const uint R_IRQ_STATUS = 0x3C / 4; + + private const uint IRQ_STATUS_RESET = 0xF8000000; + private const uint IRQ_CTRL_W_MASK = 0x017003FF; + + private const int RegisterSpaceSize = 0x1000; + } +} diff --git a/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_eSPI.cs b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_eSPI.cs new file mode 100644 index 000000000..186f9866d --- /dev/null +++ b/src/Emulator/Peripherals/Peripherals/Miscellaneous/Aspeed_eSPI.cs @@ -0,0 +1,917 @@ +// +// Copyright (c) 2026 Microsoft +// Licensed under the MIT license. +// +// AST2600 eSPI Controller — Renode model +// Ported from QEMU hw/misc/aspeed_espi.c +// +// Implements all 4 eSPI channels (Peripheral, Virtual Wire, OOB, Flash), +// FIFO-based TX/RX, DMA address registers, W1C interrupt status, +// capability registers, MMBI, and VW system event handling. +// + +using System; +using System.Collections.Generic; +using Antmicro.Renode.Core; +using Antmicro.Renode.Core.Structure.Registers; +using Antmicro.Renode.Logging; +using Antmicro.Renode.Peripherals.Bus; + +namespace Antmicro.Renode.Peripherals.Miscellaneous +{ + [AllowedTranslations(AllowedTranslation.ByteToDoubleWord | + AllowedTranslation.WordToDoubleWord)] + public class Aspeed_eSPI : IDoubleWordPeripheral, IKnownSize, IGPIOSender + { + public Aspeed_eSPI(IMachine machine) + { + this.machine = machine; + IRQ = new GPIO(); + + pcRxBuf = new byte[FifoSize]; + pcTxBuf = new byte[FifoSize]; + npTxBuf = new byte[FifoSize]; + oobRxBuf = new byte[FifoSize]; + oobTxBuf = new byte[FifoSize]; + flashRxBuf = new byte[FifoSize]; + flashTxBuf = new byte[FifoSize]; + + registers = CreateRegisters(); + Reset(); + } + + public uint ReadDoubleWord(long offset) + { + // FIFO read registers need special handling + switch((uint)offset) + { + case 0x018: // PERIF_PC_RX_DATA + if((ctrlValue & CtrlPerifPcRxDmaEn) != 0) return 0; + if(pcRxPos < pcRxLen) return pcRxBuf[pcRxPos++]; + return 0; + + case 0x048: // OOB_RX_DATA + if((ctrlValue & CtrlOobRxDmaEn) != 0) return 0; + if(oobRxPos < oobRxLen) return oobRxBuf[oobRxPos++]; + return 0; + + case 0x068: // FLASH_RX_DATA + if((ctrlValue & CtrlFlashRxDmaEn) != 0) return 0; + if(flashRxPos < flashRxLen) return flashRxBuf[flashRxPos++]; + return 0; + } + + return registers.Read(offset); + } + + public void WriteDoubleWord(long offset, uint value) + { + // FIFO write registers need special handling + switch((uint)offset) + { + case 0x028: // PERIF_PC_TX_DATA + if((ctrlValue & CtrlPerifPcTxDmaEn) == 0 && pcTxLen < FifoSize) + pcTxBuf[pcTxLen++] = (byte)value; + return; + + case 0x038: // PERIF_NP_TX_DATA + if((ctrlValue & CtrlPerifNpTxDmaEn) == 0 && npTxLen < FifoSize) + npTxBuf[npTxLen++] = (byte)value; + return; + + case 0x058: // OOB_TX_DATA + if((ctrlValue & CtrlOobTxDmaEn) == 0 && oobTxLen < FifoSize) + oobTxBuf[oobTxLen++] = (byte)value; + return; + + case 0x078: // FLASH_TX_DATA + if((ctrlValue & CtrlFlashTxDmaEn) == 0 && flashTxLen < FifoSize) + flashTxBuf[flashTxLen++] = (byte)value; + return; + } + + registers.Write(offset, value); + } + + public void Reset() + { + registers.Reset(); + + // Set capability reset values (read-only, set after register reset) + genCapValue = GenCapReset; + ch0CapValue = Ch0CapReset; + ch1CapValue = Ch1CapReset; + ch2CapValue = Ch2CapReset; + ch3CapValue = Ch3CapReset; + ch3Cap2Value = Ch3Cap2Reset; + + // CTRL2 default: MCYC read/write disabled + ctrl2Value = Ctrl2McycRdDis | Ctrl2McycWrDis; + + // SYSEVT: PLTRST# deasserted by default (host powered on) + sysevtValue = SysevtPltrst; + + // INT_STS: RST_DEASSERT set (eSPI link up) + intStsValue = IntRstDeassert; + + // Reset FIFO state + ResetPerifPcRx(); + ResetPerifPcTx(); + ResetPerifNpTx(); + ResetOobRx(); + ResetOobTx(); + ResetFlashRx(); + ResetFlashTx(); + + sysevt1Value = 0; + sysevtIntEnValue = 0; + sysevtIntStsValue = 0; + sysevt1IntEnValue = 0; + sysevt1IntStsValue = 0; + intEnValue = 0; + ctrlValue = 0; + mmbiCtrlValue = 0; + mmbiIntStsValue = 0; + mmbiIntEnValue = 0; + + UpdateIrq(); + } + + public long Size => 0x1000; + public GPIO IRQ { get; } + + // --- Public injection methods for co-simulation --- + + /// + /// Inject a peripheral channel RX packet (host → BMC). + /// + public void InjectPerifPcRx(byte cycleType, byte tag, byte[] data) + { + uint len = (uint)(data?.Length ?? 0); + if(len > FifoSize) + { + this.Log(LogLevel.Warning, "PC RX packet too large ({0} > {1})", len, FifoSize); + len = FifoSize; + } + + if(data != null) + { + Array.Copy(data, 0, pcRxBuf, 0, (int)len); + } + pcRxLen = len; + pcRxPos = 0; + + pcRxCtrlValue = ServPend | PackCtrl(cycleType, tag, len); + + intStsValue |= IntPerifPcRxCmplt; + UpdateIrq(); + } + + /// + /// Inject an OOB channel RX packet (host → BMC). + /// + public void InjectOobRx(byte cycleType, byte tag, byte[] data) + { + uint len = (uint)(data?.Length ?? 0); + if(len > FifoSize) + { + this.Log(LogLevel.Warning, "OOB RX packet too large ({0} > {1})", len, FifoSize); + len = FifoSize; + } + + if(data != null) + { + Array.Copy(data, 0, oobRxBuf, 0, (int)len); + } + oobRxLen = len; + oobRxPos = 0; + + oobRxCtrlValue = ServPend | PackCtrl(cycleType, tag, len); + + intStsValue |= IntOobRxCmplt; + UpdateIrq(); + } + + /// + /// Inject a Flash channel RX packet (host → BMC). + /// + public void InjectFlashRx(byte cycleType, byte tag, byte[] data) + { + uint len = (uint)(data?.Length ?? 0); + if(len > FifoSize) + { + this.Log(LogLevel.Warning, "Flash RX packet too large ({0} > {1})", len, FifoSize); + len = FifoSize; + } + + if(data != null) + { + Array.Copy(data, 0, flashRxBuf, 0, (int)len); + } + flashRxLen = len; + flashRxPos = 0; + + flashRxCtrlValue = ServPend | PackCtrl(cycleType, tag, len); + + intStsValue |= IntFlashRxCmplt; + UpdateIrq(); + } + + /// + /// Inject host-driven Virtual Wire system events. + /// Only modifies host-driven bits; preserves slave-driven bits. + /// + public void InjectVwSysevt(uint hostEvents) + { + uint oldVal = sysevtValue; + uint newVal = (hostEvents & SysevtHostDrivenMask) | + (oldVal & ~SysevtHostDrivenMask); + sysevtValue = newVal; + + NotifySysevtChange(oldVal, newVal); + } + + // --- Private implementation --- + + private DoubleWordRegisterCollection CreateRegisters() + { + var regs = new Dictionary(); + + // 0x000 CTRL + regs[0x000] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "CTRL", + writeCallback: (_, val) => HandleCtrlWrite((uint)val), + valueProviderCallback: _ => ctrlValue); + + // 0x004 STS (read-only) + regs[0x004] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, FieldMode.Read, name: "STS", + valueProviderCallback: _ => stsValue); + + // 0x008 INT_STS (W1C) + regs[0x008] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "INT_STS", + writeCallback: (_, val) => + { + intStsValue &= ~(uint)val; + UpdateIrq(); + }, + valueProviderCallback: _ => intStsValue); + + // 0x00C INT_EN + regs[0x00C] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "INT_EN", + writeCallback: (_, val) => + { + intEnValue = (uint)val; + UpdateIrq(); + }, + valueProviderCallback: _ => intEnValue); + + // 0x010 PERIF_PC_RX_DMA + regs[0x010] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "PERIF_PC_RX_DMA", + writeCallback: (_, val) => pcRxDmaAddr = (uint)val, + valueProviderCallback: _ => pcRxDmaAddr); + + // 0x014 PERIF_PC_RX_CTRL (SERV_PEND is W1C) + regs[0x014] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "PERIF_PC_RX_CTRL", + writeCallback: (_, val) => + { + if(((uint)val & ServPend) != 0) + { + pcRxCtrlValue &= ~ServPend; + pcRxPos = 0; + pcRxLen = 0; + } + }, + valueProviderCallback: _ => pcRxCtrlValue); + + // 0x018 PERIF_PC_RX_DATA — handled in ReadDoubleWord + regs[0x018] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, FieldMode.Read, name: "PERIF_PC_RX_DATA"); + + // 0x020 PERIF_PC_TX_DMA + regs[0x020] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "PERIF_PC_TX_DMA", + writeCallback: (_, val) => pcTxDmaAddr = (uint)val, + valueProviderCallback: _ => pcTxDmaAddr); + + // 0x024 PERIF_PC_TX_CTRL + regs[0x024] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "PERIF_PC_TX_CTRL", + writeCallback: (_, val) => + { + pcTxCtrlValue = (uint)val; + if(((uint)val & TrigPend) != 0) + CompletePcTx(); + }, + valueProviderCallback: _ => pcTxCtrlValue); + + // 0x028 PERIF_PC_TX_DATA — handled in WriteDoubleWord + regs[0x028] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, FieldMode.Write, name: "PERIF_PC_TX_DATA"); + + // 0x030 PERIF_NP_TX_DMA + regs[0x030] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "PERIF_NP_TX_DMA", + writeCallback: (_, val) => npTxDmaAddr = (uint)val, + valueProviderCallback: _ => npTxDmaAddr); + + // 0x034 PERIF_NP_TX_CTRL + regs[0x034] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "PERIF_NP_TX_CTRL", + writeCallback: (_, val) => + { + npTxCtrlValue = (uint)val; + if(((uint)val & TrigPend) != 0) + CompleteNpTx(); + }, + valueProviderCallback: _ => npTxCtrlValue); + + // 0x038 PERIF_NP_TX_DATA — handled in WriteDoubleWord + regs[0x038] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, FieldMode.Write, name: "PERIF_NP_TX_DATA"); + + // 0x040 OOB_RX_DMA + regs[0x040] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "OOB_RX_DMA", + writeCallback: (_, val) => oobRxDmaAddr = (uint)val, + valueProviderCallback: _ => oobRxDmaAddr); + + // 0x044 OOB_RX_CTRL + regs[0x044] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "OOB_RX_CTRL", + writeCallback: (_, val) => + { + if(((uint)val & ServPend) != 0) + { + oobRxCtrlValue &= ~ServPend; + oobRxPos = 0; + oobRxLen = 0; + } + }, + valueProviderCallback: _ => oobRxCtrlValue); + + // 0x048 OOB_RX_DATA — handled in ReadDoubleWord + regs[0x048] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, FieldMode.Read, name: "OOB_RX_DATA"); + + // 0x050 OOB_TX_DMA + regs[0x050] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "OOB_TX_DMA", + writeCallback: (_, val) => oobTxDmaAddr = (uint)val, + valueProviderCallback: _ => oobTxDmaAddr); + + // 0x054 OOB_TX_CTRL + regs[0x054] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "OOB_TX_CTRL", + writeCallback: (_, val) => + { + oobTxCtrlValue = (uint)val; + if(((uint)val & TrigPend) != 0) + CompleteOobTx(); + }, + valueProviderCallback: _ => oobTxCtrlValue); + + // 0x058 OOB_TX_DATA — handled in WriteDoubleWord + regs[0x058] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, FieldMode.Write, name: "OOB_TX_DATA"); + + // 0x060 FLASH_RX_DMA + regs[0x060] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "FLASH_RX_DMA", + writeCallback: (_, val) => flashRxDmaAddr = (uint)val, + valueProviderCallback: _ => flashRxDmaAddr); + + // 0x064 FLASH_RX_CTRL + regs[0x064] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "FLASH_RX_CTRL", + writeCallback: (_, val) => + { + if(((uint)val & ServPend) != 0) + { + flashRxCtrlValue &= ~ServPend; + flashRxPos = 0; + flashRxLen = 0; + } + }, + valueProviderCallback: _ => flashRxCtrlValue); + + // 0x068 FLASH_RX_DATA — handled in ReadDoubleWord + regs[0x068] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, FieldMode.Read, name: "FLASH_RX_DATA"); + + // 0x070 FLASH_TX_DMA + regs[0x070] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "FLASH_TX_DMA", + writeCallback: (_, val) => flashTxDmaAddr = (uint)val, + valueProviderCallback: _ => flashTxDmaAddr); + + // 0x074 FLASH_TX_CTRL + regs[0x074] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "FLASH_TX_CTRL", + writeCallback: (_, val) => + { + flashTxCtrlValue = (uint)val; + if(((uint)val & TrigPend) != 0) + CompleteFlashTx(); + }, + valueProviderCallback: _ => flashTxCtrlValue); + + // 0x078 FLASH_TX_DATA — handled in WriteDoubleWord + regs[0x078] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, FieldMode.Write, name: "FLASH_TX_DATA"); + + // 0x080 CTRL2 + regs[0x080] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "CTRL2", + writeCallback: (_, val) => ctrl2Value = (uint)val, + valueProviderCallback: _ => ctrl2Value); + + // 0x084 PERIF_MCYC_SADDR + regs[0x084] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "PERIF_MCYC_SADDR", + writeCallback: (_, val) => mcycSaddrValue = (uint)val, + valueProviderCallback: _ => mcycSaddrValue); + + // 0x088 PERIF_MCYC_TADDR + regs[0x088] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "PERIF_MCYC_TADDR", + writeCallback: (_, val) => mcycTaddrValue = (uint)val, + valueProviderCallback: _ => mcycTaddrValue); + + // 0x08C PERIF_MCYC_MASK + regs[0x08C] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "PERIF_MCYC_MASK", + writeCallback: (_, val) => mcycMaskValue = (uint)val, + valueProviderCallback: _ => mcycMaskValue); + + // 0x090 FLASH_SAFS_TADDR + regs[0x090] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "FLASH_SAFS_TADDR", + writeCallback: (_, val) => flashSafsTaddrValue = (uint)val, + valueProviderCallback: _ => flashSafsTaddrValue); + + // 0x094 VW_SYSEVT_INT_EN + regs[0x094] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "VW_SYSEVT_INT_EN", + writeCallback: (_, val) => + { + sysevtIntEnValue = (uint)val; + // Re-evaluate: newly enabled bits may trigger + NotifySysevtChange(0, sysevtValue); + }, + valueProviderCallback: _ => sysevtIntEnValue); + + // 0x098 VW_SYSEVT + regs[0x098] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "VW_SYSEVT", + writeCallback: (_, val) => + { + // BMC can write slave-driven bits only + sysevtValue = ((uint)val & SysevtSlaveDrivenMask) | + (sysevtValue & SysevtHostDrivenMask); + }, + valueProviderCallback: _ => sysevtValue); + + // 0x09C VW_GPIO_VAL + regs[0x09C] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "VW_GPIO_VAL", + writeCallback: (_, val) => + { + uint oldVal = vwGpioValue; + vwGpioValue = (uint)val; + if(oldVal != val) + { + intStsValue |= IntVwGpio; + UpdateIrq(); + } + }, + valueProviderCallback: _ => vwGpioValue); + + // 0x0A0 GEN_CAP_N_CONF (read-only) + regs[0x0A0] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, FieldMode.Read, name: "GEN_CAP_N_CONF", + valueProviderCallback: _ => genCapValue); + + // 0x0A4 CH0_CAP_N_CONF (read-only) + regs[0x0A4] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, FieldMode.Read, name: "CH0_CAP_N_CONF", + valueProviderCallback: _ => ch0CapValue); + + // 0x0A8 CH1_CAP_N_CONF (read-only) + regs[0x0A8] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, FieldMode.Read, name: "CH1_CAP_N_CONF", + valueProviderCallback: _ => ch1CapValue); + + // 0x0AC CH2_CAP_N_CONF (read-only) + regs[0x0AC] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, FieldMode.Read, name: "CH2_CAP_N_CONF", + valueProviderCallback: _ => ch2CapValue); + + // 0x0B0 CH3_CAP_N_CONF (read-only) + regs[0x0B0] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, FieldMode.Read, name: "CH3_CAP_N_CONF", + valueProviderCallback: _ => ch3CapValue); + + // 0x0B4 CH3_CAP_N_CONF2 (read-only) + regs[0x0B4] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, FieldMode.Read, name: "CH3_CAP_N_CONF2", + valueProviderCallback: _ => ch3Cap2Value); + + // 0x0C0 VW_GPIO_DIR + regs[0x0C0] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "VW_GPIO_DIR", + writeCallback: (_, val) => vwGpioDirValue = (uint)val, + valueProviderCallback: _ => vwGpioDirValue); + + // 0x0C4 VW_GPIO_GRP + regs[0x0C4] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "VW_GPIO_GRP", + writeCallback: (_, val) => vwGpioGrpValue = (uint)val, + valueProviderCallback: _ => vwGpioGrpValue); + + // 0x0FC INT_EN_CLR (write clears INT_EN bits) + regs[0x0FC] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "INT_EN_CLR", + writeCallback: (_, val) => + { + intEnValue &= ~(uint)val; + UpdateIrq(); + }, + valueProviderCallback: _ => 0); + + // 0x100 VW_SYSEVT1_INT_EN + regs[0x100] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "VW_SYSEVT1_INT_EN", + writeCallback: (_, val) => sysevt1IntEnValue = (uint)val, + valueProviderCallback: _ => sysevt1IntEnValue); + + // 0x104 VW_SYSEVT1 + regs[0x104] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "VW_SYSEVT1", + writeCallback: (_, val) => + { + // SUSPEND_ACK is slave-driven, SUSPEND_WARN is host-driven + sysevt1Value = ((uint)val & Sysevt1SuspendAck) | + (sysevt1Value & Sysevt1SuspendWarn); + }, + valueProviderCallback: _ => sysevt1Value); + + // 0x110 VW_SYSEVT_INT_T0 + regs[0x110] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "VW_SYSEVT_INT_T0", + writeCallback: (_, val) => sysevtIntT0Value = (uint)val, + valueProviderCallback: _ => sysevtIntT0Value); + + // 0x114 VW_SYSEVT_INT_T1 + regs[0x114] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "VW_SYSEVT_INT_T1", + writeCallback: (_, val) => sysevtIntT1Value = (uint)val, + valueProviderCallback: _ => sysevtIntT1Value); + + // 0x118 VW_SYSEVT_INT_T2 + regs[0x118] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "VW_SYSEVT_INT_T2", + writeCallback: (_, val) => sysevtIntT2Value = (uint)val, + valueProviderCallback: _ => sysevtIntT2Value); + + // 0x11C VW_SYSEVT_INT_STS (W1C) + regs[0x11C] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "VW_SYSEVT_INT_STS", + writeCallback: (_, val) => + { + sysevtIntStsValue &= ~(uint)val; + if(sysevtIntStsValue == 0) + intStsValue &= ~IntVwSysevt; + UpdateIrq(); + }, + valueProviderCallback: _ => sysevtIntStsValue); + + // 0x120 VW_SYSEVT1_INT_T0 + regs[0x120] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "VW_SYSEVT1_INT_T0", + writeCallback: (_, val) => sysevt1IntT0Value = (uint)val, + valueProviderCallback: _ => sysevt1IntT0Value); + + // 0x124 VW_SYSEVT1_INT_T1 + regs[0x124] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "VW_SYSEVT1_INT_T1", + writeCallback: (_, val) => sysevt1IntT1Value = (uint)val, + valueProviderCallback: _ => sysevt1IntT1Value); + + // 0x128 VW_SYSEVT1_INT_T2 + regs[0x128] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "VW_SYSEVT1_INT_T2", + writeCallback: (_, val) => sysevt1IntT2Value = (uint)val, + valueProviderCallback: _ => sysevt1IntT2Value); + + // 0x12C VW_SYSEVT1_INT_STS (W1C) + regs[0x12C] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "VW_SYSEVT1_INT_STS", + writeCallback: (_, val) => + { + sysevt1IntStsValue &= ~(uint)val; + if(sysevt1IntStsValue == 0) + intStsValue &= ~IntVwSysevt1; + UpdateIrq(); + }, + valueProviderCallback: _ => sysevt1IntStsValue); + + // 0x800 MMBI_CTRL + regs[0x800] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "MMBI_CTRL", + writeCallback: (_, val) => mmbiCtrlValue = (uint)val, + valueProviderCallback: _ => mmbiCtrlValue); + + // 0x808 MMBI_INT_STS (W1C) + regs[0x808] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "MMBI_INT_STS", + writeCallback: (_, val) => mmbiIntStsValue &= ~(uint)val, + valueProviderCallback: _ => mmbiIntStsValue); + + // 0x80C MMBI_INT_EN + regs[0x80C] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: "MMBI_INT_EN", + writeCallback: (_, val) => mmbiIntEnValue = (uint)val, + valueProviderCallback: _ => mmbiIntEnValue); + + // 0x810-0x848 MMBI_HOST_RWP (8 instances, 8 bytes apart) + for(int i = 0; i < MmbiMaxInst; i++) + { + long off = 0x810 + (i * 8); + int idx = i; + regs[off] = new DoubleWordRegister(this, 0x0) + .WithValueField(0, 32, name: $"MMBI_HOST_RWP{idx}", + writeCallback: (_, val) => mmbiHostRwp[idx] = (uint)val, + valueProviderCallback: _ => mmbiHostRwp[idx]); + } + + return new DoubleWordRegisterCollection(this, regs); + } + + private void HandleCtrlWrite(uint data) + { + // SW reset bits are self-clearing + if((data & CtrlPerifPcRxSwRst) != 0) ResetPerifPcRx(); + if((data & CtrlPerifPcTxSwRst) != 0) ResetPerifPcTx(); + if((data & CtrlPerifNpTxSwRst) != 0) ResetPerifNpTx(); + if((data & CtrlOobRxSwRst) != 0) ResetOobRx(); + if((data & CtrlOobTxSwRst) != 0) ResetOobTx(); + if((data & CtrlFlashRxSwRst) != 0) ResetFlashRx(); + if((data & CtrlFlashTxSwRst) != 0) ResetFlashTx(); + + // Store value with reset bits cleared + ctrlValue = data & ~SwResetMask; + } + + private void CompletePcTx() + { + pcTxCtrlValue &= ~TrigPend; + pcTxLen = 0; + intStsValue |= IntPerifPcTxCmplt; + UpdateIrq(); + } + + private void CompleteNpTx() + { + npTxCtrlValue &= ~TrigPend; + npTxLen = 0; + intStsValue |= IntPerifNpTxCmplt; + UpdateIrq(); + } + + private void CompleteOobTx() + { + oobTxCtrlValue &= ~TrigPend; + oobTxLen = 0; + intStsValue |= IntOobTxCmplt; + UpdateIrq(); + } + + private void CompleteFlashTx() + { + flashTxCtrlValue &= ~TrigPend; + flashTxLen = 0; + intStsValue |= IntFlashTxCmplt; + UpdateIrq(); + } + + private void NotifySysevtChange(uint oldVal, uint newVal) + { + uint changed = oldVal ^ newVal; + uint enabled = sysevtIntEnValue; + + if((changed & enabled) != 0) + { + sysevtIntStsValue |= (changed & enabled); + intStsValue |= IntVwSysevt; + UpdateIrq(); + } + } + + private void UpdateIrq() + { + bool active = (intStsValue & intEnValue) != 0; + IRQ.Set(active); + } + + private void ResetPerifPcRx() + { + Array.Clear(pcRxBuf, 0, pcRxBuf.Length); + pcRxLen = 0; + pcRxPos = 0; + pcRxCtrlValue = 0; + } + + private void ResetPerifPcTx() + { + Array.Clear(pcTxBuf, 0, pcTxBuf.Length); + pcTxLen = 0; + pcTxCtrlValue = 0; + } + + private void ResetPerifNpTx() + { + Array.Clear(npTxBuf, 0, npTxBuf.Length); + npTxLen = 0; + npTxCtrlValue = 0; + } + + private void ResetOobRx() + { + Array.Clear(oobRxBuf, 0, oobRxBuf.Length); + oobRxLen = 0; + oobRxPos = 0; + oobRxCtrlValue = 0; + } + + private void ResetOobTx() + { + Array.Clear(oobTxBuf, 0, oobTxBuf.Length); + oobTxLen = 0; + oobTxCtrlValue = 0; + } + + private void ResetFlashRx() + { + Array.Clear(flashRxBuf, 0, flashRxBuf.Length); + flashRxLen = 0; + flashRxPos = 0; + flashRxCtrlValue = 0; + } + + private void ResetFlashTx() + { + Array.Clear(flashTxBuf, 0, flashTxBuf.Length); + flashTxLen = 0; + flashTxCtrlValue = 0; + } + + private static uint PackCtrl(byte cyc, byte tag, uint len) + { + return ((len & 0xFFF) << 12) | (uint)((tag & 0xF) << 8) | cyc; + } + + // --- Constants (matching QEMU defines) --- + + private const int FifoSize = 256; + private const int MmbiMaxInst = 8; + + // CTRL register bits + private const uint CtrlFlashTxSwRst = 1u << 31; + private const uint CtrlFlashRxSwRst = 1u << 30; + private const uint CtrlOobTxSwRst = 1u << 29; + private const uint CtrlOobRxSwRst = 1u << 28; + private const uint CtrlPerifNpTxSwRst = 1u << 27; + private const uint CtrlPerifNpRxSwRst = 1u << 26; + private const uint CtrlPerifPcTxSwRst = 1u << 25; + private const uint CtrlPerifPcRxSwRst = 1u << 24; + private const uint CtrlFlashTxDmaEn = 1u << 23; + private const uint CtrlFlashRxDmaEn = 1u << 22; + private const uint CtrlOobTxDmaEn = 1u << 21; + private const uint CtrlOobRxDmaEn = 1u << 20; + private const uint CtrlPerifNpTxDmaEn = 1u << 19; + private const uint CtrlPerifPcTxDmaEn = 1u << 17; + private const uint CtrlPerifPcRxDmaEn = 1u << 16; + + private const uint SwResetMask = + CtrlFlashTxSwRst | CtrlFlashRxSwRst | + CtrlOobTxSwRst | CtrlOobRxSwRst | + CtrlPerifNpTxSwRst | CtrlPerifNpRxSwRst | + CtrlPerifPcTxSwRst | CtrlPerifPcRxSwRst; + + // INT_STS / INT_EN bits + private const uint IntRstDeassert = 1u << 31; + private const uint IntOobRxTmout = 1u << 23; + private const uint IntVwSysevt1 = 1u << 22; + private const uint IntFlashTxErr = 1u << 21; + private const uint IntOobTxErr = 1u << 20; + private const uint IntFlashTxAbt = 1u << 19; + private const uint IntOobTxAbt = 1u << 18; + private const uint IntPerifNpTxAbt = 1u << 17; + private const uint IntPerifPcTxAbt = 1u << 16; + private const uint IntFlashRxAbt = 1u << 15; + private const uint IntOobRxAbt = 1u << 14; + private const uint IntPerifNpRxAbt = 1u << 13; + private const uint IntPerifPcRxAbt = 1u << 12; + private const uint IntPerifNpTxErr = 1u << 11; + private const uint IntPerifPcTxErr = 1u << 10; + private const uint IntVwGpio = 1u << 9; + private const uint IntVwSysevt = 1u << 8; + private const uint IntFlashTxCmplt = 1u << 7; + private const uint IntFlashRxCmplt = 1u << 6; + private const uint IntOobTxCmplt = 1u << 5; + private const uint IntOobRxCmplt = 1u << 4; + private const uint IntPerifNpTxCmplt = 1u << 3; + private const uint IntPerifPcTxCmplt = 1u << 1; + private const uint IntPerifPcRxCmplt = 1u << 0; + + // CTRL register field bits + private const uint ServPend = 1u << 31; + private const uint TrigPend = 1u << 31; + + // SYSEVT host-driven bits + private const uint SysevtHostRstWarn = 1u << 8; + private const uint SysevtOobRstWarn = 1u << 6; + private const uint SysevtPltrst = 1u << 5; + private const uint SysevtSuspend = 1u << 4; + private const uint SysevtS5Sleep = 1u << 2; + private const uint SysevtS4Sleep = 1u << 1; + private const uint SysevtS3Sleep = 1u << 0; + + private const uint SysevtHostDrivenMask = + SysevtHostRstWarn | SysevtOobRstWarn | SysevtPltrst | + SysevtSuspend | SysevtS5Sleep | SysevtS4Sleep | SysevtS3Sleep; + + // SYSEVT slave-driven bits + private const uint SysevtHostRstAck = 1u << 27; + private const uint SysevtRstCpuInit = 1u << 26; + private const uint SysevtSlvBootSts = 1u << 23; + private const uint SysevtNonFatalErr = 1u << 22; + private const uint SysevtFatalErr = 1u << 21; + private const uint SysevtSlvBootDone = 1u << 20; + private const uint SysevtOobRstAck = 1u << 16; + private const uint SysevtNmiOut = 1u << 10; + private const uint SysevtSmiOut = 1u << 9; + + private const uint SysevtSlaveDrivenMask = + SysevtHostRstAck | SysevtRstCpuInit | SysevtSlvBootSts | + SysevtNonFatalErr | SysevtFatalErr | SysevtSlvBootDone | + SysevtOobRstAck | SysevtNmiOut | SysevtSmiOut; + + // SYSEVT1 bits + private const uint Sysevt1SuspendAck = 1u << 20; + private const uint Sysevt1SuspendWarn = 1u << 0; + + // CTRL2 bits + private const uint Ctrl2McycRdDis = 1u << 6; + private const uint Ctrl2McycWrDis = 1u << 4; + + // Capability reset values (from QEMU) + private const uint GenCapReset = 0x0000F759; + private const uint Ch0CapReset = 0x00000073; + private const uint Ch1CapReset = 0x00000033; + private const uint Ch2CapReset = 0x00000033; + private const uint Ch3CapReset = 0x00000003; + private const uint Ch3Cap2Reset = 0x00000000; + + // --- State --- + + private readonly IMachine machine; + private readonly DoubleWordRegisterCollection registers; + + // FIFO buffers + private readonly byte[] pcRxBuf, pcTxBuf, npTxBuf; + private readonly byte[] oobRxBuf, oobTxBuf; + private readonly byte[] flashRxBuf, flashTxBuf; + + // FIFO positions/lengths + private uint pcRxLen, pcRxPos, pcTxLen, npTxLen; + private uint oobRxLen, oobRxPos, oobTxLen; + private uint flashRxLen, flashRxPos, flashTxLen; + + // Register backing fields + private uint ctrlValue, stsValue, intStsValue, intEnValue; + private uint ctrl2Value; + private uint pcRxCtrlValue, pcTxCtrlValue, npTxCtrlValue; + private uint oobRxCtrlValue, oobTxCtrlValue; + private uint flashRxCtrlValue, flashTxCtrlValue; + private uint pcRxDmaAddr, pcTxDmaAddr, npTxDmaAddr; + private uint oobRxDmaAddr, oobTxDmaAddr; + private uint flashRxDmaAddr, flashTxDmaAddr; + private uint mcycSaddrValue, mcycTaddrValue, mcycMaskValue; + private uint flashSafsTaddrValue; + private uint sysevtValue, sysevtIntEnValue, sysevtIntStsValue; + private uint sysevt1Value, sysevt1IntEnValue, sysevt1IntStsValue; + private uint sysevtIntT0Value, sysevtIntT1Value, sysevtIntT2Value; + private uint sysevt1IntT0Value, sysevt1IntT1Value, sysevt1IntT2Value; + private uint vwGpioValue, vwGpioDirValue, vwGpioGrpValue; + private uint genCapValue, ch0CapValue, ch1CapValue; + private uint ch2CapValue, ch3CapValue, ch3Cap2Value; + private uint mmbiCtrlValue, mmbiIntStsValue, mmbiIntEnValue; + private uint[] mmbiHostRwp = new uint[MmbiMaxInst]; + } +}