| /* |
| * 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 messege. |
| */ |
| 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); |