diff --git a/configs/example/riscv/fs_linux.py b/configs/example/riscv/fs_linux.py index c0643c883d..246a1b24d9 100644 --- a/configs/example/riscv/fs_linux.py +++ b/configs/example/riscv/fs_linux.py @@ -145,7 +145,17 @@ Options.addFSOptions(parser) parser.add_argument( "--virtio-rng", action="store_true", help="Enable VirtIORng device" ) - +parser.add_argument( + "--semihosting", + action="store_true", + help="Enable the RISC-V semihosting interface", +) +parser.add_argument( + "--semihosting-root", + default="/some/invalid/root/directory", + type=str, + help="The root directory for files exposed to semihosting", +) # ---------------------------- Parse Options --------------------------- # args = parser.parse_args() @@ -168,11 +178,17 @@ mdesc = SysConfig( system.mem_mode = mem_mode system.mem_ranges = [AddrRange(start=0x80000000, size=mdesc.mem())] +workload_args = dict() +if args.semihosting: + workload_args["semihosting"] = RiscvSemihosting( + files_root_dir=args.semihosting_root, + cmd_line=args.kernel, + ) if args.bare_metal: - system.workload = RiscvBareMetal() + system.workload = RiscvBareMetal(**workload_args) system.workload.bootloader = args.kernel else: - system.workload = RiscvLinux() + system.workload = RiscvLinux(**workload_args) system.workload.object_file = args.kernel system.iobus = IOXBar() diff --git a/src/arch/arm/ArmSemihosting.py b/src/arch/arm/ArmSemihosting.py index 8c8375e208..ffc285fc8f 100644 --- a/src/arch/arm/ArmSemihosting.py +++ b/src/arch/arm/ArmSemihosting.py @@ -33,36 +33,10 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from m5.objects.Serial import SerialDevice -from m5.objects.Terminal import Terminal -from m5.params import * -from m5.SimObject import * +from m5.objects.BaseSemihosting import BaseSemihosting -class ArmSemihosting(SimObject): +class ArmSemihosting(BaseSemihosting): type = "ArmSemihosting" cxx_header = "arch/arm/semihosting.hh" cxx_class = "gem5::ArmSemihosting" - - cmd_line = Param.String("", "Command line to report to guest") - stdin = Param.String("stdin", "Standard input (stdin for gem5's terminal)") - stdout = Param.String( - "stdout", "Standard output (stdout for gem5's terminal)" - ) - stderr = Param.String( - "stderr", "Standard error (stderr for gem5's terminal)" - ) - files_root_dir = Param.String( - "", "Host root directory for files handled by Semihosting" - ) - - mem_reserve = Param.MemorySize( - "32MiB", - "Amount of memory to reserve at the start of the address map. This " - "memory won't be used by the heap reported to an application.", - ) - stack_size = Param.MemorySize("32MiB", "Application stack size") - - time = Param.Time( - "01/01/2009", "System time to use ('Now' for actual time)" - ) diff --git a/src/arch/arm/SConscript b/src/arch/arm/SConscript index b9f8d6e6b5..47d0b54348 100644 --- a/src/arch/arm/SConscript +++ b/src/arch/arm/SConscript @@ -134,7 +134,6 @@ SimObject('ArmCPU.py', sim_objects=[], tags='arm isa') DebugFlag('Arm', tags='arm isa') DebugFlag('ArmTme', 'Transactional Memory Extension', tags='arm isa') -DebugFlag('Semihosting', tags='arm isa') DebugFlag('PMUVerbose', "Performance Monitor", tags='arm isa') # Add files generated by the ISA description. diff --git a/src/arch/arm/semihosting.cc b/src/arch/arm/semihosting.cc index 4ce52e8741..4517e5c82c 100644 --- a/src/arch/arm/semihosting.cc +++ b/src/arch/arm/semihosting.cc @@ -43,7 +43,6 @@ #include #include "arch/arm/utility.hh" -#include "base/logging.hh" #include "base/output.hh" #include "base/time.hh" #include "debug/Semihosting.hh" @@ -55,7 +54,6 @@ #include "sim/byteswap.hh" #include "sim/full_system.hh" #include "sim/pseudo_inst.hh" -#include "sim/sim_exit.hh" #include "sim/system.hh" namespace gem5 @@ -98,73 +96,8 @@ const std::map ArmSemihosting::calls{ &ArmSemihosting::callGem5PseudoOp64 } }, }; -const std::vector ArmSemihosting::fmodes{ - "r", "rb", "r+", "r+b", - "w", "wb", "w+", "w+b", - "a", "ab", "a+", "a+b", -}; - -const std::map ArmSemihosting::exitCodes{ - { 0x20000, "semi:ADP_Stopped_BranchThroughZero" }, - { 0x20001, "semi:ADP_Stopped_UndefinedInstr" }, - { 0x20002, "semi:ADP_Stopped_SoftwareInterrupt" }, - { 0x20003, "semi:ADP_Stopped_PrefetchAbort" }, - { 0x20004, "semi:ADP_Stopped_DataAbort" }, - { 0x20005, "semi:ADP_Stopped_AddressException" }, - { 0x20006, "semi:ADP_Stopped_IRQ" }, - { 0x20007, "semi:ADP_Stopped_FIQ" }, - - { 0x20020, "semi:ADP_Stopped_BreakPoint" }, - { 0x20021, "semi:ADP_Stopped_WatchPoint" }, - { 0x20022, "semi:ADP_Stopped_StepComplete" }, - { 0x20023, "semi:ADP_Stopped_RunTimeErrorUnknown" }, - { 0x20024, "semi:ADP_Stopped_InternalError" }, - { 0x20025, "semi:ADP_Stopped_UserInterruption" }, - { 0x20026, "semi:ADP_Stopped_ApplicationExit" }, - { 0x20027, "semi:ADP_Stopped_StackOverflow" }, - { 0x20028, "semi:ADP_Stopped_DivisionByZero" }, - { 0x20029, "semi:ADP_Stopped_DivisionByZero" }, -}; - - -const std::vector ArmSemihosting::features{ - 0x53, 0x48, 0x46, 0x42, // Magic - 0x3, // EXT_EXIT_EXTENDED, EXT_STDOUT_STDERR -}; - -const std::map ArmSemihosting::stdioMap{ - {"cin", ::stdin}, - {"stdin", ::stdin}, - {"cout", ::stdout}, - {"stdout", ::stdout}, - {"cerr", ::stderr}, - {"stderr", ::stderr}, -}; - ArmSemihosting::ArmSemihosting(const ArmSemihostingParams &p) - : SimObject(p), - cmdLine(p.cmd_line), - memReserve(p.mem_reserve), - stackSize(p.stack_size), - timeBase([p]{ struct tm t = p.time; return mkutctime(&t); }()), - tickShift(calcTickShift()), - semiErrno(0), - filesRootDir(!p.files_root_dir.empty() && - p.files_root_dir.back() != '/' ? - p.files_root_dir + '/' : p.files_root_dir), - stdin(getSTDIO("stdin", p.stdin, "r")), - stdout(getSTDIO("stdout", p.stdout, "w")), - stderr(p.stderr == p.stdout ? - stdout : getSTDIO("stderr", p.stderr, "w")) -{ - // Create an empty place-holder file for position 0 as semi-hosting - // calls typically expect non-zero file handles. - files.push_back(nullptr); - - if (tickShift > 0) - inform("Semihosting: Shifting elapsed ticks by %i bits.", - tickShift); -} + : BaseSemihosting(p) {} bool ArmSemihosting::call64(ThreadContext *tc, bool gem5_ops) @@ -218,35 +151,8 @@ ArmSemihosting::call32(ThreadContext *tc, bool gem5_ops) return true; } -void -ArmSemihosting::serialize(CheckpointOut &cp) const -{ - SERIALIZE_SCALAR(semiErrno); - - paramOut(cp, "num_files", files.size()); - for (int i = 0; i < files.size(); i++) { - // File closed? - if (!files[i]) - continue; - - files[i]->serializeSection(cp, csprintf("file%i", i)); - } -} - -void -ArmSemihosting::unserialize(CheckpointIn &cp) -{ - UNSERIALIZE_SCALAR(semiErrno); - - size_t num_files; - paramIn(cp, "num_files", num_files); - files.resize(num_files); - for (int i = 0; i < num_files; i++) - files[i] = FileBase::create(*this, cp, csprintf("file%i", i)); -} - PortProxy & -ArmSemihosting::portProxy(ThreadContext *tc) +ArmSemihosting::portProxyImpl(ThreadContext *tc) { static std::unique_ptr port_proxy_s; static std::unique_ptr port_proxy_ns; @@ -280,414 +186,6 @@ ArmSemihosting::portProxy(ThreadContext *tc) } } - -std::string -ArmSemihosting::readString(ThreadContext *tc, Addr ptr, size_t len) -{ - std::vector buf(len + 1); - - buf[len] = '\0'; - portProxy(tc).readBlob(ptr, buf.data(), len); - - return std::string(buf.data()); -} - -ArmSemihosting::RetErrno -ArmSemihosting::callOpen(ThreadContext *tc, const Addr name_base, - int fmode, size_t name_size) -{ - const char *mode = fmode < fmodes.size() ? fmodes[fmode] : nullptr; - - DPRINTF(Semihosting, "Semihosting SYS_OPEN(0x%x, %i[%s], %i)\n", - name_base, fmode, mode ? mode : "-", name_size); - if (!mode || !name_base) - return retError(EINVAL); - - std::string fname = readString(tc, name_base, name_size); - if (!fname.empty() && fname.front() != '/') - fname = filesRootDir + fname; - - std::unique_ptr file = - FileBase::create(*this, fname, mode); - int64_t ret = file->open(); - DPRINTF(Semihosting, "Semihosting SYS_OPEN(\"%s\", %i[%s]): %i\n", - fname, fmode, mode, ret); - if (ret < 0) { - return retError(-ret); - } else { - files.push_back(std::move(file)); - return retOK(files.size() - 1); - } -} - -ArmSemihosting::RetErrno -ArmSemihosting::callClose(ThreadContext *tc, Handle handle) -{ - if (handle > files.size()) { - DPRINTF(Semihosting, "Semihosting SYS_CLOSE(%i): Illegal file\n"); - return retError(EBADF); - } - - std::unique_ptr &file = files[handle]; - int64_t error = file->close(); - DPRINTF(Semihosting, "Semihosting SYS_CLOSE(%i[%s]): %i\n", - handle, file->fileName(), error); - if (error < 0) { - return retError(-error); - } else { - // Zap the pointer and free the entry in the file table as - // well. - files[handle].reset(); - return retOK(0); - } -} - -ArmSemihosting::RetErrno -ArmSemihosting::callWriteC(ThreadContext *tc, InPlaceArg arg) -{ - const char c = portProxy(tc).read(arg.addr); - - DPRINTF(Semihosting, "Semihosting SYS_WRITEC('%c')\n", c); - std::cout.put(c); - - return retOK(0); -} - -ArmSemihosting::RetErrno -ArmSemihosting::callWrite0(ThreadContext *tc, InPlaceArg arg) -{ - DPRINTF(Semihosting, "Semihosting SYS_WRITE0(...)\n"); - PortProxy &proxy = portProxy(tc); - std::string str; - proxy.readString(str, arg.addr); - std::cout.write(str.c_str(), str.size()); - - return retOK(0); -} - -ArmSemihosting::RetErrno -ArmSemihosting::callWrite(ThreadContext *tc, Handle handle, Addr addr, - size_t size) -{ - if (handle > files.size() || !files[handle]) - return RetErrno(size, EBADF); - - std::vector buffer(size); - portProxy(tc).readBlob(addr, buffer.data(), buffer.size()); - - int64_t ret = files[handle]->write(buffer.data(), buffer.size()); - if (ret < 0) { - // No bytes written (we're returning the number of bytes not - // written) - return RetErrno(size, -ret); - } else { - // Return the number of bytes not written - return RetErrno(size - ret, 0); - } -} - -ArmSemihosting::RetErrno -ArmSemihosting::callRead(ThreadContext *tc, Handle handle, Addr addr, - size_t size) -{ - if (handle > files.size() || !files[handle]) - return RetErrno(size, EBADF); - - std::vector buffer(size); - int64_t ret = files[handle]->read(buffer.data(), buffer.size()); - if (ret < 0) { - return RetErrno(size, -ret); - } else { - panic_if(ret > buffer.size(), "Read longer than buffer size."); - - portProxy(tc).writeBlob(addr, buffer.data(), ret); - - // Return the number of bytes not written - return retOK(size - ret); - } -} - -ArmSemihosting::RetErrno -ArmSemihosting::callReadC(ThreadContext *tc) -{ - return retOK((char)std::cin.get()); -} - -ArmSemihosting::RetErrno -ArmSemihosting::callIsError(ThreadContext *tc, int64_t status) -{ - return retOK(status < 0 ? 1 : 0); -} - -ArmSemihosting::RetErrno -ArmSemihosting::callIsTTY(ThreadContext *tc, Handle handle) -{ - if (handle > files.size() || !files[handle]) - return retError(EBADF); - - int64_t ret = files[handle]->isTTY(); - if (ret < 0) { - return retError(-ret); - } else { - return retOK(ret ? 1 : 0); - } -} - -ArmSemihosting::RetErrno -ArmSemihosting::callSeek(ThreadContext *tc, Handle handle, uint64_t pos) -{ - if (handle > files.size() || !files[handle]) - return retError(EBADF); - - int64_t ret = files[handle]->seek(pos); - if (ret < 0) { - return retError(-ret); - } else { - return retOK(0); - } -} - -ArmSemihosting::RetErrno -ArmSemihosting::callFLen(ThreadContext *tc, Handle handle) -{ - if (handle > files.size() || !files[handle]) - return retError(EBADF); - - int64_t ret = files[handle]->flen(); - if (ret < 0) { - return retError(-ret); - } else { - return retOK(ret); - } -} - -ArmSemihosting::RetErrno -ArmSemihosting::callTmpNam(ThreadContext *tc, Addr addr, uint64_t id, - size_t size) -{ - std::string path = ""; - int64_t unlink_call_ret = 0; - - do { - path = simout.resolve(csprintf("%s.tmp%05i", name(), tmpNameIndex++)); - // remove the (potentially existing) file of the given path - unlink_call_ret = unlink(path.c_str()); - // if the file is busy, find another name - } while ((unlink_call_ret < 0) && (errno == EBUSY)); - - const size_t path_len = path.length(); - if (path_len >= size) - return retError(ENOSPC); - - portProxy(tc).writeBlob(addr, path.c_str(), path_len + 1); - return retOK(0); -} - -ArmSemihosting::RetErrno -ArmSemihosting::callRemove(ThreadContext *tc, Addr name_base, size_t name_size) -{ - std::string fname = readString(tc, name_base, name_size); - - if (remove(fname.c_str()) != 0) { - return retError(errno); - } else { - return retOK(0); - } -} - -ArmSemihosting::RetErrno -ArmSemihosting::callRename(ThreadContext *tc, Addr from_addr, size_t from_size, - Addr to_addr, size_t to_size) -{ - std::string from = readString(tc, from_addr, from_size); - std::string to = readString(tc, to_addr, to_size); - - if (rename(from.c_str(), to.c_str()) != 0) { - return retError(errno); - } else { - return retOK(0); - } -} - -ArmSemihosting::RetErrno -ArmSemihosting::callClock(ThreadContext *tc) -{ - return retOK(curTick() / (sim_clock::as_int::s / 100)); -} - -ArmSemihosting::RetErrno -ArmSemihosting::callTime(ThreadContext *tc) -{ - return retOK(timeBase + round(curTick() / sim_clock::as_float::s)); -} - -ArmSemihosting::RetErrno -ArmSemihosting::callSystem(ThreadContext *tc, Addr cmd_addr, size_t cmd_size) -{ - const std::string cmd = readString(tc, cmd_addr, cmd_size); - warn("Semihosting: SYS_SYSTEM not implemented. Guest tried to run: %s\n", - cmd); - return retError(EINVAL); - -} - -ArmSemihosting::RetErrno -ArmSemihosting::callErrno(ThreadContext *tc) -{ - // Preserve errno by returning it in errno as well. - return RetErrno(semiErrno, semiErrno); -} - -ArmSemihosting::RetErrno -ArmSemihosting::callGetCmdLine(ThreadContext *tc, Addr addr, - InPlaceArg size_arg) -{ - PortProxy &proxy = portProxy(tc); - ByteOrder endian = ArmISA::byteOrder(tc); - size_t size = size_arg.read(tc, endian); - - if (cmdLine.size() + 1 < size) { - proxy.writeBlob(addr, cmdLine.c_str(), cmdLine.size() + 1); - size_arg.write(tc, cmdLine.size(), endian); - return retOK(0); - } else { - return retError(0); - } -} - -void -ArmSemihosting::gatherHeapInfo(ThreadContext *tc, bool aarch64, - Addr &heap_base, Addr &heap_limit, - Addr &stack_base, Addr &stack_limit) -{ - const memory::PhysicalMemory &phys = tc->getSystemPtr()->getPhysMem(); - const AddrRangeList memories = phys.getConfAddrRanges(); - fatal_if(memories.size() < 1, "No memories reported from System"); - warn_if(memories.size() > 1, "Multiple physical memory ranges available. " - "Using first range heap/stack."); - const AddrRange mem = *memories.begin(); - const Addr mem_start = mem.start() + memReserve; - Addr mem_end = mem.end(); - - // Make sure that 32-bit guests can access their memory. - if (!aarch64) { - const Addr phys_max = (1ULL << 32) - 1; - panic_if(mem_start > phys_max, - "Physical memory out of range for a 32-bit guest."); - if (mem_end > phys_max) { - warn("Some physical memory out of range for a 32-bit guest."); - mem_end = phys_max; - } - } - - fatal_if(mem_start + stackSize >= mem_end, - "Physical memory too small to fit desired stack and a heap."); - - heap_base = mem_start; - heap_limit = mem_end - stackSize + 1; - stack_base = (mem_end + 1) & ~0x7ULL; // 8 byte stack alignment - stack_limit = heap_limit; - - inform("Reporting heap/stack info to guest:\n" - "\tHeap base: 0x%x\n" - "\tHeap limit: 0x%x\n" - "\tStack base: 0x%x\n" - "\tStack limit: 0x%x\n", - heap_base, heap_limit, stack_base, stack_limit); -} - -ArmSemihosting::RetErrno -ArmSemihosting::callHeapInfo32(ThreadContext *tc, Addr block_addr) -{ - uint64_t heap_base, heap_limit, stack_base, stack_limit; - gatherHeapInfo(tc, false, heap_base, heap_limit, stack_base, stack_limit); - - std::array block = {{ - (uint32_t)heap_base, (uint32_t)heap_limit, - (uint32_t)stack_base, (uint32_t)stack_limit - }}; - portProxy(tc).write(block_addr, block, ArmISA::byteOrder(tc)); - - return retOK(0); -} - -ArmSemihosting::RetErrno -ArmSemihosting::callHeapInfo64(ThreadContext *tc, Addr block_addr) -{ - uint64_t heap_base, heap_limit, stack_base, stack_limit; - gatherHeapInfo(tc, true, heap_base, heap_limit, stack_base, stack_limit); - - std::array block = {{ - heap_base, heap_limit, stack_base, stack_limit - }}; - portProxy(tc).write(block_addr, block, ArmISA::byteOrder(tc)); - - return retOK(0); -} - -ArmSemihosting::RetErrno -ArmSemihosting::callExit32(ThreadContext *tc, InPlaceArg code) -{ - semiExit(code.addr, 0); - return retOK(0); -} - -ArmSemihosting::RetErrno -ArmSemihosting::callExit64(ThreadContext *tc, uint64_t code, uint64_t subcode) -{ - semiExit(code, subcode); - return retOK(0); -} - -ArmSemihosting::RetErrno -ArmSemihosting::callExitExtended(ThreadContext *tc, - uint64_t code, uint64_t subcode) -{ - semiExit(code, subcode); - return retOK(0); -} - -void -ArmSemihosting::semiExit(uint64_t code, uint64_t subcode) -{ - auto it = exitCodes.find(code); - if (it != exitCodes.end()) { - exitSimLoop(it->second, subcode); - } else { - exitSimLoop(csprintf("semi:0x%x", code), subcode); - } -} - - -ArmSemihosting::RetErrno -ArmSemihosting::callElapsed32(ThreadContext *tc, InPlaceArg low, - InPlaceArg high) -{ - ByteOrder endian = ArmISA::byteOrder(tc); - uint64_t tick = semiTick(curTick()); - - low.write(tc, tick, endian); - high.write(tc, tick >> 32, endian); - - return retOK(0); -} - - -ArmSemihosting::RetErrno -ArmSemihosting::callElapsed64(ThreadContext *tc, InPlaceArg ticks) -{ - ticks.write(tc, semiTick(curTick()), ArmISA::byteOrder(tc)); - return retOK(0); -} - - -ArmSemihosting::RetErrno -ArmSemihosting::callTickFreq(ThreadContext *tc) -{ - return retOK(semiTick(sim_clock::Frequency)); -} - - struct SemiPseudoAbi32 : public ArmSemihosting::Abi32 { class State : public ArmSemihosting::Abi32::State @@ -756,304 +254,4 @@ ArmSemihosting::callGem5PseudoOp64(ThreadContext *tc, uint64_t encoded_func) return retError(EINVAL); } -FILE * -ArmSemihosting::getSTDIO(const char *stream_name, - const std::string &name, const char *mode) -{ - auto it = stdioMap.find(name); - if (it == stdioMap.end()) { - FILE *f = fopen(name.c_str(), mode); - if (!f) { - fatal("Failed to open %s (%s): %s\n", - stream_name, name, strerror(errno)); - } - return f; - } else { - return it->second; - } -} - -std::unique_ptr -ArmSemihosting::FileBase::create( - ArmSemihosting &parent, const std::string &fname, const char *mode) -{ - std::unique_ptr file; - if (fname == ":semihosting-features") { - file.reset(new FileFeatures(parent, fname.c_str(), mode)); - } else { - file.reset(new File(parent, fname.c_str(), mode)); - } - - return file; -} - -std::unique_ptr -ArmSemihosting::FileBase::create(ArmSemihosting &parent, - CheckpointIn &cp, const std::string &sec) -{ - std::unique_ptr file; - ScopedCheckpointSection _sec(cp, sec); - - // Was the file open when the checkpoint was created? - if (!cp.sectionExists(Serializable::currentSection())) - return file; - - std::string fname, mode; - paramIn(cp, "name", fname); - paramIn(cp, "mode", mode); - file = create(parent, fname, mode.c_str()); - assert(file); - file->unserialize(cp); - - return file; -} - -void -ArmSemihosting::FileBase::serialize(CheckpointOut &cp) const -{ - paramOut(cp, "name", _name); - SERIALIZE_SCALAR(mode); -} - -void -ArmSemihosting::FileBase::unserialize(CheckpointIn &cp) -{ - /* Unserialization of name and mode happens in - * ArmSemihosting::FileBase::create() */ -} - -int64_t -ArmSemihosting::FileBase::read(uint8_t *buffer, uint64_t size) -{ - return -EINVAL; -} - -int64_t -ArmSemihosting::FileBase::write(const uint8_t *buffer, uint64_t size) -{ - return -EINVAL; -} - -int64_t -ArmSemihosting::FileBase::seek(uint64_t pos) -{ - return -EINVAL; -} - -int64_t -ArmSemihosting::FileBase::flen() -{ - return -EINVAL; -} - - -ArmSemihosting::FileFeatures::FileFeatures( - ArmSemihosting &_parent, const char *_name, const char *_mode) - : FileBase(_parent, _name, _mode) -{ -} - -int64_t -ArmSemihosting::FileFeatures::read(uint8_t *buffer, uint64_t size) -{ - int64_t len = 0; - - for (; pos < size && pos < ArmSemihosting::features.size(); pos++) - buffer[len++] = ArmSemihosting::features[pos]; - - return len; -} - -int64_t -ArmSemihosting::FileFeatures::seek(uint64_t _pos) -{ - if (_pos < ArmSemihosting::features.size()) { - pos = _pos; - return 0; - } else { - return -ENXIO; - } -} - -void -ArmSemihosting::FileFeatures::serialize(CheckpointOut &cp) const -{ - FileBase::serialize(cp); - SERIALIZE_SCALAR(pos); -} - -void -ArmSemihosting::FileFeatures::unserialize(CheckpointIn &cp) -{ - FileBase::unserialize(cp); - UNSERIALIZE_SCALAR(pos); -} - - - -ArmSemihosting::File::File(ArmSemihosting &_parent, - const char *_name, const char *_perms) - : FileBase(_parent, _name, _perms), - file(nullptr) -{ -} - -ArmSemihosting::File::~File() -{ - if (file) - close(); -} - -int64_t -ArmSemihosting::File::openImpl(bool in_cpt) -{ - panic_if(file, "Trying to open an already open file.\n"); - - if (_name == ":tt") { - if (mode[0] == 'r') { - file = parent.stdin; - } else if (mode[0] == 'w') { - file = parent.stdout; - } else if (mode[0] == 'a') { - file = parent.stderr; - } else { - warn("Unknown file mode for the ':tt' special file"); - return -EINVAL; - } - } else { - std::string real_mode(this->mode); - // Avoid truncating the file if we are restoring from a - // checkpoint. - if (in_cpt && real_mode[0] == 'w') - real_mode[0] = 'a'; - - file = fopen(_name.c_str(), real_mode.c_str()); - } - - return file ? 0 : -errno; -} - -int64_t -ArmSemihosting::File::close() -{ - panic_if(!file, "Trying to close an already closed file.\n"); - - if (needClose()) { - fclose(file); - } - file = nullptr; - - return 0; -} - -bool -ArmSemihosting::File::isTTY() const -{ - return file == parent.stdout || - file == parent.stderr || - file == parent.stdin; -} - -int64_t -ArmSemihosting::File::read(uint8_t *buffer, uint64_t size) -{ - panic_if(!file, "Trying to read from a closed file"); - - size_t ret = fread(buffer, 1, size, file); - if (ret == 0) { - // Error or EOF. Assume errors are due to invalid file - // operations (e.g., reading a write-only stream). - return ferror(file) ? -EINVAL : 0; - } else { - return ret; - } -} - -int64_t -ArmSemihosting::File::write(const uint8_t *buffer, uint64_t size) -{ - panic_if(!file, "Trying to write to a closed file"); - - - size_t ret = fwrite(buffer, 1, size, file); - if (ret == 0) { - // Assume errors are due to invalid file operations (e.g., - // writing a read-only stream). - return -EINVAL; - } else { - return ret; - } -} - -int64_t -ArmSemihosting::File::seek(uint64_t _pos) -{ - panic_if(!file, "Trying to seek in a closed file"); - - errno = 0; - if (fseek(file, _pos, SEEK_SET) == 0) - return 0; - else - return -errno; -} - -int64_t -ArmSemihosting::File::flen() -{ - errno = 0; - long pos = ftell(file); - if (pos < 0) - return -errno; - - if (fseek(file, 0, SEEK_END) != 0) - return -errno; - - long len = ftell(file); - if (len < 0) - return -errno; - - if (fseek(file, pos, SEEK_SET) != 0) - return -errno; - - return len; -} - - -void -ArmSemihosting::File::serialize(CheckpointOut &cp) const -{ - FileBase::serialize(cp); - - if (!isTTY()) { - long pos = file ? ftell(file) : 0; - panic_if(pos < 0, "Failed to get file position."); - SERIALIZE_SCALAR(pos); - } -} - -void -ArmSemihosting::File::unserialize(CheckpointIn &cp) -{ - FileBase::unserialize(cp); - - if (openImpl(true) < 0) { - fatal("Failed to open file: %s", _name); - } - - if (!isTTY()) { - long pos = 0; - UNSERIALIZE_SCALAR(pos); - if (fseek(file, pos, SEEK_SET) != 0) { - fatal("Failed seek to current position (%i) in '%s'", pos, _name); - } - } -} - -std::ostream & -operator << (std::ostream &os, const ArmSemihosting::InPlaceArg &ipa) -{ - ccprintf(os, "[%#x-%#x)", ipa.addr, ipa.addr + ipa.size - 1); - return os; -} - } // namespace gem5 diff --git a/src/arch/arm/semihosting.hh b/src/arch/arm/semihosting.hh index 7242581eb6..8ebe7e9eb3 100644 --- a/src/arch/arm/semihosting.hh +++ b/src/arch/arm/semihosting.hh @@ -47,6 +47,7 @@ #include "arch/arm/regs/int.hh" #include "arch/arm/utility.hh" +#include "arch/generic/semihosting.hh" #include "cpu/thread_context.hh" #include "mem/port_proxy.hh" #include "sim/core.hh" @@ -60,21 +61,8 @@ 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 +/** Semihosting for AArch32 and AArch64. */ +class ArmSemihosting : public BaseSemihosting { public: enum @@ -89,63 +77,28 @@ class ArmSemihosting : public SimObject Gem5Imm = 0x5D57 }; - static PortProxy &portProxy(ThreadContext *tc); - - struct AbiBase + static PortProxy &portProxyImpl(ThreadContext *tc); + PortProxy &portProxy(ThreadContext *tc) const override { - template - 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( - argPointer, endian); - argPointer += sizeof(Arg); - return arg; - } - - using ArgType = Arg; - }; - }; + return portProxyImpl(tc); + } + ByteOrder byteOrder(ThreadContext *tc) const override + { + return ArmISA::byteOrder(tc); + } struct Abi64 : public AbiBase { using UintPtr = uint64_t; - class State : public StateBase + class State : public StateBase { public: // For 64 bit semihosting, the params are pointer to by X1. - explicit State(const ThreadContext *tc) : - StateBase(tc, tc->getReg(ArmISA::int_reg::X1)) + explicit + State(const ThreadContext *tc) : + StateBase(tc, + tc->getReg(ArmISA::int_reg::X1), &ArmISA::byteOrder) {} }; }; @@ -154,54 +107,18 @@ class ArmSemihosting : public SimObject { using UintPtr = uint32_t; - class State : public StateBase + class State : public StateBase { public: // For 32 bit semihosting, the params are pointer to by R1. - explicit State(const ThreadContext *tc) : - StateBase(tc, tc->getReg(ArmISA::int_reg::R1)) + explicit + State(const ThreadContext *tc) : + StateBase(tc, + tc->getReg(ArmISA::int_reg::R1), &ArmISA::byteOrder) {} }; }; - // 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(addr, endian); - else if (size == 4) - return proxy.read(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(addr, val, endian); - else if (size == 4) - proxy.write(addr, val, endian); - else - panic("Unexpected semihosting argument size %d.", size); - } - }; - enum Operation { SYS_OPEN = 0x01, @@ -234,372 +151,22 @@ class ArmSemihosting : public SimObject SYS_GEM5_PSEUDO_OP = 0x100 }; - ArmSemihosting(const ArmSemihostingParams &p); + using SemiCall = SemiCallBase; + + explicit 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 create( - ArmSemihosting &parent, const std::string &fname, - const char *mode); - static std::unique_ptr 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> 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 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 - 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 - static inline std::function - wrapImpl(ArmSemihosting *sh, Implementation 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; - using Dumper = std::function; - - // 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 - static inline Dispatcher - buildDispatcher(Implementation impl) - { - // This lambda is the dispatcher we're building. - return [impl](ArmSemihosting *sh, ThreadContext *tc) { - auto wrapper = wrapImpl(sh, impl); - return invokeSimcall(tc, wrapper); - }; - } - - // A function which builds a dumper for a semihosting call. - template - static inline Dumper - buildDumper(const char *name, Implementation impl) - { - // This lambda is the dumper we're building. - return [name](ThreadContext *tc) -> std::string { - return dumpSimcall(name, tc); - }; - } - - // When there's one implementation, use it for both 32 and 64 bits. - template - SemiCall(const char *_name, Implementation common) : - name(_name), call32(buildDispatcher(common)), - call64(buildDispatcher(common)), - dump32(buildDumper(_name, common)), - dump64(buildDumper(_name, common)) - {} - - // When there are two, use one for 32 bits and one for 64 bits. - template - SemiCall(const char *_name, Implementation impl32, - Implementation impl64) : - name(_name), call32(buildDispatcher(impl32)), - call64(buildDispatcher(impl64)), - dump32(buildDumper(_name, impl32)), - dump64(buildDumper(_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); - + protected: RetErrno callGem5PseudoOp32(ThreadContext *tc, uint32_t encoded_func); RetErrno callGem5PseudoOp64(ThreadContext *tc, uint64_t encoded_func); - template - void - unrecognizedCall(ThreadContext *tc, const char *format, uint64_t op) - { - warn(format, op); - std::function retErr = - [](ThreadContext *tc) { return retError(EINVAL); }; - invokeSimcall(tc, retErr); - } - - static FILE *getSTDIO(const char *stream_name, - const std::string &name, const char *mode); - static const std::map calls; - static const std::vector fmodes; - static const std::map exitCodes; - static const std::vector features; - static const std::map 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 { diff --git a/src/arch/generic/BaseSemihosting.py b/src/arch/generic/BaseSemihosting.py new file mode 100644 index 0000000000..dd0df92e3a --- /dev/null +++ b/src/arch/generic/BaseSemihosting.py @@ -0,0 +1,67 @@ +# 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. + +from m5.params import * +from m5.SimObject import * + + +class BaseSemihosting(SimObject): + type = "BaseSemihosting" + abstract = True + cxx_header = "arch/generic/semihosting.hh" + cxx_class = "gem5::BaseSemihosting" + + cmd_line = Param.String("", "Command line to report to guest") + stdin = Param.String("stdin", "Standard input (stdin for gem5's terminal)") + stdout = Param.String( + "stdout", "Standard output (stdout for gem5's terminal)" + ) + stderr = Param.String( + "stderr", "Standard error (stderr for gem5's terminal)" + ) + files_root_dir = Param.String( + "", "Host root directory for files handled by Semihosting" + ) + + mem_reserve = Param.MemorySize( + "32MiB", + "Amount of memory to reserve at the start of the address map. This " + "memory won't be used by the heap reported to an application.", + ) + stack_size = Param.MemorySize("32MiB", "Application stack size") + + time = Param.Time( + "01/01/2009", "System time to use ('Now' for actual time)" + ) diff --git a/src/arch/generic/SConscript b/src/arch/generic/SConscript index 88d8f3b3dc..b2bef74b61 100644 --- a/src/arch/generic/SConscript +++ b/src/arch/generic/SConscript @@ -40,6 +40,10 @@ Import('*') Source('htm.cc') Source('mmu.cc') +if env['CONF']['USE_ARM_ISA'] or env['CONF']['USE_RISCV_ISA']: + Source('semihosting.cc') + SimObject('BaseSemihosting.py', sim_objects=['BaseSemihosting']) + DebugFlag('Semihosting') SimObject('BaseInterrupts.py', sim_objects=['BaseInterrupts']) SimObject('BaseISA.py', sim_objects=['BaseISA']) diff --git a/src/arch/generic/semihosting.cc b/src/arch/generic/semihosting.cc new file mode 100644 index 0000000000..4da54c4b4b --- /dev/null +++ b/src/arch/generic/semihosting.cc @@ -0,0 +1,854 @@ +/* + * 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. + */ + +#include "arch/generic/semihosting.hh" + +#include + +#include +#include + +#include "base/logging.hh" +#include "base/output.hh" +#include "base/time.hh" +#include "debug/Semihosting.hh" +#include "dev/serial/serial.hh" +#include "mem/physical.hh" +#include "params/BaseSemihosting.hh" +#include "sim/byteswap.hh" +#include "sim/pseudo_inst.hh" +#include "sim/sim_exit.hh" +#include "sim/system.hh" + +namespace gem5 +{ + +const std::vector BaseSemihosting::fmodes{ + "r", + "rb", + "r+", + "r+b", + "w", + "wb", + "w+", + "w+b", + "a", + "ab", + "a+", + "a+b", +}; + +const std::map BaseSemihosting::exitCodes{ + {0x20000, "semi:ADP_Stopped_BranchThroughZero"}, + {0x20001, "semi:ADP_Stopped_UndefinedInstr"}, + {0x20002, "semi:ADP_Stopped_SoftwareInterrupt"}, + {0x20003, "semi:ADP_Stopped_PrefetchAbort"}, + {0x20004, "semi:ADP_Stopped_DataAbort"}, + {0x20005, "semi:ADP_Stopped_AddressException"}, + {0x20006, "semi:ADP_Stopped_IRQ"}, + {0x20007, "semi:ADP_Stopped_FIQ"}, + + {0x20020, "semi:ADP_Stopped_BreakPoint"}, + {0x20021, "semi:ADP_Stopped_WatchPoint"}, + {0x20022, "semi:ADP_Stopped_StepComplete"}, + {0x20023, "semi:ADP_Stopped_RunTimeErrorUnknown"}, + {0x20024, "semi:ADP_Stopped_InternalError"}, + {0x20025, "semi:ADP_Stopped_UserInterruption"}, + {0x20026, "semi:ADP_Stopped_ApplicationExit"}, + {0x20027, "semi:ADP_Stopped_StackOverflow"}, + {0x20028, "semi:ADP_Stopped_DivisionByZero"}, + {0x20029, "semi:ADP_Stopped_DivisionByZero"}, +}; + +const std::vector BaseSemihosting::features{ + 0x53, 0x48, 0x46, 0x42, // Magic + 0x3, // EXT_EXIT_EXTENDED, EXT_STDOUT_STDERR +}; + +const std::map BaseSemihosting::stdioMap{ + {"cin", ::stdin}, + {"stdin", ::stdin}, + {"cout", ::stdout}, + {"stdout", ::stdout}, + {"cerr", ::stderr}, + {"stderr", ::stderr}, +}; + +BaseSemihosting::BaseSemihosting(const BaseSemihostingParams &p) + : SimObject(p), cmdLine(p.cmd_line), memReserve(p.mem_reserve), + stackSize(p.stack_size), timeBase([p] { + struct tm t = p.time; + return mkutctime(&t); + }()), + tickShift(calcTickShift()), semiErrno(0), + filesRootDir(!p.files_root_dir.empty() && p.files_root_dir.back() != '/' ? + p.files_root_dir + '/' : + p.files_root_dir), + stdin(getSTDIO("stdin", p.stdin, "r")), + stdout(getSTDIO("stdout", p.stdout, "w")), + stderr(p.stderr == p.stdout ? stdout : getSTDIO("stderr", p.stderr, "w")) +{ + // Create an empty place-holder file for position 0 as semi-hosting + // calls typically expect non-zero file handles. + files.push_back(nullptr); + + if (tickShift > 0) + inform("Semihosting: Shifting elapsed ticks by %i bits.", tickShift); +} + +void +BaseSemihosting::serialize(CheckpointOut &cp) const +{ + SERIALIZE_SCALAR(semiErrno); + + paramOut(cp, "num_files", files.size()); + for (int i = 0; i < files.size(); i++) { + // File closed? + if (!files[i]) + continue; + + files[i]->serializeSection(cp, csprintf("file%i", i)); + } +} + +void +BaseSemihosting::unserialize(CheckpointIn &cp) +{ + UNSERIALIZE_SCALAR(semiErrno); + + size_t num_files; + paramIn(cp, "num_files", num_files); + files.resize(num_files); + for (int i = 0; i < num_files; i++) + files[i] = FileBase::create(*this, cp, csprintf("file%i", i)); +} + +std::string +BaseSemihosting::readString(ThreadContext *tc, Addr ptr, size_t len) +{ + std::vector buf(len + 1); + + buf[len] = '\0'; + portProxy(tc).readBlob(ptr, buf.data(), len); + + return std::string(buf.data()); +} + +BaseSemihosting::RetErrno +BaseSemihosting::callOpen( + ThreadContext *tc, const Addr name_base, int fmode, size_t name_size) +{ + const char *mode = fmode < fmodes.size() ? fmodes[fmode] : nullptr; + + DPRINTF(Semihosting, "Semihosting SYS_OPEN(0x%x, %i[%s], %i)\n", name_base, + fmode, mode ? mode : "-", name_size); + if (!mode || !name_base) + return retError(EINVAL); + + std::string fname = readString(tc, name_base, name_size); + if (!fname.empty() && fname.front() != '/') + fname = filesRootDir + fname; + + std::unique_ptr file = + FileBase::create(*this, fname, mode); + int64_t ret = file->open(); + DPRINTF(Semihosting, "Semihosting SYS_OPEN(\"%s\", %i[%s]): %i\n", fname, + fmode, mode, ret); + if (ret < 0) { + return retError(-ret); + } else { + files.push_back(std::move(file)); + return retOK(files.size() - 1); + } +} + +BaseSemihosting::RetErrno +BaseSemihosting::callClose(ThreadContext *tc, Handle handle) +{ + if (handle > files.size()) { + DPRINTF(Semihosting, "Semihosting SYS_CLOSE(%i): Illegal file\n"); + return retError(EBADF); + } + + std::unique_ptr &file = files[handle]; + int64_t error = file->close(); + DPRINTF(Semihosting, "Semihosting SYS_CLOSE(%i[%s]): %i\n", handle, + file->fileName(), error); + if (error < 0) { + return retError(-error); + } else { + // Zap the pointer and free the entry in the file table as + // well. + files[handle].reset(); + return retOK(0); + } +} + +BaseSemihosting::RetErrno +BaseSemihosting::callWriteC(ThreadContext *tc, InPlaceArg arg) +{ + const char c = portProxy(tc).read(arg.addr); + + DPRINTF(Semihosting, "Semihosting SYS_WRITEC('%c')\n", c); + std::cout.put(c); + + return retOK(0); +} + +BaseSemihosting::RetErrno +BaseSemihosting::callWrite0(ThreadContext *tc, InPlaceArg arg) +{ + DPRINTF(Semihosting, "Semihosting SYS_WRITE0(...)\n"); + PortProxy &proxy = portProxy(tc); + std::string str; + proxy.readString(str, arg.addr); + std::cout.write(str.c_str(), str.size()); + + return retOK(0); +} + +BaseSemihosting::RetErrno +BaseSemihosting::callWrite( + ThreadContext *tc, Handle handle, Addr addr, size_t size) +{ + if (handle > files.size() || !files[handle]) + return RetErrno(size, EBADF); + + std::vector buffer(size); + portProxy(tc).readBlob(addr, buffer.data(), buffer.size()); + + int64_t ret = files[handle]->write(buffer.data(), buffer.size()); + if (ret < 0) { + // No bytes written (we're returning the number of bytes not + // written) + return RetErrno(size, -ret); + } else { + // Return the number of bytes not written + return RetErrno(size - ret, 0); + } +} + +BaseSemihosting::RetErrno +BaseSemihosting::callRead( + ThreadContext *tc, Handle handle, Addr addr, size_t size) +{ + if (handle > files.size() || !files[handle]) + return RetErrno(size, EBADF); + + std::vector buffer(size); + int64_t ret = files[handle]->read(buffer.data(), buffer.size()); + if (ret < 0) { + return RetErrno(size, -ret); + } else { + panic_if(ret > buffer.size(), "Read longer than buffer size."); + + portProxy(tc).writeBlob(addr, buffer.data(), ret); + + // Return the number of bytes not written + return retOK(size - ret); + } +} + +BaseSemihosting::RetErrno +BaseSemihosting::callReadC(ThreadContext *tc) +{ + return retOK((char)std::cin.get()); +} + +BaseSemihosting::RetErrno +BaseSemihosting::callIsError(ThreadContext *tc, int64_t status) +{ + return retOK(status < 0 ? 1 : 0); +} + +BaseSemihosting::RetErrno +BaseSemihosting::callIsTTY(ThreadContext *tc, Handle handle) +{ + if (handle > files.size() || !files[handle]) + return retError(EBADF); + + int64_t ret = files[handle]->isTTY(); + if (ret < 0) { + return retError(-ret); + } else { + return retOK(ret ? 1 : 0); + } +} + +BaseSemihosting::RetErrno +BaseSemihosting::callSeek(ThreadContext *tc, Handle handle, uint64_t pos) +{ + if (handle > files.size() || !files[handle]) + return retError(EBADF); + + int64_t ret = files[handle]->seek(pos); + if (ret < 0) { + return retError(-ret); + } else { + return retOK(0); + } +} + +BaseSemihosting::RetErrno +BaseSemihosting::callFLen(ThreadContext *tc, Handle handle) +{ + if (handle > files.size() || !files[handle]) + return retError(EBADF); + + int64_t ret = files[handle]->flen(); + if (ret < 0) { + return retError(-ret); + } else { + return retOK(ret); + } +} + +BaseSemihosting::RetErrno +BaseSemihosting::callTmpNam( + ThreadContext *tc, Addr addr, uint64_t id, size_t size) +{ + std::string path = ""; + int64_t unlink_call_ret = 0; + + do { + path = simout.resolve(csprintf("%s.tmp%05i", name(), tmpNameIndex++)); + // remove the (potentially existing) file of the given path + unlink_call_ret = unlink(path.c_str()); + // if the file is busy, find another name + } while ((unlink_call_ret < 0) && (errno == EBUSY)); + + const size_t path_len = path.length(); + if (path_len >= size) + return retError(ENOSPC); + + portProxy(tc).writeBlob(addr, path.c_str(), path_len + 1); + return retOK(0); +} + +BaseSemihosting::RetErrno +BaseSemihosting::callRemove( + ThreadContext *tc, Addr name_base, size_t name_size) +{ + std::string fname = readString(tc, name_base, name_size); + + if (remove(fname.c_str()) != 0) { + return retError(errno); + } else { + return retOK(0); + } +} + +BaseSemihosting::RetErrno +BaseSemihosting::callRename(ThreadContext *tc, Addr from_addr, + size_t from_size, Addr to_addr, size_t to_size) +{ + std::string from = readString(tc, from_addr, from_size); + std::string to = readString(tc, to_addr, to_size); + + if (rename(from.c_str(), to.c_str()) != 0) { + return retError(errno); + } else { + return retOK(0); + } +} + +BaseSemihosting::RetErrno +BaseSemihosting::callClock(ThreadContext *tc) +{ + return retOK(curTick() / (sim_clock::as_int::s / 100)); +} + +BaseSemihosting::RetErrno +BaseSemihosting::callTime(ThreadContext *tc) +{ + return retOK(timeBase + round(curTick() / sim_clock::as_float::s)); +} + +BaseSemihosting::RetErrno +BaseSemihosting::callSystem(ThreadContext *tc, Addr cmd_addr, size_t cmd_size) +{ + const std::string cmd = readString(tc, cmd_addr, cmd_size); + warn("Semihosting: SYS_SYSTEM not implemented. Guest tried to run: %s\n", + cmd); + return retError(EINVAL); +} + +BaseSemihosting::RetErrno +BaseSemihosting::callErrno(ThreadContext *tc) +{ + // Preserve errno by returning it in errno as well. + return RetErrno(semiErrno, semiErrno); +} + +BaseSemihosting::RetErrno +BaseSemihosting::callGetCmdLine( + ThreadContext *tc, Addr addr, InPlaceArg size_arg) +{ + PortProxy &proxy = portProxy(tc); + ByteOrder endian = byteOrder(tc); + size_t size = size_arg.read(tc, proxy, endian); + + if (cmdLine.size() + 1 < size) { + proxy.writeBlob(addr, cmdLine.c_str(), cmdLine.size() + 1); + size_arg.write(tc, proxy, cmdLine.size(), endian); + return retOK(0); + } else { + return retError(0); + } +} + +void +BaseSemihosting::gatherHeapInfo(ThreadContext *tc, bool aarch64, + Addr &heap_base, Addr &heap_limit, Addr &stack_base, Addr &stack_limit) +{ + const memory::PhysicalMemory &phys = tc->getSystemPtr()->getPhysMem(); + const AddrRangeList memories = phys.getConfAddrRanges(); + fatal_if(memories.size() < 1, "No memories reported from System"); + warn_if(memories.size() > 1, + "Multiple physical memory ranges available. " + "Using first range heap/stack."); + const AddrRange mem = *memories.begin(); + const Addr mem_start = mem.start() + memReserve; + Addr mem_end = mem.end(); + + // Make sure that 32-bit guests can access their memory. + if (!aarch64) { + const Addr phys_max = (1ULL << 32) - 1; + panic_if(mem_start > phys_max, + "Physical memory out of range for a 32-bit guest."); + if (mem_end > phys_max) { + warn("Some physical memory out of range for a 32-bit guest."); + mem_end = phys_max; + } + } + + fatal_if(mem_start + stackSize >= mem_end, + "Physical memory too small to fit desired stack and a heap."); + + heap_base = mem_start; + heap_limit = mem_end - stackSize + 1; + stack_base = (mem_end + 1) & ~0x7ULL; // 8 byte stack alignment + stack_limit = heap_limit; + + inform("Reporting heap/stack info to guest:\n" + "\tHeap base: 0x%x\n" + "\tHeap limit: 0x%x\n" + "\tStack base: 0x%x\n" + "\tStack limit: 0x%x\n", + heap_base, heap_limit, stack_base, stack_limit); +} + +BaseSemihosting::RetErrno +BaseSemihosting::callHeapInfo32(ThreadContext *tc, Addr block_addr) +{ + uint64_t heap_base, heap_limit, stack_base, stack_limit; + gatherHeapInfo(tc, false, heap_base, heap_limit, stack_base, stack_limit); + + std::array block = { + {(uint32_t)heap_base, (uint32_t)heap_limit, (uint32_t)stack_base, + (uint32_t)stack_limit}}; + portProxy(tc).write(block_addr, block, byteOrder(tc)); + + return retOK(0); +} + +BaseSemihosting::RetErrno +BaseSemihosting::callHeapInfo64(ThreadContext *tc, Addr block_addr) +{ + uint64_t heap_base, heap_limit, stack_base, stack_limit; + gatherHeapInfo(tc, true, heap_base, heap_limit, stack_base, stack_limit); + + std::array block = { + {heap_base, heap_limit, stack_base, stack_limit}}; + portProxy(tc).write(block_addr, block, byteOrder(tc)); + + return retOK(0); +} + +BaseSemihosting::RetErrno +BaseSemihosting::callExit32(ThreadContext *tc, InPlaceArg code) +{ + semiExit(code.addr, 0); + return retOK(0); +} + +BaseSemihosting::RetErrno +BaseSemihosting::callExit64(ThreadContext *tc, uint64_t code, uint64_t subcode) +{ + semiExit(code, subcode); + return retOK(0); +} + +BaseSemihosting::RetErrno +BaseSemihosting::callExitExtended( + ThreadContext *tc, uint64_t code, uint64_t subcode) +{ + semiExit(code, subcode); + return retOK(0); +} + +void +BaseSemihosting::semiExit(uint64_t code, uint64_t subcode) +{ + auto it = exitCodes.find(code); + if (it != exitCodes.end()) { + exitSimLoop(it->second, subcode); + } else { + exitSimLoop(csprintf("semi:0x%x", code), subcode); + } +} + +BaseSemihosting::RetErrno +BaseSemihosting::callElapsed32( + ThreadContext *tc, InPlaceArg low, InPlaceArg high) +{ + PortProxy &proxy = portProxy(tc); + ByteOrder endian = byteOrder(tc); + uint64_t tick = semiTick(curTick()); + + low.write(tc, proxy, tick, endian); + high.write(tc, proxy, tick >> 32, endian); + + return retOK(0); +} + +BaseSemihosting::RetErrno +BaseSemihosting::callElapsed64(ThreadContext *tc, InPlaceArg ticks) +{ + ticks.write(tc, portProxy(tc), semiTick(curTick()), byteOrder(tc)); + return retOK(0); +} + +BaseSemihosting::RetErrno +BaseSemihosting::callTickFreq(ThreadContext *tc) +{ + return retOK(semiTick(sim_clock::Frequency)); +} + +FILE * +BaseSemihosting::getSTDIO( + const char *stream_name, const std::string &name, const char *mode) +{ + auto it = stdioMap.find(name); + if (it == stdioMap.end()) { + FILE *f = fopen(name.c_str(), mode); + if (!f) { + fatal("Failed to open %s (%s): %s\n", stream_name, name, + strerror(errno)); + } + return f; + } else { + return it->second; + } +} + +std::unique_ptr +BaseSemihosting::FileBase::create( + BaseSemihosting &parent, const std::string &fname, const char *mode) +{ + std::unique_ptr file; + if (fname == ":semihosting-features") { + file.reset(new FileFeatures(parent, fname.c_str(), mode)); + } else { + file.reset(new File(parent, fname.c_str(), mode)); + } + + return file; +} + +std::unique_ptr +BaseSemihosting::FileBase::create( + BaseSemihosting &parent, CheckpointIn &cp, const std::string &sec) +{ + std::unique_ptr file; + ScopedCheckpointSection _sec(cp, sec); + + // Was the file open when the checkpoint was created? + if (!cp.sectionExists(Serializable::currentSection())) + return file; + + std::string fname, mode; + paramIn(cp, "name", fname); + paramIn(cp, "mode", mode); + file = create(parent, fname, mode.c_str()); + assert(file); + file->unserialize(cp); + + return file; +} + +void +BaseSemihosting::FileBase::serialize(CheckpointOut &cp) const +{ + paramOut(cp, "name", _name); + SERIALIZE_SCALAR(mode); +} + +void +BaseSemihosting::FileBase::unserialize(CheckpointIn &cp) +{ + /* Unserialization of name and mode happens in + * BaseSemihosting::FileBase::create() */ +} + +int64_t +BaseSemihosting::FileBase::read(uint8_t *buffer, uint64_t size) +{ + return -EINVAL; +} + +int64_t +BaseSemihosting::FileBase::write(const uint8_t *buffer, uint64_t size) +{ + return -EINVAL; +} + +int64_t +BaseSemihosting::FileBase::seek(uint64_t pos) +{ + return -EINVAL; +} + +int64_t +BaseSemihosting::FileBase::flen() +{ + return -EINVAL; +} + +BaseSemihosting::FileFeatures:: +FileFeatures(BaseSemihosting &_parent, const char *_name, const char *_mode) : + FileBase(_parent, _name, _mode) +{} + +int64_t +BaseSemihosting::FileFeatures::read(uint8_t *buffer, uint64_t size) +{ + int64_t len = 0; + + for (; pos < size && pos < BaseSemihosting::features.size(); pos++) + buffer[len++] = BaseSemihosting::features[pos]; + + return len; +} + +int64_t +BaseSemihosting::FileFeatures::seek(uint64_t _pos) +{ + if (_pos < BaseSemihosting::features.size()) { + pos = _pos; + return 0; + } else { + return -ENXIO; + } +} + +void +BaseSemihosting::FileFeatures::serialize(CheckpointOut &cp) const +{ + FileBase::serialize(cp); + SERIALIZE_SCALAR(pos); +} + +void +BaseSemihosting::FileFeatures::unserialize(CheckpointIn &cp) +{ + FileBase::unserialize(cp); + UNSERIALIZE_SCALAR(pos); +} + +BaseSemihosting::File:: +File(BaseSemihosting &_parent, const char *_name, const char *_perms) : + FileBase(_parent, _name, _perms), file(nullptr) +{} + +BaseSemihosting::File::~ +File() +{ + if (file) + close(); +} + +int64_t +BaseSemihosting::File::openImpl(bool in_cpt) +{ + panic_if(file, "Trying to open an already open file.\n"); + + if (_name == ":tt") { + if (mode[0] == 'r') { + file = parent.stdin; + } else if (mode[0] == 'w') { + file = parent.stdout; + } else if (mode[0] == 'a') { + file = parent.stderr; + } else { + warn("Unknown file mode for the ':tt' special file"); + return -EINVAL; + } + } else { + std::string real_mode(this->mode); + // Avoid truncating the file if we are restoring from a + // checkpoint. + if (in_cpt && real_mode[0] == 'w') + real_mode[0] = 'a'; + + file = fopen(_name.c_str(), real_mode.c_str()); + } + + return file ? 0 : -errno; +} + +int64_t +BaseSemihosting::File::close() +{ + panic_if(!file, "Trying to close an already closed file.\n"); + + if (needClose()) { + fclose(file); + } + file = nullptr; + + return 0; +} + +bool +BaseSemihosting::File::isTTY() const +{ + return file == parent.stdout || file == parent.stderr || + file == parent.stdin; +} + +int64_t +BaseSemihosting::File::read(uint8_t *buffer, uint64_t size) +{ + panic_if(!file, "Trying to read from a closed file"); + + size_t ret = fread(buffer, 1, size, file); + if (ret == 0) { + // Error or EOF. Assume errors are due to invalid file + // operations (e.g., reading a write-only stream). + return ferror(file) ? -EINVAL : 0; + } else { + return ret; + } +} + +int64_t +BaseSemihosting::File::write(const uint8_t *buffer, uint64_t size) +{ + panic_if(!file, "Trying to write to a closed file"); + + size_t ret = fwrite(buffer, 1, size, file); + if (ret == 0) { + // Assume errors are due to invalid file operations (e.g., + // writing a read-only stream). + return -EINVAL; + } else { + return ret; + } +} + +int64_t +BaseSemihosting::File::seek(uint64_t _pos) +{ + panic_if(!file, "Trying to seek in a closed file"); + + errno = 0; + if (fseek(file, _pos, SEEK_SET) == 0) + return 0; + else + return -errno; +} + +int64_t +BaseSemihosting::File::flen() +{ + errno = 0; + long pos = ftell(file); + if (pos < 0) + return -errno; + + if (fseek(file, 0, SEEK_END) != 0) + return -errno; + + long len = ftell(file); + if (len < 0) + return -errno; + + if (fseek(file, pos, SEEK_SET) != 0) + return -errno; + + return len; +} + +void +BaseSemihosting::File::serialize(CheckpointOut &cp) const +{ + FileBase::serialize(cp); + + if (!isTTY()) { + long pos = file ? ftell(file) : 0; + panic_if(pos < 0, "Failed to get file position."); + SERIALIZE_SCALAR(pos); + } +} + +void +BaseSemihosting::File::unserialize(CheckpointIn &cp) +{ + FileBase::unserialize(cp); + + if (openImpl(true) < 0) { + fatal("Failed to open file: %s", _name); + } + + if (!isTTY()) { + long pos = 0; + UNSERIALIZE_SCALAR(pos); + if (fseek(file, pos, SEEK_SET) != 0) { + fatal("Failed seek to current position (%i) in '%s'", pos, _name); + } + } +} + +std::ostream & +operator<<(std::ostream &os, const BaseSemihosting::InPlaceArg &ipa) +{ + ccprintf(os, "[%#x-%#x)", ipa.addr, ipa.addr + ipa.size - 1); + return os; +} + +} // namespace gem5 diff --git a/src/arch/generic/semihosting.hh b/src/arch/generic/semihosting.hh new file mode 100644 index 0000000000..975cac8b78 --- /dev/null +++ b/src/arch/generic/semihosting.hh @@ -0,0 +1,555 @@ +/* + * 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_GENERIC_SEMIHOSTING_HH__ +#define __ARCH_GENERIC_SEMIHOSTING_HH__ + +#include +#include +#include +#include +#include +#include + +#include "base/time.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 BaseSemihostingParams; +class SerialDevice; + +/** + * Semihosting for AArch32, AArch64, RISCV-32 and RISCV-64: + * https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst + * + * 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. + * + * Note: The RISC-V semihosting specification reuses the Arm semihosting + * interfaces (https://github.com/riscv-non-isa/riscv-semihosting). + * + * 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 BaseSemihosting : public SimObject +{ + virtual PortProxy &portProxy(ThreadContext *tc) const = 0; + virtual ByteOrder byteOrder(ThreadContext *tc) const = 0; + + public: + struct AbiBase + { + template + class StateBase + { + private: + Addr argPointer; + ByteOrder endian; + + public: + StateBase(const ThreadContext *tc, Addr arg_pointer, + std::function + getByteOrder) : + argPointer(arg_pointer), endian(getByteOrder(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 = BaseSemihostingImpl::portProxyImpl(tc) + .template read(argPointer, endian); + argPointer += sizeof(Arg); + return arg; + } + + using ArgType = Arg; + }; + }; + + // 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, PortProxy &proxy, ByteOrder endian) + { + if (size == 8) + return proxy.read(addr, endian); + else if (size == 4) + return proxy.read(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, PortProxy &proxy, uint64_t val, + ByteOrder endian) + { + if (size == 8) + proxy.write(addr, val, endian); + else if (size == 4) + proxy.write(addr, val, endian); + else + panic("Unexpected semihosting argument size %d.", size); + } + }; + + protected: + explicit BaseSemihosting(const BaseSemihostingParams &p); + + 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 + * BaseSemihosting::FileBase::create() factory methods. These + * methods handle some the magic file names in the Arm/RISC-V 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(BaseSemihosting &_parent, const char *name, + const char *_mode) : parent(_parent), _name(name), mode(_mode) + {} + virtual ~FileBase(){}; + + FileBase() = delete; + FileBase(FileBase &) = delete; + + static std::unique_ptr create(BaseSemihosting &parent, + const std::string &fname, const char *mode); + static std::unique_ptr create(BaseSemihosting &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: + BaseSemihosting &parent; + std::string _name; + std::string mode; + }; + + /** Implementation of the ':semihosting-features' magic file. */ + class FileFeatures : public FileBase + { + public: + FileFeatures( + BaseSemihosting &_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(BaseSemihosting &_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> 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 RetErrno; + + protected: + 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. + */ + template + struct SemiCallBase + { + /** Call name */ + const char *name; + + // A type for member functions implementing semihosting calls. + template + using Implementation = RetErrno (Impl::*)( + 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 + static inline std::function + wrapImpl(SemiImpl *sh, Implementation 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; + using Dumper = std::function; + + // 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 + static inline Dispatcher + buildDispatcher(Implementation impl) + { + // This lambda is the dispatcher we're building. + return [impl](SemiImpl *sh, ThreadContext *tc) { + auto wrapper = wrapImpl(sh, impl); + return invokeSimcall(tc, wrapper); + }; + } + + // A function which builds a dumper for a semihosting call. + template + static inline Dumper + buildDumper(const char *name, Implementation impl) + { + // This lambda is the dumper we're building. + return [name](ThreadContext *tc) -> std::string { + return dumpSimcall(name, tc); + }; + } + + // When there's one implementation, use it for both 32 and 64 bits. + template + SemiCallBase( + const char *_name, Implementation common) : + name(_name), call32(buildDispatcher(common)), + call64(buildDispatcher(common)), + dump32(buildDumper(_name, common)), + dump64(buildDumper(_name, common)) + {} + + // When there are two, use one for 32 bits and one for 64 bits. + template + SemiCallBase(const char *_name, + Implementation impl32, + Implementation impl64) : + name(_name), call32(buildDispatcher(impl32)), + call64(buildDispatcher(impl64)), + dump32(buildDumper(_name, impl32)), + dump64(buildDumper(_name, impl64)) + {} + }; + + public: + 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); + + template + void + unrecognizedCall(ThreadContext *tc, const char *format, uint64_t op) + { + warn(format, op); + std::function retErr = + [](ThreadContext *tc) { return retError(EINVAL); }; + invokeSimcall(tc, retErr); + } + + static FILE *getSTDIO(const char *stream_name, const std::string &name, + const char *mode); + + static const std::vector fmodes; + static const std::map exitCodes; + static const std::vector features; + static const std::map stdioMap; + + // used in callTmpNam() to deterministically generate a temp filename + uint16_t tmpNameIndex = 0; +}; + +std::ostream &operator<<( + std::ostream &os, const BaseSemihosting::InPlaceArg &ipa); + +} // namespace gem5 + +#endif // __ARCH_GENERIC_SEMIHOSTING_HH__ diff --git a/src/arch/riscv/RiscvFsWorkload.py b/src/arch/riscv/RiscvFsWorkload.py index 5f233e1057..473bb6c280 100644 --- a/src/arch/riscv/RiscvFsWorkload.py +++ b/src/arch/riscv/RiscvFsWorkload.py @@ -27,6 +27,7 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +from m5.objects.RiscvSemihosting import RiscvSemihosting from m5.objects.System import System from m5.objects.Workload import ( KernelWorkload, @@ -43,6 +44,10 @@ class RiscvBareMetal(Workload): bootloader = Param.String("File, that contains the bootloader code") bare_metal = Param.Bool(True, "Using Bare Metal Application?") reset_vect = Param.Addr(0x0, "Reset vector") + semihosting = Param.RiscvSemihosting( + NULL, + "Enable support for RISC-V semihosting by settings this parameter", + ) auto_reset_vect = Param.Bool( True, "Use bootloader entry point as reset vector. " @@ -59,6 +64,10 @@ class RiscvLinux(KernelWorkload): "", "File that contains the Device Tree Blob. Don't use DTB if empty." ) dtb_addr = Param.Addr(0x87E00000, "DTB address") + semihosting = Param.RiscvSemihosting( + NULL, + "Enable support for RISC-V semihosting by settings this parameter", + ) # gem5 event upon guest's kernel panic # Default to false because when the kernel is compiled into the bootloader @@ -124,3 +133,7 @@ class RiscvBootloaderKernelWorkload(Workload): "Define how gem5 should behave after a Linux Kernel Oops. " "Handler might not be implemented for all architectures.", ) + semihosting = Param.RiscvSemihosting( + NULL, + "Enable support for RISC-V semihosting by settings this parameter", + ) diff --git a/src/arch/riscv/RiscvSemihosting.py b/src/arch/riscv/RiscvSemihosting.py new file mode 100644 index 0000000000..cfcbf43fe9 --- /dev/null +++ b/src/arch/riscv/RiscvSemihosting.py @@ -0,0 +1,46 @@ +# 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. + +from m5.objects.BaseSemihosting import BaseSemihosting +from m5.objects.Serial import SerialDevice +from m5.objects.Terminal import Terminal +from m5.params import * +from m5.SimObject import * + + +class RiscvSemihosting(BaseSemihosting): + type = "RiscvSemihosting" + cxx_header = "arch/riscv/semihosting.hh" + cxx_class = "gem5::RiscvSemihosting" diff --git a/src/arch/riscv/SConscript b/src/arch/riscv/SConscript index 78864523c7..55fcf46cb4 100644 --- a/src/arch/riscv/SConscript +++ b/src/arch/riscv/SConscript @@ -58,6 +58,7 @@ Source('pma_checker.cc', tags='riscv isa') Source('pmp.cc', tags='riscv isa') Source('reg_abi.cc', tags='riscv isa') Source('remote_gdb.cc', tags='riscv isa') +Source('semihosting.cc', tags='riscv isa') Source('tlb.cc', tags='riscv isa') Source('linux/se_workload.cc', tags='riscv isa') @@ -78,6 +79,8 @@ SimObject('RiscvInterrupts.py', sim_objects=['RiscvInterrupts'], SimObject('RiscvISA.py', sim_objects=['RiscvISA'], enums=['RiscvType', 'PrivilegeModeSet'], tags='riscv isa') SimObject('RiscvMMU.py', sim_objects=['RiscvMMU'], tags='riscv isa') +SimObject('RiscvSemihosting.py', sim_objects=['RiscvSemihosting'], + tags='riscv isa') SimObject('RiscvSeWorkload.py', sim_objects=[ 'RiscvSEWorkload', 'RiscvEmuLinux'], tags='riscv isa') SimObject('RiscvTLB.py', sim_objects=['RiscvPagetableWalker', 'RiscvTLB'], diff --git a/src/arch/riscv/bare_metal/fs_workload.cc b/src/arch/riscv/bare_metal/fs_workload.cc index a71af1f1d4..b596e5ce4c 100644 --- a/src/arch/riscv/bare_metal/fs_workload.cc +++ b/src/arch/riscv/bare_metal/fs_workload.cc @@ -42,7 +42,8 @@ namespace RiscvISA BareMetal::BareMetal(const Params &p) : Workload(p), _isBareMetal(p.bare_metal), - bootloader(loader::createObjectFile(p.bootloader)) + bootloader(loader::createObjectFile(p.bootloader)), + semihosting(p.semihosting) { fatal_if(!bootloader, "Could not load bootloader file %s.", p.bootloader); bootloaderSymtab = bootloader->symtab(); diff --git a/src/arch/riscv/bare_metal/fs_workload.hh b/src/arch/riscv/bare_metal/fs_workload.hh index 35f42555df..5b5725b85d 100644 --- a/src/arch/riscv/bare_metal/fs_workload.hh +++ b/src/arch/riscv/bare_metal/fs_workload.hh @@ -30,6 +30,7 @@ #define __ARCH_RISCV_BARE_METAL_SYSTEM_HH__ #include "arch/riscv/remote_gdb.hh" +#include "arch/riscv/semihosting.hh" #include "params/RiscvBareMetal.hh" #include "sim/workload.hh" @@ -48,6 +49,7 @@ class BareMetal : public Workload Addr _resetVect; loader::ObjectFile *bootloader; loader::SymbolTable bootloaderSymtab; + RiscvSemihosting* semihosting; public: PARAMS(RiscvBareMetal); @@ -86,6 +88,7 @@ class BareMetal : public Workload bool isBareMetal() const { return _isBareMetal; } Addr getEntry() const override { return _resetVect; } + RiscvSemihosting *getSemihosting() const override { return semihosting; } }; } // namespace RiscvISA diff --git a/src/arch/riscv/insts/standard.cc b/src/arch/riscv/insts/standard.cc index 14e8fe26fd..6b996701c6 100644 --- a/src/arch/riscv/insts/standard.cc +++ b/src/arch/riscv/insts/standard.cc @@ -33,8 +33,10 @@ #include #include +#include "arch/riscv/faults.hh" #include "arch/riscv/insts/static_inst.hh" #include "arch/riscv/regs/misc.hh" +#include "arch/riscv/semihosting.hh" #include "arch/riscv/utility.hh" #include "cpu/static_inst.hh" @@ -87,5 +89,21 @@ SystemOp::generateDisassembly(Addr pc, const loader::SymbolTable *symtab) const return mnemonic; } +Fault +SystemOp::executeEBreakOrSemihosting(ExecContext *xc) const +{ + // If semihosting is enabled, we may need to execute a semihosting + // operation instead of raising a breakpoint fault. + ThreadContext *tc = xc->tcBase(); + if (auto *semihosting = dynamic_cast( + tc->getSystemPtr()->workload->getSemihosting())) { + if (semihosting->isSemihostingEBreak(xc) && semihosting->call(tc)) { + return NoFault; + } + } + // No semihosting, raise a standard breakpoint exception. + return std::make_shared(xc->pcState()); +} + } // namespace RiscvISA } // namespace gem5 diff --git a/src/arch/riscv/insts/standard.hh b/src/arch/riscv/insts/standard.hh index 2e7ae8d1d8..4f1a538c73 100644 --- a/src/arch/riscv/insts/standard.hh +++ b/src/arch/riscv/insts/standard.hh @@ -80,6 +80,9 @@ class SystemOp : public RiscvStaticInst std::string generateDisassembly( Addr pc, const loader::SymbolTable *symtab) const override; + + protected: + Fault executeEBreakOrSemihosting(ExecContext *xc) const; }; /** diff --git a/src/arch/riscv/isa/decoder.isa b/src/arch/riscv/isa/decoder.isa index e3b278dc5e..b937fde5cc 100644 --- a/src/arch/riscv/isa/decoder.isa +++ b/src/arch/riscv/isa/decoder.isa @@ -463,6 +463,8 @@ decode QUADRANT default Unknown::unknown() { 0x1: decode RC2 { 0x0: decode RC1 { 0x0: SystemOp::c_ebreak({{ + // NB: Semihosting spec requires uncompressed ebreak, + // so this instruction does not check for semihosting. return std::make_shared( xc->pcState()); }}, IsSerializeAfter, IsNonSpeculative, No_OpClass); @@ -4899,8 +4901,7 @@ decode QUADRANT default Unknown::unknown() { }}, IsSerializeAfter, IsNonSpeculative, IsSyscall, No_OpClass); 0x1: ebreak({{ - return std::make_shared( - xc->pcState()); + return executeEBreakOrSemihosting(xc); }}, IsSerializeAfter, IsNonSpeculative, No_OpClass); 0x2: uret({{ MISA misa = xc->readMiscReg(MISCREG_ISA); diff --git a/src/arch/riscv/linux/fs_workload.hh b/src/arch/riscv/linux/fs_workload.hh index c5fcd70eb0..73e93e0c79 100644 --- a/src/arch/riscv/linux/fs_workload.hh +++ b/src/arch/riscv/linux/fs_workload.hh @@ -32,6 +32,7 @@ #include #include "arch/riscv/remote_gdb.hh" +#include "arch/riscv/semihosting.hh" #include "params/RiscvBootloaderKernelWorkload.hh" #include "params/RiscvLinux.hh" #include "sim/kernel_workload.hh" @@ -51,11 +52,12 @@ class FsLinux : public KernelWorkload **/ PCEvent *kernelPanicPcEvent = nullptr; PCEvent *kernelOopsPcEvent = nullptr; + RiscvSemihosting *semihosting = nullptr; void addExitOnKernelPanicEvent(); void addExitOnKernelOopsEvent(); public: PARAMS(RiscvLinux); - FsLinux(const Params &p) : KernelWorkload(p) {} + FsLinux(const Params &p) : KernelWorkload(p), semihosting(p.semihosting) {} ~FsLinux() { if (kernelPanicPcEvent != nullptr) { @@ -78,6 +80,7 @@ class FsLinux : public KernelWorkload } ByteOrder byteOrder() const override { return ByteOrder::little; } + RiscvSemihosting *getSemihosting() const override { return semihosting; } }; class BootloaderKernelWorkload: public Workload @@ -89,6 +92,7 @@ class BootloaderKernelWorkload: public Workload loader::SymbolTable kernelSymbolTable; loader::SymbolTable bootloaderSymbolTable; const std::string bootArgs; + RiscvSemihosting *semihosting; /** * Event to halt the simulator if the kernel calls panic() or @@ -109,7 +113,8 @@ class BootloaderKernelWorkload: public Workload public: PARAMS(RiscvBootloaderKernelWorkload); BootloaderKernelWorkload(const Params &p) - : Workload(p), entryPoint(p.entry_point), bootArgs(p.command_line) + : Workload(p), entryPoint(p.entry_point), bootArgs(p.command_line), + semihosting(p.semihosting) { loadBootloaderSymbolTable(); loadKernelSymbolTable(); @@ -142,6 +147,8 @@ class BootloaderKernelWorkload: public Workload loader::Arch getArch() const override { return kernel->getArch(); } + RiscvSemihosting *getSemihosting() const override { return semihosting; } + const loader::SymbolTable & symtab(ThreadContext *tc) override { diff --git a/src/arch/riscv/semihosting.cc b/src/arch/riscv/semihosting.cc new file mode 100644 index 0000000000..05b80e5742 --- /dev/null +++ b/src/arch/riscv/semihosting.cc @@ -0,0 +1,193 @@ +/* + * 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. + */ + +#include "arch/riscv/semihosting.hh" + +#include + +#include +#include + +#include "arch/riscv/isa.hh" +#include "arch/riscv/page_size.hh" +#include "base/logging.hh" +#include "base/output.hh" +#include "base/time.hh" +#include "cpu/exec_context.hh" +#include "debug/Semihosting.hh" +#include "dev/serial/serial.hh" +#include "mem/physical.hh" +#include "mem/se_translating_port_proxy.hh" +#include "mem/translating_port_proxy.hh" +#include "params/RiscvSemihosting.hh" +#include "pcstate.hh" +#include "sim/byteswap.hh" +#include "sim/full_system.hh" +#include "sim/pseudo_inst.hh" +#include "sim/system.hh" + +namespace gem5 +{ + +const std::map RiscvSemihosting::calls{ + {SYS_OPEN, {"SYS_OPEN", &RiscvSemihosting::callOpen}}, + {SYS_CLOSE, {"SYS_CLOSE", &RiscvSemihosting::callClose}}, + {SYS_WRITEC, {"SYS_WRITEC", &RiscvSemihosting::callWriteC}}, + {SYS_WRITE0, {"SYS_WRITE0", &RiscvSemihosting::callWrite0}}, + {SYS_WRITE, {"SYS_WRITE", &RiscvSemihosting::callWrite}}, + {SYS_READ, {"SYS_READ", &RiscvSemihosting::callRead}}, + {SYS_READC, {"SYS_READC", &RiscvSemihosting::callReadC}}, + {SYS_ISERROR, {"SYS_ISERROR", &RiscvSemihosting::callIsError}}, + {SYS_ISTTY, {"SYS_ISTTY", &RiscvSemihosting::callIsTTY}}, + {SYS_SEEK, {"SYS_SEEK", &RiscvSemihosting::callSeek}}, + {SYS_FLEN, {"SYS_FLEN", &RiscvSemihosting::callFLen}}, + {SYS_TMPNAM, {"SYS_TMPNAM", &RiscvSemihosting::callTmpNam}}, + {SYS_REMOVE, {"SYS_REMOVE", &RiscvSemihosting::callRemove}}, + {SYS_RENAME, {"SYS_RENAME", &RiscvSemihosting::callRename}}, + {SYS_CLOCK, {"SYS_CLOCK", &RiscvSemihosting::callClock}}, + {SYS_TIME, {"SYS_TIME", &RiscvSemihosting::callTime}}, + {SYS_SYSTEM, {"SYS_SYSTEM", &RiscvSemihosting::callSystem}}, + {SYS_ERRNO, {"SYS_ERRNO", &RiscvSemihosting::callErrno}}, + {SYS_GET_CMDLINE, + {"SYS_GET_CMDLINE", &RiscvSemihosting::callGetCmdLine}}, + {SYS_HEAPINFO, {"SYS_HEAPINFO", &RiscvSemihosting::callHeapInfo32, + &RiscvSemihosting::callHeapInfo64}}, + + {SYS_EXIT, {"SYS_EXIT", &RiscvSemihosting::callExit32, + &RiscvSemihosting::callExit64}}, + {SYS_EXIT_EXTENDED, + {"SYS_EXIT_EXTENDED", &RiscvSemihosting::callExitExtended}}, + + {SYS_ELAPSED, {"SYS_ELAPSED", &RiscvSemihosting::callElapsed32, + &RiscvSemihosting::callElapsed64}}, + {SYS_TICKFREQ, {"SYS_TICKFREQ", &RiscvSemihosting::callTickFreq}}, +}; + +RiscvSemihosting:: +RiscvSemihosting(const RiscvSemihostingParams &p) : BaseSemihosting(p) +{} + +bool +RiscvSemihosting::call64(ThreadContext *tc) +{ + RegVal op = tc->getReg(RiscvISA::int_reg::A0) & mask(32); + auto it = calls.find(op); + if (it == calls.end()) { + unrecognizedCall(tc, "Unknown semihosting call: op = 0x%x", op); + return false; + } + const SemiCall &call = it->second; + + DPRINTF(Semihosting, "Semihosting call64: %s\n", call.dump64(tc)); + auto err = call.call64(this, tc); + semiErrno = err.second; + DPRINTF(Semihosting, "\t ->: 0x%x, %i\n", err.first, err.second); + + return true; +} + +bool +RiscvSemihosting::call32(ThreadContext *tc) +{ + RegVal op = tc->getReg(RiscvISA::int_reg::A0); + auto it = calls.find(op); + if (it == calls.end()) { + unrecognizedCall( + tc, "Unknown aarch32 semihosting call: op = 0x%x", op); + return false; + } + const SemiCall &call = it->second; + + DPRINTF(Semihosting, "Semihosting call32: %s\n", call.dump32(tc)); + auto err = call.call32(this, tc); + semiErrno = err.second; + DPRINTF(Semihosting, "\t ->: 0x%x, %i\n", err.first, err.second); + + return true; +} + +bool +RiscvSemihosting::call(ThreadContext *tc) +{ + auto isa = dynamic_cast(tc->getIsaPtr()); + panic_if(!isa, "Cannot derive rv_type from non-riscv isa"); + return isa->rvType() == enums::RV32 ? call32(tc) : call64(tc); +} + +PortProxy & +RiscvSemihosting::portProxyImpl(ThreadContext *tc) +{ + static std::unique_ptr port_proxy([=]() { + return FullSystem ? new TranslatingPortProxy(tc) : + new SETranslatingPortProxy(tc); + }()); + return *port_proxy; +} + +bool +RiscvSemihosting::isSemihostingEBreak(ExecContext *xc) +{ + // Check if the surrounding bytes match the semihosting magic sequence. + PortProxy& proxy = portProxyImpl(xc->tcBase()); + Addr PrevInstAddr = xc->pcState().instAddr() - 4; + Addr NextInstAddr = xc->pcState().instAddr() + 4; + if (roundDown(PrevInstAddr, RiscvISA::PageBytes) != + roundDown(NextInstAddr, RiscvISA::PageBytes)) { + DPRINTF(Semihosting, + "Ebreak cannot be a semihosting ebreak since previous " + "and next instruction are on different pages\n"); + return false; + } + uint32_t instSequence[3]; + if (!proxy.tryReadBlob(PrevInstAddr, instSequence, sizeof(instSequence))) { + DPRINTF(Semihosting, + "Ebreak cannot be a semihosting ebreak since surrounding " + "instructions at %#x cannot be accessed\n"); + return false; + } + uint32_t PrevInst = gtoh(instSequence[0], ByteOrder::little); + uint32_t EBreakInst = gtoh(instSequence[1], ByteOrder::little); + uint32_t NextInst = gtoh(instSequence[2], ByteOrder::little); + DPRINTF(Semihosting, + "Checking ebreak for semihosting: Prev=%#x EBreak=%#x Next=%#x\n", + PrevInst, EBreakInst, NextInst); + return PrevInst == (uint32_t)Opcode::Prefix && + EBreakInst == (uint32_t)Opcode::EBreak && + NextInst == (uint32_t)Opcode::Suffix; +} + +} // namespace gem5 diff --git a/src/arch/riscv/semihosting.hh b/src/arch/riscv/semihosting.hh new file mode 100644 index 0000000000..d427936f9f --- /dev/null +++ b/src/arch/riscv/semihosting.hh @@ -0,0 +1,201 @@ +/* + * 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_RISCV_SEMIHOSTING_HH__ +#define __ARCH_RISCV_SEMIHOSTING_HH__ + +#include "arch/generic/semihosting.hh" +#include "arch/riscv/isa.hh" +#include "arch/riscv/regs/int.hh" +#include "cpu/thread_context.hh" +#include "sim/guest_abi.hh" +#include "sim/sim_object.hh" + +namespace gem5 +{ + +struct RiscvSemihostingParams; +class SerialDevice; + +/** Semihosting for RV32 and RV64. */ +class RiscvSemihosting : public BaseSemihosting +{ + public: + enum class Opcode : uint32_t + { + // https://github.com/riscv-software-src/riscv-semihosting/blob/main/ + // riscv-semihosting-spec.adoc#21-semihosting-trap-instruction-sequence + Prefix = 0x01f01013, // slli x0, x0, 0x1f Entry NOP + EBreak = 0x00100073, // ebreak Break to debugger + Suffix = 0x40705013, // srai x0, x0, 7 NOP encoding semihosting + }; + static PortProxy &portProxyImpl(ThreadContext *tc); + PortProxy & + portProxy(ThreadContext *tc) const override + { + return portProxyImpl(tc); + } + ByteOrder + byteOrder(ThreadContext *tc) const override + { + return ByteOrder::little; + } + + template + struct RiscvSemihostingAbi : public AbiBase + { + using UintPtr = ArgType; + + class State : public StateBase + { + public: + explicit + State(const ThreadContext *tc) : + StateBase(tc, + tc->getReg(RiscvISA::ArgumentRegs[1]), + [](const ThreadContext *) { + return ByteOrder::little; + }) + {} + }; + }; + + struct Abi64 : public RiscvSemihostingAbi + {}; + struct Abi32 : public RiscvSemihostingAbi + {}; + + 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 + }; + + using SemiCall = SemiCallBase; + + explicit RiscvSemihosting(const RiscvSemihostingParams &p); + + /** Perform a RISC-V Semihosting call */ + bool isSemihostingEBreak(ExecContext *xc); + bool call(ThreadContext *tc); + protected: + bool call64(ThreadContext *tc); + bool call32(ThreadContext *tc); + static const std::map calls; +}; + +namespace guest_abi +{ + +template +struct Argument>> +{ + static Arg + get(ThreadContext *tc, RiscvSemihosting::Abi64::State &state) + { + return state.get(tc); + } +}; + +template +struct Argument>> +{ + static Arg + get(ThreadContext *tc, RiscvSemihosting::Abi32::State &state) + { + if (std::is_signed_v) + return sext<32>(state.get(tc)); + else + return state.get(tc); + } +}; + +template +struct Argument>> +{ + static RiscvSemihosting::InPlaceArg + get(ThreadContext *tc, typename Abi::State &state) + { + return RiscvSemihosting::InPlaceArg( + state.getAddr(), sizeof(typename Abi::State::ArgType)); + } +}; + +template +struct Result +{ + static void + store(ThreadContext *tc, const RiscvSemihosting::RetErrno &err) + { + tc->setReg(RiscvISA::ReturnValueReg, err.first); + } +}; + +} // namespace guest_abi +} // namespace gem5 + +#endif // __ARCH_RISCV_SEMIHOSTING_HH__ diff --git a/src/sim/workload.hh b/src/sim/workload.hh index 727a283fd4..9dc7a9a07c 100644 --- a/src/sim/workload.hh +++ b/src/sim/workload.hh @@ -103,6 +103,8 @@ class Workload : public SimObject virtual Addr getEntry() const = 0; virtual ByteOrder byteOrder() const = 0; virtual loader::Arch getArch() const = 0; + /// Returns the semihosting interface if supported by the current workload. + virtual SimObject* getSemihosting() const { return nullptr; } virtual const loader::SymbolTable &symtab(ThreadContext *tc) = 0; virtual bool insertSymbol(const loader::Symbol &symbol) = 0;