/*
 * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#pragma once

#include <string.h>
#include <sys/errno.h>

#ifdef __XTENSA__
#include "xtensa/semihosting.h"
#elif __riscv
#include "riscv/semihosting.h"
#else
#error Unsupported architecture
#endif

#ifdef __cplusplus
extern "C" {
#endif

/**
 * Semihosting call numbers and functions for OpenOCD.
 * In OpenOCD, ARM semihosting call numbers and parameters are used for
 * RISC-V and Xtensa targets.
 *
 * These conventions are not compatible with Xtensa ISS and QEMU for Xtensa,
 * which the actual Xtensa-specific semihosting call numbers and formats;
 * these are not supported in ESP-IDF yet.
 */

#define SEMIHOSTING_SYS_CLOCK 0x10
#define SEMIHOSTING_SYS_CLOSE 0x02
#define SEMIHOSTING_SYS_ERRNO 0x13
#define SEMIHOSTING_SYS_EXIT 0x18
#define SEMIHOSTING_SYS_EXIT_EXTENDED 0x20
#define SEMIHOSTING_SYS_FLEN 0x0C
#define SEMIHOSTING_SYS_GET_CMDLINE 0x15
#define SEMIHOSTING_SYS_HEAPINFO 0x16
#define SEMIHOSTING_SYS_ISERROR 0x08
#define SEMIHOSTING_SYS_ISTTY 0x09
#define SEMIHOSTING_SYS_OPEN 0x01
#define SEMIHOSTING_SYS_READ 0x06
#define SEMIHOSTING_SYS_READC 0x07
#define SEMIHOSTING_SYS_REMOVE 0x0E
#define SEMIHOSTING_SYS_RENAME 0x0F
#define SEMIHOSTING_SYS_SEEK 0x0A
#define SEMIHOSTING_SYS_SYSTEM 0x12
#define SEMIHOSTING_SYS_TIME 0x11
#define SEMIHOSTING_SYS_WRITE 0x05
#define SEMIHOSTING_SYS_WRITEC 0x03
#define SEMIHOSTING_SYS_WRITE0 0x04

/* This call is an Espressif OpenOCD extension to send the version
 * information to the host. This lets the host support different IDF versions,
 * allowing semihosting interface to be modified over time.
 *
 * The parameters of this call are:
 * - pointer to the version info structure,
 * - size of the version info structure.
 *
 * At present, the structure should contain a single word, indicating
 * the semihosting interface version used by the target.
 *
 * If the syscall is recognized, the return value is zero.
 */
#define ESP_SEMIHOSTING_SYS_DRV_INFO                0x100

/* 0x101...0x104 used by RiscV for custom semihosting calls */

/* Other Espressif extension sys calls */
#define ESP_SEMIHOSTING_SYS_SEEK                    0x105	/* custom lseek with whence */
/* not implemented yet */
#define ESP_SEMIHOSTING_SYS_MKDIR                   0x106
#define ESP_SEMIHOSTING_SYS_OPENDIR                 0x107
#define ESP_SEMIHOSTING_SYS_READDIR                 0x108
#define ESP_SEMIHOSTING_SYS_READDIR_R               0x109
#define ESP_SEMIHOSTING_SYS_SEEKDIR                 0x10A
#define ESP_SEMIHOSTING_SYS_TELLDIR                 0x10B
#define ESP_SEMIHOSTING_SYS_CLOSEDIR                0x10C
#define ESP_SEMIHOSTING_SYS_RMDIR                   0x10D
#define ESP_SEMIHOSTING_SYS_ACCESS                  0x10E
#define ESP_SEMIHOSTING_SYS_TRUNCATE                0x10F
#define ESP_SEMIHOSTING_SYS_UTIME                   0x110
#define ESP_SEMIHOSTING_SYS_FSTAT                   0x111
#define ESP_SEMIHOSTING_SYS_STAT                    0x112
#define ESP_SEMIHOSTING_SYS_FSYNC                   0x113
#define ESP_SEMIHOSTING_SYS_LINK                    0x114
#define ESP_SEMIHOSTING_SYS_UNLINK                  0x115

