diff --git a/NEWS b/NEWS index 1af13311b..38ae4da62 100644 --- a/NEWS +++ b/NEWS @@ -17,6 +17,7 @@ Target Layer: - register names use "sp" not "r13" - add top-level "mcr" and "mrc" commands, replacing various core-specific operations + - basic semihosting support ARM11 - Preliminary ETM and ETB hookup - accelerated "flash erase_check" diff --git a/doc/openocd.texi b/doc/openocd.texi index 7e232117c..4d13f073c 100644 --- a/doc/openocd.texi +++ b/doc/openocd.texi @@ -5674,6 +5674,17 @@ cables (FT2232), but might be unsafe if used with targets running at very low speeds, like the 32kHz startup clock of an AT91RM9200. @end deffn +@deffn Command {arm7_9 semihosting} [@option{enable}|@option{disable}] +Display status of semihosting, after optionally changing that status. + +Semihosting allows for code executing on an ARM target to use the +I/O facilities on the host computer i.e. the system where OpenOCD +is running. The target application must be linked against a library +implementing the ARM semihosting convention that forwards operation +requests by using a special SVC instruction that is trapped at the +Supervisor Call vector by OpenOCD. +@end deffn + @subsection ARM720T specific commands @cindex ARM720T diff --git a/src/target/Makefile.am b/src/target/Makefile.am index 7631beaad..bd7bf7ae2 100644 --- a/src/target/Makefile.am +++ b/src/target/Makefile.am @@ -78,6 +78,7 @@ ARM_DEBUG_SRC = \ arm_jtag.c \ arm_disassembler.c \ arm_simulator.c \ + arm_semihosting.c \ arm_adi_v5.c \ embeddedice.c \ trace.c \ @@ -101,6 +102,7 @@ noinst_HEADERS = \ arm_adi_v5.h \ arm_disassembler.h \ arm_simulator.h \ + arm_semihosting.h \ arm7_9_common.h \ arm7tdmi.h \ arm720t.h \ diff --git a/src/target/arm7_9_common.c b/src/target/arm7_9_common.c index 255a85f57..7318b5f36 100644 --- a/src/target/arm7_9_common.c +++ b/src/target/arm7_9_common.c @@ -36,6 +36,7 @@ #include "etm.h" #include #include "arm_simulator.h" +#include "arm_semihosting.h" #include "algorithm.h" #include "register.h" @@ -915,6 +916,9 @@ int arm7_9_poll(struct target *target) } } + if (arm_semihosting(target, &retval) != 0) + return retval; + if ((retval = target_call_event_callbacks(target, TARGET_EVENT_HALTED)) != ERROR_OK) { return retval; @@ -2814,6 +2818,39 @@ COMMAND_HANDLER(handle_arm7_9_dcc_downloads_command) return ERROR_OK; } +COMMAND_HANDLER(handle_arm7_9_semihosting_command) +{ + struct target *target = get_current_target(CMD_CTX); + struct arm7_9_common *arm7_9 = target_to_arm7_9(target); + + if (!is_arm7_9(arm7_9)) + { + command_print(CMD_CTX, "current target isn't an ARM7/ARM9 target"); + return ERROR_TARGET_INVALID; + } + + if (CMD_ARGC > 0) + { + COMMAND_PARSE_ENABLE(CMD_ARGV[0], semihosting_active); + + /* TODO: support other methods if vector catch is unavailable */ + if (arm7_9->has_vector_catch) { + struct reg *vector_catch = &arm7_9->eice_cache->reg_list[EICE_VEC_CATCH]; + if (!vector_catch->valid) + embeddedice_read_reg(vector_catch); + buf_set_u32(vector_catch->value, 2, 1, semihosting_active); + embeddedice_store_reg(vector_catch); + } else if (semihosting_active) { + command_print(CMD_CTX, "vector catch unavailable"); + semihosting_active = 0; + } + } + + command_print(CMD_CTX, "semihosting is %s", (semihosting_active) ? "enabled" : "disabled"); + + return ERROR_OK; +} + int arm7_9_init_arch_info(struct target *target, struct arm7_9_common *arm7_9) { int retval = ERROR_OK; @@ -2867,6 +2904,13 @@ static const struct command_registration arm7_9_any_command_handlers[] = { .usage = "", .help = "use DCC downloads for larger memory writes", }, + { + "semihosting", + .handler = &handle_arm7_9_semihosting_command, + .mode = COMMAND_EXEC, + .usage = "", + .help = "activate support for semihosting operations", + }, COMMAND_REGISTRATION_DONE }; const struct command_registration arm7_9_command_handlers[] = { diff --git a/src/target/arm_semihosting.c b/src/target/arm_semihosting.c new file mode 100644 index 000000000..5e0a2bedc --- /dev/null +++ b/src/target/arm_semihosting.c @@ -0,0 +1,442 @@ +/*************************************************************************** + * Copyright (C) 2009 by Marvell Technology Group Ltd. * + * Written by Nicolas Pitre * + * * + * 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +/** + * @file + * Hold ARM semihosting support. + * + * Semihosting enables code running on an ARM target to use the I/O + * facilities on the host computer. The target application must be linked + * against a library that forwards operation requests by using the SVC + * instruction trapped at the Supervisor Call vector by the debugger. + * Details can be found in chapter 8 of DUI0203I_rvct_developer_guide.pdf + * from ARM Ltd. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "armv4_5.h" +#include "register.h" +#include "arm_semihosting.h" +#include +#include + +/* TODO: this needs to be per target */ +int semihosting_active; +int semihosting_errno; + +static int do_semihosting(struct target *target) +{ + struct arm *armv4_5 = target_to_armv4_5(target); + uint32_t r0 = buf_get_u32(armv4_5->core_cache->reg_list[0].value, 0, 32); + uint32_t r1 = buf_get_u32(armv4_5->core_cache->reg_list[1].value, 0, 32); + uint32_t lr = buf_get_u32(ARMV4_5_CORE_REG_MODE(armv4_5->core_cache, ARMV4_5_MODE_SVC, 14).value, 0, 32); + uint32_t spsr = buf_get_u32(armv4_5->spsr->value, 0, 32);; + uint8_t params[16]; + int retval, result; + + /* + * TODO: lots of security issues are not considered yet, such as: + * - no validation on target provided file descriptors + * - no safety checks on opened/deleted/renamed file paths + * Beware the target app you use this support with. + */ + switch (r0) { + case 0x01: /* SYS_OPEN */ + retval = target_read_memory(target, r1, 4, 3, params); + if (retval != ERROR_OK) + return retval; + else { + uint32_t a = target_buffer_get_u32(target, params+0); + uint32_t m = target_buffer_get_u32(target, params+4); + uint32_t l = target_buffer_get_u32(target, params+8); + if (l <= 255 && m <= 11) { + uint8_t fn[256]; + int mode; + retval = target_read_memory(target, a, 1, l, fn); + if (retval != ERROR_OK) + return retval; + fn[l] = 0; + if (m & 0x2) + mode = O_RDWR; + else if (m & 0xc) + mode = O_WRONLY; + else + mode = O_RDONLY; + if (m >= 8) + mode |= O_CREAT|O_APPEND; + else if (m >= 4) + mode |= O_CREAT|O_TRUNC; + if (strcmp((char *)fn, ":tt") == 0) { + if ((mode & 3) == 0) + result = dup(0); + else + result = dup(1); + } else + result = open((char *)fn, mode); + semihosting_errno = errno; + } else { + result = -1; + semihosting_errno = EINVAL; + } + } + break; + + case 0x02: /* SYS_CLOSE */ + retval = target_read_memory(target, r1, 4, 1, params); + if (retval != ERROR_OK) + return retval; + else { + int fd = target_buffer_get_u32(target, params+0); + result = close(fd); + semihosting_errno = errno; + } + break; + + case 0x03: /* SYS_WRITEC */ + { + unsigned char c; + retval = target_read_memory(target, r1, 1, 1, &c); + if (retval != ERROR_OK) + return retval; + putchar(c); + result = 0; + } + break; + + case 0x04: /* SYS_WRITE0 */ + do { + unsigned char c; + retval = target_read_memory(target, r1, 1, 1, &c); + if (retval != ERROR_OK) + return retval; + if (!c) + break; + putchar(c); + } while (1); + result = 0; + break; + + case 0x05: /* SYS_WRITE */ + retval = target_read_memory(target, r1, 4, 3, params); + if (retval != ERROR_OK) + return retval; + else { + int fd = target_buffer_get_u32(target, params+0); + uint32_t a = target_buffer_get_u32(target, params+4); + size_t l = target_buffer_get_u32(target, params+8); + uint8_t *buf = malloc(l); + if (!buf) { + result = -1; + semihosting_errno = ENOMEM; + } else { + retval = target_read_buffer(target, a, l, buf); + if (retval != ERROR_OK) { + free(buf); + return retval; + } + result = write(fd, buf, l); + semihosting_errno = errno; + if (result >= 0) + result = l - result; + free(buf); + } + } + break; + + case 0x06: /* SYS_READ */ + retval = target_read_memory(target, r1, 4, 3, params); + if (retval != ERROR_OK) + return retval; + else { + int fd = target_buffer_get_u32(target, params+0); + uint32_t a = target_buffer_get_u32(target, params+4); + ssize_t l = target_buffer_get_u32(target, params+8); + uint8_t *buf = malloc(l); + if (!buf) { + result = -1; + semihosting_errno = ENOMEM; + } else { + result = read(fd, buf, l); + semihosting_errno = errno; + if (result > 0) { + retval = target_write_buffer(target, a, result, buf); + if (retval != ERROR_OK) { + free(buf); + return retval; + } + result = l - result; + } + free(buf); + } + } + break; + + case 0x07: /* SYS_READC */ + result = getchar(); + break; + + case 0x08: /* SYS_ISERROR */ + retval = target_read_memory(target, r1, 4, 1, params); + if (retval != ERROR_OK) + return retval; + result = (target_buffer_get_u32(target, params+0) != 0); + break; + + case 0x09: /* SYS_ISTTY */ + retval = target_read_memory(target, r1, 4, 1, params); + if (retval != ERROR_OK) + return retval; + result = isatty(target_buffer_get_u32(target, params+0)); + break; + + case 0x0a: /* SYS_SEEK */ + retval = target_read_memory(target, r1, 4, 2, params); + if (retval != ERROR_OK) + return retval; + else { + int fd = target_buffer_get_u32(target, params+0); + off_t pos = target_buffer_get_u32(target, params+4); + result = lseek(fd, pos, SEEK_SET); + semihosting_errno = errno; + if (result == pos) + result = 0; + } + break; + + case 0x0c: /* SYS_FLEN */ + retval = target_read_memory(target, r1, 4, 1, params); + if (retval != ERROR_OK) + return retval; + else { + int fd = target_buffer_get_u32(target, params+0); + off_t cur = lseek(fd, 0, SEEK_CUR); + if (cur == (off_t)-1) { + semihosting_errno = errno; + result = -1; + break; + } + result = lseek(fd, 0, SEEK_END); + semihosting_errno = errno; + if (lseek(fd, cur, SEEK_SET) == (off_t)-1) { + semihosting_errno = errno; + result = -1; + } + } + break; + + case 0x0e: /* SYS_REMOVE */ + retval = target_read_memory(target, r1, 4, 2, params); + if (retval != ERROR_OK) + return retval; + else { + uint32_t a = target_buffer_get_u32(target, params+0); + uint32_t l = target_buffer_get_u32(target, params+4); + if (l <= 255) { + uint8_t fn[256]; + retval = target_read_memory(target, a, 1, l, fn); + if (retval != ERROR_OK) + return retval; + fn[l] = 0; + result = remove((char *)fn); + semihosting_errno = errno; + } else { + result = -1; + semihosting_errno = EINVAL; + } + } + break; + + case 0x0f: /* SYS_RENAME */ + retval = target_read_memory(target, r1, 4, 4, params); + if (retval != ERROR_OK) + return retval; + else { + uint32_t a1 = target_buffer_get_u32(target, params+0); + uint32_t l1 = target_buffer_get_u32(target, params+4); + uint32_t a2 = target_buffer_get_u32(target, params+8); + uint32_t l2 = target_buffer_get_u32(target, params+12); + if (l1 <= 255 && l2 <= 255) { + uint8_t fn1[256], fn2[256]; + retval = target_read_memory(target, a1, 1, l1, fn1); + if (retval != ERROR_OK) + return retval; + retval = target_read_memory(target, a2, 1, l2, fn2); + if (retval != ERROR_OK) + return retval; + fn1[l1] = 0; + fn2[l2] = 0; + result = rename((char *)fn1, (char *)fn2); + semihosting_errno = errno; + } else { + result = -1; + semihosting_errno = EINVAL; + } + } + break; + + case 0x11: /* SYS_TIME */ + result = time(NULL); + break; + + case 0x13: /* SYS_ERRNO */ + result = semihosting_errno; + break; + + case 0x15: /* SYS_GET_CMDLINE */ + retval = target_read_memory(target, r1, 4, 2, params); + if (retval != ERROR_OK) + return retval; + else { + uint32_t a = target_buffer_get_u32(target, params+0); + uint32_t l = target_buffer_get_u32(target, params+4); + char *arg = "foobar"; + uint32_t s = strlen(arg) + 1; + if (l < s) + result = -1; + else { + retval = target_write_buffer(target, a, s, (void*)arg); + if (retval != ERROR_OK) + return retval; + result = 0; + } + } + break; + + case 0x16: /* SYS_HEAPINFO */ + retval = target_read_memory(target, r1, 4, 1, params); + if (retval != ERROR_OK) + return retval; + else { + uint32_t a = target_buffer_get_u32(target, params+0); + /* tell the remote we have no idea */ + memset(params, 0, 4*4); + retval = target_write_memory(target, a, 4, 4, params); + if (retval != ERROR_OK) + return retval; + result = 0; + } + break; + + case 0x18: /* angel_SWIreason_ReportException */ + switch (r1) { + case 0x20026: /* ADP_Stopped_ApplicationExit */ + fprintf(stderr, "semihosting: *** application exited ***\n"); + break; + case 0x20000: /* ADP_Stopped_BranchThroughZero */ + case 0x20001: /* ADP_Stopped_UndefinedInstr */ + case 0x20002: /* ADP_Stopped_SoftwareInterrupt */ + case 0x20003: /* ADP_Stopped_PrefetchAbort */ + case 0x20004: /* ADP_Stopped_DataAbort */ + case 0x20005: /* ADP_Stopped_AddressException */ + case 0x20006: /* ADP_Stopped_IRQ */ + case 0x20007: /* ADP_Stopped_FIQ */ + case 0x20020: /* ADP_Stopped_BreakPoint */ + case 0x20021: /* ADP_Stopped_WatchPoint */ + case 0x20022: /* ADP_Stopped_StepComplete */ + case 0x20023: /* ADP_Stopped_RunTimeErrorUnknown */ + case 0x20024: /* ADP_Stopped_InternalError */ + case 0x20025: /* ADP_Stopped_UserInterruption */ + case 0x20027: /* ADP_Stopped_StackOverflow */ + case 0x20028: /* ADP_Stopped_DivisionByZero */ + case 0x20029: /* ADP_Stopped_OSSpecific */ + default: + fprintf(stderr, "semihosting: exception %#x\n", r1); + } + return target_call_event_callbacks(target, TARGET_EVENT_HALTED); + + case 0x0d: /* SYS_TMPNAM */ + case 0x10: /* SYS_CLOCK */ + case 0x12: /* SYS_SYSTEM */ + case 0x17: /* angel_SWIreason_EnterSVC */ + case 0x30: /* SYS_ELAPSED */ + case 0x31: /* SYS_TICKFREQ */ + default: + fprintf(stderr, "semihosting: unsupported call %#x\n", r0); + result = -1; + semihosting_errno = ENOTSUP; + } + + /* resume execution to the original mode */ + buf_set_u32(armv4_5->core_cache->reg_list[0].value, 0, 32, result); + armv4_5->core_cache->reg_list[0].dirty = 1; + buf_set_u32(armv4_5->core_cache->reg_list[15].value, 0, 32, lr); + armv4_5->core_cache->reg_list[15].dirty = 1; + buf_set_u32(armv4_5->core_cache->reg_list[ARMV4_5_CPSR].value, 0, 32, spsr); + armv4_5->core_cache->reg_list[ARMV4_5_CPSR].dirty = 1; + armv4_5->core_mode = spsr & 0x1f; + if (spsr & 0x20) + armv4_5->core_state = ARMV4_5_STATE_THUMB; + return target_resume(target, 1, 0, 0, 0); +} + +/** + * Checks for and processes an ARM semihosting request. This is meant + * to be called when the target is stopped due to a debug mode entry. + * If the value 0 is returned then there was nothing to process. A non-zero + * return value signifies that a request was processed and the target resumed, + * or an error was encountered, in which case the caller must return + * immediately. + * + * @param target Pointer to the ARM target to process + * @param retval Pointer to a location where the return code will be stored + * @return non-zero value if a request was processed or an error encountered + */ +int arm_semihosting(struct target *target, int *retval) +{ + struct arm *armv4_5 = target_to_armv4_5(target); + uint32_t lr, spsr; + + if (!semihosting_active || + armv4_5->core_mode != ARMV4_5_MODE_SVC || + buf_get_u32(armv4_5->core_cache->reg_list[15].value, 0, 32) != 0x08) + return 0; + + lr = buf_get_u32(ARMV4_5_CORE_REG_MODE(armv4_5->core_cache, ARMV4_5_MODE_SVC, 14).value, 0, 32); + spsr = buf_get_u32(armv4_5->spsr->value, 0, 32); + + /* check instruction that triggered this trap */ + if (spsr & (1 << 5)) { + /* was in Thumb mode */ + uint8_t insn_buf[2]; + uint16_t insn; + *retval = target_read_memory(target, lr-2, 2, 1, insn_buf); + if (*retval != ERROR_OK) + return 1; + insn = target_buffer_get_u16(target, insn_buf); + if (insn != 0xDFAB) + return 0; + } else { + /* was in ARM mode */ + uint8_t insn_buf[4]; + uint32_t insn; + *retval = target_read_memory(target, lr-4, 4, 1, insn_buf); + if (*retval != ERROR_OK) + return 1; + insn = target_buffer_get_u32(target, insn_buf); + if (insn != 0xEF123456) + return 0; + } + + *retval = do_semihosting(target); + return 1; +} diff --git a/src/target/arm_semihosting.h b/src/target/arm_semihosting.h new file mode 100644 index 000000000..6b9ac566a --- /dev/null +++ b/src/target/arm_semihosting.h @@ -0,0 +1,28 @@ +/*************************************************************************** + * Copyright (C) 2009 by Marvell Technology Group Ltd. * + * Written by Nicolas Pitre * + * * + * 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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef ARM_SEMIHOSTING_H +#define ARM_SEMIHOSTING_H + +extern int semihosting_active; + +int arm_semihosting(struct target *target, int *retval); + +#endif