Hi, I'm using OpenOCD to debug our MSP432 launchpad board as part of a course at the Ruhr University Bochum.
A major problem I encountered was, that semihosted IO does not work at all: The mandated Texas Instruments toolchain uses a custom semihosting protocol called CIO, which is not supported by OpenOCD. It is documented here: https://e2echina.ti.com/cfs-file/__key/communityserver-discussions-components-files/120/CIO-System-Call-Protocol-_2D00_-Texas-Instruments-Wiki.pdf I made an effort to implement a minimal version of a CIO host (cf. Attachment), but due to a lack of understanding of the OpenOCD codebase, I don't know how to properly structure the code. I see potential for such support to be merged into OpenOCD and would be happy to work on a production ready implementation. This likely requires some refactoring of the semihosting_common structures. I'd be happy to hear your thoughts. ---------------------------------------- diff --git a/src/target/Makefile.am b/src/target/Makefile.am index 1fc7d2afa..9c1d4a03f 100644 --- a/src/target/Makefile.am +++ b/src/target/Makefile.am @@ -74,6 +74,7 @@ ARMV6_SRC = \ %D%/arm11_dbgtap.c ARMV7_SRC = \ + %D%/cio.c \ %D%/armv7m.c \ %D%/armv7m_trace.c \ %D%/cortex_m.c \ diff --git a/src/target/armv7m.c b/src/target/armv7m.c index a403b25a9..f71c240f3 100644 --- a/src/target/armv7m.c +++ b/src/target/armv7m.c @@ -28,6 +28,7 @@ #include "config.h" #endif +#include "cio.h" #include "breakpoints.h" #include "armv7m.h" #include "algorithm.h" @@ -1104,5 +1105,8 @@ const struct command_registration armv7m_command_handlers[] = { .usage = "", .chain = arm_all_profiles_command_handlers, }, + { + .chain = cio_command_handlers, + }, COMMAND_REGISTRATION_DONE }; diff --git a/src/target/cio.c b/src/target/cio.c new file mode 100644 index 000000000..ae23b587b --- /dev/null +++ b/src/target/cio.c @@ -0,0 +1,196 @@ +#include "cio.h" + +#include "register.h" + +#include "helper/command.h" +#include "helper/binarybuffer.h" +#include "helper/log.h" + +#include "target/arm.h" +#include "target/target.h" +#include "target/breakpoints.h" + +#define CIOBUF_MIN 288u +#define CIOBUF_ILEN 13u +#define CIOBUF_OLEN 12u + +static int cio_ensure_bufsz(struct cio *cio, uint32_t size) +{ + if (size <= cio->bufsz) { + return ERROR_OK; + } + + uint8_t *new_buf = realloc(cio->buf, size); + if (new_buf != NULL) { + cio->buf = new_buf; + cio->bufsz = size; + return ERROR_OK; + } + + LOG_ERROR("out of memory"); + free(new_buf); + return ERROR_FAIL; +} + +int cio_semihosting(struct target *target, int *retval) +{ + struct cio *cio = target->cio; + if (!cio) { + return 0; + } + + struct arm *arm = target_to_arm(target); + uint32_t pc = buf_get_u32(arm->pc->value, 0, 32); + if (pc != cio->addr_C$$IO$$) { + return 0; + } + + // read 13 byte preamble + if (target_read_buffer(target, cio->addr_CIOBUF, CIOBUF_ILEN, cio->buf) != ERROR_OK) { + LOG_ERROR("read buffer"); + return ERROR_FAIL; + } + + uint32_t sysno = buf_get_u32(cio->buf, 32, 8); + switch (sysno) { + case CIO_DTWRITE: { + uint32_t dev_fd = buf_get_u32(cio->buf, 40, 16); + uint32_t in_length = buf_get_u32(cio->buf, 56, 16); + uint32_t out_length = -1; + + if (dev_fd != cio->stdout_fd && dev_fd != cio->stderr_fd) { + LOG_ERROR("unsupported fileio requested through cio"); + goto dtwrite_response; + } + + if (cio_ensure_bufsz(cio, CIOBUF_ILEN + in_length) != ERROR_OK) { + goto dtwrite_response; + } + + if (target_read_buffer(target, cio->addr_CIOBUF + CIOBUF_ILEN, in_length, cio->buf + CIOBUF_ILEN) != ERROR_OK) { + goto dtwrite_response; + } + + LOG_USER_N("%.*s", in_length, &cio->buf[CIOBUF_ILEN]); + out_length = in_length; + +dtwrite_response: + buf_set_u32(cio->buf, 0, 32, 0); + buf_set_u32(cio->buf, 32, 16, out_length); + if (target_write_buffer(target, cio->addr_CIOBUF, CIOBUF_OLEN, cio->buf) != ERROR_OK) { + LOG_ERROR("unable to write cio response"); + *retval = ERROR_FAIL; + return 0; // pass to gdb + } + break; + } + default: + LOG_INFO("unknown syscall: %hhx", cio->buf[5]); + break; + } + + // Ideally current=1 and breakpoints=0, but for whatever reason, we get stuck at the nop instruction. + if ((*retval = target_resume(target, 0, pc + 2, 0, 0)) != ERROR_OK) { + LOG_INFO("resume failed"); + return 0; + } + + *retval = ERROR_OK; + return 1; +} + +int cio_semihosting_init(struct target *target) +{ + target->cio = malloc(sizeof(*target->cio)); + if (target->cio == NULL) { + LOG_ERROR("out of memory"); + return ERROR_FAIL; + } + + target->cio->buf = malloc(CIOBUF_MIN); + if (target->cio->buf == NULL) { + LOG_ERROR("out of memory"); + free(target->cio); + target->cio = NULL; + return ERROR_FAIL; + } + + target->cio->bufsz = CIOBUF_MIN; + target->cio->addr_C$$IO$$ = 0x0; + target->cio->addr_CIOBUF = 0x20000604; + target->cio->addr_C$$EXIT = 0x0; + // cio expects these open by default + target->cio->stdin_fd = 0; + target->cio->stdout_fd = 1; + target->cio->stderr_fd = 2; + + target->cio->is_active = false; + return ERROR_OK; +} + +COMMAND_HANDLER(handle_cio_semihosting) +{ + struct target *target = get_current_target(CMD_CTX); + + struct cio *cio = target->cio; + + if (CMD_ARGC > 0) { + int is_active; + + COMMAND_PARSE_ENABLE(CMD_ARGV[0], is_active); + + if (cio->is_active) { + // always remove old breakpoint, ignore error + breakpoint_remove(target, cio->addr_C$$IO$$); + } + + if (is_active) { + target_addr_t addr_C$$IO$$ = cio->addr_C$$IO$$; + target_addr_t addr_CIOBUF = cio->addr_CIOBUF; + target_addr_t addr_C$$EXIT = cio->addr_C$$EXIT; + + if (CMD_ARGC > 1) { + COMMAND_PARSE_ADDRESS(CMD_ARGV[1], addr_C$$IO$$); + } + + if (CMD_ARGC > 2) { + COMMAND_PARSE_ADDRESS(CMD_ARGV[2], addr_CIOBUF); + } + + if (CMD_ARGC > 3) { + COMMAND_PARSE_ADDRESS(CMD_ARGV[3], addr_C$$EXIT); + } + + if (breakpoint_add(target, addr_C$$IO$$, 2, BKPT_HARD) != ERROR_OK) { + LOG_ERROR("can't set breakpoint for C$$IO$$"); + return ERROR_TARGET_FAILURE; + } + + /* + if (breakpoint_add(target, addr_C$$EXIT, 2, BKPT_HARD) != ERROR_OK) { + LOG_ERROR("can't set breakpoint for C$$EXIT"); + return ERROR_TARGET_FAILURE; + } + */ + + cio->addr_C$$IO$$ = addr_C$$IO$$; + cio->addr_CIOBUF = addr_CIOBUF; + cio->addr_C$$EXIT = addr_C$$EXIT; + } + + cio->is_active = is_active; + } + + command_print(CMD, "cio is %s", cio->is_active ? "enabled" : "disabled"); + return ERROR_OK; +} + +const struct command_registration cio_command_handlers[] = { + { + .name = "cio", + .mode = COMMAND_EXEC, + .handler = handle_cio_semihosting, + .usage = "('enable' io_addr [buf_addr] [exit_addr] | 'disable')" + }, + COMMAND_REGISTRATION_DONE +}; diff --git a/src/target/cio.h b/src/target/cio.h new file mode 100644 index 000000000..7c8b93213 --- /dev/null +++ b/src/target/cio.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef OPENOCD_TARGET_CIO_H +#define OPENOCD_TARGET_CIO_H + +#include "helper/command.h" + +enum cio_request { + CIO_DTOPEN = 0xf0, + CIO_DTCLOSE = 0xf1, + CIO_DTREAD = 0xf2, + CIO_DTWRITE = 0xf3, + CIO_DTLSEEK = 0xf4, + CIO_DTUNLINK = 0xf5, + CIO_GETENV = 0xf6, + CIO_RENAME = 0xf7, + CIO_GETTIME = 0xf8, + CIO_GETCLK = 0xf9, + CIO_GETTIME64 = 0xfa, + CIO_SYNC = 0xff, +}; + +// @todo: integrate this with struct semihosting +struct cio { + bool is_active; + + uint32_t stdin_fd, stdout_fd, stderr_fd; + + uint8_t *buf; + uint32_t bufsz; + + target_addr_t addr_CIOBUF; + target_addr_t addr_C$$IO$$; + target_addr_t addr_C$$EXIT; +}; + +int cio_semihosting_init(struct target *target); + +int cio_semihosting(struct target *target, int *retval); + +extern const struct command_registration cio_command_handlers[]; + +#endif /* OPENOCD_TARGET_ARM_SEMIHOSTING_H */ diff --git a/src/target/cortex_m.c b/src/target/cortex_m.c index fa95fcbc7..6a9607ed0 100644 --- a/src/target/cortex_m.c +++ b/src/target/cortex_m.c @@ -27,6 +27,7 @@ #include "arm_disassembler.h" #include "register.h" #include "arm_opcodes.h" +#include "cio.h" #include "arm_semihosting.h" #include "smp.h" #include <helper/nvp.h> @@ -1048,7 +1049,11 @@ static int cortex_m_poll_one(struct target *target) return ERROR_OK; } - /* arm_semihosting needs to know registers, don't run if debug entry returned error */ + /* semihosting needs to know registers, don't run if debug entry returned error */ + if (retval == ERROR_OK && cio_semihosting(target, &retval) != 0) { + return retval; + } + if (retval == ERROR_OK && arm_semihosting(target, &retval) != 0) return retval; @@ -2267,6 +2272,7 @@ static int cortex_m_init_target(struct command_context *cmd_ctx, struct target *target) { armv7m_build_reg_cache(target); + cio_semihosting_init(target); arm_semihosting_init(target); return ERROR_OK; } diff --git a/src/target/target.h b/src/target/target.h index abd0b5825..a11a40df1 100644 --- a/src/target/target.h +++ b/src/target/target.h @@ -207,6 +207,7 @@ struct target { /* The semihosting information, extracted from the target. */ struct semihosting *semihosting; + struct cio *cio; }; struct target_list {
diff --git a/src/target/Makefile.am b/src/target/Makefile.am index 1fc7d2afa..9c1d4a03f 100644 --- a/src/target/Makefile.am +++ b/src/target/Makefile.am @@ -74,6 +74,7 @@ ARMV6_SRC = \ %D%/arm11_dbgtap.c ARMV7_SRC = \ + %D%/cio.c \ %D%/armv7m.c \ %D%/armv7m_trace.c \ %D%/cortex_m.c \ diff --git a/src/target/armv7m.c b/src/target/armv7m.c index a403b25a9..f71c240f3 100644 --- a/src/target/armv7m.c +++ b/src/target/armv7m.c @@ -28,6 +28,7 @@ #include "config.h" #endif +#include "cio.h" #include "breakpoints.h" #include "armv7m.h" #include "algorithm.h" @@ -1104,5 +1105,8 @@ const struct command_registration armv7m_command_handlers[] = { .usage = "", .chain = arm_all_profiles_command_handlers, }, + { + .chain = cio_command_handlers, + }, COMMAND_REGISTRATION_DONE }; diff --git a/src/target/cio.c b/src/target/cio.c new file mode 100644 index 000000000..ae23b587b --- /dev/null +++ b/src/target/cio.c @@ -0,0 +1,196 @@ +#include "cio.h" + +#include "register.h" + +#include "helper/command.h" +#include "helper/binarybuffer.h" +#include "helper/log.h" + +#include "target/arm.h" +#include "target/target.h" +#include "target/breakpoints.h" + +#define CIOBUF_MIN 288u +#define CIOBUF_ILEN 13u +#define CIOBUF_OLEN 12u + +static int cio_ensure_bufsz(struct cio *cio, uint32_t size) +{ + if (size <= cio->bufsz) { + return ERROR_OK; + } + + uint8_t *new_buf = realloc(cio->buf, size); + if (new_buf != NULL) { + cio->buf = new_buf; + cio->bufsz = size; + return ERROR_OK; + } + + LOG_ERROR("out of memory"); + free(new_buf); + return ERROR_FAIL; +} + +int cio_semihosting(struct target *target, int *retval) +{ + struct cio *cio = target->cio; + if (!cio) { + return 0; + } + + struct arm *arm = target_to_arm(target); + uint32_t pc = buf_get_u32(arm->pc->value, 0, 32); + if (pc != cio->addr_C$$IO$$) { + return 0; + } + + // read 13 byte preamble + if (target_read_buffer(target, cio->addr_CIOBUF, CIOBUF_ILEN, cio->buf) != ERROR_OK) { + LOG_ERROR("read buffer"); + return ERROR_FAIL; + } + + uint32_t sysno = buf_get_u32(cio->buf, 32, 8); + switch (sysno) { + case CIO_DTWRITE: { + uint32_t dev_fd = buf_get_u32(cio->buf, 40, 16); + uint32_t in_length = buf_get_u32(cio->buf, 56, 16); + uint32_t out_length = -1; + + if (dev_fd != cio->stdout_fd && dev_fd != cio->stderr_fd) { + LOG_ERROR("unsupported fileio requested through cio"); + goto dtwrite_response; + } + + if (cio_ensure_bufsz(cio, CIOBUF_ILEN + in_length) != ERROR_OK) { + goto dtwrite_response; + } + + if (target_read_buffer(target, cio->addr_CIOBUF + CIOBUF_ILEN, in_length, cio->buf + CIOBUF_ILEN) != ERROR_OK) { + goto dtwrite_response; + } + + LOG_USER_N("%.*s", in_length, &cio->buf[CIOBUF_ILEN]); + out_length = in_length; + +dtwrite_response: + buf_set_u32(cio->buf, 0, 32, 0); + buf_set_u32(cio->buf, 32, 16, out_length); + if (target_write_buffer(target, cio->addr_CIOBUF, CIOBUF_OLEN, cio->buf) != ERROR_OK) { + LOG_ERROR("unable to write cio response"); + *retval = ERROR_FAIL; + return 0; // pass to gdb + } + break; + } + default: + LOG_INFO("unknown syscall: %hhx", cio->buf[5]); + break; + } + + // Ideally current=1 and breakpoints=0, but for whatever reason, we get stuck at the nop instruction. + if ((*retval = target_resume(target, 0, pc + 2, 0, 0)) != ERROR_OK) { + LOG_INFO("resume failed"); + return 0; + } + + *retval = ERROR_OK; + return 1; +} + +int cio_semihosting_init(struct target *target) +{ + target->cio = malloc(sizeof(*target->cio)); + if (target->cio == NULL) { + LOG_ERROR("out of memory"); + return ERROR_FAIL; + } + + target->cio->buf = malloc(CIOBUF_MIN); + if (target->cio->buf == NULL) { + LOG_ERROR("out of memory"); + free(target->cio); + target->cio = NULL; + return ERROR_FAIL; + } + + target->cio->bufsz = CIOBUF_MIN; + target->cio->addr_C$$IO$$ = 0x0; + target->cio->addr_CIOBUF = 0x20000604; + target->cio->addr_C$$EXIT = 0x0; + // cio expects these open by default + target->cio->stdin_fd = 0; + target->cio->stdout_fd = 1; + target->cio->stderr_fd = 2; + + target->cio->is_active = false; + return ERROR_OK; +} + +COMMAND_HANDLER(handle_cio_semihosting) +{ + struct target *target = get_current_target(CMD_CTX); + + struct cio *cio = target->cio; + + if (CMD_ARGC > 0) { + int is_active; + + COMMAND_PARSE_ENABLE(CMD_ARGV[0], is_active); + + if (cio->is_active) { + // always remove old breakpoint, ignore error + breakpoint_remove(target, cio->addr_C$$IO$$); + } + + if (is_active) { + target_addr_t addr_C$$IO$$ = cio->addr_C$$IO$$; + target_addr_t addr_CIOBUF = cio->addr_CIOBUF; + target_addr_t addr_C$$EXIT = cio->addr_C$$EXIT; + + if (CMD_ARGC > 1) { + COMMAND_PARSE_ADDRESS(CMD_ARGV[1], addr_C$$IO$$); + } + + if (CMD_ARGC > 2) { + COMMAND_PARSE_ADDRESS(CMD_ARGV[2], addr_CIOBUF); + } + + if (CMD_ARGC > 3) { + COMMAND_PARSE_ADDRESS(CMD_ARGV[3], addr_C$$EXIT); + } + + if (breakpoint_add(target, addr_C$$IO$$, 2, BKPT_HARD) != ERROR_OK) { + LOG_ERROR("can't set breakpoint for C$$IO$$"); + return ERROR_TARGET_FAILURE; + } + + /* + if (breakpoint_add(target, addr_C$$EXIT, 2, BKPT_HARD) != ERROR_OK) { + LOG_ERROR("can't set breakpoint for C$$EXIT"); + return ERROR_TARGET_FAILURE; + } + */ + + cio->addr_C$$IO$$ = addr_C$$IO$$; + cio->addr_CIOBUF = addr_CIOBUF; + cio->addr_C$$EXIT = addr_C$$EXIT; + } + + cio->is_active = is_active; + } + + command_print(CMD, "cio is %s", cio->is_active ? "enabled" : "disabled"); + return ERROR_OK; +} + +const struct command_registration cio_command_handlers[] = { + { + .name = "cio", + .mode = COMMAND_EXEC, + .handler = handle_cio_semihosting, + .usage = "('enable' io_addr [buf_addr] [exit_addr] | 'disable')" + }, + COMMAND_REGISTRATION_DONE +}; diff --git a/src/target/cio.h b/src/target/cio.h new file mode 100644 index 000000000..7c8b93213 --- /dev/null +++ b/src/target/cio.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef OPENOCD_TARGET_CIO_H +#define OPENOCD_TARGET_CIO_H + +#include "helper/command.h" + +enum cio_request { + CIO_DTOPEN = 0xf0, + CIO_DTCLOSE = 0xf1, + CIO_DTREAD = 0xf2, + CIO_DTWRITE = 0xf3, + CIO_DTLSEEK = 0xf4, + CIO_DTUNLINK = 0xf5, + CIO_GETENV = 0xf6, + CIO_RENAME = 0xf7, + CIO_GETTIME = 0xf8, + CIO_GETCLK = 0xf9, + CIO_GETTIME64 = 0xfa, + CIO_SYNC = 0xff, +}; + +// @todo: integrate this with struct semihosting +struct cio { + bool is_active; + + uint32_t stdin_fd, stdout_fd, stderr_fd; + + uint8_t *buf; + uint32_t bufsz; + + target_addr_t addr_CIOBUF; + target_addr_t addr_C$$IO$$; + target_addr_t addr_C$$EXIT; +}; + +int cio_semihosting_init(struct target *target); + +int cio_semihosting(struct target *target, int *retval); + +extern const struct command_registration cio_command_handlers[]; + +#endif /* OPENOCD_TARGET_ARM_SEMIHOSTING_H */ diff --git a/src/target/cortex_m.c b/src/target/cortex_m.c index fa95fcbc7..6a9607ed0 100644 --- a/src/target/cortex_m.c +++ b/src/target/cortex_m.c @@ -27,6 +27,7 @@ #include "arm_disassembler.h" #include "register.h" #include "arm_opcodes.h" +#include "cio.h" #include "arm_semihosting.h" #include "smp.h" #include <helper/nvp.h> @@ -1048,7 +1049,11 @@ static int cortex_m_poll_one(struct target *target) return ERROR_OK; } - /* arm_semihosting needs to know registers, don't run if debug entry returned error */ + /* semihosting needs to know registers, don't run if debug entry returned error */ + if (retval == ERROR_OK && cio_semihosting(target, &retval) != 0) { + return retval; + } + if (retval == ERROR_OK && arm_semihosting(target, &retval) != 0) return retval; @@ -2267,6 +2272,7 @@ static int cortex_m_init_target(struct command_context *cmd_ctx, struct target *target) { armv7m_build_reg_cache(target); + cio_semihosting_init(target); arm_semihosting_init(target); return ERROR_OK; } diff --git a/src/target/target.h b/src/target/target.h index abd0b5825..a11a40df1 100644 --- a/src/target/target.h +++ b/src/target/target.h @@ -207,6 +207,7 @@ struct target { /* The semihosting information, extracted from the target. */ struct semihosting *semihosting; + struct cio *cio; }; struct target_list {