blob: b13690005c451b989ff488e159c6668fc8e5918c [file] [log] [blame]
//
// Copyright (c) 2023 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.Exceptions;
using Antmicro.Renode.Logging;
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;
using Machine = Antmicro.Renode.Core.Machine;
namespace Antmicro.Renode.Peripherals.CPU
{
public class KelvinCPU : BaseCPU, ICPUWithRegisters, 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 KelvinCPU(uint id, string cpuType, Machine machine,
Endianess endianess = Endianess.LittleEndian,
CpuBitness bitness = CpuBitness.Bits32)
: base(id, cpuType, machine, endianess, bitness)
{
error_ptr = Marshal.AllocHGlobal(4);
value_ptr = Marshal.AllocHGlobal(8);
reg_info_ptr = Marshal.AllocHGlobal(Marshal.SizeOf<RegInfo>());
string_ptr = Marshal.AllocHGlobal(maxStringLen);
memoryOffset = 0;
}
~KelvinCPU()
{
Marshal.FreeHGlobal(error_ptr);
Marshal.FreeHGlobal(value_ptr);
Marshal.FreeHGlobal(reg_info_ptr);
Marshal.FreeHGlobal(string_ptr);
}
public override void Start()
{
base.Start();
if (kelvin_id < 0)
{
LogAndThrowRE("Failed to create kelvin sim instance");
return;
}
Int32 result = load_image(kelvin_id, executableFile, memoryOffset);
if (result < 0)
{
LogAndThrowRE("Failed to load executable image");
}
this.NoisyLog("Load executable image " + executableFile);
// Start with halted CPU
IsHalted = true;
PC = 0;
}
public override void Reset()
{
base.Reset();
instructionsExecutedThisRound = 0;
totalExecutedInstructions = 0;
// Reset simulator.
var result = reset(kelvin_id);
if (result < 0)
{
LogAndThrowRE("Failed to reset kelvin");
}
this.NoisyLog("Reset kelvin");
}
public override void Dispose()
{
base.Dispose();
// Cleanup: simulator and any unmanaged resources.
destruct(kelvin_id);
}
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(kelvin_id, PC_ID, value_ptr);
// TODO(hcindyl): Check different error types.
if (error < 0)
{
this.ErrorLog("Failed to read PC");
}
Int64 value = Marshal.ReadInt64(value_ptr);
return Convert.ToUInt64(value);
}
set
{
Int32 error = write_register(kelvin_id, PC_ID, value);
// TODO(hcindyl): Check different error types.
if (error < 0)
{
this.NoisyLog("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(kelvin_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;
}
if (numberOfInstructionsToExecute > instructionsExecutedThisRound &&
result == ExecutionResult.Ok)
{
// TODO(hcindyl): Change to control block callbacks.
this.NoisyLog("CPU finish excution.");
this.NoisyLog("Total instruction count: {0}",
totalExecutedInstructions);
IsHalted = true;
}
return result;
}
// ICPUWithRegisters methods implementations.
public void SetRegisterUnsafe(int register, RegisterValue value)
{
var status = write_register((Int32)kelvin_id, (Int32)register, (UInt64)value);
if (status < 0)
{
LogAndThrowRE("Failed to write register " + register);
}
}
public RegisterValue GetRegisterUnsafe(int register)
{
var status = read_register(kelvin_id, (UInt32)register, value_ptr);
if (status < 0)
{
LogAndThrowRE("Failed to read register " + register);
}
Int64 value = Marshal.ReadInt64(value_ptr);
return (UInt64)value;
}
public IEnumerable<CPURegister> GetRegisters()
{
if (registerMap.Count == 0)
{
Int32 num_regs = get_reg_info_size(kelvin_id);
for (Int32 i = 0; i < num_regs; i++)
{
Int32 result = get_reg_info(kelvin_id, i, string_ptr, reg_info_ptr);
if (result < 0)
{
this.ErrorLog("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);
}
}
return registerMap.Values.OrderBy(x => x.Index);
}
public string[,] GetRegistersValues()
{
if (registerMap.Count == 0)
{
GetRegisters();
}
var result = new Dictionary<string, ulong>();
foreach (var reg in registerNamesMap)
{
var status = read_register(kelvin_id, (UInt32)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();
}
public string CpuLibraryPath
{
get
{
return cpuLibraryPath;
}
set
{
if (!String.IsNullOrWhiteSpace(value))
{
cpuLibraryPath = value;
try
{
binder = new NativeBinder(this, cpuLibraryPath);
}
catch (System.Exception e)
{
LogAndThrowRE("Failed to load CPU library: " + e.Message);
}
// Simulator initialization code goes here.
kelvin_id = construct(maxStringLen);
if (kelvin_id < 0)
{
LogAndThrowRE("Failed to create simulator instance");
return;
}
this.NoisyLog("Construct kelvin");
}
else
{
return;
}
}
}
public string ExecutableFile
{
get
{
return executableFile;
}
set
{
if (!String.IsNullOrWhiteSpace(value))
{
executableFile = value;
}
else
{
return;
}
}
}
public UInt64 MemoryOffset
{
get
{
return memoryOffset;
}
set
{
memoryOffset = value;
}
}
private void LogAndThrowRE(string info)
{
this.Log(LogLevel.Error, info);
throw new RecoverableException(info);
}
private Dictionary<Int32, CPURegister> registerMap = new Dictionary<Int32, CPURegister>();
private Dictionary<string, Int32> registerNamesMap = new Dictionary<string, Int32>();
protected string cpuLibraryPath;
protected string executableFile;
protected UInt64 memoryOffset;
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; }
#pragma warning disable 649
[Import(UseExceptionWrapper = false)]
private FuncInt32Int32 construct; // Int32(Int32 max_string_lenth)
[Import(UseExceptionWrapper = false)]
private FuncInt32Int32 destruct; // Int32(Int32 id);
[Import(UseExceptionWrapper = false)]
private FuncInt32Int32 reset; // Int32(Int32 id);
[Import(UseExceptionWrapper = false)]
private FuncInt32Int32 get_reg_info_size; // Int32(Int32 id)
[Import(UseExceptionWrapper = false)]
private FuncInt32Int32Int32IntPtrIntPtr get_reg_info; // Int32(Int32 id, Int32 index, char *name, IntPtr *info);
[Import(UseExceptionWrapper = false)]
private FuncInt32Int32StringUInt64 load_image; // Int32(Int32 id, String file_name, Int64 address);
[Import(UseExceptionWrapper = false)]
private FuncUInt64Int32UInt64IntPtr step; // UInt64(Int32 id, UInt64 step, IntPtr *status);
[Import(UseExceptionWrapper = false)]
private FuncInt32Int32UInt32IntPtr read_register; // Int32(Int32 id, UInt32 reg_id, IntPtr *value);
[Import(UseExceptionWrapper = false)]
private FuncInt32Int32Int32UInt64 write_register; // Int32(Int32 id, Int32 reg_id, UInt64 value);
#pragma warning restore 649
private Int32 kelvin_id { get; set; }
private ulong instructionsExecutedThisRound { get; set; }
private ulong totalExecutedInstructions { get; set; }
private const int PC_ID = 0x07b1; // PC magic ID
}
}