// SPDX-License-Identifier: GPL-2.0-or-later

/***************************************************************************
 *   Copyright (C) 2018 by Liviu Ionescu                                   *
 *   <ilg@livius.net>                                                      *
 *                                                                         *
 *   Copyright (C) 2018 by Marvell Technology Group Ltd.                   *
 *   Written by Nicolas Pitre <nico@marvell.com>                           *
 *                                                                         *
 *   Copyright (C) 2010 by Spencer Oliver                                  *
 *   spen@spen-soft.co.uk                                                  *
 *                                                                         *
 *   Copyright (C) 2016 by Square, Inc.                                    *
 *   Steven Stallion <stallion@squareup.com>                               *
 ***************************************************************************/

/**
 * @file
 * Common ARM semihosting support.
 *
 * Semihosting enables code running on a target to use some of the I/O
 * facilities on the host computer. The target application must be linked
 * against a library that forwards operation requests by using  an
 * instruction trapped by the debugger.
 *
 * Details can be found in
 * "Semihosting for AArch32 and AArch64, Release 2.0"
 * https://static.docs.arm.com/100863/0200/semihosting.pdf
 * from ARM Ltd.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "target.h"
#include "target_type.h"
#include "semihosting_common.h"

#include <helper/binarybuffer.h>
#include <helper/log.h>
#include <server/gdb_server.h>
#include <sys/stat.h>

/**
 * It is not possible to use O_... flags defined in sys/stat.h because they
 * are not guaranteed to match the values defined by the GDB Remote Protocol.
 * See https://sourceware.org/gdb/onlinedocs/gdb/Open-Flags.html#Open-Flags
 */
enum {
	TARGET_O_RDONLY = 0x000,
	TARGET_O_WRONLY = 0x001,
	TARGET_O_RDWR   = 0x002,
	TARGET_O_APPEND = 0x008,
	TARGET_O_CREAT  = 0x200,
	TARGET_O_TRUNC  = 0x400,
	/* O_EXCL=0x800 is not required in this implementation. */
};

/* GDB remote protocol does not differentiate between text and binary open modes. */
static const int open_gdb_modeflags[12] = {
	TARGET_O_RDONLY,
	TARGET_O_RDONLY,
	TARGET_O_RDWR,
	TARGET_O_RDWR,
	TARGET_O_WRONLY | TARGET_O_CREAT | TARGET_O_TRUNC,
	TARGET_O_WRONLY | TARGET_O_CREAT | TARGET_O_TRUNC,
	TARGET_O_RDWR   | TARGET_O_CREAT | TARGET_O_TRUNC,
	TARGET_O_RDWR   | TARGET_O_CREAT | TARGET_O_TRUNC,
	TARGET_O_WRONLY | TARGET_O_CREAT | TARGET_O_APPEND,
	TARGET_O_WRONLY | TARGET_O_CREAT | TARGET_O_APPEND,
	TARGET_O_RDWR   | TARGET_O_CREAT | TARGET_O_APPEND,
	TARGET_O_RDWR   | TARGET_O_CREAT | TARGET_O_APPEND
};

static const int open_host_modeflags[12] = {
	O_RDONLY,
	O_RDONLY | O_BINARY,
	O_RDWR,
	O_RDWR   | O_BINARY,
	O_WRONLY | O_CREAT | O_TRUNC,
	O_WRONLY | O_CREAT | O_TRUNC  | O_BINARY,
	O_RDWR   | O_CREAT | O_TRUNC,
	O_RDWR   | O_CREAT | O_TRUNC  | O_BINARY,
	O_WRONLY | O_CREAT | O_APPEND,
	O_WRONLY | O_CREAT | O_APPEND | O_BINARY,
	O_RDWR   | O_CREAT | O_APPEND,
	O_RDWR   | O_CREAT | O_APPEND | O_BINARY
};

static int semihosting_common_fileio_info(struct target *target,
	struct gdb_fileio_info *fileio_info);
static int semihosting_common_fileio_end(struct target *target, int result,
	int fileio_errno, bool ctrl_c);

/**
 * Initialize common semihosting support.
 *
 * @param target Pointer to the target to initialize.
 * @param setup
 * @param post_result
 * @return An error status if there is a problem during initialization.
 */
int semihosting_common_init(struct target *target, void *setup,
	void *post_result)
{
	LOG_DEBUG(" ");

	target->fileio_info = malloc(sizeof(*target->fileio_info));
	if (!target->fileio_info) {
		LOG_ERROR("out of memory");
		return ERROR_FAIL;
	}
	memset(target->fileio_info, 0, sizeof(*target->fileio_info));

	struct semihosting *semihosting;
	semihosting = malloc(sizeof(*target->semihosting));
	if (!semihosting) {
		LOG_ERROR("out of memory");
		return ERROR_FAIL;
	}

	semihosting->is_active = false;
	semihosting->redirect_cfg = SEMIHOSTING_REDIRECT_CFG_NONE;
	semihosting->tcp_connection = NULL;
	semihosting->stdin_fd = -1;
	semihosting->stdout_fd = -1;
	semihosting->stderr_fd = -1;
	semihosting->is_fileio = false;
	semihosting->hit_fileio = false;
	semihosting->is_resumable = false;
	semihosting->has_resumable_exit = false;
	semihosting->word_size_bytes = 0;
	semihosting->op = -1;
	semihosting->param = 0;
	semihosting->result = -1;
	semihosting->sys_errno = -1;
	semihosting->cmdline = NULL;
	semihosting->basedir = NULL;

	/* If possible, update it in setup(). */
	semihosting->setup_time = clock();

	semihosting->setup = setup;
	semihosting->post_result = post_result;
	semihosting->user_command_extension = NULL;

	target->semihosting = semihosting;

	target->type->get_gdb_fileio_info = semihosting_common_fileio_info;
	target->type->gdb_fileio_end = semihosting_common_fileio_end;

	return ERROR_OK;
}

struct semihosting_tcp_service {
	struct semihosting *semihosting;
	char *name;
	int error;
};

static bool semihosting_is_redirected(struct semihosting *semihosting, int fd)
{
	if (semihosting->redirect_cfg == SEMIHOSTING_REDIRECT_CFG_NONE)
		return false;

	bool is_read_op = false;

	switch (semihosting->op) {
	/* check debug semihosting operations: READC, WRITEC and WRITE0 */
	case SEMIHOSTING_SYS_READC:
		is_read_op = true;
		/* fall through */
	case SEMIHOSTING_SYS_WRITEC:
	case SEMIHOSTING_SYS_WRITE0:
		/* debug operations are redirected when CFG is either DEBUG or ALL */
		if (semihosting->redirect_cfg == SEMIHOSTING_REDIRECT_CFG_STDIO)
			return false;
		break;

	/* check stdio semihosting operations: READ and WRITE */
	case SEMIHOSTING_SYS_READ:
		is_read_op = true;
		/* fall through */
	case SEMIHOSTING_SYS_WRITE:
		/* stdio operations are redirected when CFG is either STDIO or ALL */
		if (semihosting->redirect_cfg == SEMIHOSTING_REDIRECT_CFG_DEBUG)
			return false;
		break;

	default:
		return false;
	}

	if (is_read_op)
		return fd == semihosting->stdin_fd;

	/* write operation */
	return fd == semihosting->stdout_fd || fd == semihosting->stderr_fd;
}

static ssize_t semihosting_redirect_write(struct semihosting *semihosting, void *buf, int size)
{
	if (!semihosting->tcp_connection) {
		LOG_ERROR("No connected TCP client for semihosting");
		semihosting->sys_errno = EBADF; /* Bad file number */
		return -1;
	}

	struct semihosting_tcp_service *service = semihosting->tcp_connection->service->priv;

	int retval = connection_write(semihosting->tcp_connection, buf, size);

	if (retval < 0)
		log_socket_error(service->name);

	return retval;
}

static ssize_t semihosting_write(struct semihosting *semihosting, int fd, void *buf, int size)
{
	if (semihosting_is_redirected(semihosting, fd))
		return semihosting_redirect_write(semihosting, buf, size);

	/* default write */
	int result = write(fd, buf, size);
	if (result == -1)
		semihosting->sys_errno = errno;
	return result;
}

static ssize_t semihosting_redirect_read(struct semihosting *semihosting, void *buf, int size)
{
	if (!semihosting->tcp_connection) {
		LOG_ERROR("No connected TCP client for semihosting");
		semihosting->sys_errno = EBADF; /* Bad file number */
		return -1;
	}

	struct semihosting_tcp_service *service = semihosting->tcp_connection->service->priv;

	service->error = ERROR_OK;
	semihosting->tcp_connection->input_pending = true;

	int retval = connection_read(semihosting->tcp_connection, buf, size);

	if (retval <= 0)
		service->error = ERROR_SERVER_REMOTE_CLOSED;

	if (retval < 0)
		log_socket_error(service->name);

	semihosting->tcp_connection->input_pending = false;

	return retval;
}

