| // |
| // Copyright (c) 2024 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| using System; |
| using System.Linq; |
| using System.Runtime.InteropServices; |
| using System.Threading; |
| using System.Collections.Generic; |
| using System.Collections.Concurrent; |
| using Antmicro.Renode.Core; |
| using Antmicro.Renode.Debugging; |
| using Antmicro.Renode.Exceptions; |
| using Antmicro.Renode.Hooks; |
| using Antmicro.Renode.Logging; |
| using Antmicro.Renode.Peripherals; |
| using Antmicro.Renode.Peripherals.Bus; |
| using Antmicro.Renode.Peripherals.CPU; |
| using Antmicro.Renode.Peripherals.Timers; |
| using Antmicro.Renode.Peripherals.CPU.Disassembler; |
| using Antmicro.Renode.Peripherals.CPU.Registers; |
| using Antmicro.Renode.Utilities; |
| using Antmicro.Renode.Time; |
| using Antmicro.Renode.Utilities.Binding; |
| using ELFSharp.ELF; |
| using ELFSharp.UImage; |
| |
| namespace Antmicro.Renode.Peripherals.MpactCPU |
| { |
| // Declare some additional function signatures. |
| [UnmanagedFunctionPointer(CallingConvention.Cdecl)] |
| public delegate Int32 FuncInt32Int32ActionInt32(Int32 param0, ActionInt32 param1); |
| [UnmanagedFunctionPointer(CallingConvention.Cdecl)] |
| public delegate Int32 FuncInt32Int32Int32FuncInt32UInt64IntPtrInt32FuncInt32UInt64IntPtrInt32(Int32 param0, Int32 param1, FuncInt32UInt64IntPtrInt32 param2, |
| FuncInt32UInt64IntPtrInt32 param3); |
| [UnmanagedFunctionPointer(CallingConvention.Cdecl)] |
| public delegate Int32 FuncInt32Int32FuncInt32UInt64IntPtrInt32FuncInt32UInt64IntPtrInt32(Int32 param0, FuncInt32UInt64IntPtrInt32 param1, |
| FuncInt32UInt64IntPtrInt32 param2); |
| [UnmanagedFunctionPointer(CallingConvention.Cdecl)] |
| public delegate Int32 FuncInt32Int32StringArrStringArrInt32(Int32 param0, string[] param1, string[] param2, Int32 param3); |
| [UnmanagedFunctionPointer(CallingConvention.Cdecl)] |
| public delegate Int32 FuncInt32Int32Int32Bool(Int32 param0, Int32 param1, bool param2); |
| |
| public class MpactCheriotCPU : BaseCPU, ICpuSupportingGdb, ICPUWithRegisters, ICPUWithHooks, IGPIOReceiver, ITimeSink, IDisposable { |
| [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi, Pack = 1)] |
| private struct RegInfo { |
| [FieldOffset(0)] |
| public Int32 index; |
| [FieldOffset(4)] |
| public Int32 width; |
| [FieldOffset(8)] |
| public bool isGeneral; |
| [FieldOffset(9)] |
| public bool isReadOnly; |
| } |
| |
| public MpactCheriotCPU(uint id, UInt64 memoryBase, UInt64 memorySize, |
| UInt64 revocationMemoryBase, UInt64 clintMMRBase, |
| string cpuType, IMachine machine, Endianess endianness, |
| CpuBitness bitness = CpuBitness.Bits32) |
| : base(id, cpuType, machine, endianness, bitness) |
| { |
| this.memoryBase = memoryBase; |
| this.memorySize = memorySize; |
| revocationMemBase = revocationMemoryBase; |
| clintBase = clintMMRBase; |
| error_ptr = Marshal.AllocHGlobal(4); |
| value_ptr = Marshal.AllocHGlobal(8); |
| reg_info_ptr = Marshal.AllocHGlobal(Marshal.SizeOf<RegInfo>()); |
| string_ptr = Marshal.AllocHGlobal(maxStringLen); |
| run_cb_delegate = new ActionInt32(RunComplete); |
| read_sysmem_delegate = new FuncInt32UInt64IntPtrInt32(ReadSysMemory); |
| write_sysmem_delegate = new FuncInt32UInt64IntPtrInt32(WriteSysMemory); |
| cpuLibraryRegistered = false; |
| } |
| |
| ~MpactCheriotCPU() { |
| Marshal.FreeHGlobal(error_ptr); |
| Marshal.FreeHGlobal(value_ptr); |
| Marshal.FreeHGlobal(reg_info_ptr); |
| Marshal.FreeHGlobal(string_ptr); |
| } |
| |
| public override string Architecture { get { return "MpactCheriot";} } |
| |
| public override void Start() |
| { |
| if (!cpuLibraryRegistered) |
| { |
| LogAndThrowRE("Failed to register cpu library"); |
| return; |
| } |
| base.Start(); |
| |
| // Simulator initialization code goes here. |
| mpact_id = connect_with_sysbus((Int32)Id, maxStringLen, read_sysmem_delegate, write_sysmem_delegate); |
| if (mpact_id < 0) { |
| LogAndThrowRE("Failed to create simulator instance"); |
| return; |
| } |
| // Set up configuration array. |
| var config_names = new string[4] {"memoryBase", "memorySize", "revocationMemoryBase", "clintMMRBase"}; |
| var config_values = new string[4]; |
| config_values[0] = "0x" + memoryBase.ToString("X"); |
| config_values[1] = "0x" + memorySize.ToString("X"); |
| config_values[2] = "0x" + revocationMemBase.ToString("X"); |
| config_values[3] = "0x" + clintBase.ToString("X"); |
| Int32 cfg_res = set_config(mpact_id, config_names, config_values, 4); |
| if (cfg_res < 0) { |
| LogAndThrowRE("Failed to set configuration information"); |
| } |
| } |
| |
| public override void Reset() |
| { |
| base.Reset(); |
| instructionsExecutedThisRound = 0; |
| totalExecutedInstructions = 0; |
| // Call simulator to reset. |
| reset(mpact_id); |
| } |
| |
| public override void Dispose() |
| { |
| base.Dispose(); |
| // Cleanup: simulator and any unmanaged resources. |
| halt(mpact_id, error_ptr); |
| destruct(mpact_id); |
| } |
| |
| public string CpuLibraryPath |
| { |
| get |
| { |
| return cpuLibraryPath; |
| } |
| set |
| { |
| if (!String.IsNullOrWhiteSpace(value)) |
| { |
| if (cpuLibraryRegistered) |
| { |
| this.WarningLog("cpu library already registerd and " + |
| "should not be updated again."); |
| return; |
| } |
| cpuLibraryPath = value; |
| try |
| { |
| binder = new NativeBinder(this, cpuLibraryPath); |
| } |
| catch (System.Exception e) |
| { |
| LogAndThrowRE("Failed to load CPU library: " + e.Message); |
| } |
| cpuLibraryRegistered = true; |
| } |
| else |
| { |
| return; |
| } |
| } |
| } |
| |
| public override ulong ExecutedInstructions => totalExecutedInstructions; |
| |
| public override ExecutionMode ExecutionMode |
| { |
| get |
| { |
| return executionMode; |
| } |
| set |
| { |
| lock(singleStepSynchronizer.Guard) |
| { |
| if (executionMode == value) |
| { |
| return; |
| } |
| |
| executionMode = value; |
| |
| singleStepSynchronizer.Enabled = IsSingleStepMode; |
| UpdateHaltedState(); |
| } |
| } |
| } |
| |
| [Register] |
| public override RegisterValue PC |
| { |
| get |
| { |
| Int32 error = read_register(mpact_id, PC_ID, value_ptr); |
| // TODO(torerik): Check for error. |
| if (error < 0) { |
| this.Log(LogLevel.Error, "Failed to read PC"); |
| } |
| Int64 value = Marshal.ReadInt64(value_ptr); |
| return Convert.ToUInt64(value); |
| } |
| |
| set |
| { |
| Int32 error = write_register(mpact_id, PC_ID, value); |
| // TODO(torerik): Check for error. |
| if (error < 0) { |
| this.Log(LogLevel.Error, "Failed to write PC"); |
| } |
| } |
| } |
| |
| protected override ExecutionResult ExecuteInstructions(ulong numberOfInstructionsToExecute, out ulong numberOfExecutedInstructions) |
| { |
| UInt64 instructionsExecutedThisRound = 0UL; |
| ExecutionResult result = ExecutionResult.Ok; |
| try |
| { |
| // Invoke simulator for the number of instructions. |
| instructionsExecutedThisRound = step(mpact_id, numberOfInstructionsToExecute, error_ptr); |
| // Parse the result. |
| Int32 step_result = Marshal.ReadInt32(error_ptr); |
| switch (step_result) { |
| case -1: |
| result = ExecutionResult.Aborted; |
| break; |
| case 0: |
| result = ExecutionResult.Ok; |
| break; |
| case 1: |
| result = ExecutionResult.Interrupted; |
| break; |
| case 2: |
| result = ExecutionResult.WaitingForInterrupt; |
| break; |
| case 3: |
| result = ExecutionResult.StoppedAtBreakpoint; |
| break; |
| case 4: |
| result = ExecutionResult.StoppedAtWatchpoint; |
| break; |
| case 5: |
| result = ExecutionResult.ExternalMmuFault; |
| break; |
| default: |
| LogAndThrowRE("Unknown return value from step - " + step_result); |
| break; |
| } |
| } |
| catch (Exception) |
| { |
| this.NoisyLog("CPU exception detected, halting."); |
| InvokeHalted(new HaltArguments(HaltReason.Abort, Id)); |
| return ExecutionResult.Aborted; |
| } |
| finally |
| { |
| numberOfExecutedInstructions = instructionsExecutedThisRound; |
| totalExecutedInstructions += instructionsExecutedThisRound; |
| } |
| |
| return result; |
| } |
| |
| // ICPUWithRegisters methods implementations. |
| public void SetRegisterUnsafe(int register, RegisterValue value) { |
| var status = write_register((Int32)mpact_id, (Int32)register, (UInt64)value); |
| if (status < 0) { |
| LogAndThrowRE("Failed to write register " + register); |
| } |
| } |
| |
| public RegisterValue GetRegisterUnsafe(int register) { |
| var status = read_register(mpact_id, register, value_ptr); |
| if (status < 0) { |
| LogAndThrowRE("Failed to read register " + register); |
| } |
| Int64 value = Marshal.ReadInt64(value_ptr); |
| return (UInt64)value; |
| } |
| |
| private void GetMpactRegisters() { |
| if (registerMap.Count != 0) return; |
| Int32 num_regs = get_reg_info_size(mpact_id); |
| for (Int32 i = 0; i < num_regs; i++) { |
| Int32 result = get_reg_info(mpact_id, i, string_ptr, reg_info_ptr); |
| if (result < 0) { |
| this.Log(LogLevel.Error, "Failed to get register info for index " + i); |
| continue; |
| } |
| var reg_info = Marshal.PtrToStructure<RegInfo>(reg_info_ptr); |
| var cpu_reg = new CPURegister(reg_info.index, reg_info.width, reg_info.isGeneral, reg_info.isReadOnly); |
| var reg_name = Marshal.PtrToStringAuto(string_ptr); |
| registerMap.Add(reg_info.index, cpu_reg); |
| registerNamesMap.Add(reg_name, reg_info.index); |
| } |
| } |
| |
| public IEnumerable<CPURegister> GetRegisters() { |
| if (registerMap.Count == 0) { |
| GetMpactRegisters(); |
| } |
| return registerMap.Values.OrderBy(x => x.Index); |
| } |
| |
| public new string[,] GetRegistersValues() { |
| if (registerMap.Count == 0) { |
| GetMpactRegisters(); |
| } |
| var result = new Dictionary<string, ulong>(); |
| foreach (var reg in registerNamesMap) { |
| var status = read_register(mpact_id, reg.Value, value_ptr); |
| if (status < 0) continue; |
| Int64 value = Marshal.ReadInt64(value_ptr); |
| result.Add(reg.Key, Convert.ToUInt64(value)); |
| } |
| var table = new Table().AddRow("Name", "Value"); |
| table.AddRows(result, x=> x.Key, x=> "0x{0:X}".FormatWith(x.Value)); |
| return table.ToArray(); |
| } |
| |
| private void LogAndThrowRE(string info) |
| { |
| this.Log(LogLevel.Error, info); |
| throw new RecoverableException(info); |
| } |
| |
| public Int32 LoadImageFile(String file_name, UInt64 address) { |
| return load_image(mpact_id, file_name, address); |
| } |
| |
| public void RunComplete(Int32 result) { |
| Console.Error.WriteLine($"RunComplete({result})"); |
| } |
| |
| public void Run() { |
| Int32 status = run(mpact_id, run_cb_delegate); |
| Console.Error.WriteLine($"Run -> {status}"); |
| } |
| |
| public void OnGPIO(int number, bool value) { |
| if (mpact_id <= 0) { |
| this.Log(LogLevel.Noisy, "OnGPIO: no simulator, discard gpio {0}:{1}", number, value); |
| return; |
| } |
| Int32 status = set_irq_value(mpact_id, number, value); |
| if (status < 0) { |
| Console.Error.WriteLine("Failure in setting irq value"); |
| } |
| } |
| |
| public Int32 ReadSysMemory(UInt64 address, IntPtr buffer, Int32 length) { |
| // Read from System Bus. |
| switch (length) { |
| case 1: |
| var data8 = new byte[length]; |
| data8[0] = machine.SystemBus.ReadByte(address, this); |
| Marshal.Copy(data8, 0, buffer, length); |
| break; |
| case 2: |
| var data16 = new Int16[1]; |
| data16[0] = (Int16) machine.SystemBus.ReadWord(address, this); |
| Marshal.Copy(data16, 0, buffer, 1); |
| break; |
| case 4: |
| var data32 = new Int32[1]; |
| data32[0] = (Int32) machine.SystemBus.ReadDoubleWord(address, this); |
| Marshal.Copy(data32, 0, buffer, 1); |
| break; |
| case 8: |
| var data64 = new Int64[1]; |
| data64[0] = (Int64) machine.SystemBus.ReadQuadWord(address, this); |
| Marshal.Copy(data64, 0, buffer, 1); |
| break; |
| default: |
| var dataN = new byte[length]; |
| machine.SystemBus.ReadBytes(address, length, dataN, 0, false, this); |
| Marshal.Copy(dataN, 0, buffer, length); |
| break; |
| } |
| return length; |
| } |
| |
| public Int32 WriteSysMemory(UInt64 address, IntPtr buffer, Int32 length) { |
| // Create a managed buffer for the store data. |
| // Copy the store data to the managed buffer. |
| // Write the data to the system bus. |
| switch (length) { |
| case 1: |
| var data8 = new byte[1]; |
| Marshal.Copy(buffer, data8, 0, 1); |
| machine.SystemBus.WriteByte(address, data8[0], this); |
| break; |
| case 2: |
| var data16 = new Int16[1]; |
| Marshal.Copy(buffer, data16, 0, 1); |
| machine.SystemBus.WriteWord(address, (ushort)data16[0], this); |
| break; |
| case 4: |
| var data32 = new Int32[1]; |
| Marshal.Copy(buffer, data32, 0, 1); |
| machine.SystemBus.WriteDoubleWord(address, (uint)data32[0], this); |
| break; |
| case 8: |
| var data64 = new Int64[1]; |
| Marshal.Copy(buffer, data64, 0, 1); |
| machine.SystemBus.WriteQuadWord(address, (ulong)data64[0], this); |
| break; |
| default: |
| var dataN = new byte[length]; |
| Marshal.Copy(buffer, dataN, 0, length); |
| machine.SystemBus.WriteBytes(dataN, address); |
| break; |
| } |
| return length; |
| } |
| |
| // ICPUWithHooks methods. |
| |
| public void AddHookAtInterruptBegin(Action<ulong> hook) { |
| throw new RecoverableException("Interrupt hooks not currently supported"); |
| } |
| public void AddHookAtInterruptEnd(Action<ulong> hook) { |
| throw new RecoverableException("Interrupt hooks not currently supported"); |
| } |
| public void AddHookAtWfiStateChange(Action<bool> hook) { |
| throw new RecoverableException("Wfi state change hook not currently supported"); |
| } |
| |
| public void AddHook(ulong addr, Action<ICpuSupportingGdb, ulong> hook) { |
| throw new RecoverableException("Hooks not currently supported"); |
| } |
| public void RemoveHook(ulong addr, Action<ICpuSupportingGdb, ulong> hook) { |
| } |
| public void RemoveHooksAt(ulong addr) { |
| } |
| public void RemoveAllHooks() { |
| } |
| |
| // ICpuSuportingGdb methods. |
| |
| public void EnterSingleStepModeSafely(HaltArguments args, bool? blocking = null) { |
| CheckCpuThreadId(); |
| ChangeExecutionModeToSingleStep(blocking); |
| UpdateHaltedState(); |
| InvokeHalted(args); |
| } |
| |
| public string GDBArchitecture { get { return "riscv:cheriot"; } } |
| |
| public List<GDBFeatureDescriptor> GDBFeatures { |
| get { |
| if (gdbFeatures.Any()) return gdbFeatures; |
| |
| // Populate the register map if it hasn't been done yet. |
| if (registerNamesMap.Count == 0) { |
| GetMpactRegisters(); |
| } |
| var cpuGroup = new GDBFeatureDescriptor("org.gnu.gdb.riscv.cpu"); |
| var intType = $"uint32"; |
| var c0_index = registerNamesMap["c0"]; |
| for (var i = 0u; i < 16; ++i) { |
| cpuGroup.Registers.Add(new GDBRegisterDescriptor((uint)c0_index + i, 32u, $"c{i}", intType, "general")); |
| } |
| var pcc_index = registerNamesMap["pcc"]; |
| cpuGroup.Registers.Add(new GDBRegisterDescriptor((uint)pcc_index, 32u, "pcc", "code_ptr", "general")); |
| gdbFeatures.Add(cpuGroup); |
| |
| return gdbFeatures; |
| } |
| } |
| |
| // End of ICpuSupportingGdb methods. |
| |
| private List<GDBFeatureDescriptor> gdbFeatures = new List<GDBFeatureDescriptor>(); |
| private UInt64 memoryBase; |
| private UInt64 memorySize; |
| private UInt64 revocationMemBase; |
| private UInt64 clintBase; |
| private Dictionary<Int32, CPURegister> registerMap = new Dictionary<Int32, CPURegister>(); |
| private Dictionary<string, Int32> registerNamesMap = new Dictionary<string, Int32>(); |
| private string cpuLibraryPath; |
| private bool cpuLibraryRegistered; |
| private string executableFile; |
| private string simulationFilePath; |
| private NativeBinder binder; |
| private readonly object nativeLock; |
| private readonly Int32 maxStringLen = 32; |
| private IntPtr value_ptr {get; set;} |
| private IntPtr error_ptr {get; set;} |
| private IntPtr reg_info_ptr {get; set;} |
| private IntPtr string_ptr {get; set;} |
| private ActionInt32 run_cb_delegate; |
| private FuncInt32UInt64IntPtrInt32 read_sysmem_delegate; |
| private FuncInt32UInt64IntPtrInt32 write_sysmem_delegate; |
| #pragma warning disable 649 |
| [Import(UseExceptionWrapper = false)] |
| private FuncInt32Int32FuncInt32UInt64IntPtrInt32FuncInt32UInt64IntPtrInt32 construct_with_sysbus; // (Int32 id, Int32 callback(Uint64, IntPtr, Int32), Int32 callback(Uint64, IntPtr, Int32)); |
| [Import(UseExceptionWrapper = false)] |
| private FuncInt32Int32Int32FuncInt32UInt64IntPtrInt32FuncInt32UInt64IntPtrInt32 connect_with_sysbus; // (Int32 id, Int32 callback(Uint64, IntPtr, Int32), Int32 callback(Uint64, IntPtr, Int32)); |
| [Import(UseExceptionWrapper = false)] |
| private ActionInt32 destruct; // (Int32 id); |
| [Import(UseExceptionWrapper = false)] |
| private ActionInt32 reset; // (Int32 id); |
| [Import(UseExceptionWrapper = false)] |
| private FuncInt32Int32 get_reg_info_size; // (Int32 id) |
| [Import(UseExceptionWrapper = false)] |
| private FuncInt32Int32Int32IntPtrIntPtr get_reg_info; // (Int32 id, IntPtr *name, IntPtr *struct); |
| [Import(UseExceptionWrapper = false)] |
| private FuncUInt64Int32StringIntPtr load_executable; // (Int32 id, String file_name); |
| [Import(UseExceptionWrapper = false)] |
| private FuncInt32Int32StringUInt64 load_image; // (Int32 id, String file_name, UInt64 address); |
| [Import(UseExceptionWrapper = false)] |
| private FuncUInt64Int32UInt64IntPtr step; // (Int32 id, UInt64 step, IntPtr *error); |
| [Import(UseExceptionWrapper = false)] |
| private FuncInt32Int32Int32IntPtr read_register; // (Int32 id, Int32 reg_id, IntPtr *value); |
| [Import(UseExceptionWrapper = false)] |
| private FuncInt32Int32Int32UInt64 write_register; // (Int32 id, Int32 reg_id, UInt64 value); |
| [Import(UseExceptionWrapper = false)] |
| private FuncInt32Int32UInt64IntPtrInt32 read_memory; // (Int32 id, UInt64 address, IntPtr buffer, Int32 size); |
| [Import(UseExceptionWrapper = false)] |
| private FuncInt32Int32UInt64IntPtrInt32 write_memory; // (Int32 id, UInt64 address, IntPtr buffer, Int32 size); |
| [Import(UseExceptionWrapper = false)] |
| private FuncInt32Int32ActionInt32 run; // (Int32 id, void callback(Int32) ) |
| [Import(UseExceptionWrapper = false)] |
| private FuncInt32Int32IntPtr halt; // (Int32 id, IntPtr *value) |
| // Set config. |
| [Import(UseExceptionWrapper = false)] |
| private FuncInt32Int32StringArrStringArrInt32 set_config; // Int32 set_config(Int32 id, string[] names string[] values, Int32 size); |
| [Import(UseExceptionWrapper = false)] |
| private FuncInt32Int32Int32Bool set_irq_value; // Int32 set_irq_value(Int32 id, Int32 irq_no, bool value); |
| |
| #pragma warning restore 649 |
| |
| private Int32 mpact_id {get; set;} |
| private ulong instructionsExecutedThisRound {get; set;} |
| private ulong totalExecutedInstructions {get; set;} |
| private const int PC_ID = 0x07b1; |
| } |
| } |
| |