aboutsummaryrefslogtreecommitdiff
path: root/target/arm/arm-semi.c
diff options
context:
space:
mode:
Diffstat (limited to 'target/arm/arm-semi.c')
-rw-r--r--target/arm/arm-semi.c228
1 files changed, 214 insertions, 14 deletions
diff --git a/target/arm/arm-semi.c b/target/arm/arm-semi.c
index 302529f227..a4741d7e11 100644
--- a/target/arm/arm-semi.c
+++ b/target/arm/arm-semi.c
@@ -106,6 +106,113 @@ static int open_modeflags[12] = {
O_RDWR | O_CREAT | O_APPEND | O_BINARY
};
+typedef enum GuestFDType {
+ GuestFDUnused = 0,
+ GuestFDHost = 1,
+} GuestFDType;
+
+/*
+ * Guest file descriptors are integer indexes into an array of
+ * these structures (we will dynamically resize as necessary).
+ */
+typedef struct GuestFD {
+ GuestFDType type;
+ int hostfd;
+} GuestFD;
+
+static GArray *guestfd_array;
+
+/*
+ * Allocate a new guest file descriptor and return it; if we
+ * couldn't allocate a new fd then return -1.
+ * This is a fairly simplistic implementation because we don't
+ * expect that most semihosting guest programs will make very
+ * heavy use of opening and closing fds.
+ */
+static int alloc_guestfd(void)
+{
+ guint i;
+
+ if (!guestfd_array) {
+ /* New entries zero-initialized, i.e. type GuestFDUnused */
+ guestfd_array = g_array_new(FALSE, TRUE, sizeof(GuestFD));
+ }
+
+ for (i = 0; i < guestfd_array->len; i++) {
+ GuestFD *gf = &g_array_index(guestfd_array, GuestFD, i);
+
+ if (gf->type == GuestFDUnused) {
+ return i;
+ }
+ }
+
+ /* All elements already in use: expand the array */
+ g_array_set_size(guestfd_array, i + 1);
+ return i;
+}
+
+/*
+ * Look up the guestfd in the data structure; return NULL
+ * for out of bounds, but don't check whether the slot is unused.
+ * This is used internally by the other guestfd functions.
+ */
+static GuestFD *do_get_guestfd(int guestfd)
+{
+ if (!guestfd_array) {
+ return NULL;
+ }
+
+ if (guestfd < 0 || guestfd >= guestfd_array->len) {
+ return NULL;
+ }
+
+ return &g_array_index(guestfd_array, GuestFD, guestfd);
+}
+
+/*
+ * Associate the specified guest fd (which must have been
+ * allocated via alloc_fd() and not previously used) with
+ * the specified host fd.
+ */
+static void associate_guestfd(int guestfd, int hostfd)
+{
+ GuestFD *gf = do_get_guestfd(guestfd);
+
+ assert(gf);
+ gf->type = GuestFDHost;
+ gf->hostfd = hostfd;
+}
+
+/*
+ * Deallocate the specified guest file descriptor. This doesn't
+ * close the host fd, it merely undoes the work of alloc_fd().
+ */
+static void dealloc_guestfd(int guestfd)
+{
+ GuestFD *gf = do_get_guestfd(guestfd);
+
+ assert(gf);
+ gf->type = GuestFDUnused;
+}
+
+/*
+ * Given a guest file descriptor, get the associated struct.
+ * If the fd is not valid, return NULL. This is the function
+ * used by the various semihosting calls to validate a handle
+ * from the guest.
+ * Note: calling alloc_guestfd() or dealloc_guestfd() will
+ * invalidate any GuestFD* obtained by calling this function.
+ */
+static GuestFD *get_guestfd(int guestfd)
+{
+ GuestFD *gf = do_get_guestfd(guestfd);
+
+ if (!gf || gf->type == GuestFDUnused) {
+ return NULL;
+ }
+ return gf;
+}
+
#ifdef CONFIG_USER_ONLY
static inline uint32_t set_swi_errno(TaskState *ts, uint32_t code)
{
@@ -207,6 +314,34 @@ static void arm_semi_flen_cb(CPUState *cs, target_ulong ret, target_ulong err)
#endif
}
+static int arm_semi_open_guestfd;
+
+static void arm_semi_open_cb(CPUState *cs, target_ulong ret, target_ulong err)
+{
+ ARMCPU *cpu = ARM_CPU(cs);
+ CPUARMState *env = &cpu->env;
+#ifdef CONFIG_USER_ONLY
+ TaskState *ts = cs->opaque;
+#endif
+ if (ret == (target_ulong)-1) {
+#ifdef CONFIG_USER_ONLY
+ ts->swi_errno = err;
+#else
+ syscall_err = err;
+#endif
+ dealloc_guestfd(arm_semi_open_guestfd);
+ } else {
+ associate_guestfd(arm_semi_open_guestfd, ret);
+ ret = arm_semi_open_guestfd;
+ }
+
+ if (is_a64(env)) {
+ env->xregs[0] = ret;
+ } else {
+ env->regs[0] = ret;
+ }
+}
+
static target_ulong arm_gdb_syscall(ARMCPU *cpu, gdb_syscall_complete_cb cb,
const char *fmt, ...)
{
@@ -283,6 +418,7 @@ target_ulong do_arm_semihosting(CPUARMState *env)
#else
CPUARMState *ts = env;
#endif
+ GuestFD *gf;
if (is_a64(env)) {
/* Note that the syscall number is in W0, not X0 */
@@ -295,6 +431,9 @@ target_ulong do_arm_semihosting(CPUARMState *env)
switch (nr) {
case TARGET_SYS_OPEN:
+ {
+ int guestfd;
+
GET_ARG(0);
GET_ARG(1);
GET_ARG(2);
@@ -308,26 +447,52 @@ target_ulong do_arm_semihosting(CPUARMState *env)
errno = EINVAL;
return set_swi_errno(ts, -1);
}
+
+ guestfd = alloc_guestfd();
+ if (guestfd < 0) {
+ unlock_user(s, arg0, 0);
+ errno = EMFILE;
+ return set_swi_errno(ts, -1);
+ }
+
if (strcmp(s, ":tt") == 0) {
int result_fileno = arg1 < 4 ? STDIN_FILENO : STDOUT_FILENO;
+ associate_guestfd(guestfd, result_fileno);
unlock_user(s, arg0, 0);
- return result_fileno;
+ return guestfd;
}
if (use_gdb_syscalls()) {
- ret = arm_gdb_syscall(cpu, arm_semi_cb, "open,%s,%x,1a4", arg0,
+ arm_semi_open_guestfd = guestfd;
+ ret = arm_gdb_syscall(cpu, arm_semi_open_cb, "open,%s,%x,1a4", arg0,
(int)arg2+1, gdb_open_modeflags[arg1]);
} else {
ret = set_swi_errno(ts, open(s, open_modeflags[arg1], 0644));
+ if (ret == (uint32_t)-1) {
+ dealloc_guestfd(guestfd);
+ } else {
+ associate_guestfd(guestfd, ret);
+ ret = guestfd;
+ }
}
unlock_user(s, arg0, 0);
return ret;
+ }
case TARGET_SYS_CLOSE:
GET_ARG(0);
+
+ gf = get_guestfd(arg0);
+ if (!gf) {
+ errno = EBADF;
+ return set_swi_errno(ts, -1);
+ }
+
if (use_gdb_syscalls()) {
- return arm_gdb_syscall(cpu, arm_semi_cb, "close,%x", arg0);
+ ret = arm_gdb_syscall(cpu, arm_semi_cb, "close,%x", gf->hostfd);
} else {
- return set_swi_errno(ts, close(arg0));
+ ret = set_swi_errno(ts, close(gf->hostfd));
}
+ dealloc_guestfd(arg0);
+ return ret;
case TARGET_SYS_WRITEC:
qemu_semihosting_console_outc(env, args);
return 0xdeadbeef;
@@ -338,17 +503,24 @@ target_ulong do_arm_semihosting(CPUARMState *env)
GET_ARG(1);
GET_ARG(2);
len = arg2;
+
+ gf = get_guestfd(arg0);
+ if (!gf) {
+ errno = EBADF;
+ return set_swi_errno(ts, -1);
+ }
+
if (use_gdb_syscalls()) {
arm_semi_syscall_len = len;
return arm_gdb_syscall(cpu, arm_semi_cb, "write,%x,%x,%x",
- arg0, arg1, len);
+ gf->hostfd, arg1, len);
} else {
s = lock_user(VERIFY_READ, arg1, len, 1);
if (!s) {
/* Return bytes not written on error */
return len;
}
- ret = set_swi_errno(ts, write(arg0, s, len));
+ ret = set_swi_errno(ts, write(gf->hostfd, s, len));
unlock_user(s, arg1, 0);
if (ret == (uint32_t)-1) {
ret = 0;
@@ -361,10 +533,17 @@ target_ulong do_arm_semihosting(CPUARMState *env)
GET_ARG(1);
GET_ARG(2);
len = arg2;
+
+ gf = get_guestfd(arg0);
+ if (!gf) {
+ errno = EBADF;
+ return set_swi_errno(ts, -1);
+ }
+
if (use_gdb_syscalls()) {
arm_semi_syscall_len = len;
return arm_gdb_syscall(cpu, arm_semi_cb, "read,%x,%x,%x",
- arg0, arg1, len);
+ gf->hostfd, arg1, len);
} else {
s = lock_user(VERIFY_WRITE, arg1, len, 0);
if (!s) {
@@ -372,7 +551,7 @@ target_ulong do_arm_semihosting(CPUARMState *env)
return len;
}
do {
- ret = set_swi_errno(ts, read(arg0, s, len));
+ ret = set_swi_errno(ts, read(gf->hostfd, s, len));
} while (ret == -1 && errno == EINTR);
unlock_user(s, arg1, len);
if (ret == (uint32_t)-1) {
@@ -386,31 +565,52 @@ target_ulong do_arm_semihosting(CPUARMState *env)
return 0;
case TARGET_SYS_ISTTY:
GET_ARG(0);
+
+ gf = get_guestfd(arg0);
+ if (!gf) {
+ errno = EBADF;
+ return set_swi_errno(ts, -1);
+ }
+
if (use_gdb_syscalls()) {
- return arm_gdb_syscall(cpu, arm_semi_cb, "isatty,%x", arg0);
+ return arm_gdb_syscall(cpu, arm_semi_cb, "isatty,%x", gf->hostfd);
} else {
- return isatty(arg0);
+ return isatty(gf->hostfd);
}
case TARGET_SYS_SEEK:
GET_ARG(0);
GET_ARG(1);
+
+ gf = get_guestfd(arg0);
+ if (!gf) {
+ errno = EBADF;
+ return set_swi_errno(ts, -1);
+ }
+
if (use_gdb_syscalls()) {
return arm_gdb_syscall(cpu, arm_semi_cb, "lseek,%x,%x,0",
- arg0, arg1);
+ gf->hostfd, arg1);
} else {
- ret = set_swi_errno(ts, lseek(arg0, arg1, SEEK_SET));
+ ret = set_swi_errno(ts, lseek(gf->hostfd, arg1, SEEK_SET));
if (ret == (uint32_t)-1)
return -1;
return 0;
}
case TARGET_SYS_FLEN:
GET_ARG(0);
+
+ gf = get_guestfd(arg0);
+ if (!gf) {
+ errno = EBADF;
+ return set_swi_errno(ts, -1);
+ }
+
if (use_gdb_syscalls()) {
return arm_gdb_syscall(cpu, arm_semi_flen_cb, "fstat,%x,%x",
- arg0, arm_flen_buf(cpu));
+ gf->hostfd, arm_flen_buf(cpu));
} else {
struct stat buf;
- ret = set_swi_errno(ts, fstat(arg0, &buf));
+ ret = set_swi_errno(ts, fstat(gf->hostfd, &buf));
if (ret == (uint32_t)-1)
return -1;
return buf.st_size;