static inline int semihosting_putchar(struct semihosting *semihosting, int fd, int c)
{
	if (semihosting_is_redirected(semihosting, fd))
		return semihosting_redirect_write(semihosting, &c, 1);

	/* default putchar */
	return putchar(c);
}

static inline ssize_t semihosting_read(struct semihosting *semihosting, int fd, void *buf, int size)
{
	if (semihosting_is_redirected(semihosting, fd))
		return semihosting_redirect_read(semihosting, buf, size);

	/* default read */
	ssize_t result = read(fd, buf, size);
	if (result == -1)
		semihosting->sys_errno = errno;

	return result;
}

static inline int semihosting_getchar(struct semihosting *semihosting, int fd)
{
	if (semihosting_is_redirected(semihosting, fd)) {
		unsigned char c;

		if (semihosting_redirect_read(semihosting, &c, 1) > 0)
			return c;

		return EOF;
	}

	/* default getchar */
	return getchar();
}

/**
 * User operation parameter string storage buffer. Contains valid data when the
 * TARGET_EVENT_SEMIHOSTING_USER_CMD_xxxxx event callbacks are running.
 */
static char *semihosting_user_op_params;

const char *semihosting_opcode_to_str(const uint64_t opcode)
{
	switch (opcode) {
		case SEMIHOSTING_SYS_CLOSE:
			return "CLOSE";
		case SEMIHOSTING_SYS_CLOCK:
			return "CLOCK";
		case SEMIHOSTING_SYS_ELAPSED:
			return "ELAPSED";
		case SEMIHOSTING_SYS_ERRNO:
			return "ERRNO";
		case SEMIHOSTING_SYS_EXIT:
			return "EXIT";
		case SEMIHOSTING_SYS_EXIT_EXTENDED:
			return "EXIT_EXTENDED";
		case SEMIHOSTING_SYS_FLEN:
			return "FLEN";
		case SEMIHOSTING_SYS_GET_CMDLINE:
			return "GET_CMDLINE";
		case SEMIHOSTING_SYS_HEAPINFO:
			return "HEAPINFO";
		case SEMIHOSTING_SYS_ISERROR:
			return "ISERROR";
		case SEMIHOSTING_SYS_ISTTY:
			return "ISTTY";
		case SEMIHOSTING_SYS_OPEN:
			return "OPEN";
		case SEMIHOSTING_SYS_READ:
			return "READ";
		case SEMIHOSTING_SYS_READC:
			return "READC";
		case SEMIHOSTING_SYS_REMOVE:
			return "REMOVE";
		case SEMIHOSTING_SYS_RENAME:
			return "RENAME";
		case SEMIHOSTING_SYS_SEEK:
			return "SEEK";
		case SEMIHOSTING_SYS_SYSTEM:
			return "SYSTEM";
		case SEMIHOSTING_SYS_TICKFREQ:
			return "TICKFREQ";
		case SEMIHOSTING_SYS_TIME:
			return "TIME";
		case SEMIHOSTING_SYS_TMPNAM:
			return "TMPNAM";
		case SEMIHOSTING_SYS_WRITE:
			return "WRITE";
		case SEMIHOSTING_SYS_WRITEC:
			return "WRITEC";
		case SEMIHOSTING_SYS_WRITE0:
			return "WRITE0";
		case SEMIHOSTING_USER_CMD_0X100 ... SEMIHOSTING_USER_CMD_0X1FF:
			return "USER_CMD";
		case SEMIHOSTING_ARM_RESERVED_START ... SEMIHOSTING_ARM_RESERVED_END:
			return "ARM_RESERVED_CMD";
		default:
			return "<unknown>";
	}
}

/**
 * Portable implementation of ARM semihosting calls.
 * Performs the currently pending semihosting operation
 * encoded in target->semihosting.
 */
