blob: 20161b4a7390c1115d85c902184808cc040fa925 [file] [log] [blame]
//
// 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;
}
}