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 {


Reply via email to