int semihosting_common(struct target *target)
{
	struct semihosting *semihosting = target->semihosting;
	if (!semihosting) {
		/* Silently ignore if the semihosting field was not set. */
		return ERROR_OK;
	}

	struct gdb_fileio_info *fileio_info = target->fileio_info;

	/*
	 * By default return an error.
	 * The actual result must be set by each function
	 */
	semihosting->result = -1;

	/* Most operations are resumable, except the two exit calls. */
	semihosting->is_resumable = true;

	int retval;

	/* Enough space to hold 4 long words. */
	uint8_t fields[4*8];

	LOG_DEBUG("op=0x%x (%s), param=0x%" PRIx64, semihosting->op,
			  semihosting_opcode_to_str(semihosting->op),
			  semihosting->param);

	switch (semihosting->op) {

		case SEMIHOSTING_SYS_CLOCK:	/* 0x10 */
			/*
			 * Returns the number of centiseconds (hundredths of a second)
			 * since the execution started.
			 *
			 * Values returned can be of limited use for some benchmarking
			 * purposes because of communication overhead or other
			 * agent-specific factors. For example, with a debug hardware
			 * unit the request is passed back to the host for execution.
			 * This can lead to unpredictable delays in transmission and
			 * process scheduling.
			 *
			 * Use this function to calculate time intervals, by calculating
			 * differences between intervals with and without the code
			 * sequence to be timed.
			 *
			 * Entry
			 * The PARAMETER REGISTER must contain 0. There are no other
			 * parameters.
			 *
			 * Return
			 * On exit, the RETURN REGISTER contains:
			 * - The number of centiseconds since some arbitrary start
			 * point, if the call is successful.
			 * - –1 if the call is not successful. For example, because
			 * of a communications error.
			 */
		{
			clock_t delta = clock() - semihosting->setup_time;

			semihosting->result = delta / (CLOCKS_PER_SEC / 100);
		}
		break;

		case SEMIHOSTING_SYS_CLOSE:	/* 0x02 */
			/*
			 * Closes a file on the host system. The handle must reference
			 * a file that was opened with SYS_OPEN.
			 *
			 * Entry
			 * On entry, the PARAMETER REGISTER contains a pointer to a
			 * one-field argument block:
			 * - field 1 Contains a handle for an open file.
			 *
			 * Return
			 * On exit, the RETURN REGISTER contains:
			 * - 0 if the call is successful
			 * - –1 if the call is not successful.
			 */
			retval = semihosting_read_fields(target, 1, fields);
			if (retval != ERROR_OK)
				return retval;
			else {
				int fd = semihosting_get_field(target, 0, fields);
				/* Do not allow to close OpenOCD's own standard streams */
				if (fd == 0 || fd == 1 || fd == 2) {
					LOG_DEBUG("ignoring semihosting attempt to close %s",
							(fd == 0) ? "stdin" :
							(fd == 1) ? "stdout" : "stderr");
					/* Just pretend success */
					semihosting->result = 0;
					break;
				}
				/* Close the descriptor */
				if (semihosting->is_fileio) {
					semihosting->hit_fileio = true;
					fileio_info->identifier = "close";
					fileio_info->param_1 = fd;
				} else {
					semihosting->result = close(fd);
					if (semihosting->result == -1)
						semihosting->sys_errno = errno;
					LOG_DEBUG("close(%d)=%" PRId64, fd, semihosting->result);
				}
			}
			break;

		case SEMIHOSTING_SYS_ERRNO:	/* 0x13 */
			/*
			 * Returns the value of the C library errno variable that is
			 * associated with the semihosting implementation. The errno
			 * variable can be set by a number of C library semihosted
			 * functions, including:
			 * - SYS_REMOVE
			 * - SYS_OPEN
			 * - SYS_CLOSE
			 * - SYS_READ
			 * - SYS_WRITE
			 * - SYS_SEEK.
			 *
			 * Whether errno is set or not, and to what value, is entirely
			 * host-specific, except where the ISO C standard defines the
			 * behavior.
			 *
			 * Entry
			 * There are no parameters. The PARAMETER REGISTER must be 0.
			 *
			 * Return
			 * On exit, the RETURN REGISTER contains the value of the C
			 * library errno variable.
			 */
			semihosting->result = semihosting->sys_errno;
			break;

		case SEMIHOSTING_SYS_EXIT:	/* 0x18 */
			/*
			 * Note: SYS_EXIT was called angel_SWIreason_ReportException in
			 * previous versions of the documentation.
			 *
			 * An application calls this operation to report an exception
			 * to the debugger directly. The most common use is to report
			 * that execution has completed, using ADP_Stopped_ApplicationExit.
			 *
			 * Note: This semihosting operation provides no means for 32-bit
			 * callers to indicate an application exit with a specified exit
			 * code. Semihosting callers may prefer to check for the presence
			 * of the SH_EXT_EXTENDED_REPORT_EXCEPTION extension and use
			 * the SYS_REPORT_EXCEPTION_EXTENDED operation instead, if it
			 * is available.
			 *
			 * Entry (32-bit)
			 * On entry, the PARAMETER register is set to a reason code
			 * describing the cause of the trap. Not all semihosting client
			 * implementations will necessarily trap every corresponding
			 * event. Important reason codes are:
			 *
			 * - ADP_Stopped_ApplicationExit 0x20026
			 * - ADP_Stopped_RunTimeErrorUnknown 0x20023
			 *
			 * Entry (64-bit)
			 * On entry, the PARAMETER REGISTER contains a pointer to a
			 * two-field argument block:
			 * - field 1 The exception type, which is one of the set of
			 * reason codes in the above tables.
			 * - field 2 A subcode, whose meaning depends on the reason
			 * code in field 1.
			 * In particular, if field 1 is ADP_Stopped_ApplicationExit
			 * then field 2 is an exit status code, as passed to the C
			 * standard library exit() function. A simulator receiving
			 * this request must notify a connected debugger, if present,
			 * and then exit with the specified status.
			 *
			 * Return
			 * No return is expected from these calls. However, it is
			 * possible for the debugger to request that the application
			 * continues by performing an RDI_Execute request or equivalent.
			 * In this case, execution continues with the registers as they
			 * were on entry to the operation, or as subsequently modified
			 * by the debugger.
			 */
			if (semihosting->word_size_bytes == 8) {
				retval = semihosting_read_fields(target, 2, fields);
				if (retval != ERROR_OK)
					return retval;
				else {
					int type = semihosting_get_field(target, 0, fields);
					int code = semihosting_get_field(target, 1, fields);

					if (type == ADP_STOPPED_APPLICATION_EXIT) {
						if (!gdb_get_actual_connections())
							exit(code);
						else {
							fprintf(stderr,
								"semihosting: *** application exited with %d ***\n",
								code);
						}
					} else {
						fprintf(stderr,
							"semihosting: application exception %#x\n",
							type);
					}
				}
			} else {
				if (semihosting->param == ADP_STOPPED_APPLICATION_EXIT) {
					if (!gdb_get_actual_connections())
						exit(0);
					else {
						fprintf(stderr,
							"semihosting: *** application exited normally ***\n");
					}
				} else if (semihosting->param == ADP_STOPPED_RUN_TIME_ERROR) {
					/* Chosen more or less arbitrarily to have a nicer message,
					 * otherwise all other return the same exit code 1. */
					if (!gdb_get_actual_connections())
						exit(1);
					else {
						fprintf(stderr,
							"semihosting: *** application exited with error ***\n");
					}
				} else {
					if (!gdb_get_actual_connections())
						exit(1);
					else {
						fprintf(stderr,
							"semihosting: application exception %#x\n",
							(unsigned) semihosting->param);
					}
				}
			}
			if (!semihosting->has_resumable_exit) {
				semihosting->is_resumable = false;
				return target_call_event_callbacks(target, TARGET_EVENT_HALTED);
			}
			break;

		case SEMIHOSTING_SYS_EXIT_EXTENDED:	/* 0x20 */
			/*
			 * This operation is only supported if the semihosting extension
			 * SH_EXT_EXIT_EXTENDED is implemented. SH_EXT_EXIT_EXTENDED is
			 * reported using feature byte 0, bit 0. If this extension is
			 * supported, then the implementation provides a means to
			 * report a normal exit with a nonzero exit status in both 32-bit
			 * and 64-bit semihosting APIs.
			 *
			 * The implementation must provide the semihosting call
			 * SYS_EXIT_EXTENDED for both A64 and A32/T32 semihosting APIs.
			 *
			 * SYS_EXIT_EXTENDED is used by an application to report an
			 * exception or exit to the debugger directly. The most common
			 * use is to report that execution has completed, using
			 * ADP_Stopped_ApplicationExit.
			 *
			 * Entry
			 * On entry, the PARAMETER REGISTER contains a pointer to a
			 * two-field argument block:
			 * - field 1 The exception type, which should be one of the set
			 * of reason codes that are documented for the SYS_EXIT
			 * (0x18) call. For example, ADP_Stopped_ApplicationExit.
			 * - field 2 A subcode, whose meaning depends on the reason
			 * code in field 1. In particular, if field 1 is
			 * ADP_Stopped_ApplicationExit then field 2 is an exit status
			 * code, as passed to the C standard library exit() function.
			 * A simulator receiving this request must notify a connected
			 * debugger, if present, and then exit with the specified status.
			 *
			 * Return
			 * No return is expected from these calls.
			 *
			 * For the A64 API, this call is identical to the behavior of
			 * the mandatory SYS_EXIT (0x18) call. If this extension is
			 * supported, then both calls must be implemented.
			 */
			retval = semihosting_read_fields(target, 2, fields);
			if (retval != ERROR_OK)
				return retval;
			else {
				int type = semihosting_get_field(target, 0, fields);
				int code = semihosting_get_field(target, 1, fields);

				if (type == ADP_STOPPED_APPLICATION_EXIT) {
					if (!gdb_get_actual_connections())
						exit(code);
					else {
						fprintf(stderr,
							"semihosting: *** application exited with %d ***\n",
							code);
					}
				} else {
					fprintf(stderr, "semihosting: exception %#x\n",
						type);
				}
			}
			if (!semihosting->has_resumable_exit) {
				semihosting->is_resumable = false;
				return target_call_event_callbacks(target, TARGET_EVENT_HALTED);
			}
			break;

		case SEMIHOSTING_SYS_FLEN:	/* 0x0C */
			/*
			 * Returns the length of a specified file.
			 *
			 * Entry
			 * On entry, the PARAMETER REGISTER contains a pointer to a
			 * one-field argument block:
			 * - field 1 A handle for a previously opened, seekable file
			 * object.
			 *
			 * Return
			 * On exit, the RETURN REGISTER contains:
			 * - The current length of the file object, if the call is
			 * successful.
			 * - –1 if an error occurs.
			 */
			if (semihosting->is_fileio) {
				semihosting->result = -1;
				semihosting->sys_errno = EINVAL;
			}
			retval = semihosting_read_fields(target, 1, fields);
			if (retval != ERROR_OK)
				return retval;
			else {
				int fd = semihosting_get_field(target, 0, fields);
				struct stat buf;
				semihosting->result = fstat(fd, &buf);
				if (semihosting->result == -1) {
					semihosting->sys_errno = errno;
					LOG_DEBUG("fstat(%d)=%" PRId64, fd, semihosting->result);
					break;
				}
				LOG_DEBUG("fstat(%d)=%" PRId64, fd, semihosting->result);
				semihosting->result = buf.st_size;
			}
			break;

		case SEMIHOSTING_SYS_GET_CMDLINE:	/* 0x15 */
			/*
			 * Returns the command line that is used for the call to the
			 * executable, that is, argc and argv.
			 *
			 * Entry
			 * On entry, the PARAMETER REGISTER points to a two-field data
			 * block to be used for returning the command string and its length:
			 * - field 1 A pointer to a buffer of at least the size that is
			 * specified in field 2.
			 * - field 2 The length of the buffer in bytes.
			 *
			 * Return
			 * On exit:
			 * If the call is successful, then the RETURN REGISTER contains 0,
			 * the PARAMETER REGISTER is unchanged, and the data block is
			 * updated as follows:
			 * - field 1 A pointer to a null-terminated string of the command
			 * line.
			 * - field 2 The length of the string in bytes.
			 * If the call is not successful, then the RETURN REGISTER
			 * contains -1.
			 *
			 * Note: The semihosting implementation might impose limits on
			 * the maximum length of the string that can be transferred.
			 * However, the implementation must be able to support a
			 * command-line length of at least 80 bytes.
			 */
			retval = semihosting_read_fields(target, 2, fields);
			if (retval != ERROR_OK)
				return retval;
			else {
				uint64_t addr = semihosting_get_field(target, 0, fields);
				size_t size = semihosting_get_field(target, 1, fields);

				char *arg = semihosting->cmdline ?
					semihosting->cmdline : "";
				uint32_t len = strlen(arg) + 1;
				if (len > size)
					semihosting->result = -1;
				else {
					semihosting_set_field(target, len, 1, fields);
					retval = target_write_buffer(target, addr, len,
							(uint8_t *)arg);
					if (retval != ERROR_OK)
						return retval;
					semihosting->result = 0;

					retval = semihosting_write_fields(target, 2, fields);
					if (retval != ERROR_OK)
						return retval;
				}
				LOG_DEBUG("SYS_GET_CMDLINE=[%s], %" PRId64, arg, semihosting->result);
			}
			break;

		case SEMIHOSTING_SYS_HEAPINFO:	/* 0x16 */
			/*
			 * Returns the system stack and heap parameters.
			 *
			 * Entry
			 * On entry, the PARAMETER REGISTER contains the address of a
			 * pointer to a four-field data block. The contents of the data
			 * block are filled by the function. The following C-like
			 * pseudocode describes the layout of the block:
			 * struct block {
			 *   void* heap_base;
			 *   void* heap_limit;
			 *   void* stack_base;
			 *   void* stack_limit;
			 * };
			 *
			 * Return
			 * On exit, the PARAMETER REGISTER is unchanged and the data
			 * block has been updated.
			 */
			retval = semihosting_read_fields(target, 1, fields);
			if (retval != ERROR_OK)
				return retval;
			else {
				uint64_t addr = semihosting_get_field(target, 0, fields);
				/* tell the remote we have no idea */
				memset(fields, 0, 4 * semihosting->word_size_bytes);
				retval = target_write_memory(target, addr, 4,
						semihosting->word_size_bytes,
						fields);
				if (retval != ERROR_OK)
					return retval;
				semihosting->result = 0;
			}
			break;

		case SEMIHOSTING_SYS_ISERROR:	/* 0x08 */
			/*
			 * Determines whether the return code from another semihosting
			 * call is an error status or not.
			 *
			 * This call is passed a parameter block containing the error
			 * code to examine.
			 *
			 * Entry
			 * On entry, the PARAMETER REGISTER contains a pointer to a
			 * one-field data block:
			 * - field 1 The required status word to check.
			 *
			 * Return
			 * On exit, the RETURN REGISTER contains:
			 * - 0 if the status field is not an error indication
			 * - A nonzero value if the status field is an error indication.
			 */
			retval = semihosting_read_fields(target, 1, fields);
			if (retval != ERROR_OK)
				return retval;

			uint64_t code = semihosting_get_field(target, 0, fields);
			semihosting->result = (code != 0);
			break;

		case SEMIHOSTING_SYS_ISTTY:	/* 0x09 */
			/*
			 * Checks whether a file is connected to an interactive device.
			 *
			 * Entry
			 * On entry, the PARAMETER REGISTER contains a pointer to a
			 * one-field argument block:
			 * field 1 A handle for a previously opened file object.
			 *
			 * Return
			 * On exit, the RETURN REGISTER contains:
			 * - 1 if the handle identifies an interactive device.
			 * - 0 if the handle identifies a file.
			 * - A value other than 1 or 0 if an error occurs.
			 */
			if (semihosting->is_fileio) {
				semihosting->hit_fileio = true;
				fileio_info->identifier = "isatty";
				fileio_info->param_1 = semihosting->param;
			} else {
				retval = semihosting_read_fields(target, 1, fields);
				if (retval != ERROR_OK)
					return retval;
				int fd = semihosting_get_field(target, 0, fields);
				// isatty() on Windows may return any non-zero value if fd is a terminal
				semihosting->result = isatty(fd) ? 1 : 0;
				if (semihosting->result == 0)
					semihosting->sys_errno = errno;
				LOG_DEBUG("isatty(%d)=%" PRId64, fd, semihosting->result);
			}
			break;

		case SEMIHOSTING_SYS_OPEN:	/* 0x01 */
			/*
			 * Opens a file on the host system.
			 *
			 * The file path is specified either as relative to the current
			 * directory of the host process, or absolute, using the path
			 * conventions of the host operating system.
			 *
			 * Semihosting implementations must support opening the special
			 * path name :semihosting-features as part of the semihosting
			 * extensions reporting mechanism.
			 *
			 * ARM targets interpret the special path name :tt as meaning
			 * the console input stream, for an open-read or the console
			 * output stream, for an open-write. Opening these streams is
			 * performed as part of the standard startup code for those
			 * applications that reference the C stdio streams. The
			 * semihosting extension SH_EXT_STDOUT_STDERR allows the
			 * semihosting caller to open separate output streams
			 * corresponding to stdout and stderr. This extension is
			 * reported using feature byte 0, bit 1. Use SYS_OPEN with
			 * the special path name :semihosting-features to access the
			 * feature bits.
			 *
			 * If this extension is supported, the implementation must
			 * support the following additional semantics to SYS_OPEN:
			 * - If the special path name :tt is opened with an fopen
			 * mode requesting write access (w, wb, w+, or w+b), then
			 * this is a request to open stdout.
			 * - If the special path name :tt is opened with a mode
			 * requesting append access (a, ab, a+, or a+b), then this is
			 * a request to open stderr.
			 *
			 * Entry
			 * On entry, the PARAMETER REGISTER contains a pointer to a
			 * three-field argument block:
			 * - field 1 A pointer to a null-terminated string containing
			 * a file or device name.
			 * - field 2 An integer that specifies the file opening mode.
			 * - field 3 An integer that gives the length of the string
			 * pointed to by field 1.
			 *
			 * The length does not include the terminating null character
			 * that must be present.
			 *
			 * Return
			 * On exit, the RETURN REGISTER contains:
			 * - A nonzero handle if the call is successful.
			 * - –1 if the call is not successful.
			 */
			retval = semihosting_read_fields(target, 3, fields);
			if (retval != ERROR_OK)
				return retval;
			else {
				uint64_t addr = semihosting_get_field(target, 0, fields);
				uint32_t mode = semihosting_get_field(target, 1, fields);
				size_t len = semihosting_get_field(target, 2, fields);

				if (mode > 11) {
					semihosting->result = -1;
					semihosting->sys_errno = EINVAL;
					break;
				}
				size_t basedir_len = semihosting->basedir ? strlen(semihosting->basedir) : 0;
				uint8_t *fn = malloc(basedir_len + len + 2);
				if (!fn) {
					semihosting->result = -1;
					semihosting->sys_errno = ENOMEM;
				} else {
					if (basedir_len > 0) {
						strcpy((char *)fn, semihosting->basedir);
						if (fn[basedir_len - 1] != '/')
							fn[basedir_len++] = '/';
					}
					retval = target_read_memory(target, addr, 1, len, fn + basedir_len);
					if (retval != ERROR_OK) {
						free(fn);
						return retval;
					}
					fn[basedir_len + len] = 0;
					/* TODO: implement the :semihosting-features special file.
					 * */
					if (semihosting->is_fileio) {
						if (strcmp((char *)fn, ":semihosting-features") == 0) {
							semihosting->result = -1;
							semihosting->sys_errno = EINVAL;
						} else if (strcmp((char *)fn, ":tt") == 0) {
							if (mode == 0) {
								semihosting->result = 0;
							} else if (mode == 4) {
								semihosting->result = 1;
							} else if (mode == 8) {
								semihosting->result = 2;
							} else {
								semihosting->result = -1;
								semihosting->sys_errno = EINVAL;
							}
						} else {
							semihosting->hit_fileio = true;
							fileio_info->identifier = "open";
							fileio_info->param_1 = addr;
							fileio_info->param_2 = len;
							fileio_info->param_3 = open_gdb_modeflags[mode];
							fileio_info->param_4 = 0644;
						}
					} else {
						if (strcmp((char *)fn, ":tt") == 0) {
							/* Mode is:
							 * - 0-3 ("r") for stdin,
							 * - 4-7 ("w") for stdout,
							 * - 8-11 ("a") for stderr */
							int fd;
							if (mode < 4) {
								fd = dup(STDIN_FILENO);
								semihosting->stdin_fd = fd;
								LOG_DEBUG("dup(STDIN)=%d", fd);
							} else if (mode < 8) {
								fd = dup(STDOUT_FILENO);
								semihosting->stdout_fd = fd;
								LOG_DEBUG("dup(STDOUT)=%d", fd);
							} else {
								fd = dup(STDERR_FILENO);
								semihosting->stderr_fd = fd;
								LOG_DEBUG("dup(STDERR)=%d", fd);
							}
							semihosting->result = fd;
							if (fd == -1)
								semihosting->sys_errno = errno;
						} else {
							/* cygwin requires the permission setting
							 * otherwise it will fail to reopen a previously
							 * written file */
							semihosting->result = open((char *)fn,
									open_host_modeflags[mode],
									0644);
							if (semihosting->result == -1)
								semihosting->sys_errno = errno;
							LOG_DEBUG("open('%s')=%" PRId64, fn, semihosting->result);
						}
					}
					free(fn);
				}
			}
			break;

		case SEMIHOSTING_SYS_READ:	/* 0x06 */
			/*
			 * Reads the contents of a file into a buffer. The file position
			 * is specified either:
			 * - Explicitly by a SYS_SEEK.
			 * - Implicitly one byte beyond the previous SYS_READ or
			 * SYS_WRITE request.
			 *
			 * The file position is at the start of the file when it is
			 * opened, and is lost when the file is closed. Perform the
			 * file operation as a single action whenever possible. For
			 * example, do not split a read of 16KB into four 4KB chunks
			 * unless there is no alternative.
			 *
			 * Entry
			 * On entry, the PARAMETER REGISTER contains a pointer to a
			 * three-field data block:
			 * - field 1 Contains a handle for a file previously opened
			 * with SYS_OPEN.
			 * - field 2 Points to a buffer.
			 * - field 3 Contains the number of bytes to read to the buffer
			 * from the file.
			 *
			 * Return
			 * On exit, the RETURN REGISTER contains the number of bytes not
			 * filled in the buffer (buffer_length - bytes_read) as follows:
			 * - If the RETURN REGISTER is 0, the entire buffer was
			 * successfully filled.
			 * - If the RETURN REGISTER is the same as field 3, no bytes
			 * were read (EOF can be assumed).
			 * - If the RETURN REGISTER contains a value smaller than
			 * field 3, the read succeeded but the buffer was only partly
			 * filled. For interactive devices, this is the most common
			 * return value.
			 */
			retval = semihosting_read_fields(target, 3, fields);
			if (retval != ERROR_OK)
				return retval;
			else {
				int fd = semihosting_get_field(target, 0, fields);
				uint64_t addr = semihosting_get_field(target, 1, fields);
				size_t len = semihosting_get_field(target, 2, fields);
				if (semihosting->is_fileio) {
					semihosting->hit_fileio = true;
					fileio_info->identifier = "read";
					fileio_info->param_1 = fd;
					fileio_info->param_2 = addr;
					fileio_info->param_3 = len;
				} else {
					uint8_t *buf = malloc(len);
					if (!buf) {
						semihosting->result = -1;
						semihosting->sys_errno = ENOMEM;
					} else {
						semihosting->result = semihosting_read(semihosting, fd, buf, len);
						LOG_DEBUG("read(%d, 0x%" PRIx64 ", %zu)=%" PRId64,
							fd,
							addr,
							len,
							semihosting->result);
						if (semihosting->result >= 0) {
							retval = target_write_buffer(target, addr,
									semihosting->result,
									buf);
							if (retval != ERROR_OK) {
								free(buf);
								return retval;
							}
							/* the number of bytes NOT filled in */
							semihosting->result = len -
								semihosting->result;
						}
						free(buf);
					}
				}
			}
			break;

		case SEMIHOSTING_SYS_READC:	/* 0x07 */
			/*
			 * Reads a byte from the console.
			 *
			 * Entry
			 * The PARAMETER REGISTER must contain 0. There are no other
			 * parameters or values possible.
			 *
			 * Return
			 * On exit, the RETURN REGISTER contains the byte read from
			 * the console.
			 */
			if (semihosting->is_fileio) {
				LOG_ERROR("SYS_READC not supported by semihosting fileio");
				return ERROR_FAIL;
			}
			semihosting->result = semihosting_getchar(semihosting, semihosting->stdin_fd);
			LOG_DEBUG("getchar()=%" PRId64, semihosting->result);
			break;

		case SEMIHOSTING_SYS_REMOVE:	/* 0x0E */
			/*
			 * Deletes a specified file on the host filing system.
			 *
			 * Entry
			 * On entry, the PARAMETER REGISTER contains a pointer to a
			 * two-field argument block:
			 * - field 1 Points to a null-terminated string that gives the
			 * path name of the file to be deleted.
			 * - field 2 The length of the string.
			 *
			 * Return
			 * On exit, the RETURN REGISTER contains:
			 * - 0 if the delete is successful
			 * - A nonzero, host-specific error code if the delete fails.
			 */
			retval = semihosting_read_fields(target, 2, fields);
			if (retval != ERROR_OK)
				return retval;
			else {
				uint64_t addr = semihosting_get_field(target, 0, fields);
				size_t len = semihosting_get_field(target, 1, fields);
				if (semihosting->is_fileio) {
					semihosting->hit_fileio = true;
					fileio_info->identifier = "unlink";
					fileio_info->param_1 = addr;
					fileio_info->param_2 = len;
				} else {
					uint8_t *fn = malloc(len+1);
					if (!fn) {
						semihosting->result = -1;
						semihosting->sys_errno = ENOMEM;
					} else {
						retval =
							target_read_memory(target, addr, 1, len,
								fn);
						if (retval != ERROR_OK) {
							free(fn);
							return retval;
						}
						fn[len] = 0;
						semihosting->result = remove((char *)fn);
						if (semihosting->result == -1)
							semihosting->sys_errno = errno;
						LOG_DEBUG("remove('%s')=%" PRId64, fn, semihosting->result);

						free(fn);
					}
				}
			}
			break;

		case SEMIHOSTING_SYS_RENAME:	/* 0x0F */
			/*
			 * Renames a specified file.
			 *
			 * Entry
			 * On entry, the PARAMETER REGISTER contains a pointer to a
			 * four-field data block:
			 * - field 1 A pointer to the name of the old file.
			 * - field 2 The length of the old filename.
			 * - field 3 A pointer to the new filename.
			 * - field 4 The length of the new filename. Both strings are
			 * null-terminated.
			 *
			 * Return
			 * On exit, the RETURN REGISTER contains:
			 * - 0 if the rename is successful.
			 * - A nonzero, host-specific error code if the rename fails.
			 */
			retval = semihosting_read_fields(target, 4, fields);
			if (retval != ERROR_OK)
				return retval;
			else {
				uint64_t addr1 = semihosting_get_field(target, 0, fields);
				size_t len1 = semihosting_get_field(target, 1, fields);
				uint64_t addr2 = semihosting_get_field(target, 2, fields);
				size_t len2 = semihosting_get_field(target, 3, fields);
				if (semihosting->is_fileio) {
					semihosting->hit_fileio = true;
					fileio_info->identifier = "rename";
					fileio_info->param_1 = addr1;
					fileio_info->param_2 = len1;
					fileio_info->param_3 = addr2;
					fileio_info->param_4 = len2;
				} else {
					uint8_t *fn1 = malloc(len1+1);
					uint8_t *fn2 = malloc(len2+1);
					if (!fn1 || !fn2) {
						free(fn1);
						free(fn2);
						semihosting->result = -1;
						semihosting->sys_errno = ENOMEM;
					} else {
						retval = target_read_memory(target, addr1, 1, len1,
								fn1);
						if (retval != ERROR_OK) {
							free(fn1);
							free(fn2);
							return retval;
						}
						retval = target_read_memory(target, addr2, 1, len2,
								fn2);
						if (retval != ERROR_OK) {
							free(fn1);
							free(fn2);
							return retval;
						}
						fn1[len1] = 0;
						fn2[len2] = 0;
						semihosting->result = rename((char *)fn1,
								(char *)fn2);
						// rename() on Windows returns nonzero on error
						if (semihosting->result != 0)
							semihosting->sys_errno = errno;
						LOG_DEBUG("rename('%s', '%s')=%" PRId64 " %d", fn1, fn2, semihosting->result, errno);
						free(fn1);
						free(fn2);
					}
				}
			}
			break;

		case SEMIHOSTING_SYS_SEEK:	/* 0x0A */
			/*
			 * Seeks to a specified position in a file using an offset
			 * specified from the start of the file. The file is assumed
			 * to be a byte array and the offset is given in bytes.
			 *
			 * Entry
			 * On entry, the PARAMETER REGISTER contains a pointer to a
			 * two-field data block:
			 * - field 1 A handle for a seekable file object.
			 * - field 2 The absolute byte position to seek to.
			 *
			 * Return
			 * On exit, the RETURN REGISTER contains:
			 * - 0 if the request is successful.
			 * - A negative value if the request is not successful.
			 * Use SYS_ERRNO to read the value of the host errno variable
			 * describing the error.
			 *
			 * Note: The effect of seeking outside the current extent of
			 * the file object is undefined.
			 */
			retval = semihosting_read_fields(target, 2, fields);
			if (retval != ERROR_OK)
				return retval;
			else {
				int fd = semihosting_get_field(target, 0, fields);
				off_t pos = semihosting_get_field(target, 1, fields);
				if (semihosting->is_fileio) {
					semihosting->hit_fileio = true;
					fileio_info->identifier = "lseek";
					fileio_info->param_1 = fd;
					fileio_info->param_2 = pos;
					fileio_info->param_3 = SEEK_SET;
				} else {
					semihosting->result = lseek(fd, pos, SEEK_SET);
					if (semihosting->result == -1)
						semihosting->sys_errno = errno;
					LOG_DEBUG("lseek(%d, %d)=%" PRId64, fd, (int)pos, semihosting->result);
					if (semihosting->result == pos)
						semihosting->result = 0;
				}
			}
			break;

		case SEMIHOSTING_SYS_SYSTEM:	/* 0x12 */
			/*
			 * Passes a command to the host command-line interpreter.
			 * This enables you to execute a system command such as dir,
			 * ls, or pwd. The terminal I/O is on the host, and is not
			 * visible to the target.
			 *
			 * Entry
			 * On entry, the PARAMETER REGISTER contains a pointer to a
			 * two-field argument block:
			 * - field 1 Points to a string to be passed to the host
			 * command-line interpreter.
			 * - field 2 The length of the string.
			 *
			 * Return
			 * On exit, the RETURN REGISTER contains the return status.
			 */

			/* Provide SYS_SYSTEM functionality.  Uses the
			 * libc system command, there may be a reason *NOT*
			 * to use this, but as I can't think of one, I
			 * implemented it this way.
			 */
			retval = semihosting_read_fields(target, 2, fields);
			if (retval != ERROR_OK)
				return retval;
			else {
				uint64_t addr = semihosting_get_field(target, 0, fields);
				size_t len = semihosting_get_field(target, 1, fields);
				if (semihosting->is_fileio) {
					semihosting->hit_fileio = true;
					fileio_info->identifier = "system";
					fileio_info->param_1 = addr;
					fileio_info->param_2 = len;
				} else {
					uint8_t *cmd = malloc(len+1);
					if (!cmd) {
						semihosting->result = -1;
						semihosting->sys_errno = ENOMEM;
					} else {
						retval = target_read_memory(target,
								addr,
								1,
								len,
								cmd);
						if (retval != ERROR_OK) {
							free(cmd);
							return retval;
						} else {
							cmd[len] = 0;
							semihosting->result = system(
									(const char *)cmd);
							LOG_DEBUG("system('%s')=%" PRId64, cmd, semihosting->result);
						}

						free(cmd);
					}
				}
			}
			break;

		case SEMIHOSTING_SYS_TIME:	/* 0x11 */
			/*
			 * Returns the number of seconds since 00:00 January 1, 1970.
			 * This value is real-world time, regardless of any debug agent
			 * configuration.
			 *
			 * Entry
			 * There are no parameters.
			 *
			 * Return
			 * On exit, the RETURN REGISTER contains the number of seconds.
			 */
			semihosting->result = time(NULL);
			break;

		case SEMIHOSTING_SYS_WRITE:	/* 0x05 */
			/*
			 * Writes the contents of a buffer to a specified file at the
			 * current file position. The file position is specified either:
			 * - Explicitly, by a SYS_SEEK.
			 * - Implicitly as one byte beyond the previous SYS_READ or
			 * SYS_WRITE request.
			 *
			 * The file position is at the start of the file when the file
			 * is opened, and is lost when the file is closed.
			 *
			 * Perform the file operation as a single action whenever
			 * possible. For example, do not split a write of 16KB into
			 * four 4KB chunks unless there is no alternative.
			 *
			 * Entry
			 * On entry, the PARAMETER REGISTER contains a pointer to a
			 * three-field data block:
			 * - field 1 Contains a handle for a file previously opened
			 * with SYS_OPEN.
			 * - field 2 Points to the memory containing the data to be written.
			 * - field 3 Contains the number of bytes to be written from
			 * the buffer to the file.
			 *
			 * Return
			 * On exit, the RETURN REGISTER contains:
			 * - 0 if the call is successful.
			 * - The number of bytes that are not written, if there is an error.
			 */
			retval = semihosting_read_fields(target, 3, fields);
			if (retval != ERROR_OK)
				return retval;
			else {
				int fd = semihosting_get_field(target, 0, fields);
				uint64_t addr = semihosting_get_field(target, 1, fields);
				size_t len = semihosting_get_field(target, 2, fields);
				if (semihosting->is_fileio) {
					semihosting->hit_fileio = true;
					fileio_info->identifier = "write";
					fileio_info->param_1 = fd;
					fileio_info->param_2 = addr;
					fileio_info->param_3 = len;
				} else {
					uint8_t *buf = malloc(len);
					if (!buf) {
						semihosting->result = -1;
						semihosting->sys_errno = ENOMEM;
					} else {
						retval = target_read_buffer(target, addr, len, buf);
						if (retval != ERROR_OK) {
							free(buf);
							return retval;
						}
						semihosting->result = semihosting_write(semihosting, fd, buf, len);
						LOG_DEBUG("write(%d, 0x%" PRIx64 ", %zu)=%" PRId64,
							fd,
							addr,
							len,
							semihosting->result);
						if (semihosting->result >= 0) {
							/* The number of bytes that are NOT written.
							 * */
							semihosting->result = len -
								semihosting->result;
						}

						free(buf);
					}
				}
			}
			break;

		case SEMIHOSTING_SYS_WRITEC:	/* 0x03 */
			/*
			 * Writes a character byte, pointed to by the PARAMETER REGISTER,
			 * to the debug channel. When executed under a semihosting
			 * debugger, the character appears on the host debugger console.
			 *
			 * Entry
			 * On entry, the PARAMETER REGISTER contains a pointer to the
			 * character.
			 *
			 * Return
			 * None. The RETURN REGISTER is corrupted.
			 */
			if (semihosting->is_fileio) {
				semihosting->hit_fileio = true;
				fileio_info->identifier = "write";
				fileio_info->param_1 = 1;
				fileio_info->param_2 = semihosting->param;
				fileio_info->param_3 = 1;
			} else {
				uint64_t addr = semihosting->param;
				unsigned char c;
				retval = target_read_memory(target, addr, 1, 1, &c);
				if (retval != ERROR_OK)
					return retval;
				semihosting_putchar(semihosting, semihosting->stdout_fd, c);
				semihosting->result = 0;
			}
			break;

		case SEMIHOSTING_SYS_WRITE0:	/* 0x04 */
			/*
			 * Writes a null-terminated string to the debug channel.
			 * When executed under a semihosting debugger, the characters
			 * appear on the host debugger console.
			 *
			 * Entry
			 * On entry, the PARAMETER REGISTER contains a pointer to the
			 * first byte of the string.
			 *
			 * Return
			 * None. The RETURN REGISTER is corrupted.
			 */
			if (semihosting->is_fileio) {
				size_t count = 0;
				uint64_t addr = semihosting->param;
				for (;; addr++) {
					unsigned char c;
					retval = target_read_memory(target, addr, 1, 1, &c);
					if (retval != ERROR_OK)
						return retval;
					if (c == '\0')
						break;
					count++;
				}
				semihosting->hit_fileio = true;
				fileio_info->identifier = "write";
				fileio_info->param_1 = 1;
				fileio_info->param_2 = semihosting->param;
				fileio_info->param_3 = count;
			} else {
				uint64_t addr = semihosting->param;
				do {
					unsigned char c;
					retval = target_read_memory(target, addr++, 1, 1, &c);
					if (retval != ERROR_OK)
						return retval;
					if (!c)
						break;
					semihosting_putchar(semihosting, semihosting->stdout_fd, c);
				} while (1);
				semihosting->result = 0;
			}
			break;

		case SEMIHOSTING_USER_CMD_0X100 ... SEMIHOSTING_USER_CMD_0X107:
			/**
			 * This is a user defined operation (while user cmds 0x100-0x1ff
			 * are possible, only 0x100-0x107 are currently implemented).
			 *
			 * Reads the user operation parameters from target, then fires the
			 * corresponding target event. When the target callbacks returned,
			 * cleans up the command parameter buffer.
			 *
			 * Entry
			 * On entry, the PARAMETER REGISTER contains a pointer to a
			 * two-field data block:
			 * - field 1 Contains a pointer to the bound command parameter
			 * string
			 * - field 2 Contains the command parameter string length
			 *
			 * Return
			 * On exit, the RETURN REGISTER contains the return status.
			 */
			if (semihosting->user_command_extension) {
				retval = semihosting->user_command_extension(target);
				if (retval != ERROR_NOT_IMPLEMENTED)
					break;
				/* If custom user command not handled, we are looking for the TCL handler */
			}

			assert(!semihosting_user_op_params);
			retval = semihosting_read_fields(target, 2, fields);
			if (retval != ERROR_OK) {
				LOG_ERROR("Failed to read fields for user defined command"
						" op=0x%x", semihosting->op);
				return retval;
			}

			uint64_t addr = semihosting_get_field(target, 0, fields);

			size_t len = semihosting_get_field(target, 1, fields);
			if (len > SEMIHOSTING_MAX_TCL_COMMAND_FIELD_LENGTH) {
				LOG_ERROR("The maximum length for user defined command "
						"parameter is %u, received length is %zu (op=0x%x)",
						SEMIHOSTING_MAX_TCL_COMMAND_FIELD_LENGTH,
						len,
						semihosting->op);
				return ERROR_FAIL;
			}

			semihosting_user_op_params = malloc(len + 1);
			if (!semihosting_user_op_params)
				return ERROR_FAIL;
			semihosting_user_op_params[len] = 0;

			retval = target_read_buffer(target, addr, len,
					(uint8_t *)(semihosting_user_op_params));
			if (retval != ERROR_OK) {
				LOG_ERROR("Failed to read from target, semihosting op=0x%x (%s)",
						semihosting->op,
						semihosting_opcode_to_str(semihosting->op));
				free(semihosting_user_op_params);
				semihosting_user_op_params = NULL;
				return retval;
			}

			target_handle_event(target, semihosting->op);
			free(semihosting_user_op_params);
			semihosting_user_op_params = NULL;
			semihosting->result = 0;
			break;

		case SEMIHOSTING_SYS_ELAPSED:	/* 0x30 */
		/*
		 * Returns the number of elapsed target ticks since execution
		 * started.
		 * Use SYS_TICKFREQ to determine the tick frequency.
		 *
		 * Entry (32-bit)
		 * On entry, the PARAMETER REGISTER points to a two-field data
		 * block to be used for returning the number of elapsed ticks:
		 * - field 1 The least significant field and is at the low address.
		 * - field 2 The most significant field and is at the high address.
		 *
		 * Entry (64-bit)
		 * On entry the PARAMETER REGISTER points to a one-field data
		 * block to be used for returning the number of elapsed ticks:
		 * - field 1 The number of elapsed ticks as a 64-bit value.
		 *
		 * Return
		 * On exit:
		 * - On success, the RETURN REGISTER contains 0, the PARAMETER
		 * REGISTER is unchanged, and the data block pointed to by the
		 * PARAMETER REGISTER is filled in with the number of elapsed
		 * ticks.
		 * - On failure, the RETURN REGISTER contains -1, and the
		 * PARAMETER REGISTER contains -1.
		 *
		 * Note: Some semihosting implementations might not support this
		 * semihosting operation, and they always return -1 in the
		 * RETURN REGISTER.
		 */

		case SEMIHOSTING_SYS_TICKFREQ:	/* 0x31 */
		/*
		 * Returns the tick frequency.
		 *
		 * Entry
		 * The PARAMETER REGISTER must contain 0 on entry to this routine.
		 *
		 * Return
		 * On exit, the RETURN REGISTER contains either:
		 * - The number of ticks per second.
		 * - –1 if the target does not know the value of one tick.
		 *
		 * Note: Some semihosting implementations might not support
		 * this semihosting operation, and they always return -1 in the
		 * RETURN REGISTER.
		 */

		case SEMIHOSTING_SYS_TMPNAM:	/* 0x0D */
		/*
		 * Returns a temporary name for a file identified by a system
		 * file identifier.
		 *
		 * Entry
		 * On entry, the PARAMETER REGISTER contains a pointer to a
		 * three-word argument block:
		 * - field 1 A pointer to a buffer.
		 * - field 2 A target identifier for this filename. Its value
		 * must be an integer in the range 0-255.
		 * - field 3 Contains the length of the buffer. The length must
		 * be at least the value of L_tmpnam on the host system.
		 *
		 * Return
		 * On exit, the RETURN REGISTER contains:
		 * - 0 if the call is successful.
		 * - –1 if an error occurs.
		 *
		 * The buffer pointed to by the PARAMETER REGISTER contains
		 * the filename, prefixed with a suitable directory name.
		 * If you use the same target identifier again, the same
		 * filename is returned.
		 *
		 * Note: The returned string must be null-terminated.
		 */

		default:
			fprintf(stderr, "semihosting: unsupported call %#x\n",
				(unsigned) semihosting->op);
			semihosting->result = -1;
			semihosting->sys_errno = ENOTSUP;
	}

	if (!semihosting->hit_fileio) {
		retval = semihosting->post_result(target);
		if (retval != ERROR_OK) {
			LOG_ERROR("Failed to post semihosting result");
			return retval;
		}
	}

	return ERROR_OK;
}