/*  Semihosting version bumped to 2. Changelog;
    1 - Memory based approach with 2 registers implemented as defined in the ARM standard.
    2 - User defined syscall numbers located between 0x100-0x1FF
    3 - The break instruction operands updated to (1, 14)
    4 - Absolute path support is dropped
*/
#define SEMIHOSTING_DRV_VERSION 2

/**
 * @brief Perform semihosting call and retrieve errno
 *
 * @param id    semihosting call number
 * @param data  data block to pass to the host; number of items and their
 *              meaning depends on the semihosting call. See the spec for
 *              details.
 * @param[out] out_errno  output, errno value from the host. Only set if
 *                        the return value is negative.
 * @return   return value from the host
 */
static inline long semihosting_call(long id, long *data, int *out_errno)
{
    long ret = semihosting_call_noerrno(id, data);
    if (ret < 0) {
        const int semihosting_sys_errno = SEMIHOSTING_SYS_ERRNO;
        *out_errno = (int) semihosting_call_noerrno(semihosting_sys_errno, NULL);
    }
    return ret;
}

static inline int semihosting_open(const char *path, int open_mode, int mode)
{
    int host_errno = 0;
    long args[] = {(long) path, open_mode, strlen(path), 0};
    (void) mode;  // unused in OpenOCD
    int result = (int) semihosting_call(SEMIHOSTING_SYS_OPEN, args, &host_errno);
    if (result < 0) {
        errno = host_errno;
    }
    return result;
}

static inline ssize_t semihosting_write(int fd, const void *data, size_t size)
{
    int host_errno = 0;
    long args[] = {fd, (long) data, size, 0};
    ssize_t ret = (ssize_t) semihosting_call(SEMIHOSTING_SYS_WRITE, args, &host_errno);
    if (ret < 0) {
        errno = host_errno;
        return ret;
    }
    /* On success, write syscall returns the number of bytes NOT written,
     * adjust the return value to match POSIX.
     */
    return size - (ssize_t)ret;
}

static inline ssize_t semihosting_read(int fd, void *data, size_t size)
{
    int host_errno = 0;
    long args[] = {fd, (long) data, size, 0};
    ssize_t ret = (ssize_t) semihosting_call(SEMIHOSTING_SYS_READ, args, &host_errno);
    if (ret < 0) {
        errno = host_errno;
        return ret;
    }
    /* On success, read syscall returns the number of bytes NOT read,
     * adjust the return value to match POSIX.
     */
    return size - (ssize_t)ret;
}

static inline int semihosting_close(int fd)
{
    int host_errno = 0;
    long args[] = {fd, 0, 0, 0};
    int ret = (int) semihosting_call(SEMIHOSTING_SYS_CLOSE, args, &host_errno);
    if (ret < 0) {
        errno = host_errno;
    }
    return ret;
}

static inline off_t semihosting_seek(int fd, off_t offset, int mode)
{
    int host_errno = 0;
    long args[] = {fd, offset, mode, 0};
    off_t ret = (off_t) semihosting_call(ESP_SEMIHOSTING_SYS_SEEK, args, &host_errno);
    if (ret == -1) {
        errno = host_errno;
    }
    return ret;
}

static inline int semihosting_ver_info(void)
{
    int host_errno = 0;
    struct {
        int version;
    } ver_info = { SEMIHOSTING_DRV_VERSION };
    long args[] = {(long) &ver_info, sizeof(ver_info), 0, 0};
    int ret = (int) semihosting_call(ESP_SEMIHOSTING_SYS_DRV_INFO, args, &host_errno);
    (void) host_errno;  /* errno not set by this call */
    return ret;
}

#ifdef __cplusplus
}
#endif