1 /*
2  * Copyright (c) 2020 Intel Corporation
3  * Copyright (c) 2021 Antmicro <www.antmicro.com>
4  * Copyright (c) 2022 Meta
5  *
6  * SPDX-License-Identifier: Apache-2.0
7  */
8 
9 #undef _POSIX_C_SOURCE
10 #define _POSIX_C_SOURCE 200809L
11 
12 #include <stdlib.h>
13 #include <zephyr/sys/sys_getopt.h>
14 #include <zephyr/device.h>
15 #include <zephyr/shell/shell.h>
16 #include <zephyr/sys/byteorder.h>
17 #include <zephyr/sys/util.h>
18 
is_ascii(uint8_t data)19 static inline bool is_ascii(uint8_t data)
20 {
21 	return (data >= 0x30 && data <= 0x39) || (data >= 0x61 && data <= 0x66) ||
22 	       (data >= 0x41 && data <= 0x46);
23 }
24 
25 static unsigned char *bytes;
26 static uint32_t *data;
27 static int sum;
28 static int chunk_element;
29 static char chunk[2];
30 static bool littleendian;
31 
32 #define CHAR_CAN 0x18
33 #define CHAR_DC1 0x11
34 
memory_dump(const struct shell * sh,mem_addr_t phys_addr,size_t size,uint8_t width)35 static int memory_dump(const struct shell *sh, mem_addr_t phys_addr, size_t size, uint8_t width)
36 {
37 	uint64_t value;
38 	size_t data_offset;
39 	mm_reg_t addr;
40 	const size_t vsize = width / BITS_PER_BYTE;
41 	uint8_t hex_data[SHELL_HEXDUMP_BYTES_IN_LINE];
42 
43 #if defined(CONFIG_MMU) || defined(CONFIG_PCIE)
44 	device_map((mm_reg_t *)&addr, phys_addr, size, K_MEM_CACHE_NONE);
45 
46 	shell_print(sh, "Mapped 0x%lx to 0x%lx\n", phys_addr, addr);
47 #else
48 	addr = phys_addr;
49 #endif /* defined(CONFIG_MMU) || defined(CONFIG_PCIE) */
50 
51 	for (; size > 0;
52 	     addr += SHELL_HEXDUMP_BYTES_IN_LINE, size -= MIN(size, SHELL_HEXDUMP_BYTES_IN_LINE)) {
53 		for (data_offset = 0;
54 		     size >= vsize && data_offset + vsize <= SHELL_HEXDUMP_BYTES_IN_LINE;
55 		     data_offset += vsize) {
56 			switch (width) {
57 			case 8:
58 				value = sys_read8(addr + data_offset);
59 				hex_data[data_offset] = value;
60 				break;
61 			case 16:
62 				value = sys_le16_to_cpu(sys_read16(addr + data_offset));
63 				sys_put_le16(value, &hex_data[data_offset]);
64 				break;
65 			case 32:
66 				value = sys_le32_to_cpu(sys_read32(addr + data_offset));
67 				sys_put_le32(value, &hex_data[data_offset]);
68 				break;
69 #ifdef CONFIG_64BIT
70 			case 64:
71 				value = sys_le64_to_cpu(sys_read64(addr + data_offset));
72 				sys_put_le64(value, &hex_data[data_offset]);
73 				break;
74 #endif /* CONFIG_64BIT */
75 			default:
76 				shell_print(sh, "Incorrect data width");
77 				return -EINVAL;
78 			}
79 		}
80 
81 		shell_hexdump_line(sh, addr, hex_data, MIN(size, SHELL_HEXDUMP_BYTES_IN_LINE));
82 	}
83 
84 	return 0;
85 }
86 
cmd_dump(const struct shell * sh,size_t argc,char ** argv)87 static int cmd_dump(const struct shell *sh, size_t argc, char **argv)
88 {
89 	int rv;
90 	int err = 0;
91 	size_t size = -1;
92 	size_t width = 32;
93 	mem_addr_t addr = -1;
94 
95 	sys_getopt_optind = 1;
96 	sys_getopt_init();
97 
98 	while ((rv = sys_getopt(argc, argv, "a:s:w:")) != -1) {
99 		switch (rv) {
100 		case 'a':
101 			addr = (mem_addr_t)shell_strtoul(sys_getopt_optarg, 16, &err);
102 			if (err != 0) {
103 				shell_error(sh, "invalid addr '%s'", sys_getopt_optarg);
104 				return -EINVAL;
105 			}
106 			break;
107 		case 's':
108 			size = (size_t)shell_strtoul(sys_getopt_optarg, 0, &err);
109 			if (err != 0) {
110 				shell_error(sh, "invalid size '%s'", sys_getopt_optarg);
111 				return -EINVAL;
112 			}
113 			break;
114 		case 'w':
115 			width = (size_t)shell_strtoul(sys_getopt_optarg, 0, &err);
116 			if (err != 0) {
117 				shell_error(sh, "invalid width '%s'", sys_getopt_optarg);
118 				return -EINVAL;
119 			}
120 			break;
121 		case '?':
122 		default:
123 			return -EINVAL;
124 		}
125 	}
126 
127 	if (addr == -1) {
128 		shell_error(sh, "'-a <address>' is mandatory");
129 		return -EINVAL;
130 	}
131 
132 	if (size == -1) {
133 		shell_error(sh, "'-s <size>' is mandatory");
134 		return -EINVAL;
135 	}
136 
137 	return memory_dump(sh, addr, size, width);
138 }
139 
set_bypass(const struct shell * sh,shell_bypass_cb_t bypass)140 static int set_bypass(const struct shell *sh, shell_bypass_cb_t bypass)
141 {
142 	static bool in_use;
143 
144 	if (bypass && in_use) {
145 		shell_error(sh, "devmem load supports setting bypass on a single instance.");
146 
147 		return -EBUSY;
148 	}
149 
150 	in_use = !in_use;
151 	if (in_use) {
152 		shell_print(sh, "Loading...\npress ctrl-x ctrl-q to escape");
153 		in_use = true;
154 	}
155 
156 	shell_set_bypass(sh, bypass, NULL);
157 
158 	return 0;
159 }
160 
bypass_cb(const struct shell * sh,uint8_t * recv,size_t len,void * user_data)161 static void bypass_cb(const struct shell *sh, uint8_t *recv, size_t len, void *user_data)
162 {
163 	bool escape = false;
164 	static uint8_t tail;
165 	uint8_t byte;
166 
167 	ARG_UNUSED(user_data);
168 
169 	for (size_t i = 0; i < len; i++) {
170 		if (tail == CHAR_CAN && recv[i] == CHAR_DC1) {
171 			escape = true;
172 			tail = 0;
173 			break;
174 		}
175 		tail = recv[i];
176 
177 		if (is_ascii(recv[i])) {
178 			chunk[chunk_element] = recv[i];
179 			chunk_element++;
180 		}
181 
182 		if (chunk_element == 2) {
183 			byte = (uint8_t)strtoul(chunk, NULL, 16);
184 			*bytes = byte;
185 			bytes++;
186 			sum++;
187 			chunk_element = 0;
188 		}
189 	}
190 
191 	if (escape) {
192 		shell_print(sh, "Number of bytes read: %d", sum);
193 		set_bypass(sh, NULL);
194 
195 		if (!littleendian) {
196 			while (sum > 4) {
197 				*data = BSWAP_32(*data);
198 				data++;
199 				sum = sum - 4;
200 			}
201 			if (sum % 4 == 0) {
202 				*data = BSWAP_32(*data);
203 			} else if (sum % 4 == 2) {
204 				*data = BSWAP_16(*data);
205 			} else if (sum % 4 == 3) {
206 				*data = BSWAP_24(*data);
207 			}
208 		}
209 		return;
210 	}
211 }
212 
cmd_load(const struct shell * sh,size_t argc,char ** argv)213 static int cmd_load(const struct shell *sh, size_t argc, char **argv)
214 {
215 	littleendian = false;
216 	char *arg;
217 
218 	chunk_element = 0;
219 	sum = 0;
220 
221 	while (argc >= 2) {
222 		arg = argv[1] + (!strncmp(argv[1], "--", 2) && argv[1][2]);
223 		if (!strncmp(arg, "-e", 2)) {
224 			littleendian = true;
225 		} else if (!strcmp(arg, "--")) {
226 			argv++;
227 			argc--;
228 			break;
229 		} else if (arg[0] == '-' && arg[1]) {
230 			shell_print(sh, "Unknown option \"%s\"", arg);
231 		} else {
232 			break;
233 		}
234 		argv++;
235 		argc--;
236 	}
237 
238 	bytes = (unsigned char *)strtoul(argv[1], NULL, 0);
239 	data = (uint32_t *)strtoul(argv[1], NULL, 0);
240 
241 	set_bypass(sh, bypass_cb);
242 	return 0;
243 }
244 
memory_read(const struct shell * sh,mem_addr_t addr,uint8_t width)245 static int memory_read(const struct shell *sh, mem_addr_t addr, uint8_t width)
246 {
247 	uint64_t value;
248 	int err = 0;
249 
250 	switch (width) {
251 	case 8:
252 		value = sys_read8(addr);
253 		break;
254 	case 16:
255 		value = sys_read16(addr);
256 		break;
257 	case 32:
258 		value = sys_read32(addr);
259 		break;
260 #ifdef CONFIG_64BIT
261 	case 64:
262 		value = sys_read64(addr);
263 		break;
264 #endif /* CONFIG_64BIT */
265 	default:
266 		shell_print(sh, "Incorrect data width");
267 		err = -EINVAL;
268 		break;
269 	}
270 
271 	if (err == 0) {
272 		shell_print(sh, "Read value 0x%llx", value);
273 	}
274 
275 	return err;
276 }
277 
memory_write(const struct shell * sh,mem_addr_t addr,uint8_t width,uint64_t value)278 static int memory_write(const struct shell *sh, mem_addr_t addr, uint8_t width, uint64_t value)
279 {
280 	int err = 0;
281 
282 	switch (width) {
283 	case 8:
284 		sys_write8(value, addr);
285 		break;
286 	case 16:
287 		sys_write16(value, addr);
288 		break;
289 	case 32:
290 		sys_write32(value, addr);
291 		break;
292 #ifdef CONFIG_64BIT
293 	case 64:
294 		sys_write64(value, addr);
295 		break;
296 #endif /* CONFIG_64BIT */
297 	default:
298 		shell_print(sh, "Incorrect data width");
299 		err = -EINVAL;
300 		break;
301 	}
302 
303 	return err;
304 }
305 
306 /* The syntax of the command is similar to busybox's devmem */
cmd_devmem(const struct shell * sh,size_t argc,char ** argv)307 static int cmd_devmem(const struct shell *sh, size_t argc, char **argv)
308 {
309 	mem_addr_t phys_addr, addr;
310 	uint64_t value = 0;
311 	uint8_t width;
312 
313 	phys_addr = strtoul(argv[1], NULL, 16);
314 
315 #if defined(CONFIG_MMU) || defined(CONFIG_PCIE)
316 	device_map((mm_reg_t *)&addr, phys_addr, 0x100, K_MEM_CACHE_NONE);
317 
318 	shell_print(sh, "Mapped 0x%lx to 0x%lx\n", phys_addr, addr);
319 #else
320 	addr = phys_addr;
321 #endif /* defined(CONFIG_MMU) || defined(CONFIG_PCIE) */
322 
323 	if (argc < 3) {
324 		width = 32;
325 	} else {
326 		width = strtoul(argv[2], NULL, 10);
327 	}
328 
329 	shell_print(sh, "Using data width %d", width);
330 
331 	if (argc <= 3) {
332 		return memory_read(sh, addr, width);
333 	}
334 
335 	/* If there are more then 3 arguments, that means we are going to write
336 	 * this value at the address provided
337 	 */
338 
339 	value = (uint64_t)strtoull(argv[3], NULL, 16);
340 
341 	shell_print(sh, "Writing value 0x%llx", value);
342 
343 	return memory_write(sh, addr, width, value);
344 }
345 
346 SHELL_STATIC_SUBCMD_SET_CREATE(sub_devmem,
347 			       SHELL_CMD_ARG(dump, NULL,
348 					     "Usage:\n"
349 					     "devmem dump -a <address> -s <size> [-w <width>]\n",
350 					     cmd_dump, 5, 2),
351 			       SHELL_CMD_ARG(load, NULL,
352 					     "Usage:\n"
353 					     "devmem load [options] [address]\n"
354 					     "Options:\n"
355 					     "-e\tlittle-endian parse",
356 					     cmd_load, 2, 1),
357 			       SHELL_SUBCMD_SET_END);
358 
359 SHELL_CMD_ARG_REGISTER(devmem, &sub_devmem,
360 		   "Read/write physical memory\n"
361 		   "Usage:\n"
362 		   "Read memory at address with optional width:\n"
363 		   "devmem <address> [<width>]\n"
364 		   "Write memory at address with mandatory width and value:\n"
365 		   "devmem <address> <width> <value>",
366 		   cmd_devmem, 2, 2);
367