diff mbox

[2/4] target-lm32: add semihosting support

Message ID 1398190724-10478-3-git-send-email-michael@walle.cc
State New
Headers show

Commit Message

Michael Walle April 22, 2014, 6:18 p.m. UTC
Intercept certain system calls if semihosting is enabled. This should
behave like the GDB simulator.

Signed-off-by: Michael Walle <michael@walle.cc>
---
 qemu-options.hx           |    3 +-
 target-lm32/Makefile.objs |    1 +
 target-lm32/README        |    9 ++
 target-lm32/helper.h      |    1 +
 target-lm32/lm32-semi.c   |  212 +++++++++++++++++++++++++++++++++++++++++++++
 target-lm32/translate.c   |   19 +++-
 6 files changed, 241 insertions(+), 4 deletions(-)
 create mode 100644 target-lm32/lm32-semi.c

Comments

Peter Maydell April 28, 2014, 5:27 p.m. UTC | #1
On 22 April 2014 19:18, Michael Walle <michael@walle.cc> wrote:
> Intercept certain system calls if semihosting is enabled. This should
> behave like the GDB simulator.
> @@ -608,8 +609,20 @@ static void dec_scall(DisasContext *dc)
>          break;
>      case 7:
>          LOG_DIS("scall\n");
> -        tcg_gen_movi_tl(cpu_pc, dc->pc);
> -        t_gen_raise_exception(dc, EXCP_SYSTEMCALL);
> +        if (unlikely(semihosting_enabled)) {
> +            TCGv t0 = tcg_temp_new();
> +            int l1 = gen_new_label();
> +
> +            gen_helper_semihosting(t0, cpu_env);
> +            tcg_gen_brcondi_tl(TCG_COND_EQ, t0, 0, l1);
> +            tcg_gen_movi_tl(cpu_pc, dc->pc);
> +            t_gen_raise_exception(dc, EXCP_SYSTEMCALL);
> +            gen_set_label(l1);
> +            tcg_temp_free(t0);
> +        } else {
> +            tcg_gen_movi_tl(cpu_pc, dc->pc);
> +            t_gen_raise_exception(dc, EXCP_SYSTEMCALL);
> +        }
>          break;
>      default:
>          qemu_log_mask(LOG_GUEST_ERROR, "invalid opcode @0x%x", dc->pc);

This seems a bit odd to me -- if semihosting is enabled,
we generate a call to a helper. If that helper returns
failure (eg attempt to open nonexistent file) we raise
an exception. Why not just do what the ARM semihosting
does and put the call to the semihosting handling code
in the lm32_cpu_do_interrupt() function?

(It's a bit sad that the semihosting API for lm32
doesn't seem to provide any way of distinguishing
"syscall for semihosting" from "other syscall",
incidentally.)

thanks
-- PMM
Michael Walle April 28, 2014, 6:04 p.m. UTC | #2
Am 2014-04-28 19:27, schrieb Peter Maydell:
> On 22 April 2014 19:18, Michael Walle <michael@walle.cc> wrote:
>> Intercept certain system calls if semihosting is enabled. This should
>> behave like the GDB simulator.
>> @@ -608,8 +609,20 @@ static void dec_scall(DisasContext *dc)
>>          break;
>>      case 7:
>>          LOG_DIS("scall\n");
>> -        tcg_gen_movi_tl(cpu_pc, dc->pc);
>> -        t_gen_raise_exception(dc, EXCP_SYSTEMCALL);
>> +        if (unlikely(semihosting_enabled)) {
>> +            TCGv t0 = tcg_temp_new();
>> +            int l1 = gen_new_label();
>> +
>> +            gen_helper_semihosting(t0, cpu_env);
>> +            tcg_gen_brcondi_tl(TCG_COND_EQ, t0, 0, l1);
>> +            tcg_gen_movi_tl(cpu_pc, dc->pc);
>> +            t_gen_raise_exception(dc, EXCP_SYSTEMCALL);
>> +            gen_set_label(l1);
>> +            tcg_temp_free(t0);
>> +        } else {
>> +            tcg_gen_movi_tl(cpu_pc, dc->pc);
>> +            t_gen_raise_exception(dc, EXCP_SYSTEMCALL);
>> +        }
>>          break;
>>      default:
>>          qemu_log_mask(LOG_GUEST_ERROR, "invalid opcode @0x%x", 
>> dc->pc);
> 
> This seems a bit odd to me -- if semihosting is enabled,
> we generate a call to a helper. If that helper returns
> failure (eg attempt to open nonexistent file) we raise
> an exception.