/* -------------------------------------------------------------------------
 * Local functions. */

static int semihosting_common_fileio_info(struct target *target,
	struct gdb_fileio_info *fileio_info)
{
	struct semihosting *semihosting = target->semihosting;
	if (!semihosting)
		return ERROR_FAIL;

	/*
	 * To avoid unnecessary duplication, semihosting prepares the
	 * fileio_info structure out-of-band when the target halts. See
	 * do_semihosting for more detail.
	 */
	if (!semihosting->is_fileio || !semihosting->hit_fileio)
		return ERROR_FAIL;

	return ERROR_OK;
}

static int semihosting_common_fileio_end(struct target *target, int result,
	int fileio_errno, bool ctrl_c)
{
	struct gdb_fileio_info *fileio_info = target->fileio_info;
	struct semihosting *semihosting = target->semihosting;
	if (!semihosting)
		return ERROR_FAIL;

	/* clear pending status */
	semihosting->hit_fileio = false;

	semihosting->result = result;

	/*
	 * Some fileio results do not match up with what the semihosting
	 * operation expects; for these operations, we munge the results
	 * below:
	 */
	switch (semihosting->op) {
		case SEMIHOSTING_SYS_WRITE:	/* 0x05 */
		case SEMIHOSTING_SYS_READ:	/* 0x06 */
			if (result < 0)
				semihosting->result = fileio_info->param_3;  /* Zero bytes read/written. */
			else
				semihosting->result = (int64_t)fileio_info->param_3 - result;
			break;

		case SEMIHOSTING_SYS_SEEK:	/* 0x0a */
			if (result > 0)
				semihosting->result = 0;
			break;
	}

	bool fileio_failed = false;
	if (semihosting->op == SEMIHOSTING_SYS_ISTTY)
		fileio_failed = (semihosting->result == 0);
	else if (semihosting->op == SEMIHOSTING_SYS_RENAME)
		fileio_failed = (semihosting->result != 0);
	else
		fileio_failed = (semihosting->result == -1);

	if (fileio_failed)
		semihosting->sys_errno = fileio_errno;

	return semihosting->post_result(target);
}

