From patchwork Thu Dec 11 15:50:45 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Maciej W. Rozycki" X-Patchwork-Id: 420174 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id ED9F314009B for ; Fri, 12 Dec 2014 02:53:37 +1100 (AEDT) Received: from localhost ([::1]:52196 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Xz63c-0007YG-8c for incoming@patchwork.ozlabs.org; Thu, 11 Dec 2014 10:53:36 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:55032) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Xz615-0003UB-82 for qemu-devel@nongnu.org; Thu, 11 Dec 2014 10:51:05 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1Xz60y-0005Zy-UY for qemu-devel@nongnu.org; Thu, 11 Dec 2014 10:50:59 -0500 Received: from relay1.mentorg.com ([192.94.38.131]:50162) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1Xz60y-0005Zh-IK for qemu-devel@nongnu.org; Thu, 11 Dec 2014 10:50:52 -0500 Received: from nat-ies.mentorg.com ([192.94.31.2] helo=SVR-IES-FEM-01.mgc.mentorg.com) by relay1.mentorg.com with esmtp id 1Xz60x-0002oQ-5s from Maciej_Rozycki@mentor.com ; Thu, 11 Dec 2014 07:50:51 -0800 Received: from localhost (137.202.0.76) by SVR-IES-FEM-01.mgc.mentorg.com (137.202.0.104) with Microsoft SMTP Server (TLS) id 14.3.181.6; Thu, 11 Dec 2014 15:50:48 +0000 Date: Thu, 11 Dec 2014 15:50:45 +0000 From: "Maciej W. Rozycki" To: In-Reply-To: Message-ID: References: User-Agent: Alpine 1.10 (DEB 962 2008-03-14) MIME-Version: 1.0 X-detected-operating-system: by eggs.gnu.org: Windows NT kernel [generic] [fuzzy] X-Received-From: 192.94.38.131 Cc: Leon Alrae , Aurelien Jarno Subject: [Qemu-devel] [PATCH 3/4] target-mips: Add support for MDI semihosting X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Add support for MDI semihosting and for loading executables from GDB through the GDB stub rather via the command line and the `-kernel' option. Use the `qSymbol' RSP packet to interrogate GDB about the `_mdi_syscall' semihosting entry point. Fall back to physical memory mapping for `-kernel /dev/null' to work and to set up the MMU in preparation to loading an executable from GDB. Signed-off-by: Nathan Froyd Signed-off-by: Maciej W. Rozycki --- qemu-mips-semihosting.diff Index: qemu-git-trunk/gdbstub.c =================================================================== --- qemu-git-trunk.orig/gdbstub.c 2014-12-10 20:43:10.000000000 +0000 +++ qemu-git-trunk/gdbstub.c 2014-12-10 20:51:44.628959510 +0000 @@ -1092,6 +1092,36 @@ static int gdb_handle_packet(GDBState *s put_packet(s, buf); } break; + } else if (strncmp(p, "Symbol:", 7) == 0) { +#if defined(TARGET_MIPS) && !defined(CONFIG_USER_ONLY) +#define MDI_SYSCALL_SYMBOL "_mdi_syscall" + if (strcmp(p+7, ":") == 0) { + /* GDB is telling us we can ask for symbols. Look for + _mdi_syscall. */ + memtohex((char *)mem_buf, (const uint8_t *)MDI_SYSCALL_SYMBOL, + strlen(MDI_SYSCALL_SYMBOL)); + mem_buf[strlen(MDI_SYSCALL_SYMBOL)*2] = 0; + snprintf(buf, sizeof(buf), "qSymbol:%s", mem_buf); + put_packet(s, buf); + break; + } else { + /* A response from a previous query. We only send one + query so it must be _mdi_syscall. */ + if (*(p+7) != ':') { + addr = strtoull(p+7, (char **)&p, 16); + hextomem(mem_buf, p+1, strlen(MDI_SYSCALL_SYMBOL)*2); + + if (memcmp(mem_buf, MDI_SYSCALL_SYMBOL, + strlen(MDI_SYSCALL_SYMBOL)) == 0) { + install_semihosting_breakpoint(s->c_cpu, addr); + } + } + } + /* All done, regardless of whether we got the right symbol. */ + put_packet(s, "OK"); + break; +#undef MDI_SYSCALL_SYMBOL +#endif } #ifdef CONFIG_USER_ONLY else if (strncmp(p, "Offsets", 7) == 0) { Index: qemu-git-trunk/hw/core/loader.c =================================================================== --- qemu-git-trunk.orig/hw/core/loader.c 2014-12-10 20:42:12.000000000 +0000 +++ qemu-git-trunk/hw/core/loader.c 2014-12-10 20:44:25.587693472 +0000 @@ -325,9 +325,12 @@ const char *load_elf_strerror(int error) } /* return < 0 if error, otherwise the number of bytes loaded in memory */ -int load_elf(const char *filename, uint64_t (*translate_fn)(void *, uint64_t), - void *translate_opaque, uint64_t *pentry, uint64_t *lowaddr, - uint64_t *highaddr, int big_endian, int elf_machine, int clear_lsb) +int load_elf_introspect(const char *filename, + uint64_t (*translate_fn)(void *, uint64_t), + void *translate_opaque, + uint64_t *pentry, uint64_t *lowaddr, uint64_t *highaddr, + int big_endian, int elf_machine, int clear_lsb, + section_callback_t section_callback) { int fd, data_order, target_data_order, must_swab, ret = ELF_LOAD_FAILED; uint8_t e_ident[EI_NIDENT]; @@ -366,10 +369,12 @@ int load_elf(const char *filename, uint6 lseek(fd, 0, SEEK_SET); if (e_ident[EI_CLASS] == ELFCLASS64) { ret = load_elf64(filename, fd, translate_fn, translate_opaque, must_swab, - pentry, lowaddr, highaddr, elf_machine, clear_lsb); + pentry, lowaddr, highaddr, elf_machine, clear_lsb, + section_callback); } else { ret = load_elf32(filename, fd, translate_fn, translate_opaque, must_swab, - pentry, lowaddr, highaddr, elf_machine, clear_lsb); + pentry, lowaddr, highaddr, elf_machine, clear_lsb, + section_callback); } fail: @@ -377,6 +382,17 @@ int load_elf(const char *filename, uint6 return ret; } +/* return < 0 if error, otherwise the number of bytes loaded in memory */ +int load_elf(const char *filename, uint64_t (*translate_fn)(void *, uint64_t), + void *translate_opaque, + uint64_t *pentry, uint64_t *lowaddr, uint64_t *highaddr, + int big_endian, int elf_machine, int clear_lsb) +{ + return load_elf_introspect(filename, translate_fn, translate_opaque, + pentry, lowaddr, highaddr, + big_endian, elf_machine, clear_lsb, NULL); +} + static void bswap_uboot_header(uboot_image_header_t *hdr) { #ifndef HOST_WORDS_BIGENDIAN Index: qemu-git-trunk/hw/mips/mips_malta.c =================================================================== --- qemu-git-trunk.orig/hw/mips/mips_malta.c 2014-12-10 20:42:12.000000000 +0000 +++ qemu-git-trunk/hw/mips/mips_malta.c 2014-12-10 20:56:26.157522237 +0000 @@ -56,6 +56,8 @@ //#define DEBUG_BOARD_INIT +#define KERNEL_LOAD_ADDR 0x00100000 + #define ENVP_ADDR 0x80002000l #define ENVP_NB_ENTRIES 16 #define ENVP_ENTRY_SIZE 256 @@ -772,6 +774,7 @@ static void GCC_FMT_ATTR(3, 4) prom_set( static int64_t load_kernel (void) { int64_t kernel_entry, kernel_high; + int kernel_size; long initrd_size; ram_addr_t initrd_offset; int big_endian; @@ -786,9 +789,19 @@ static int64_t load_kernel (void) big_endian = 0; #endif - if (load_elf(loaderparams.kernel_filename, cpu_mips_kseg0_to_phys, NULL, - (uint64_t *)&kernel_entry, NULL, (uint64_t *)&kernel_high, - big_endian, ELF_MACHINE, 1) < 0) { + kernel_size = load_elf(loaderparams.kernel_filename, + cpu_mips_kseg0_to_phys, NULL, + (uint64_t *)&kernel_entry, NULL, + (uint64_t *)&kernel_high, + big_endian, ELF_MACHINE, 1); + if (kernel_size < 0) { + kernel_size = load_image_targphys(loaderparams.kernel_filename, + KERNEL_LOAD_ADDR, + ram_size - KERNEL_LOAD_ADDR); + kernel_high = KERNEL_LOAD_ADDR + kernel_size; + kernel_entry = KERNEL_LOAD_ADDR; + } + if (kernel_size < 0) { fprintf(stderr, "qemu: could not load kernel '%s'\n", loaderparams.kernel_filename); exit(1); Index: qemu-git-trunk/hw/mips/mips_mipssim.c =================================================================== --- qemu-git-trunk.orig/hw/mips/mips_mipssim.c 2014-12-10 20:42:12.000000000 +0000 +++ qemu-git-trunk/hw/mips/mips_mipssim.c 2014-12-10 20:51:44.628959510 +0000 @@ -40,6 +40,8 @@ #include "qemu/error-report.h" #include "sysemu/qtest.h" +#define KERNEL_LOAD_ADDR 0x00100000 + static struct _loaderparams { int ram_size; const char *kernel_filename; @@ -47,12 +49,75 @@ static struct _loaderparams { const char *initrd_filename; } loaderparams; +static target_ulong mdi_semihost_bkpt; + +#define ENTWORDS 2 + +static void find_sdeosabi_section(int fd, int elf_class, int must_swab, + uint64_t size, uint64_t offset, + char *name) +{ + const int entsize = ENTWORDS * (elf_class / 8); + + if (semihosting_enabled && + size >= entsize && + strcmp(name, ".sdeosabi") == 0) { + uint64_t section_offset = 0; + + if (lseek(fd, offset, SEEK_SET) < 0) { + return; + } + + while (section_offset < size) { + /* .sdeosabi is organized into pairs of pointer-size words. The + first word in each pair is a numeric tag; the second word + is interpreted according to the tag. For our purposes, + we're looking for tag 2. The second word will be the + address of the _mdi_syscall function. */ + union { + uint64_t u64[ENTWORDS]; + int64_t i64[ENTWORDS]; + uint32_t u32[ENTWORDS]; + int32_t i32[ENTWORDS]; + } bkpt_info; + + if (read(fd, &bkpt_info, entsize) == entsize) { + if (elf_class == 32) { + if (must_swab) { + bswap32s(&bkpt_info.u32[0]); + bswap32s(&bkpt_info.u32[1]); + } + + if (bkpt_info.i32[0] == 2 && bkpt_info.i32[1]) { + mdi_semihost_bkpt = bkpt_info.i32[1]; + break; + } + } else { + if (must_swab) { + bswap64s(&bkpt_info.u64[0]); + bswap64s(&bkpt_info.u64[1]); + } + + if (bkpt_info.i64[0] == 2 && bkpt_info.i64[1]) { + mdi_semihost_bkpt = bkpt_info.i64[1]; + break; + } + } + } else { + break; + } + + section_offset += entsize; + } + } +} + typedef struct ResetData { MIPSCPU *cpu; uint64_t vector; } ResetData; -static int64_t load_kernel(void) +static int64_t load_kernel(CPUMIPSState *env) { int64_t entry, kernel_high; long kernel_size; @@ -66,10 +131,20 @@ static int64_t load_kernel(void) big_endian = 0; #endif - kernel_size = load_elf(loaderparams.kernel_filename, cpu_mips_kseg0_to_phys, - NULL, (uint64_t *)&entry, NULL, - (uint64_t *)&kernel_high, big_endian, - ELF_MACHINE, 1); + kernel_size = load_elf_introspect(loaderparams.kernel_filename, + cpu_mips_kseg0_to_phys, NULL, + (uint64_t *)&entry, + NULL, + (uint64_t *)&kernel_high, + big_endian, ELF_MACHINE, 1, + find_sdeosabi_section); + if (kernel_size < 0) { + kernel_size = load_image_targphys(loaderparams.kernel_filename, + KERNEL_LOAD_ADDR, + ram_size - KERNEL_LOAD_ADDR); + kernel_high = KERNEL_LOAD_ADDR + kernel_size; + entry = KERNEL_LOAD_ADDR; + } if (kernel_size >= 0) { if ((entry & ~0x7fffffffULL) == 0x80000000) entry = (int32_t)entry; @@ -79,6 +154,11 @@ static int64_t load_kernel(void) exit(1); } + /* set up semihosting */ + if (semihosting_enabled && mdi_semihost_bkpt) { + install_semihosting_breakpoint(ENV_GET_CPU(env), mdi_semihost_bkpt); + } + /* load initrd */ initrd_size = 0; initrd_offset = 0; @@ -209,7 +289,7 @@ mips_mipssim_init(MachineState *machine) loaderparams.kernel_filename = kernel_filename; loaderparams.kernel_cmdline = kernel_cmdline; loaderparams.initrd_filename = initrd_filename; - reset_info->vector = load_kernel(); + reset_info->vector = load_kernel(env); } /* Init CPU internal devices. */ Index: qemu-git-trunk/include/exec/softmmu-semi.h =================================================================== --- qemu-git-trunk.orig/include/exec/softmmu-semi.h 2014-12-10 20:43:37.000000000 +0000 +++ qemu-git-trunk/include/exec/softmmu-semi.h 2014-12-10 21:05:49.227897642 +0000 @@ -9,6 +9,12 @@ #ifndef SOFTMMU_SEMI_H #define SOFTMMU_SEMI_H 1 +#ifdef __GNUC__ +#define SOFTMMU_UNUSED __attribute__ ((unused)) +#else +#define SOFTMMU_UNUSED +#endif + static inline uint32_t softmmu_tget32(CPUArchState *env, target_ulong addr) { uint32_t val; @@ -67,6 +73,21 @@ static char *softmmu_lock_user_string(CP return s; } #define lock_user_string(p) softmmu_lock_user_string(env, p) +SOFTMMU_UNUSED +static int softmmu_target_strlen(CPUArchState *env, target_ulong addr) +{ + uint8_t c; + int len; + + len = 0; + do { + cpu_memory_rw_debug(ENV_GET_CPU(env), addr + len, &c, 1, 0); + len++; + } while (c); + + return len - 1; +} +#define target_strlen(p) softmmu_target_strlen(env, p) static void softmmu_unlock_user(CPUArchState *env, void *p, target_ulong addr, target_ulong len) { @@ -77,4 +98,6 @@ static void softmmu_unlock_user(CPUArchS } #define unlock_user(s, args, len) softmmu_unlock_user(env, s, args, len) +#undef SOFTMMU_UNUSED + #endif Index: qemu-git-trunk/include/hw/elf_ops.h =================================================================== --- qemu-git-trunk.orig/include/hw/elf_ops.h 2014-12-10 20:42:12.000000000 +0000 +++ qemu-git-trunk/include/hw/elf_ops.h 2014-12-10 20:51:45.128079709 +0000 @@ -97,8 +97,14 @@ static int glue(symcmp, SZ)(const void * : ((sym0->st_value > sym1->st_value) ? 1 : 0); } +/* Load function symbols for later groveling. If SECTION_CALLBACK is + non-NULL, it will be called with information about each section in + the binary. This interface enables to the caller to mine the binary + for useful information about the ABI, such as whether the CPU chosen is + compatible with the binary. */ static int glue(load_symbols, SZ)(struct elfhdr *ehdr, int fd, int must_swab, - int clear_lsb) + int clear_lsb, + section_callback_t section_callback) { struct elf_shdr *symtab, *strtab, *shdr_table = NULL; struct elf_sym *syms = NULL; @@ -117,6 +123,28 @@ static int glue(load_symbols, SZ)(struct } } + /* Permit machines to grovel through the ELF file looking for + interesting bits of information. */ + if (section_callback && ehdr->e_shstrndx != SHN_UNDEF) { + char *shstr = NULL; + struct elf_shdr *shstrtab = &shdr_table[ehdr->e_shstrndx]; + + shstr = load_at(fd, shstrtab->sh_offset, shstrtab->sh_size); + if (!shstr) { + goto fail_callback; + } + + for (i = 0; i < ehdr->e_shnum; i++) { + struct elf_shdr *sh = shdr_table + i; + + section_callback(fd, SZ, must_swab, sh->sh_size, sh->sh_offset, + &shstr[sh->sh_name]); + } + + fail_callback: + free(shstr); + } + symtab = glue(find_section, SZ)(shdr_table, ehdr->e_shnum, SHT_SYMTAB); if (!symtab) goto fail; @@ -187,7 +215,8 @@ static int glue(load_elf, SZ)(const char void *translate_opaque, int must_swab, uint64_t *pentry, uint64_t *lowaddr, uint64_t *highaddr, - int elf_machine, int clear_lsb) + int elf_machine, int clear_lsb, + section_callback_t section_callback) { struct elfhdr ehdr; struct elf_phdr *phdr = NULL, *ph; @@ -236,7 +265,7 @@ static int glue(load_elf, SZ)(const char if (pentry) *pentry = (uint64_t)(elf_sword)ehdr.e_entry; - glue(load_symbols, SZ)(&ehdr, fd, must_swab, clear_lsb); + glue(load_symbols, SZ)(&ehdr, fd, must_swab, clear_lsb, section_callback); size = ehdr.e_phnum * sizeof(phdr[0]); lseek(fd, ehdr.e_phoff, SEEK_SET); Index: qemu-git-trunk/include/hw/loader.h =================================================================== --- qemu-git-trunk.orig/include/hw/loader.h 2014-12-10 20:42:12.000000000 +0000 +++ qemu-git-trunk/include/hw/loader.h 2014-12-10 20:51:45.629009422 +0000 @@ -27,6 +27,14 @@ int load_elf(const char *filename, uint6 void *translate_opaque, uint64_t *pentry, uint64_t *lowaddr, uint64_t *highaddr, int big_endian, int elf_machine, int clear_lsb); +typedef void (*section_callback_t)(int fd, int elf_class, int must_swab, + uint64_t size, uint64_t offset, char *name); +int load_elf_introspect(const char *filename, + uint64_t (*translate_fn)(void *, uint64_t), + void *translate_opaque, + uint64_t *pentry, uint64_t *lowaddr, uint64_t *highaddr, + int big_endian, int elf_machine, int clear_lsb, + section_callback_t callback); int load_aout(const char *filename, hwaddr addr, int max_sz, int bswap_needed, hwaddr target_page_size); int load_uimage(const char *filename, hwaddr *ep, Index: qemu-git-trunk/qemu-options.hx =================================================================== --- qemu-git-trunk.orig/qemu-options.hx 2014-12-10 20:42:12.000000000 +0000 +++ qemu-git-trunk/qemu-options.hx 2014-12-10 20:44:27.087583724 +0000 @@ -3212,11 +3212,11 @@ Set OpenBIOS nvram @var{variable} to giv ETEXI DEF("semihosting", 0, QEMU_OPTION_semihosting, "-semihosting semihosting mode\n", - QEMU_ARCH_ARM | QEMU_ARCH_M68K | QEMU_ARCH_XTENSA | QEMU_ARCH_LM32) + QEMU_ARCH_ARM | QEMU_ARCH_M68K | QEMU_ARCH_MIPS | QEMU_ARCH_XTENSA | QEMU_ARCH_LM32) STEXI @item -semihosting @findex -semihosting -Semihosting mode (ARM, M68K, Xtensa only). +Semihosting mode (ARM, M68K, MIPS, Xtensa only). ETEXI DEF("old-param", 0, QEMU_OPTION_old_param, "-old-param old param mode\n", QEMU_ARCH_ARM) Index: qemu-git-trunk/target-mips/Makefile.objs =================================================================== --- qemu-git-trunk.orig/target-mips/Makefile.objs 2014-12-10 20:43:52.000000000 +0000 +++ qemu-git-trunk/target-mips/Makefile.objs 2014-12-10 20:44:27.587970655 +0000 @@ -1,5 +1,5 @@ obj-y += translate.o dsp_helper.o lmi_helper.o msa_helper.o op_helper.o obj-y += helper.o cpu.o -obj-y += gdbstub.o +obj-y += gdbstub.o mips-semi.o obj-$(CONFIG_SOFTMMU) += machine.o obj-$(CONFIG_KVM) += kvm.o Index: qemu-git-trunk/target-mips/cpu.h =================================================================== --- qemu-git-trunk.orig/target-mips/cpu.h 2014-12-10 20:43:10.000000000 +0000 +++ qemu-git-trunk/target-mips/cpu.h 2014-12-10 20:51:45.629009422 +0000 @@ -789,6 +789,10 @@ static inline void restore_flush_mode(CP &env->active_fpu.fp_status); } +extern target_ulong mdi_semihost_breakpoint; +void do_mips_semihosting(CPUMIPSState *env); +void install_semihosting_breakpoint(CPUState *cs, target_ulong bkpt_address); + static inline void cpu_get_tb_cpu_state(CPUMIPSState *env, target_ulong *pc, target_ulong *cs_base, int *flags) { Index: qemu-git-trunk/target-mips/helper.c =================================================================== --- qemu-git-trunk.orig/target-mips/helper.c 2014-12-10 20:43:09.000000000 +0000 +++ qemu-git-trunk/target-mips/helper.c 2014-12-10 20:51:46.127892399 +0000 @@ -18,14 +18,13 @@ */ #include #include -#include #include -#include #include #include "cpu.h" -#include "sysemu/kvm.h" #include "exec/cpu_ldst.h" +#include "sysemu/kvm.h" +#include "sysemu/sysemu.h" enum { TLBRET_XI = -6, @@ -390,6 +389,17 @@ hwaddr cpu_mips_translate_address(CPUMIP } #endif +#if !defined(CONFIG_USER_ONLY) +target_ulong mdi_semihost_breakpoint; + +void install_semihosting_breakpoint(CPUState *cs, target_ulong bkpt_address) +{ + if (semihosting_enabled) { + mdi_semihost_breakpoint = bkpt_address & ~(target_ulong)1; + } +} +#endif + static const char * const excp_names[EXCP_LAST + 1] = { [EXCP_RESET] = "reset", [EXCP_SRESET] = "soft reset", Index: qemu-git-trunk/target-mips/helper.h =================================================================== --- qemu-git-trunk.orig/target-mips/helper.h 2014-12-10 20:43:09.000000000 +0000 +++ qemu-git-trunk/target-mips/helper.h 2014-12-10 20:51:35.128974656 +0000 @@ -173,6 +173,7 @@ DEF_HELPER_0(dmt, tl) DEF_HELPER_0(emt, tl) DEF_HELPER_1(dvpe, tl, env) DEF_HELPER_1(evpe, tl, env) +DEF_HELPER_1(semihosting_call, void, env) #endif /* !CONFIG_USER_ONLY */ /* microMIPS functions */ Index: qemu-git-trunk/target-mips/mips-semi.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ qemu-git-trunk/target-mips/mips-semi.c 2014-12-10 20:44:28.588927918 +0000 @@ -0,0 +1,242 @@ +/* + * MIPS MDI semihosting syscalls + * + * Copyright (c) 2009 CodeSourcery. + * Written by Nathan Froyd. + * + * 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, see . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cpu.h" +#include "qemu-common.h" +#include "sysemu/sysemu.h" +#include "exec/gdbstub.h" +#include "exec/softmmu-semi.h" + +#define HOSTED_OPEN 0 +#define HOSTED_CLOSE 1 +#define HOSTED_READ 2 +#define HOSTED_WRITE 3 +#define HOSTED_GETCHAR 4 +#define HOSTED_PUTCHAR 5 +#define HOSTED_LSEEK32 6 +#define HOSTED_GETTIME 7 +#define HOSTED_EXIT 8 +#define HOSTED_MOVED 9 +#define HOSTED_GETARGS 10 +#define HOSTED_ISATTY 11 +#define HOSTED_PROFIL 12 +#define HOSTED_SIGHOOK 13 + +#define ARG(n) (env->active_tc.gpr[4 + (n)]) + +static void mips_store_result(CPUMIPSState *env, + target_ulong ret, target_ulong err) +{ + env->active_tc.PC = env->active_tc.gpr[31] & ~(target_ulong)1; + if (env->active_tc.gpr[31] & 1) { + env->hflags |= MIPS_HFLAG_M16; + } else { + env->hflags &= ~MIPS_HFLAG_M16; + } + env->active_tc.gpr[2] = ret; + env->active_tc.gpr[3] = err; +} + +static void mips_semi_cb(CPUState *cs, target_ulong ret, target_ulong err) +{ + MIPSCPU *cpu = MIPS_CPU(cs); + CPUMIPSState *env = &cpu->env; + + mips_store_result(env, ret, err); +} + +#define GDB_O_RDONLY 0x0 +#define GDB_O_WRONLY 0x1 +#define GDB_O_RDWR 0x2 +#define GDB_O_APPEND 0x8 +#define GDB_O_CREAT 0x200 +#define GDB_O_TRUNC 0x400 +#define GDB_O_EXCL 0x800 + +static int translate_openflags(int flags) +{ + int hf; + + if (flags & GDB_O_WRONLY) { + hf = O_WRONLY; + } else if (flags & GDB_O_RDWR) { + hf = O_RDWR; + } else { + hf = O_RDONLY; + } + + if (flags & GDB_O_APPEND) { + hf |= O_APPEND; + } + if (flags & GDB_O_CREAT) { + hf |= O_CREAT; + } + if (flags & GDB_O_TRUNC) { + hf |= O_TRUNC; + } + if (flags & GDB_O_EXCL) { + hf |= O_EXCL; + } + + return hf; +} + +void do_mips_semihosting(CPUMIPSState *env) +{ + target_ulong result; + void *p; + uint32_t len; + target_ulong err = 0; + char *s; + + switch (env->active_tc.gpr[2]) { + case HOSTED_OPEN: + if (use_gdb_syscalls()) { + gdb_do_syscall(mips_semi_cb, "open,%s,%x,%x", ARG(0), + target_strlen(ARG(0))+1, ARG(1), ARG(2)); + return; + } else { + s = lock_user_string(ARG(0)); + if (s) { + result = open(s, translate_openflags(ARG(1)), ARG(2)); + unlock_user(s, ARG(0), 0); + } else { + result = -1; + } + } + break; + case HOSTED_CLOSE: + /* Ignore attempts to close stdin/out/err */ + if (ARG(0) > 2) { + if (use_gdb_syscalls()) { + gdb_do_syscall(mips_semi_cb, "close,%x", ARG(0)); + return; + } else { + result = close(ARG(0)); + } + } else { + result = 0; + } + break; + case HOSTED_READ: + len = ARG(2); + if (use_gdb_syscalls()) { + gdb_do_syscall(mips_semi_cb, "read,%x,%x,%x", + ARG(0), ARG(1), len); + return; + } else { + p = lock_user(VERIFY_WRITE, ARG(1), len, 0); + if (p) { + result = read(ARG(0), p, len); + unlock_user(p, ARG(1), len); + } else { + result = -1; + } + } + break; + case HOSTED_WRITE: + len = ARG(2); + if (use_gdb_syscalls()) { + gdb_do_syscall(mips_semi_cb, "write,%x,%x,%x", + ARG(0), ARG(1), len); + return; + } else { + p = lock_user(VERIFY_READ, ARG(1), len, 1); + if (p) { + result = write(ARG(0), p, len); + unlock_user(p, ARG(1), len); + } else { + result = -1; + } + } + break; + case HOSTED_LSEEK32: + { + off_t off = (target_long)ARG(1); + if (use_gdb_syscalls()) { + gdb_do_syscall(mips_semi_cb, "lseek,%x,%lx,%x", + ARG(0), off, ARG(2)); + return; + } else { + off = lseek(ARG(0), off, ARG(2)); + result = (uint32_t)off; + } + } + break; + case HOSTED_GETTIME: + { + qemu_timeval tv; + result = qemu_gettimeofday(&tv); + if (!result) { + result = tv.tv_sec; + err = tv.tv_usec; + } else { + result = -1; + err = errno; + } + } + break; + case HOSTED_EXIT: + /* FIXME: ideally we want to inform gdb about program + exit whenever gdb is connected, even if syscalls + are not handled by gdb. */ + if (use_gdb_syscalls()) { + gdb_exit(env, ARG(0)); + } + exit(ARG(0)); + break; + case HOSTED_ISATTY: + if (use_gdb_syscalls()) { + gdb_do_syscall(mips_semi_cb, "isatty,%x", ARG(0)); + return; + } else { + result = isatty(ARG(0)); + } + break; + case HOSTED_GETARGS: + /* argc gets placed in A0, argv gets copied onto the stack and + the address of the copy placed in A1. We have nothing to + provide in terms of argc/argv, so just stuff NULL in + each. */ + ARG(1) = ARG(0) = 0; + result = 0; + break; + case HOSTED_GETCHAR: + case HOSTED_PUTCHAR: + case HOSTED_MOVED: + case HOSTED_PROFIL: + case HOSTED_SIGHOOK: + default: + result = -1; + err = 88; /* ENOSYS */ + break; + } + + mips_store_result(env, result, err); +} Index: qemu-git-trunk/target-mips/op_helper.c =================================================================== --- qemu-git-trunk.orig/target-mips/op_helper.c 2014-12-10 20:43:10.000000000 +0000 +++ qemu-git-trunk/target-mips/op_helper.c 2014-12-10 20:51:35.128974656 +0000 @@ -1764,6 +1764,11 @@ target_ulong helper_evpe(CPUMIPSState *e } return prev; } + +void helper_semihosting_call(CPUMIPSState *env) +{ + do_mips_semihosting(env); +} #endif /* !CONFIG_USER_ONLY */ void helper_fork(target_ulong arg1, target_ulong arg2) Index: qemu-git-trunk/target-mips/translate.c =================================================================== --- qemu-git-trunk.orig/target-mips/translate.c 2014-12-10 20:43:10.000000000 +0000 +++ qemu-git-trunk/target-mips/translate.c 2014-12-10 20:51:35.128974656 +0000 @@ -29,6 +29,7 @@ #include "exec/helper-proto.h" #include "exec/helper-gen.h" #include "sysemu/kvm.h" +#include "sysemu/sysemu.h" #include "trace-tcg.h" @@ -19142,6 +19143,17 @@ gen_intermediate_code_internal(MIPSCPU * } } } +#ifndef CONFIG_USER_ONLY + if (unlikely(semihosting_enabled) && + mdi_semihost_breakpoint && + ctx.pc == mdi_semihost_breakpoint) { + ctx.bstate = BS_EXCP; + gen_helper_semihosting_call(cpu_env); + tcg_gen_exit_tb(0); + ctx.pc += 4; + goto done_generating; + } +#endif if (search_pc) { j = tcg_ctx.gen_opc_ptr - tcg_ctx.gen_opc_buf;