diff options
-rw-r--r-- | Makefile.target | 2 | ||||
-rw-r--r-- | linux-user/arm-semi.c | 193 | ||||
-rw-r--r-- | linux-user/arm/syscall.h | 5 | ||||
-rw-r--r-- | linux-user/main.c | 7 | ||||
-rw-r--r-- | linux-user/qemu.h | 6 | ||||
-rw-r--r-- | linux-user/syscall.c | 2 |
6 files changed, 213 insertions, 2 deletions
diff --git a/Makefile.target b/Makefile.target index 789848e531..5375cb32b7 100644 --- a/Makefile.target +++ b/Makefile.target @@ -233,7 +233,7 @@ endif ifeq ($(TARGET_ARCH), arm) OBJS+=nwfpe/fpa11.o nwfpe/fpa11_cpdo.o \ nwfpe/fpa11_cpdt.o nwfpe/fpa11_cprt.o nwfpe/fpopcode.o nwfpe/single_cpdo.o \ - nwfpe/double_cpdo.o nwfpe/extended_cpdo.o + nwfpe/double_cpdo.o nwfpe/extended_cpdo.o arm-semi.o endif SRCS:= $(OBJS:.o=.c) OBJS+= libqemu.a diff --git a/linux-user/arm-semi.c b/linux-user/arm-semi.c new file mode 100644 index 0000000000..f37b65c471 --- /dev/null +++ b/linux-user/arm-semi.c @@ -0,0 +1,193 @@ +/* + * Arm "Angel" semihosting syscalls + * + * Copyright (c) 2005 CodeSourcery, LLC. Written by Paul Brook. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> + +#include "qemu.h" + +#define ARM_ANGEL_HEAP_SIZE (128 * 1024 * 1024) + +#define SYS_OPEN 0x01 +#define SYS_CLOSE 0x02 +#define SYS_WRITEC 0x03 +#define SYS_WRITE0 0x04 +#define SYS_WRITE 0x05 +#define SYS_READ 0x06 +#define SYS_READC 0x07 +#define SYS_ISTTY 0x09 +#define SYS_SEEK 0x0a +#define SYS_FLEN 0x0c +#define SYS_TMPNAM 0x0d +#define SYS_REMOVE 0x0e +#define SYS_RENAME 0x0f +#define SYS_CLOCK 0x10 +#define SYS_TIME 0x11 +#define SYS_SYSTEM 0x12 +#define SYS_ERRNO 0x13 +#define SYS_GET_CMDLINE 0x15 +#define SYS_HEAPINFO 0x16 +#define SYS_EXIT 0x18 + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +int open_modeflags[12] = { + O_RDONLY, + O_RDONLY | O_BINARY, + O_RDWR, + O_RDWR | O_BINARY, + O_WRONLY | O_CREAT | O_TRUNC, + O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + O_RDWR | O_CREAT | O_TRUNC, + O_RDWR | O_CREAT | O_TRUNC | O_BINARY, + O_WRONLY | O_CREAT | O_APPEND, + O_WRONLY | O_CREAT | O_APPEND | O_BINARY, + O_RDWR | O_CREAT | O_APPEND, + O_RDWR | O_CREAT | O_APPEND | O_BINARY +}; + +static inline uint32_t set_swi_errno(TaskState *ts, uint32_t code) +{ + if (code == (uint32_t)-1) + ts->swi_errno = errno; + return code; +} + +#define ARG(x) tswap32(args[x]) +uint32_t do_arm_semihosting(CPUState *env) +{ + uint32_t *args; + char * s; + int nr; + uint32_t ret; + TaskState *ts = env->opaque; + + nr = env->regs[0]; + args = (uint32_t *)env->regs[1]; + switch (nr) { + case SYS_OPEN: + s = (char *)ARG(0); + if (ARG(1) >= 12) + return (uint32_t)-1; + if (strcmp(s, ":tt") == 0) { + if (ARG(1) < 4) + return STDIN_FILENO; + else + return STDOUT_FILENO; + } + return set_swi_errno(ts, open(s, open_modeflags[ARG(1)])); + case SYS_CLOSE: + return set_swi_errno(ts, close(ARG(0))); + case SYS_WRITEC: + /* Write to debug console. stderr is near enough. */ + return write(STDERR_FILENO, args, 1); + case SYS_WRITE0: + s = (char *)args; + return write(STDERR_FILENO, s, strlen(s)); + case SYS_WRITE: + ret = set_swi_errno(ts, write(ARG(0), (void *)ARG(1), ARG(2))); + if (ret == (uint32_t)-1) + return -1; + return ARG(2) - ret; + case SYS_READ: + ret = set_swi_errno(ts, read(ARG(0), (void *)ARG(1), ARG(2))); + if (ret == (uint32_t)-1) + return -1; + return ARG(2) - ret; + case SYS_READC: + /* XXX: Read from debug cosole. Not implemented. */ + return 0; + case SYS_ISTTY: + return isatty(ARG(0)); + case SYS_SEEK: + return set_swi_errno(ts, lseek(ARG(0), ARG(1), SEEK_SET)); + case SYS_FLEN: + { + struct stat buf; + ret = set_swi_errno(ts, fstat(ARG(0), &buf)); + if (ret == (uint32_t)-1) + return -1; + return buf.st_size; + } + case SYS_TMPNAM: + /* XXX: Not implemented. */ + return -1; + case SYS_REMOVE: + return set_swi_errno(ts, remove((char *)ARG(0))); + case SYS_RENAME: + return set_swi_errno(ts, rename((char *)ARG(0), (char *)ARG(2))); + case SYS_CLOCK: + return clock() / (CLOCKS_PER_SEC / 100); + case SYS_TIME: + return set_swi_errno(ts, time(NULL)); + case SYS_SYSTEM: + return set_swi_errno(ts, system((char *)ARG(0))); + case SYS_ERRNO: + return ts->swi_errno; + case SYS_GET_CMDLINE: + /* XXX: Not implemented. */ + s = (char *)ARG(0); + *s = 0; + return -1; + case SYS_HEAPINFO: + { + uint32_t *ptr; + uint32_t limit; + + /* Some C llibraries assume the heap immediately follows .bss, so + allocate it using sbrk. */ + if (!ts->heap_limit) { + long ret; + + ts->heap_base = do_brk(NULL); + limit = ts->heap_base + ARM_ANGEL_HEAP_SIZE; + /* Try a big heap, and reduce the size if that fails. */ + for (;;) { + ret = do_brk((char *)limit); + if (ret != -1) + break; + limit = (ts->heap_base >> 1) + (limit >> 1); + } + ts->heap_limit = limit; + } + + ptr = (uint32_t *)tswap32(ARG(0)); + ptr[0] = tswap32(ts->heap_base); + ptr[1] = tswap32(ts->heap_limit); + ptr[2] = tswap32(ts->stack_base); + ptr[3] = tswap32(0); /* Stack limit. */ + return 0; + } + case SYS_EXIT: + exit(0); + default: + fprintf(stderr, "qemu: Unsupported SemiHosting SWI 0x%02x\n", nr); + cpu_dump_state(env, stderr, fprintf, 0); + abort(); + } +} + diff --git a/linux-user/arm/syscall.h b/linux-user/arm/syscall.h index f74d765289..daf4b34af8 100644 --- a/linux-user/arm/syscall.h +++ b/linux-user/arm/syscall.h @@ -29,8 +29,13 @@ struct target_pt_regs { #define ARM_NR_cacheflush (ARM_SYSCALL_BASE + 0xf0000 + 2) +#define ARM_NR_semihosting 0x123456 +#define ARM_NR_thumb_semihosting 0xAB + #if defined(TARGET_WORDS_BIGENDIAN) #define UNAME_MACHINE "armv5teb" #else #define UNAME_MACHINE "armv5tel" #endif + +uint32_t do_arm_semihosting(CPUState *); diff --git a/linux-user/main.c b/linux-user/main.c index 4d1f8ce88c..bcdf8441b8 100644 --- a/linux-user/main.c +++ b/linux-user/main.c @@ -363,6 +363,9 @@ void cpu_loop(CPUARMState *env) n = insn & 0xffffff; if (n == ARM_NR_cacheflush) { arm_cache_flush(env->regs[0], env->regs[1]); + } else if (n == ARM_NR_semihosting + || n == ARM_NR_thumb_semihosting) { + env->regs[0] = do_arm_semihosting (env); } else if (n >= ARM_SYSCALL_BASE) { /* linux syscall */ n -= ARM_SYSCALL_BASE; @@ -1207,6 +1210,10 @@ int main(int argc, char **argv) env->regs[i] = regs->uregs[i]; } env->cpsr = regs->uregs[16]; + ts->stack_base = info->start_stack; + ts->heap_base = info->brk; + /* This will be filled in on the first SYS_HEAPINFO call. */ + ts->heap_limit = 0; } #elif defined(TARGET_SPARC) { diff --git a/linux-user/qemu.h b/linux-user/qemu.h index 2a815eb81f..f385a1c92b 100644 --- a/linux-user/qemu.h +++ b/linux-user/qemu.h @@ -62,6 +62,11 @@ typedef struct TaskState { #ifdef TARGET_ARM /* FPA state */ FPA11 fpa; + /* Extra fields for semihosted binaries. */ + uint32_t stack_base; + uint32_t heap_base; + uint32_t heap_limit; + int swi_errno; #endif #ifdef TARGET_I386 struct target_vm86plus_struct *target_v86; @@ -80,6 +85,7 @@ int elf_exec(const char * filename, char ** argv, char ** envp, struct target_pt_regs * regs, struct image_info *infop); void target_set_brk(char *new_brk); +long do_brk(char *new_brk); void syscall_init(void); long do_syscall(void *cpu_env, int num, long arg1, long arg2, long arg3, long arg4, long arg5, long arg6); diff --git a/linux-user/syscall.c b/linux-user/syscall.c index d69b21e6f6..b00ce90cbf 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -264,7 +264,7 @@ void target_set_brk(char *new_brk) target_original_brk = new_brk; } -static long do_brk(char *new_brk) +long do_brk(char *new_brk) { char *brk_page; long mapped_addr; |