/* -------------------------------------------------------------------------
 * Utility functions. */

/**
 * Read all fields of a command from target to buffer.
 */
int semihosting_read_fields(struct target *target, size_t number,
	uint8_t *fields)
{
	struct semihosting *semihosting = target->semihosting;
	/* Use 4-byte multiples to trigger fast memory access. */
	return target_read_memory(target, semihosting->param, 4,
			number * (semihosting->word_size_bytes / 4), fields);
}

/**
 * Write all fields of a command from buffer to target.
 */
int semihosting_write_fields(struct target *target, size_t number,
	uint8_t *fields)
{
	struct semihosting *semihosting = target->semihosting;
	/* Use 4-byte multiples to trigger fast memory access. */
	return target_write_memory(target, semihosting->param, 4,
			number * (semihosting->word_size_bytes / 4), fields);
}

/**
 * Extract a field from the buffer, considering register size and endianness.
 */
uint64_t semihosting_get_field(struct target *target, size_t index,
	uint8_t *fields)
{
	struct semihosting *semihosting = target->semihosting;
	if (semihosting->word_size_bytes == 8)
		return target_buffer_get_u64(target, fields + (index * 8));
	else
		return target_buffer_get_u32(target, fields + (index * 4));
}

/**
 * Store a field in the buffer, considering register size and endianness.
 */
