blob: 557eb7663693b4756377786f634c72fc580fd4e6 [file] [log] [blame]
/*
* Copyright (c) 2018, 2019 ARM Limited
* All rights reserved
*
* The license below extends only to copyright in the software and shall
* not be construed as granting a license to any other intellectual
* property including but not limited to intellectual property relating
* to a hardware implementation of the functionality of the software
* licensed hereunder. You may use the software subject to the license
* terms below provided that you ensure that this notice is replicated
* unmodified and in its entirety in all distributions of the software,
* modified or unmodified, in source code or in binary form.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met: redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer;
* redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution;
* neither the name of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __ARCH_ARM_SEMIHOSTING_HH__
#define __ARCH_ARM_SEMIHOSTING_HH__
#include <cstdio>
#include <functional>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "arch/arm/regs/int.hh"
#include "arch/arm/utility.hh"
#include "cpu/thread_context.hh"
#include "mem/port_proxy.hh"
#include "sim/core.hh"
#include "sim/guest_abi.hh"
#include "sim/sim_object.hh"
namespace gem5
{
struct ArmSemihostingParams;
class SerialDevice;
/**
* Semihosting for AArch32 and AArch64
*
* This class implements the Arm semihosting interface. This interface
* allows baremetal code access service, such as IO, from the
* simulator. It is conceptually a simplified version of gem5's more
* general syscall emulation mode.
*
* Exits calls (SYS_EXIT, SYS_EXIT_EXTENDED) from the guest get
* translated into simualtion exits. Well-known exit codes are
* translated to messages on the form 'semi:ADP_.*' while unknown
* codes are returned in hex ('semi:0x..'). The subcode is reported in
* the gem5 exit event.
*/
class ArmSemihosting : public SimObject
{
public:
enum
{
// Standard ARM immediate values which trigger semihosting.
T32Imm = 0xAB,
A32Imm = 0x123456,
A64Imm = 0xF000,
// The immediate value which enables gem5 semihosting calls. Use the
// standard value for thumb.
Gem5Imm = 0x5D57
};
static PortProxy &portProxy(ThreadContext *tc);
struct AbiBase
{
template <typename Arg>
class StateBase
{
private:
Addr argPointer;
ByteOrder endian;
public:
StateBase(const ThreadContext *tc, Addr arg_pointer) :
argPointer(arg_pointer), endian(ArmISA::byteOrder(tc))
{}
/*
* These two methods are used to both read an argument or its
* address, and to move position on to the next location. Normally
* State would be more passive, but since it behaves almost the
* same no matter what the argument type is we can simplify and
* consolidate a little bit by centralizing these methods.
*/
// Return the address of an argument slot and move past it.
Addr
getAddr()
{
Addr addr = argPointer;
argPointer += sizeof(Arg);
return addr;
}
// Read the value in an argument slot and move past it.
Arg
get(ThreadContext *tc)
{
Arg arg = ArmSemihosting::portProxy(tc).read<Arg>(
argPointer, endian);
argPointer += sizeof(Arg);
return arg;
}
using ArgType = Arg;
};
};
struct Abi64 : public AbiBase
{
using UintPtr = uint64_t;
class State : public StateBase<uint64_t>
{
public:
// For 64 bit semihosting, the params are pointer to by X1.
explicit State(const ThreadContext *tc) :
StateBase<uint64_t>(tc, tc->getReg(ArmISA::int_reg::X1))
{}
};
};
struct Abi32 : public AbiBase
{
using UintPtr = uint32_t;
class State : public StateBase<uint64_t>
{
public:
// For 32 bit semihosting, the params are pointer to by R1.
explicit State(const ThreadContext *tc) :
StateBase<uint64_t>(tc, tc->getReg(ArmISA::int_reg::R1))
{}
};
};
// Use this argument type when you need to modify an argument in place.
// This will give you the address of the argument itself and the size of
// each argument slot, rather than the actual value of the argument.
struct InPlaceArg
{
Addr addr;
size_t size;
InPlaceArg(Addr _addr, size_t _size) : addr(_addr), size(_size) {}
// A helper function to read the argument since the guest ABI mechanism
// didn't do that for us.
uint64_t
read(ThreadContext *tc, ByteOrder endian)
{
auto &proxy = ArmSemihosting::portProxy(tc);
if (size == 8)
return proxy.read<uint64_t>(addr, endian);
else if (size == 4)
return proxy.read<uint32_t>(addr, endian);
else
panic("Unexpected semihosting argument size %d.", size);
}
// A helper function to write to the argument's slot in the params.
void
write(ThreadContext *tc, uint64_t val, ByteOrder endian)
{
auto &proxy = ArmSemihosting::portProxy(tc);
if (size == 8)
proxy.write<uint64_t>(addr, val, endian);
else if (size == 4)
proxy.write<uint32_t>(addr, val, endian);
else
panic("Unexpected semihosting argument size %d.", size);
}
};
enum Operation
{
SYS_OPEN = 0x01,
SYS_CLOSE = 0x02,
SYS_WRITEC = 0x03,
SYS_WRITE0 = 0x04,
SYS_WRITE = 0x05,
SYS_READ = 0x06,
SYS_READC = 0x07,
SYS_ISERROR = 0x08,
SYS_ISTTY = 0x09,
SYS_SEEK = 0x0A,
SYS_FLEN = 0x0C,
SYS_TMPNAM = 0x0D,
SYS_REMOVE = 0x0E,
SYS_RENAME = 0x0F,
SYS_CLOCK = 0x10,
SYS_TIME = 0x11,
SYS_SYSTEM = 0x12,
SYS_ERRNO = 0x13,
SYS_GET_CMDLINE = 0x15,
SYS_HEAPINFO = 0x16,
SYS_EXIT = 0x18,
SYS_EXIT_EXTENDED = 0x20,
SYS_ELAPSED = 0x30,
SYS_TICKFREQ = 0x31,
MaxStandardOp = 0xFF,
SYS_GEM5_PSEUDO_OP = 0x100
};
ArmSemihosting(const ArmSemihostingParams &p);
/** Perform an Arm Semihosting call from aarch64 code. */
bool call64(ThreadContext *tc, bool gem5_ops);
/** Perform an Arm Semihosting call from aarch32 code. */
bool call32(ThreadContext *tc, bool gem5_ops);
public: // SimObject and related interfaces
void serialize(CheckpointOut &cp) const override;
void unserialize(CheckpointIn &cp) override;
protected: // Configuration
const std::string cmdLine;
const Addr memReserve;
const Addr stackSize;
/**
* Base time when the simulation started. This is used to
* calculate the time of date when the guest call SYS_TIME.
*/
const time_t timeBase;
/** Number of bits to right shift gem5 ticks to fit in a uint32_t */
const unsigned tickShift;
protected: // Internal state
typedef uint64_t SemiErrno;
SemiErrno semiErrno;
protected: // File IO
/**
* Internal state for open files
*
* This class describes the internal state of a file opened
* through the semihosting interface.
*
* A file instance is normally created using one of the
* ArmSemihosting::FileBase::create() factory methods. These
* methods handle some the magic file names in the Arm Semihosting
* specification and instantiate the right implementation. For the
* same, when unserializing a checkpoint, the create method must
* be used to unserialize a new instance of a file descriptor.
*/
class FileBase : public Serializable
{
public:
FileBase(ArmSemihosting &_parent, const char *name, const char *_mode)
: parent(_parent), _name(name), mode(_mode) {}
virtual ~FileBase() {};
FileBase() = delete;
FileBase(FileBase &) = delete;
static std::unique_ptr<FileBase> create(
ArmSemihosting &parent, const std::string &fname,
const char *mode);
static std::unique_ptr<FileBase> create(
ArmSemihosting &parent, CheckpointIn &cp, const std::string &sec);
void serialize(CheckpointOut &cp) const override;
void unserialize(CheckpointIn &cp) override;
const std::string &fileName() { return _name; }
public:
/** @{
* Semihosting file IO interfaces
*
* These interfaces implement common IO functionality in the
* Semihosting interface.
*
* All functions return a negative value that corresponds to a
* UNIX errno value when they fail and >=0 on success.
*/
/**
* Open the the file.
*
* @return <0 on error (-errno), 0 on success.
*/
virtual int64_t open() { return 0; }
/**
* Close the file.
*
* @return <0 on error (-errno), 0 on success.
*/
virtual int64_t close() { return 0; }
/**
* Check if a file corresponds to a TTY device.
*
* @return True if the file is a TTY, false otherwise.
*/
virtual bool isTTY() const { return false; }
/**
* Read data from file.
*
* @return <0 on error (-errno), bytes read on success (0 for EOF).
*/
virtual int64_t read(uint8_t *buffer, uint64_t size);
/**
* Write data to file.
*
* @return <0 on error (-errno), bytes written on success.
*/
virtual int64_t write(const uint8_t *buffer, uint64_t size);
/**
* Seek to an absolute position in the file.
*
* @param pos Byte offset from start of file.
* @return <0 on error (-errno), 0 on success.
*/
virtual int64_t seek(uint64_t pos);
/**
* Get the length of a file in bytes.
*
* @return <0 on error (-errno), length on success
*/
virtual int64_t flen();
/** @} */
protected:
ArmSemihosting &parent;
std::string _name;
std::string mode;
};
/** Implementation of the ':semihosting-features' magic file. */
class FileFeatures : public FileBase
{
public:
FileFeatures(ArmSemihosting &_parent,
const char *name, const char *mode);
void serialize(CheckpointOut &cp) const override;
void unserialize(CheckpointIn &cp) override;
int64_t read(uint8_t *buffer, uint64_t size) override;
int64_t seek(uint64_t pos) override;
protected:
size_t pos;
};
class File : public FileBase
{
public:
File(ArmSemihosting &_parent, const char *name, const char *mode);
~File();
void serialize(CheckpointOut &cp) const override;
void unserialize(CheckpointIn &cp) override;
int64_t open() override { return openImpl(false); }
int64_t close() override;
bool isTTY() const override;
int64_t read(uint8_t *buffer, uint64_t size) override;
int64_t write(const uint8_t *buffer, uint64_t size) override;
int64_t seek(uint64_t pos) override;
int64_t flen() override;
protected:
int64_t openImpl(bool unserialize);
bool needClose() const { return !isTTY(); }
FILE *file;
};
std::string filesRootDir;
std::vector<std::unique_ptr<FileBase>> files;
using Handle = size_t;
FILE *stdin;
FILE *stdout;
FILE *stderr;
protected: // Helper functions
unsigned
calcTickShift() const
{
int msb = findMsbSet(sim_clock::Frequency);
return msb > 31 ? msb - 31 : 0;
}
uint64_t
semiTick(Tick tick) const
{
return tick >> tickShift;
}
void semiExit(uint64_t code, uint64_t subcode);
std::string readString(ThreadContext *tc, Addr ptr, size_t len);
public:
typedef std::pair<uint64_t, SemiErrno> RetErrno;
private:
static RetErrno
retError(SemiErrno e)
{
return RetErrno((uint64_t)-1, e);
}
static RetErrno
retOK(uint64_t r)
{
return RetErrno(r, 0);
}
/**
* Semihosting call information structure.
*
* This structure describes how a semi-hosting call is
* implemented. It contains debug information (e.g., the name of
* the call), and a way to invoke it in a particular context.
*/
struct SemiCall
{
/** Call name */
const char *name;
// A type for member functions implementing semihosting calls.
template <typename ...Args>
using Implementation =
RetErrno (ArmSemihosting::*)(ThreadContext *tc, Args... args);
// Since guest ABI doesn't know how to call member function pointers,
// this template builds a wrapper that takes care of that.
template <typename ...Args>
static inline std::function<RetErrno(ThreadContext *tc, Args... args)>
wrapImpl(ArmSemihosting *sh, Implementation<Args...> impl)
{
return [sh, impl](ThreadContext *tc, Args... args) {
return (sh->*impl)(tc, args...);
};
}
// A type for functions which dispatch semihosting calls through the
// guest ABI mechanism.
using Dispatcher =
std::function<RetErrno(ArmSemihosting *sh, ThreadContext *tc)>;
using Dumper = std::function<std::string(ThreadContext *tc)>;
// Dispatchers for 32 and 64 bits.
Dispatcher call32;
Dispatcher call64;
// Dumpers which print semihosting calls and their arguments.
Dumper dump32;
Dumper dump64;
// A function which builds a dispatcher for a semihosting call.
template <typename Abi, typename ...Args>
static inline Dispatcher
buildDispatcher(Implementation<Args...> impl)
{
// This lambda is the dispatcher we're building.
return [impl](ArmSemihosting *sh, ThreadContext *tc) {
auto wrapper = wrapImpl(sh, impl);
return invokeSimcall<Abi>(tc, wrapper);
};
}
// A function which builds a dumper for a semihosting call.
template <typename Abi, typename ...Args>
static inline Dumper
buildDumper(const char *name, Implementation<Args...> impl)
{
// This lambda is the dumper we're building.
return [name](ThreadContext *tc) -> std::string {
return dumpSimcall<Abi, RetErrno, Args...>(name, tc);
};
}
// When there's one implementation, use it for both 32 and 64 bits.
template <typename ...Args>
SemiCall(const char *_name, Implementation<Args...> common) :
name(_name), call32(buildDispatcher<Abi32>(common)),
call64(buildDispatcher<Abi64>(common)),
dump32(buildDumper<Abi32>(_name, common)),
dump64(buildDumper<Abi64>(_name, common))
{}
// When there are two, use one for 32 bits and one for 64 bits.
template <typename ...Args32, typename ...Args64>
SemiCall(const char *_name, Implementation<Args32...> impl32,
Implementation<Args64...> impl64) :
name(_name), call32(buildDispatcher<Abi32>(impl32)),
call64(buildDispatcher<Abi64>(impl64)),
dump32(buildDumper<Abi32>(_name, impl32)),
dump64(buildDumper<Abi64>(_name, impl64))
{}
};
RetErrno callOpen(ThreadContext *tc, const Addr name_base,
int fmode, size_t name_size);
RetErrno callClose(ThreadContext *tc, Handle handle);
RetErrno callWriteC(ThreadContext *tc, InPlaceArg c);
RetErrno callWrite0(ThreadContext *tc, InPlaceArg str);
RetErrno callWrite(ThreadContext *tc, Handle handle,
Addr buffer, size_t size);
RetErrno callRead(ThreadContext *tc, Handle handle,
Addr buffer, size_t size);
RetErrno callReadC(ThreadContext *tc);
RetErrno callIsError(ThreadContext *tc, int64_t status);
RetErrno callIsTTY(ThreadContext *tc, Handle handle);
RetErrno callSeek(ThreadContext *tc, Handle handle, uint64_t pos);
RetErrno callFLen(ThreadContext *tc, Handle handle);
RetErrno callTmpNam(ThreadContext *tc, Addr buffer,
uint64_t id, size_t size);
RetErrno callRemove(ThreadContext *tc, Addr name_base, size_t name_size);
RetErrno callRename(ThreadContext *tc, Addr from_addr, size_t from_size,
Addr to_addr, size_t to_size);
RetErrno callClock(ThreadContext *tc);
RetErrno callTime(ThreadContext *tc);
RetErrno callSystem(ThreadContext *tc, Addr cmd_addr, size_t cmd_size);
RetErrno callErrno(ThreadContext *tc);
RetErrno callGetCmdLine(ThreadContext *tc, Addr addr, InPlaceArg size_arg);
void gatherHeapInfo(ThreadContext *tc, bool aarch64,
Addr &heap_base, Addr &heap_limit,
Addr &stack_base, Addr &stack_limit);
RetErrno callHeapInfo32(ThreadContext *tc, Addr block_addr);
RetErrno callHeapInfo64(ThreadContext *tc, Addr block_addr);
RetErrno callExit32(ThreadContext *tc, InPlaceArg code);
RetErrno callExit64(ThreadContext *tc, uint64_t code, uint64_t subcode);
RetErrno callExitExtended(ThreadContext *tc, uint64_t code,
uint64_t subcode);
RetErrno callElapsed32(ThreadContext *tc, InPlaceArg low, InPlaceArg high);
RetErrno callElapsed64(ThreadContext *tc, InPlaceArg ticks);
RetErrno callTickFreq(ThreadContext *tc);
RetErrno callGem5PseudoOp32(ThreadContext *tc, uint32_t encoded_func);
RetErrno callGem5PseudoOp64(ThreadContext *tc, uint64_t encoded_func);
template <typename Abi>
void
unrecognizedCall(ThreadContext *tc, const char *format, uint64_t op)
{
warn(format, op);
std::function<RetErrno(ThreadContext *tc)> retErr =
[](ThreadContext *tc) { return retError(EINVAL); };
invokeSimcall<Abi>(tc, retErr);
}
static FILE *getSTDIO(const char *stream_name,
const std::string &name, const char *mode);
static const std::map<uint32_t, SemiCall> calls;
static const std::vector<const char *> fmodes;
static const std::map<uint64_t, const char *> exitCodes;
static const std::vector<uint8_t> features;
static const std::map<const std::string, FILE *> stdioMap;
// used in callTmpNam() to deterministically generate a temp filename
uint16_t tmpNameIndex = 0;
};
std::ostream &operator << (
std::ostream &os, const ArmSemihosting::InPlaceArg &ipa);
namespace guest_abi
{
template <typename Arg>
struct Argument<ArmSemihosting::Abi64, Arg,
typename std::enable_if_t<std::is_integral_v<Arg>>>
{
static Arg
get(ThreadContext *tc, ArmSemihosting::Abi64::State &state)
{
return state.get(tc);
}
};
template <typename Arg>
struct Argument<ArmSemihosting::Abi32, Arg,
typename std::enable_if_t<std::is_integral_v<Arg>>>
{
static Arg
get(ThreadContext *tc, ArmSemihosting::Abi32::State &state)
{
if (std::is_signed_v<Arg>)
return sext<32>(state.get(tc));
else
return state.get(tc);
}
};
template <typename Abi>
struct Argument<Abi, ArmSemihosting::InPlaceArg, typename std::enable_if_t<
std::is_base_of_v<ArmSemihosting::AbiBase, Abi>>>
{
static ArmSemihosting::InPlaceArg
get(ThreadContext *tc, typename Abi::State &state)
{
return ArmSemihosting::InPlaceArg(
state.getAddr(), sizeof(typename Abi::State::ArgType));
}
};
template <>
struct Result<ArmSemihosting::Abi32, ArmSemihosting::RetErrno>
{
static void
store(ThreadContext *tc, const ArmSemihosting::RetErrno &err)
{
tc->setReg(ArmISA::int_reg::R0, err.first);
}
};
template <>
struct Result<ArmSemihosting::Abi64, ArmSemihosting::RetErrno>
{
static void
store(ThreadContext *tc, const ArmSemihosting::RetErrno &err)
{
tc->setReg(ArmISA::int_reg::X0, err.first);
}
};
} // namespace guest_abi
} // namespace gem5
#endif // __ARCH_ARM_SEMIHOSTING_HH__