this is not an indication of a failure, but an indication if the syscall 
was unhandled, eg. not a known semicall and thus will be handled as a 
'normal' syscall.

> Why not just do what the ARM semihosting
> does and put the call to the semihosting handling code
> in the lm32_cpu_do_interrupt() function?

mhh, i'll look into that. actually, that sounds better.

> (It's a bit sad that the semihosting API for lm32
> doesn't seem to provide any way of distinguishing
> "syscall for semihosting" from "other syscall",
> incidentally.)

You mean within qemu? See above.

Thanks for the review.

-michael
diff mbox

Patch

diff --git a/qemu-options.hx b/qemu-options.hx
index 2d33815..1de2833 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -2992,7 +2992,8 @@  STEXI
 Set OpenBIOS nvram @var{variable} to given @var{value} (PPC, SPARC only).
 ETEXI
 DEF("semihosting", 0, QEMU_OPTION_semihosting,
-    "-semihosting    semihosting mode\n", QEMU_ARCH_ARM | QEMU_ARCH_M68K | QEMU_ARCH_XTENSA)
+    "-semihosting    semihosting mode\n",
+    QEMU_ARCH_ARM | QEMU_ARCH_M68K | QEMU_ARCH_XTENSA | QEMU_ARCH_LM32)
 STEXI
 @item -semihosting
 @findex -semihosting
diff --git a/target-lm32/Makefile.objs b/target-lm32/Makefile.objs
index 4023687..c3e1bd6 100644
--- a/target-lm32/Makefile.objs
+++ b/target-lm32/Makefile.objs
@@ -1,3 +1,4 @@ 
 obj-y += translate.o op_helper.o helper.o cpu.o
 obj-y += gdbstub.o
+obj-y += lm32-semi.o
 obj-$(CONFIG_SOFTMMU) += machine.o
diff --git a/target-lm32/README b/target-lm32/README
index a1c2c7e..03ddbff 100644
--- a/target-lm32/README
+++ b/target-lm32/README
@@ -26,6 +26,15 @@  first BSP which instantiate this model. A (32 bit) write to 0xfff0000
 causes a vm shutdown.
 
 
+Semihosting
+-----------
+Semihosting on this target is supported. Some system calls like read, write
+and exit are executed on the host if semihosting is enabled. See
+target/lm32-semi.c for all supported system calls. Emulation aware programs
+can use this mechanism to shut down the virtual machine and print to the
+host console. See the tcg tests for an example.
+
+
 Special instructions
 --------------------
 The translation recognizes one special instruction to halt the cpu:
diff --git a/target-lm32/helper.h b/target-lm32/helper.h
index f4442e0..8d3aba5 100644
--- a/target-lm32/helper.h
+++ b/target-lm32/helper.h
@@ -14,5 +14,6 @@  DEF_HELPER_1(rcsr_ip, i32, env)
 DEF_HELPER_1(rcsr_jtx, i32, env)
 DEF_HELPER_1(rcsr_jrx, i32, env)
 DEF_HELPER_1(ill, void, env)
+DEF_HELPER_1(semihosting, i32, env)
 
 #include "exec/def-helper.h"
