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 {