void semihosting_set_field(struct target *target, uint64_t value,
	size_t index,
	uint8_t *fields)
{
	struct semihosting *semihosting = target->semihosting;
	if (semihosting->word_size_bytes == 8)
		target_buffer_set_u64(target, fields + (index * 8), value);
	else
		target_buffer_set_u32(target, fields + (index * 4), value);
}

/* -------------------------------------------------------------------------
 * Semihosting redirect over TCP structs and functions */

static int semihosting_service_new_connection_handler(struct connection *connection)
{
	struct semihosting_tcp_service *service = connection->service->priv;
	service->semihosting->tcp_connection = connection;

	return ERROR_OK;
}

static int semihosting_service_input_handler(struct connection *connection)
{
	struct semihosting_tcp_service *service = connection->service->priv;

	if (!connection->input_pending) {
		/* consume received data, not for semihosting IO */
		const int buf_len = 100;
		char buf[buf_len];
		int bytes_read = connection_read(connection, buf, buf_len);

		if (bytes_read == 0) {
			return ERROR_SERVER_REMOTE_CLOSED;
		} else if (bytes_read == -1) {
			LOG_ERROR("error during read: %s", strerror(errno));
			return ERROR_SERVER_REMOTE_CLOSED;
		}
	} else if (service->error != ERROR_OK) {
		return ERROR_SERVER_REMOTE_CLOSED;
	}

	return ERROR_OK;
}

