zephyr/drivers/serial/uart_hostlink.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);