404 lines
11 KiB
C
404 lines
11 KiB
C
/*
|
|
* Copyright (c) 2023 Synopsys Inc. All rights reserved.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/drivers/uart.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <string.h>
|
|
|
|
#define DT_DRV_COMPAT snps_hostlink_uart
|
|
|
|
/* Only supported by HW and nSIM targets */
|
|
BUILD_ASSERT(!IS_ENABLED(CONFIG_QEMU_TARGET));
|
|
/* Only supported by ARC targets */
|
|
BUILD_ASSERT(IS_ENABLED(CONFIG_ARC));
|
|
|
|
#define HL_SYSCALL_OPEN 0
|
|
#define HL_SYSCALL_CLOSE 1
|
|
#define HL_SYSCALL_READ 2
|
|
#define HL_SYSCALL_WRITE 3
|
|
#define HL_SYSCALL_LSEEK 4
|
|
#define HL_SYSCALL_UNLINK 5
|
|
#define HL_SYSCALL_ISATTY 6
|
|
#define HL_SYSCALL_TMPNAM 7
|
|
#define HL_SYSCALL_GETENV 8
|
|
#define HL_SYSCALL_CLOCK 9
|
|
#define HL_SYSCALL_TIME 10
|
|
#define HL_SYSCALL_RENAME 11
|
|
#define HL_SYSCALL_ARGC 12
|
|
#define HL_SYSCALL_ARGV 13
|
|
#define HL_SYSCALL_RETCODE 14
|
|
#define HL_SYSCALL_ACCESS 15
|
|
#define HL_SYSCALL_GETPID 16
|
|
#define HL_SYSCALL_GETCWD 17
|
|
#define HL_SYSCALL_USER 18
|
|
|
|
#ifndef __noinline
|
|
#define __noinline __attribute__((noinline))
|
|
#endif /* __noinline */
|
|
|
|
#define HL_VERSION 1
|
|
|
|
/* "No message here" mark. */
|
|
#define HL_NOADDRESS 0xFFFFFFFF
|
|
|
|
/* TODO: if we want to carve some additional space we can use the actual maximum processor cache
|
|
* line size here (i.e 128)
|
|
*/
|
|
#define HL_MAX_DCACHE_LINE 256
|
|
|
|
/* Hostlink gateway structure. */
|
|
struct hl_hdr {
|
|
uint32_t version; /* Current version is 1. */
|
|
uint32_t target2host_addr; /* Packet address from target to host. */
|
|
uint32_t host2target_addr; /* Packet address from host to target. */
|
|
uint32_t buf_addr; /* Address for host to write answer. */
|
|
uint32_t payload_size; /* Buffer size without packet header. */
|
|
uint32_t options; /* For future use. */
|
|
uint32_t break_to_mon_addr; /* For future use. */
|
|
} __packed;
|
|
|
|
/* Hostlink packet header. */
|
|
struct hl_pkt_hdr {
|
|
uint32_t packet_id; /* Packet id. Always set to 1 here. */
|
|
uint32_t total_size; /* Size of packet including header. */
|
|
uint32_t priority; /* For future use. */
|
|
uint32_t type; /* For future use. */
|
|
uint32_t checksum; /* For future use. */
|
|
} __packed;
|
|
|
|
struct hl_packed_int {
|
|
volatile uint16_t type;
|
|
volatile uint16_t size;
|
|
volatile int32_t value;
|
|
} __packed;
|
|
|
|
struct hl_packed_short_buff {
|
|
volatile uint16_t type;
|
|
volatile uint16_t size;
|
|
volatile uint8_t payload_short[4];
|
|
} __packed;
|
|
|
|
BUILD_ASSERT(sizeof(struct hl_packed_int) == sizeof(struct hl_packed_short_buff));
|
|
|
|
struct hl_pkt_write_char_put {
|
|
struct hl_packed_int syscall_nr;
|
|
struct hl_packed_int fd;
|
|
struct hl_packed_short_buff buff;
|
|
struct hl_packed_int nbyte;
|
|
} __packed;
|
|
|
|
struct hl_pkt_write_char_get {
|
|
struct hl_packed_int byte_written;
|
|
struct hl_packed_int host_errno;
|
|
} __packed;
|
|
|
|
#define MAX_PKT_SZ MAX(sizeof(struct hl_pkt_write_char_put), sizeof(struct hl_pkt_write_char_get))
|
|
#define HL_HEADERS_SZ (sizeof(struct hl_hdr) + sizeof(struct hl_pkt_hdr))
|
|
BUILD_ASSERT(HL_HEADERS_SZ + MAX_PKT_SZ < HL_MAX_DCACHE_LINE);
|
|
|
|
union payload_u {
|
|
struct hl_pkt_write_char_put pkt_write_char_put;
|
|
struct hl_pkt_write_char_get pkt_write_char_get;
|
|
char reserved[HL_MAX_DCACHE_LINE - HL_HEADERS_SZ];
|
|
} __packed;
|
|
|
|
BUILD_ASSERT(sizeof(union payload_u) % 4 == 0);
|
|
|
|
/* Main hostlink structure. */
|
|
struct hl {
|
|
/* General hostlink information. */
|
|
volatile struct hl_hdr hdr;
|
|
/* Start of the hostlink buffer. */
|
|
volatile struct hl_pkt_hdr pkt_hdr;
|
|
/* Payload buffer */
|
|
volatile union payload_u payload;
|
|
} __aligned(HL_MAX_DCACHE_LINE) __packed;
|
|
|
|
/* In general we must exactly fit into one or multiple cache lines as we shouldn't share hostlink
|
|
* buffer (which is uncached) with any cached data
|
|
*/
|
|
BUILD_ASSERT(sizeof(struct hl) % HL_MAX_DCACHE_LINE == 0);
|
|
/* However, with current supported functionality we fit into one MAX cache line. If we add
|
|
* some features which require bigger payload buffer this might become not true.
|
|
*/
|
|
BUILD_ASSERT(sizeof(struct hl) == HL_MAX_DCACHE_LINE);
|
|
|
|
/* Main structure. Do not rename as nSIM simulator / MDB debugger looks for the '__HOSTLINK__'
|
|
* symbol. We need to keep it initialized so it won't be put into BSS (so we won't write with
|
|
* regular cached access in it).
|
|
*/
|
|
volatile struct hl __HOSTLINK__ = {
|
|
.hdr = {
|
|
.version = HL_VERSION,
|
|
.target2host_addr = HL_NOADDRESS
|
|
}
|
|
};
|
|
|
|
BUILD_ASSERT(sizeof(__HOSTLINK__) % HL_MAX_DCACHE_LINE == 0);
|
|
|
|
#if defined(__CCAC__)
|
|
#define HL_HAS_C_ACCESSORS 0
|
|
#elif defined(CONFIG_ISA_ARCV3)
|
|
#define HL_HAS_C_ACCESSORS 0
|
|
#else
|
|
#define HL_HAS_C_ACCESSORS 1
|
|
#endif
|
|
|
|
#if HL_HAS_C_ACCESSORS
|
|
|
|
#ifndef __uncached
|
|
#define __uncached __attribute__((uncached))
|
|
#endif /* __uncached */
|
|
|
|
static inline void hl_write32(volatile void *addr, uint32_t val)
|
|
{
|
|
*(volatile __uncached uint32_t *)addr = val;
|
|
}
|
|
|
|
static inline void hl_write16(volatile void *addr, uint16_t val)
|
|
{
|
|
*(volatile __uncached uint16_t *)addr = val;
|
|
}
|
|
|
|
static inline void hl_write8(volatile void *addr, uint8_t val)
|
|
{
|
|
*(volatile __uncached uint8_t *)addr = val;
|
|
}
|
|
|
|
static inline uint32_t hl_read32(volatile void *addr)
|
|
{
|
|
return *(volatile __uncached uint32_t *)addr;
|
|
}
|
|
|
|
static inline uint16_t hl_read16(volatile void *addr)
|
|
{
|
|
return *(volatile __uncached uint16_t *)addr;
|
|
}
|
|
#else
|
|
static inline void hl_write32(volatile void *addr, uint32_t val)
|
|
{
|
|
__asm__ __volatile__("st.di %0, [%1]" :: "r" (val), "r" (addr) : "memory");
|
|
}
|
|
|
|
static inline void hl_write16(volatile void *addr, uint16_t val)
|
|
{
|
|
__asm__ __volatile__("stb.di %0, [%1]" :: "r" (val), "r" (addr) : "memory");
|
|
}
|
|
|
|
static inline void hl_write8(volatile void *addr, uint8_t val)
|
|
{
|
|
__asm__ __volatile__("sth.di %0, [%1]" :: "r" (val), "r" (addr) : "memory");
|
|
}
|
|
|
|
static inline uint32_t hl_read32(volatile void *addr)
|
|
{
|
|
uint32_t w;
|
|
|
|
__asm__ __volatile__("ld.di %0, [%1]" : "=r" (w) : "r" (addr) : "memory");
|
|
|
|
return w;
|
|
}
|
|
|
|
static inline uint16_t hl_read16(volatile void *addr)
|
|
{
|
|
uint16_t w;
|
|
|
|
__asm__ __volatile__("ld.di %0, [%1]" : "=r" (w) : "r" (addr) : "memory");
|
|
|
|
return w;
|
|
}
|
|
#endif /* HL_HAS_C_ACCESSORS */
|
|
|
|
/* Get hostlink payload size (iochunk + reserved space). */
|
|
static uint32_t hl_payload_size(void)
|
|
{
|
|
return sizeof(__HOSTLINK__.payload);
|
|
}
|
|
|
|
#define ALIGN(x, y) (((x) + ((y) - 1)) & ~((y) - 1))
|
|
|
|
/* Fill hostlink packet header. */
|
|
static void hl_pkt_init(volatile struct hl_pkt_hdr *pkt, int size)
|
|
{
|
|
hl_write32(&pkt->packet_id, 1);
|
|
hl_write32(&pkt->total_size, ALIGN(size, 4) + sizeof(struct hl_pkt_hdr));
|
|
hl_write32(&pkt->priority, 0);
|
|
hl_write32(&pkt->type, 0);
|
|
hl_write32(&pkt->checksum, 0);
|
|
}
|
|
|
|
/* Send hostlink packet to the host. */
|
|
static void hl_static_send(size_t payload_used)
|
|
{
|
|
/* We are OK to cast pointer to uint32_t even on 64bit platforms as we support to build
|
|
* Zephyr on ARCv3 64bit only to lower 4GiB. Still we need to cast via uintptr_t to avoid
|
|
* compiler warnings.
|
|
*/
|
|
uint32_t buf_addr = (uint32_t)(uintptr_t)(&__HOSTLINK__.pkt_hdr);
|
|
|
|
hl_pkt_init(&__HOSTLINK__.pkt_hdr, payload_used);
|
|
|
|
hl_write32(&__HOSTLINK__.hdr.buf_addr, buf_addr);
|
|
hl_write32(&__HOSTLINK__.hdr.payload_size, hl_payload_size());
|
|
hl_write32(&__HOSTLINK__.hdr.host2target_addr, HL_NOADDRESS);
|
|
hl_write32(&__HOSTLINK__.hdr.version, HL_VERSION);
|
|
hl_write32(&__HOSTLINK__.hdr.options, 0);
|
|
hl_write32(&__HOSTLINK__.hdr.break_to_mon_addr, 0);
|
|
|
|
compiler_barrier();
|
|
|
|
/* This tells the debugger we have a command.
|
|
* It is responsibility of debugger to set this back to HL_NOADDRESS
|
|
* after receiving the packet.
|
|
* Please note that we don't wait here because some implementations
|
|
* use hl_blockedPeek() function as a signal that we send a message.
|
|
*/
|
|
hl_write32(&__HOSTLINK__.hdr.target2host_addr, buf_addr);
|
|
|
|
compiler_barrier();
|
|
}
|
|
|
|
/*
|
|
* Wait for host response and return pointer to hostlink payload.
|
|
* Symbol hl_blockedPeek() is used by the simulator as message signal.
|
|
*/
|
|
static void __noinline _hl_blockedPeek(void)
|
|
{
|
|
while (hl_read32(&__HOSTLINK__.hdr.host2target_addr) == HL_NOADDRESS) {
|
|
/* TODO: Timeout. */
|
|
}
|
|
}
|
|
|
|
static void hl_static_recv(void)
|
|
{
|
|
compiler_barrier();
|
|
_hl_blockedPeek();
|
|
compiler_barrier();
|
|
}
|
|
|
|
/* Mark hostlink buffer as "No message here". */
|
|
static void hl_delete(void)
|
|
{
|
|
hl_write32(&__HOSTLINK__.hdr.target2host_addr, HL_NOADDRESS);
|
|
}
|
|
|
|
/* Parameter types. */
|
|
#define PAT_CHAR 1
|
|
#define PAT_SHORT 2
|
|
#define PAT_INT 3
|
|
#define PAT_STRING 4
|
|
/* For future use. */
|
|
#define PAT_INT64 5
|
|
|
|
static void hl_static_pack_int(volatile struct hl_packed_int *pack, int32_t value)
|
|
{
|
|
hl_write16(&pack->type, PAT_INT);
|
|
hl_write16(&pack->size, 4);
|
|
hl_write32(&pack->value, value);
|
|
}
|
|
|
|
static void hl_static_pack_char(volatile struct hl_packed_short_buff *pack, unsigned char c)
|
|
{
|
|
hl_write16(&pack->type, PAT_STRING);
|
|
hl_write16(&pack->size, 1);
|
|
hl_write8(&pack->payload_short, c);
|
|
}
|
|
|
|
static int hl_static_unpack_int(volatile struct hl_packed_int *pack, int32_t *value)
|
|
{
|
|
uint16_t type = hl_read16(&pack->type);
|
|
uint16_t size = hl_read16(&pack->size);
|
|
|
|
if (type != PAT_INT) {
|
|
return -1;
|
|
}
|
|
|
|
if (size != 4) {
|
|
return -1;
|
|
}
|
|
|
|
*value = hl_read32(&pack->value);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int32_t hl_write_char(int fd, const char c)
|
|
{
|
|
/*
|
|
* Format:
|
|
* in, int -> syscall (HL_SYSCALL_WRITE)
|
|
* in, int -> file descriptor
|
|
* in, ptr -> buffer
|
|
* in, int -> bytes number
|
|
* out, int -> bytes written
|
|
* out, int, host errno
|
|
*/
|
|
|
|
hl_static_pack_int(&__HOSTLINK__.payload.pkt_write_char_put.syscall_nr, HL_SYSCALL_WRITE);
|
|
|
|
hl_static_pack_int(&__HOSTLINK__.payload.pkt_write_char_put.fd, fd);
|
|
|
|
hl_static_pack_char(&__HOSTLINK__.payload.pkt_write_char_put.buff, c);
|
|
|
|
hl_static_pack_int(&__HOSTLINK__.payload.pkt_write_char_put.nbyte, 1);
|
|
|
|
hl_static_send(sizeof(struct hl_pkt_write_char_put));
|
|
hl_static_recv();
|
|
|
|
int32_t bwr = 0;
|
|
int ret = hl_static_unpack_int(&__HOSTLINK__.payload.pkt_write_char_get.byte_written, &bwr);
|
|
|
|
/* we can get host errno here with:
|
|
* hl_static_unpack_int(&__HOSTLINK__.pkt_write_char_get.host_errno, &host_errno);
|
|
* but we don't need it for UART emulation.
|
|
*/
|
|
|
|
if (bwr <= 0) {
|
|
ret = -1;
|
|
}
|
|
|
|
hl_delete();
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Poll the device for input.
|
|
*
|
|
* @param dev UART device struct
|
|
* @param c Pointer to character
|
|
*
|
|
* @return 0 if a character arrived, -1 if the input buffer if empty.
|
|
*/
|
|
static int uart_hostlink_poll_in(const struct device *dev, unsigned char *c)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
/* We plan to use hostlink for logging, so no much sense in poll_in implementation */
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* @brief Output a character in polled mode.
|
|
*
|
|
* @param dev UART device struct
|
|
* @param c Character to send
|
|
*/
|
|
static void uart_hostlink_poll_out(const struct device *dev, unsigned char c)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
hl_write_char(1, c);
|
|
}
|
|
|
|
static const struct uart_driver_api uart_hostlink_driver_api = {
|
|
.poll_in = uart_hostlink_poll_in,
|
|
.poll_out = uart_hostlink_poll_out,
|
|
};
|
|
|
|
DEVICE_DT_DEFINE(DT_NODELABEL(hostlink), NULL, NULL, NULL, NULL, PRE_KERNEL_1,
|
|
CONFIG_SERIAL_INIT_PRIORITY, &uart_hostlink_driver_api);
|