static int semihosting_service_connection_closed_handler(struct connection *connection)
{
	struct semihosting_tcp_service *service = connection->service->priv;
	if (service) {
		free(service->name);
		free(service);
	}

	return ERROR_OK;
}

static void semihosting_tcp_close_cnx(struct semihosting *semihosting)
{
	if (!semihosting->tcp_connection)
		return;

	struct service *service = semihosting->tcp_connection->service;
	remove_service(service->name, service->port);
	semihosting->tcp_connection = NULL;

}

static const struct service_driver semihosting_service_driver = {
	.name = "semihosting",
	.new_connection_during_keep_alive_handler = NULL,
	.new_connection_handler = semihosting_service_new_connection_handler,
	.input_handler = semihosting_service_input_handler,
	.connection_closed_handler = semihosting_service_connection_closed_handler,
	.keep_client_alive_handler = NULL,
};

/* -------------------------------------------------------------------------
 * Common semihosting commands handlers. */

COMMAND_HANDLER(handle_common_semihosting_command)
{
	struct target *target = get_current_target(CMD_CTX);

	if (!target) {
		LOG_ERROR("No target selected");
		return ERROR_FAIL;
	}

	struct semihosting *semihosting = target->semihosting;
	if (!semihosting) {
		command_print(CMD, "semihosting not supported for current target");
		return ERROR_FAIL;
	}

	if (CMD_ARGC > 0) {
		int is_active;

		COMMAND_PARSE_ENABLE(CMD_ARGV[0], is_active);

		if (!target_was_examined(target)) {
			LOG_ERROR("Target not examined yet");
			return ERROR_FAIL;
		}

		if (semihosting && semihosting->setup(target, is_active) != ERROR_OK) {
			LOG_ERROR("Failed to Configure semihosting");
			return ERROR_FAIL;
		}

		/* FIXME never let that "catch" be dropped! (???) */
		semihosting->is_active = is_active;
	}

	command_print(CMD, "semihosting is %s",
		semihosting->is_active
		? "enabled" : "disabled");

	return ERROR_OK;
}

