1 /*
2  * Copyright (c) 2023 Synopsys Inc. All rights reserved.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/drivers/uart.h>
8 #include <zephyr/kernel.h>
9 #include <zephyr/toolchain.h>
10 #include <string.h>
11 
12 #define DT_DRV_COMPAT snps_hostlink_uart
13 
14 /* Only supported by HW and nSIM targets */
15 BUILD_ASSERT(!IS_ENABLED(CONFIG_QEMU_TARGET));
16 /* Only supported by ARC targets */
17 BUILD_ASSERT(IS_ENABLED(CONFIG_ARC));
18 
19 #define HL_SYSCALL_OPEN		0
20 #define HL_SYSCALL_CLOSE	1
21 #define HL_SYSCALL_READ		2
22 #define HL_SYSCALL_WRITE	3
23 #define HL_SYSCALL_LSEEK	4
24 #define HL_SYSCALL_UNLINK	5
25 #define HL_SYSCALL_ISATTY	6
26 #define HL_SYSCALL_TMPNAM	7
27 #define HL_SYSCALL_GETENV	8
28 #define HL_SYSCALL_CLOCK	9
29 #define HL_SYSCALL_TIME		10
30 #define HL_SYSCALL_RENAME	11
31 #define HL_SYSCALL_ARGC		12
32 #define HL_SYSCALL_ARGV		13
33 #define HL_SYSCALL_RETCODE	14
34 #define HL_SYSCALL_ACCESS	15
35 #define HL_SYSCALL_GETPID	16
36 #define HL_SYSCALL_GETCWD	17
37 #define HL_SYSCALL_USER		18
38 
39 #define HL_VERSION 1
40 
41 /* "No message here" mark.  */
42 #define HL_NOADDRESS 0xFFFFFFFF
43 
44 /* TODO: if we want to carve some additional space we can use the actual maximum processor cache
45  * line size here (i.e 128)
46  */
47 #define HL_MAX_DCACHE_LINE 256
48 
49 /* Hostlink gateway structure.  */
50 struct hl_hdr {
51 	uint32_t version;		/* Current version is 1.  */
52 	uint32_t target2host_addr;	/* Packet address from target to host.  */
53 	uint32_t host2target_addr;	/* Packet address from host to target.  */
54 	uint32_t buf_addr;		/* Address for host to write answer.  */
55 	uint32_t payload_size;		/* Buffer size without packet header.  */
56 	uint32_t options;		/* For future use.  */
57 	uint32_t break_to_mon_addr;	/* For future use.  */
58 } __packed;
59 
60 /* Hostlink packet header.  */
61 struct hl_pkt_hdr {
62 	uint32_t packet_id;	/* Packet id.  Always set to 1 here.  */
63 	uint32_t total_size;	/* Size of packet including header.  */
64 	uint32_t priority;	/* For future use.  */
65 	uint32_t type;		/* For future use.  */
66 	uint32_t checksum;	/* For future use.  */
67 } __packed;
68 
69 struct hl_packed_int {
70 	volatile uint16_t type;
71 	volatile uint16_t size;
72 	volatile int32_t value;
73 } __packed;
74 
75 struct hl_packed_short_buff {
76 	volatile uint16_t type;
77 	volatile uint16_t size;
78 	volatile uint8_t payload_short[4];
79 } __packed;
80 
81 BUILD_ASSERT(sizeof(struct hl_packed_int) == sizeof(struct hl_packed_short_buff));
82 
83 struct hl_pkt_write_char_put {
84 	struct hl_packed_int syscall_nr;
85 	struct hl_packed_int fd;
86 	struct hl_packed_short_buff buff;
87 	struct hl_packed_int nbyte;
88 } __packed;
89 
90 struct hl_pkt_write_char_get {
91 	struct hl_packed_int byte_written;
92 	struct hl_packed_int host_errno;
93 } __packed;
94 
95 #define MAX_PKT_SZ MAX(sizeof(struct hl_pkt_write_char_put), sizeof(struct hl_pkt_write_char_get))
96 #define HL_HEADERS_SZ (sizeof(struct hl_hdr) + sizeof(struct hl_pkt_hdr))
97 BUILD_ASSERT(HL_HEADERS_SZ + MAX_PKT_SZ < HL_MAX_DCACHE_LINE);
98 
99 union payload_u {
100 	struct hl_pkt_write_char_put pkt_write_char_put;
101 	struct hl_pkt_write_char_get pkt_write_char_get;
102 	char reserved[HL_MAX_DCACHE_LINE - HL_HEADERS_SZ];
103 } __packed;
104 
105 BUILD_ASSERT(sizeof(union payload_u) % 4 == 0);
106 
107 /* Main hostlink structure.  */
108 struct hl {
109 	/* General hostlink information.  */
110 	volatile struct hl_hdr hdr;
111 	/* Start of the hostlink buffer.  */
112 	volatile struct hl_pkt_hdr pkt_hdr;
113 	/* Payload buffer */
114 	volatile union payload_u payload;
115 } __aligned(HL_MAX_DCACHE_LINE) __packed;
116 
117 /* In general we must exactly fit into one or multiple cache lines as we shouldn't share hostlink
118  * buffer (which is uncached) with any cached data
119  */
120 BUILD_ASSERT(sizeof(struct hl) % HL_MAX_DCACHE_LINE == 0);
121 /* However, with current supported functionality we fit into one MAX cache line. If we add
122  * some features which require bigger payload buffer this might become not true.
123  */
124 BUILD_ASSERT(sizeof(struct hl) == HL_MAX_DCACHE_LINE);
125 
126 /* Main structure. Do not rename as nSIM simulator / MDB debugger looks for the '__HOSTLINK__'
127  * symbol. We need to keep it initialized so it won't be put into BSS (so we won't write with
128  * regular cached access in it).
129  */
130 volatile struct hl __HOSTLINK__ = {
131 	.hdr = {
132 		.version = HL_VERSION,
133 		.target2host_addr = HL_NOADDRESS
134 	}
135 };
136 
137 BUILD_ASSERT(sizeof(__HOSTLINK__) % HL_MAX_DCACHE_LINE == 0);
138 
139 #if defined(__CCAC__)
140 #define HL_HAS_C_ACCESSORS 0
141 #elif defined(CONFIG_ISA_ARCV3)
142 #define HL_HAS_C_ACCESSORS 0
143 #else
144 #define HL_HAS_C_ACCESSORS 1
145 #endif
146 
147 #if HL_HAS_C_ACCESSORS
148 
149 #ifndef __uncached
150 #define __uncached __attribute__((uncached))
151 #endif /* __uncached */
152 
hl_write32(volatile void * addr,uint32_t val)153 static inline void hl_write32(volatile void *addr, uint32_t val)
154 {
155 	*(volatile __uncached uint32_t *)addr = val;
156 }
157 
hl_write16(volatile void * addr,uint16_t val)158 static inline void hl_write16(volatile void *addr, uint16_t val)
159 {
160 	*(volatile __uncached uint16_t *)addr = val;
161 }
162 
hl_write8(volatile void * addr,uint8_t val)163 static inline void hl_write8(volatile void *addr, uint8_t val)
164 {
165 	*(volatile __uncached uint8_t *)addr = val;
166 }
167 
hl_read32(volatile void * addr)168 static inline uint32_t hl_read32(volatile void *addr)
169 {
170 	return *(volatile __uncached uint32_t *)addr;
171 }
172 
hl_read16(volatile void * addr)173 static inline uint16_t hl_read16(volatile void *addr)
174 {
175 	return *(volatile __uncached uint16_t *)addr;
176 }
177 #else
hl_write32(volatile void * addr,uint32_t val)178 static inline void hl_write32(volatile void *addr, uint32_t val)
179 {
180 	__asm__ __volatile__("st.di %0, [%1]" :: "r" (val), "r" (addr) : "memory");
181 }
182 
hl_write16(volatile void * addr,uint16_t val)183 static inline void hl_write16(volatile void *addr, uint16_t val)
184 {
185 	__asm__ __volatile__("stb.di %0, [%1]" :: "r" (val), "r" (addr) : "memory");
186 }
187 
hl_write8(volatile void * addr,uint8_t val)188 static inline void hl_write8(volatile void *addr, uint8_t val)
189 {
190 	__asm__ __volatile__("sth.di %0, [%1]" :: "r" (val), "r" (addr) : "memory");
191 }
192 
hl_read32(volatile void * addr)193 static inline uint32_t hl_read32(volatile void *addr)
194 {
195 	uint32_t w;
196 
197 	__asm__ __volatile__("ld.di %0, [%1]" : "=r" (w) : "r" (addr) : "memory");
198 
199 	return w;
200 }
201 
hl_read16(volatile void * addr)202 static inline uint16_t hl_read16(volatile void *addr)
203 {
204 	uint16_t w;
205 
206 	__asm__ __volatile__("ld.di %0, [%1]" : "=r" (w) : "r" (addr) : "memory");
207 
208 	return w;
209 }
210 #endif /* HL_HAS_C_ACCESSORS */
211 
212 /* Get hostlink payload size (iochunk + reserved space).  */
hl_payload_size(void)213 static uint32_t hl_payload_size(void)
214 {
215 	return sizeof(__HOSTLINK__.payload);
216 }
217 
218 #define ALIGN(x, y) (((x) + ((y) - 1)) & ~((y) - 1))
219 
220 /* Fill hostlink packet header.  */
hl_pkt_init(volatile struct hl_pkt_hdr * pkt,int size)221 static void hl_pkt_init(volatile struct hl_pkt_hdr *pkt, int size)
222 {
223 	hl_write32(&pkt->packet_id, 1);
224 	hl_write32(&pkt->total_size, ALIGN(size, 4) + sizeof(struct hl_pkt_hdr));
225 	hl_write32(&pkt->priority, 0);
226 	hl_write32(&pkt->type, 0);
227 	hl_write32(&pkt->checksum, 0);
228 }
229 
230 /* Send hostlink packet to the host.  */
hl_static_send(size_t payload_used)231 static void hl_static_send(size_t payload_used)
232 {
233 	/* We are OK to cast pointer to uint32_t even on 64bit platforms as we support to build
234 	 * Zephyr on ARCv3 64bit only to lower 4GiB. Still we need to cast via uintptr_t to avoid
235 	 * compiler warnings.
236 	 */
237 	uint32_t buf_addr = (uint32_t)(uintptr_t)(&__HOSTLINK__.pkt_hdr);
238 
239 	hl_pkt_init(&__HOSTLINK__.pkt_hdr, payload_used);
240 
241 	hl_write32(&__HOSTLINK__.hdr.buf_addr, buf_addr);
242 	hl_write32(&__HOSTLINK__.hdr.payload_size, hl_payload_size());
243 	hl_write32(&__HOSTLINK__.hdr.host2target_addr, HL_NOADDRESS);
244 	hl_write32(&__HOSTLINK__.hdr.version, HL_VERSION);
245 	hl_write32(&__HOSTLINK__.hdr.options, 0);
246 	hl_write32(&__HOSTLINK__.hdr.break_to_mon_addr, 0);
247 
248 	compiler_barrier();
249 
250 	/* This tells the debugger we have a command.
251 	 * It is responsibility of debugger to set this back to HL_NOADDRESS
252 	 * after receiving the packet.
253 	 * Please note that we don't wait here because some implementations
254 	 * use hl_blockedPeek() function as a signal that we send a message.
255 	 */
256 	hl_write32(&__HOSTLINK__.hdr.target2host_addr, buf_addr);
257 
258 	compiler_barrier();
259 }
260 
261 /*
262  * Wait for host response and return pointer to hostlink payload.
263  * Symbol hl_blockedPeek() is used by the simulator as message signal.
264  */
_hl_blockedPeek(void)265 static void __noinline _hl_blockedPeek(void)
266 {
267 	while (hl_read32(&__HOSTLINK__.hdr.host2target_addr) == HL_NOADDRESS) {
268 		/* TODO: Timeout.  */
269 	}
270 }
271 
hl_static_recv(void)272 static void hl_static_recv(void)
273 {
274 	compiler_barrier();
275 	_hl_blockedPeek();
276 	compiler_barrier();
277 }
278 
279 /* Mark hostlink buffer as "No message here".  */
hl_delete(void)280 static void hl_delete(void)
281 {
282 	hl_write32(&__HOSTLINK__.hdr.target2host_addr, HL_NOADDRESS);
283 }
284 
285 /* Parameter types.  */
286 #define PAT_CHAR	1
287 #define PAT_SHORT	2
288 #define PAT_INT		3
289 #define PAT_STRING	4
290 /* For future use.  */
291 #define PAT_INT64	5
292 
hl_static_pack_int(volatile struct hl_packed_int * pack,int32_t value)293 static void hl_static_pack_int(volatile struct hl_packed_int *pack, int32_t value)
294 {
295 	hl_write16(&pack->type, PAT_INT);
296 	hl_write16(&pack->size, 4);
297 	hl_write32(&pack->value, value);
298 }
299 
hl_static_pack_char(volatile struct hl_packed_short_buff * pack,unsigned char c)300 static void hl_static_pack_char(volatile struct hl_packed_short_buff *pack, unsigned char c)
301 {
302 	hl_write16(&pack->type, PAT_STRING);
303 	hl_write16(&pack->size, 1);
304 	hl_write8(&pack->payload_short, c);
305 }
306 
hl_static_unpack_int(volatile struct hl_packed_int * pack,int32_t * value)307 static int hl_static_unpack_int(volatile struct hl_packed_int *pack, int32_t *value)
308 {
309 	uint16_t type = hl_read16(&pack->type);
310 	uint16_t size = hl_read16(&pack->size);
311 
312 	if (type != PAT_INT) {
313 		return -1;
314 	}
315 
316 	if (size != 4)  {
317 		return -1;
318 	}
319 
320 	*value = hl_read32(&pack->value);
321 
322 	return 0;
323 }
324 
hl_write_char(int fd,const char c)325 static inline int32_t hl_write_char(int fd, const char c)
326 {
327 	/*
328 	 * Format:
329 	 * in, int -> syscall (HL_SYSCALL_WRITE)
330 	 * in, int -> file descriptor
331 	 * in, ptr -> buffer
332 	 * in, int -> bytes number
333 	 * out, int -> bytes written
334 	 * out, int, host errno
335 	 */
336 
337 	hl_static_pack_int(&__HOSTLINK__.payload.pkt_write_char_put.syscall_nr, HL_SYSCALL_WRITE);
338 
339 	hl_static_pack_int(&__HOSTLINK__.payload.pkt_write_char_put.fd, fd);
340 
341 	hl_static_pack_char(&__HOSTLINK__.payload.pkt_write_char_put.buff, c);
342 
343 	hl_static_pack_int(&__HOSTLINK__.payload.pkt_write_char_put.nbyte, 1);
344 
345 	hl_static_send(sizeof(struct hl_pkt_write_char_put));
346 	hl_static_recv();
347 
348 	int32_t bwr = 0;
349 	int ret = hl_static_unpack_int(&__HOSTLINK__.payload.pkt_write_char_get.byte_written, &bwr);
350 
351 	/* we can get host errno here with:
352 	 * hl_static_unpack_int(&__HOSTLINK__.pkt_write_char_get.host_errno, &host_errno);
353 	 * but we don't need it for UART emulation.
354 	 */
355 
356 	if (bwr <= 0) {
357 		ret = -1;
358 	}
359 
360 	hl_delete();
361 
362 	return ret;
363 }
364 
365 /**
366  * @brief Poll the device for input.
367  *
368  * @param dev UART device struct
369  * @param c Pointer to character
370  *
371  * @return 0 if a character arrived, -1 if the input buffer if empty.
372  */
uart_hostlink_poll_in(const struct device * dev,unsigned char * c)373 static int uart_hostlink_poll_in(const struct device *dev, unsigned char *c)
374 {
375 	ARG_UNUSED(dev);
376 
377 	/* We plan to use hostlink for logging, so no much sense in poll_in implementation */
378 	return -1;
379 }
380 
381 /**
382  * @brief Output a character in polled mode.
383  *
384  * @param dev UART device struct
385  * @param c Character to send
386  */
uart_hostlink_poll_out(const struct device * dev,unsigned char c)387 static void uart_hostlink_poll_out(const struct device *dev, unsigned char c)
388 {
389 	ARG_UNUSED(dev);
390 
391 	hl_write_char(1, c);
392 }
393 
394 static DEVICE_API(uart, uart_hostlink_driver_api) = {
395 	.poll_in = uart_hostlink_poll_in,
396 	.poll_out = uart_hostlink_poll_out,
397 };
398 
399 DEVICE_DT_DEFINE(DT_NODELABEL(hostlink), NULL, NULL, NULL, NULL, PRE_KERNEL_1,
400 		 CONFIG_SERIAL_INIT_PRIORITY, &uart_hostlink_driver_api);
401