You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
400 lines
10 KiB
400 lines
10 KiB
/* |
|
* Copyright (c) 2023 Synopsys Inc. All rights reserved. |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include <zephyr/drivers/uart.h> |
|
#include <zephyr/kernel.h> |
|
#include <zephyr/toolchain.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 |
|
|
|
#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 DEVICE_API(uart, 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);
|
|
|