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