diff --git a/target-lm32/lm32-semi.c b/target-lm32/lm32-semi.c
new file mode 100644
index 0000000..4caffe7
--- /dev/null
+++ b/target-lm32/lm32-semi.c
@@ -0,0 +1,212 @@ 
+/*
+ *  Lattice Mico32 semihosting syscall interface
+ *
+ *  Copyright (c) 2014 Michael Walle <michael@walle.cc>
+ *
+ * Based on target-m68k/m68k-semi.c, which is
+ *  Copyright (c) 2005-2007 CodeSourcery.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <stddef.h>
+#include "cpu.h"
+#include "helper.h"
+#include "qemu/log.h"
+#include "exec/softmmu-semi.h"
+
+enum {
+    TARGET_SYS_exit    = 1,
+    TARGET_SYS_open    = 2,
+    TARGET_SYS_close   = 3,
+    TARGET_SYS_read    = 4,
+    TARGET_SYS_write   = 5,
+    TARGET_SYS_lseek   = 6,
+    TARGET_SYS_fstat   = 10,
+    TARGET_SYS_stat    = 15,
+};
+
+enum {
+    NEWLIB_O_RDONLY    =   0x0,
+    NEWLIB_O_WRONLY    =   0x1,
+    NEWLIB_O_RDWR      =   0x2,
+    NEWLIB_O_APPEND    =   0x8,
+    NEWLIB_O_CREAT     = 0x200,
+    NEWLIB_O_TRUNC     = 0x400,
+    NEWLIB_O_EXCL      = 0x800,
+};
+
+static int translate_openflags(int flags)
+{
+    int hf;
+
+    if (flags & NEWLIB_O_WRONLY) {
+        hf = O_WRONLY;
+    } else if (flags & NEWLIB_O_RDWR) {
+        hf = O_RDWR;
+    } else {
+        hf = O_RDONLY;
+    }
+
+    if (flags & NEWLIB_O_APPEND) {
+        hf |= O_APPEND;
+    }
+
+    if (flags & NEWLIB_O_CREAT) {
+        hf |= O_CREAT;
+    }
+
+    if (flags & NEWLIB_O_TRUNC) {
+        hf |= O_TRUNC;
+    }
+
+    if (flags & NEWLIB_O_EXCL) {
+        hf |= O_EXCL;
+    }
+
+    return hf;
+}
+
+struct newlib_stat {
+    int16_t     newlib_st_dev;     /* device */
+    uint16_t    newlib_st_ino;     /* inode */
+    uint16_t    newlib_st_mode;    /* protection */
+    uint16_t    newlib_st_nlink;   /* number of hard links */
+    uint16_t    newlib_st_uid;     /* user ID of owner */
+    uint16_t    newlib_st_gid;     /* group ID of owner */
+    int16_t     newlib_st_rdev;    /* device type (if inode device) */
+    int32_t     newlib_st_size;    /* total size, in bytes */
+    int32_t     newlib_st_atime;   /* time of last access */
+    uint32_t    newlib_st_spare1;
+    int32_t     newlib_st_mtime;   /* time of last modification */
+    uint32_t    newlib_st_spare2;
+    int32_t     newlib_st_ctime;   /* time of last change */
+    uint32_t    newlib_st_spare3;
+} QEMU_PACKED;
+
+static int translate_stat(CPULM32State *env, target_ulong addr,
+        struct stat *s)
+{
+    struct newlib_stat *p;
+
+    p = lock_user(VERIFY_WRITE, addr, sizeof(struct newlib_stat), 0);
+    if (!p) {
+        return 0;
+    }
+    p->newlib_st_dev = cpu_to_be16(s->st_dev);
+    p->newlib_st_ino = cpu_to_be16(s->st_ino);
+    p->newlib_st_mode = cpu_to_be16(s->st_mode);
+    p->newlib_st_nlink = cpu_to_be16(s->st_nlink);
+    p->newlib_st_uid = cpu_to_be16(s->st_uid);
+    p->newlib_st_gid = cpu_to_be16(s->st_gid);
+    p->newlib_st_rdev = cpu_to_be16(s->st_rdev);
+    p->newlib_st_size = cpu_to_be32(s->st_size);
+    p->newlib_st_atime = cpu_to_be32(s->st_atime);
+    p->newlib_st_mtime = cpu_to_be32(s->st_mtime);
+    p->newlib_st_ctime = cpu_to_be32(s->st_ctime);
+    unlock_user(p, addr, sizeof(struct newlib_stat));
+
+    return 1;
+}
+
+uint32_t HELPER(semihosting)(CPULM32State *env)
+{
+    int ret = -1;
+    target_ulong nr, arg0, arg1, arg2;
+    void *p;
+    struct stat s;
+
+    nr = env->regs[R_R8];
+    arg0 = env->regs[R_R1];
+    arg1 = env->regs[R_R2];
+    arg2 = env->regs[R_R3];
+
+    switch (nr) {
+    case TARGET_SYS_exit:
+        /* void _exit(int rc) */
+        exit(arg0);
+
+    case TARGET_SYS_open:
+        /* int open(const char *pathname, int flags) */
+        p = lock_user_string(arg0);
+        if (!p) {
+            ret = -1;
+        } else {
+            ret = open(p, translate_openflags(arg2));
+            unlock_user(p, arg0, 0);
+        }
+        break;
+
+    case TARGET_SYS_read:
+        /* ssize_t read(int fd, const void *buf, size_t count) */
+        p = lock_user(VERIFY_WRITE, arg1, arg2, 0);
+        if (!p) {
+            ret = -1;
+        } else {
+            ret = read(arg0, p, arg2);
+            unlock_user(p, arg1, arg2);
+        }
+        break;
+
+    case TARGET_SYS_write:
+        /* ssize_t write(int fd, const void *buf, size_t count) */
+        p = lock_user(VERIFY_READ, arg1, arg2, 1);
+        if (!p) {
+            ret = -1;
+        } else {
+            ret = write(arg0, p, arg2);
+            unlock_user(p, arg1, 0);
+        }
+        break;
+
+    case TARGET_SYS_close:
+        /* int close(int fd) */
+        /* don't close stdin/stdout/stderr */
+        if (arg0 > 2) {
+            ret = close(arg0);
+        } else {
+            ret = 0;
+        }
+        break;
+
+    case TARGET_SYS_lseek:
+        /* off_t lseek(int fd, off_t offset, int whence */
+        ret = lseek(arg0, arg1, arg2);
+        break;
+
+    case TARGET_SYS_stat:
+        /* int stat(const char *path, struct stat *buf) */
+        p = lock_user_string(arg0);
+        if (!p) {
+            ret = -1;
+        } else {
+            ret = stat(p, &s);
+            unlock_user(p, arg0, 0);
+            if (translate_stat(env, arg1, &s) == 0) {
+                ret = -1;
+            }
+        }
+        break;
+
+    case TARGET_SYS_fstat:
+        /* int stat(int fd, struct stat *buf) */
+        ret = fstat(arg0, &s);
+        if (ret == 0) {
+            if (translate_stat(env, arg1, &s) == 0) {
+                ret = -1;
+            }
+        }
+        break;
+
+    default:
+        /* unhandled */
+        return 1;
+    }
+
+    env->regs[R_R1] = ret;
+    return 0;
+}
diff --git a/target-lm32/translate.c b/target-lm32/translate.c
index c8abd1f..e6c52c5 100644
--- a/target-lm32/translate.c
+++ b/target-lm32/translate.c
@@ -1,7 +1,7 @@ 
 /*
  *  LatticeMico32 main translation routines.
  *
- *  Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *  Copyright (c) 2010-2014 Michael Walle <michael@walle.cc>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -21,6 +21,7 @@ 
 #include "disas/disas.h"
 #include "helper.h"
 #include "tcg-op.h"
+#include "sysemu/sysemu.h"
 
 #include "hw/lm32/lm32_pic.h"
 
@@ -608,8 +609,20 @@  static void dec_scall(DisasContext *dc)
         break;
     case 7:
         LOG_DIS("scall\n");
-        tcg_gen_movi_tl(cpu_pc, dc->pc);
-        t_gen_raise_exception(dc, EXCP_SYSTEMCALL);
+        if (unlikely(semihosting_enabled)) {
+            TCGv t0 = tcg_temp_new();
+            int l1 = gen_new_label();
+
+            gen_helper_semihosting(t0, cpu_env);
+            tcg_gen_brcondi_tl(TCG_COND_EQ, t0, 0, l1);
+            tcg_gen_movi_tl(cpu_pc, dc->pc);
+            t_gen_raise_exception(dc, EXCP_SYSTEMCALL);
+            gen_set_label(l1);
+            tcg_temp_free(t0);
+        } else {
+            tcg_gen_movi_tl(cpu_pc, dc->pc);
+            t_gen_raise_exception(dc, EXCP_SYSTEMCALL);
+        }
         break;
     default:
         qemu_log_mask(LOG_GUEST_ERROR, "invalid opcode @0x%x", dc->pc);