COMMAND_HANDLER(handle_common_semihosting_redirect_command)
{
	struct target *target = get_current_target(CMD_CTX);

	if (!target) {
		LOG_ERROR("No target selected");
		return ERROR_FAIL;
	}

	struct semihosting *semihosting = target->semihosting;
	if (!semihosting) {
		command_print(CMD, "semihosting not supported for current target");
		return ERROR_FAIL;
	}

	if (!semihosting->is_active) {
		command_print(CMD, "semihosting not yet enabled for current target");
		return ERROR_FAIL;
	}

	enum semihosting_redirect_config cfg;
	const char *port;

	if (CMD_ARGC < 1)
		return ERROR_COMMAND_SYNTAX_ERROR;

	if (strcmp(CMD_ARGV[0], "disable") == 0) {
		cfg = SEMIHOSTING_REDIRECT_CFG_NONE;
		if (CMD_ARGC > 1)
			return ERROR_COMMAND_SYNTAX_ERROR;
	} else if (strcmp(CMD_ARGV[0], "tcp") == 0) {
		if (CMD_ARGC < 2 || CMD_ARGC > 3)
			return ERROR_COMMAND_SYNTAX_ERROR;

		port = CMD_ARGV[1];

		cfg = SEMIHOSTING_REDIRECT_CFG_ALL;
		if (CMD_ARGC == 3) {
			if (strcmp(CMD_ARGV[2], "debug") == 0)
				cfg = SEMIHOSTING_REDIRECT_CFG_DEBUG;
			else if (strcmp(CMD_ARGV[2], "stdio") == 0)
				cfg = SEMIHOSTING_REDIRECT_CFG_STDIO;
			else if (strcmp(CMD_ARGV[2], "all") != 0)
				return ERROR_COMMAND_SYNTAX_ERROR;
		}
	} else {
		return ERROR_COMMAND_SYNTAX_ERROR;
	}

	semihosting_tcp_close_cnx(semihosting);
	semihosting->redirect_cfg = SEMIHOSTING_REDIRECT_CFG_NONE;

	if (cfg != SEMIHOSTING_REDIRECT_CFG_NONE) {
		struct semihosting_tcp_service *service =
				calloc(1, sizeof(struct semihosting_tcp_service));
		if (!service) {
			LOG_ERROR("Failed to allocate semihosting TCP service.");
			return ERROR_FAIL;
		}

		service->semihosting = semihosting;

		service->name = alloc_printf("%s semihosting service", target_name(target));
		if (!service->name) {
			LOG_ERROR("Out of memory");
			free(service);
			return ERROR_FAIL;
		}

		int ret = add_service(&semihosting_service_driver,
				port, 1, service);

		if (ret != ERROR_OK) {
			LOG_ERROR("failed to initialize %s", service->name);
			free(service->name);
			free(service);
			return ERROR_FAIL;
		}
	}

	semihosting->redirect_cfg = cfg;

	return ERROR_OK;
}

COMMAND_HANDLER(handle_common_semihosting_fileio_command)
{
	struct target *target = get_current_target(CMD_CTX);

	if (!target) {
		LOG_ERROR("No target selected");
		return ERROR_FAIL;
	}

	struct semihosting *semihosting = target->semihosting;
	if (!semihosting) {
		command_print(CMD, "semihosting not supported for current target");
		return ERROR_FAIL;
	}

	if (!semihosting->is_active) {
		command_print(CMD, "semihosting not yet enabled for current target");
		return ERROR_FAIL;
	}

	if (CMD_ARGC > 0)
		COMMAND_PARSE_ENABLE(CMD_ARGV[0], semihosting->is_fileio);

	command_print(CMD, "semihosting fileio is %s",
		semihosting->is_fileio
		? "enabled" : "disabled");

	return ERROR_OK;
}

COMMAND_HANDLER(handle_common_semihosting_cmdline)
{
	struct target *target = get_current_target(CMD_CTX);
	unsigned int i;

	if (!target) {
		LOG_ERROR("No target selected");
		return ERROR_FAIL;
	}

	struct semihosting *semihosting = target->semihosting;
	if (!semihosting) {
		command_print(CMD, "semihosting not supported for current target");
		return ERROR_FAIL;
	}

	free(semihosting->cmdline);
	semihosting->cmdline = CMD_ARGC > 0 ? strdup(CMD_ARGV[0]) : NULL;

	for (i = 1; i < CMD_ARGC; i++) {
		char *cmdline = alloc_printf("%s %s", semihosting->cmdline, CMD_ARGV[i]);
		if (!cmdline)
			break;
		free(semihosting->cmdline);
		semihosting->cmdline = cmdline;
	}

	command_print(CMD, "semihosting command line is [%s]",
		semihosting->cmdline);

	return ERROR_OK;
}

COMMAND_HANDLER(handle_common_semihosting_resumable_exit_command)
{
	struct target *target = get_current_target(CMD_CTX);

	if (!target) {
		LOG_ERROR("No target selected");
		return ERROR_FAIL;
	}

	struct semihosting *semihosting = target->semihosting;
	if (!semihosting) {
		command_print(CMD, "semihosting not supported for current target");
		return ERROR_FAIL;
	}

	if (!semihosting->is_active) {
		command_print(CMD, "semihosting not yet enabled for current target");
		return ERROR_FAIL;
	}

	if (CMD_ARGC > 0)
		COMMAND_PARSE_ENABLE(CMD_ARGV[0], semihosting->has_resumable_exit);

	command_print(CMD, "semihosting resumable exit is %s",
		semihosting->has_resumable_exit
		? "enabled" : "disabled");

	return ERROR_OK;
}

COMMAND_HANDLER(handle_common_semihosting_read_user_param_command)
{
	struct target *target = get_current_target(CMD_CTX);
	struct semihosting *semihosting = target->semihosting;

	if (CMD_ARGC)
		return ERROR_COMMAND_SYNTAX_ERROR;

	if (!semihosting->is_active) {
		LOG_ERROR("semihosting not yet enabled for current target");
		return ERROR_FAIL;
	}

	if (!semihosting_user_op_params) {
		LOG_ERROR("This command is usable only from a registered user "
				"semihosting event callback.");
		return ERROR_FAIL;
	}

	command_print_sameline(CMD, "%s", semihosting_user_op_params);

	return ERROR_OK;
}

COMMAND_HANDLER(handle_common_semihosting_basedir_command)
{
	struct target *target = get_current_target(CMD_CTX);

	if (CMD_ARGC > 1)
		return ERROR_COMMAND_SYNTAX_ERROR;

	if (!target) {
		LOG_ERROR("No target selected");
		return ERROR_FAIL;
	}

	struct semihosting *semihosting = target->semihosting;
	if (!semihosting) {
		command_print(CMD, "semihosting not supported for current target");
		return ERROR_FAIL;
	}

	if (!semihosting->is_active) {
		command_print(CMD, "semihosting not yet enabled for current target");
		return ERROR_FAIL;
	}

	if (CMD_ARGC > 0) {
		free(semihosting->basedir);
		semihosting->basedir = strdup(CMD_ARGV[0]);
		if (!semihosting->basedir) {
			command_print(CMD, "semihosting failed to allocate memory for basedir!");
			return ERROR_FAIL;
		}
	}

	command_print(CMD, "semihosting base dir: %s",
		semihosting->basedir ? semihosting->basedir : "");

	return ERROR_OK;
}

const struct command_registration semihosting_common_handlers[] = {
	{
		.name = "semihosting",
		.handler = handle_common_semihosting_command,
		.mode = COMMAND_EXEC,
		.usage = "['enable'|'disable']",
		.help = "activate support for semihosting operations",
	},
	{
		.name = "semihosting_redirect",
		.handler = handle_common_semihosting_redirect_command,
		.mode = COMMAND_EXEC,
		.usage = "(disable | tcp <port> ['debug'|'stdio'|'all'])",
		.help = "redirect semihosting IO",
	},
	{
		.name = "semihosting_cmdline",
		.handler = handle_common_semihosting_cmdline,
		.mode = COMMAND_EXEC,
		.usage = "arguments",
		.help = "command line arguments to be passed to program",
	},
	{
		.name = "semihosting_fileio",
		.handler = handle_common_semihosting_fileio_command,
		.mode = COMMAND_EXEC,
		.usage = "['enable'|'disable']",
		.help = "activate support for semihosting fileio operations",
	},
	{
		.name = "semihosting_resexit",
		.handler = handle_common_semihosting_resumable_exit_command,
		.mode = COMMAND_EXEC,
		.usage = "['enable'|'disable']",
		.help = "activate support for semihosting resumable exit",
	},
	{
		.name = "semihosting_read_user_param",
		.handler = handle_common_semihosting_read_user_param_command,
		.mode = COMMAND_EXEC,
		.usage = "",
		.help = "read parameters in semihosting-user-cmd-0x10X callbacks",
	},
	{
		.name = "semihosting_basedir",
		.handler = handle_common_semihosting_basedir_command,
		.mode = COMMAND_EXEC,
		.usage = "[dir]",
		.help = "set the base directory for semihosting I/O operations",
	},
	COMMAND_REGISTRATION_DONE
};
