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.
369 lines
7.9 KiB
369 lines
7.9 KiB
/* |
|
* Copyright (c) 2020 Intel Corporation |
|
* Copyright (c) 2021 Antmicro <www.antmicro.com> |
|
* Copyright (c) 2022 Meta |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#undef _POSIX_C_SOURCE |
|
#define _POSIX_C_SOURCE 200809L |
|
|
|
#include <stdlib.h> |
|
#ifdef CONFIG_NATIVE_LIBC |
|
#include <unistd.h> |
|
#else |
|
#include <zephyr/posix/unistd.h> |
|
#endif |
|
#include <zephyr/device.h> |
|
#include <zephyr/shell/shell.h> |
|
#include <zephyr/sys/byteorder.h> |
|
#include <zephyr/sys/util.h> |
|
|
|
#ifndef CONFIG_NATIVE_LIBC |
|
extern void getopt_init(void); |
|
#endif |
|
|
|
static inline bool is_ascii(uint8_t data) |
|
{ |
|
return (data >= 0x30 && data <= 0x39) || (data >= 0x61 && data <= 0x66) || |
|
(data >= 0x41 && data <= 0x46); |
|
} |
|
|
|
static unsigned char *bytes; |
|
static uint32_t *data; |
|
static int sum; |
|
static int chunk_element; |
|
static char chunk[2]; |
|
static bool littleendian; |
|
|
|
#define CHAR_CAN 0x18 |
|
#define CHAR_DC1 0x11 |
|
|
|
static int memory_dump(const struct shell *sh, mem_addr_t phys_addr, size_t size, uint8_t width) |
|
{ |
|
uint32_t value; |
|
size_t data_offset; |
|
mm_reg_t addr; |
|
const size_t vsize = width / BITS_PER_BYTE; |
|
uint8_t hex_data[SHELL_HEXDUMP_BYTES_IN_LINE]; |
|
|
|
#if defined(CONFIG_MMU) || defined(CONFIG_PCIE) |
|
device_map((mm_reg_t *)&addr, phys_addr, size, K_MEM_CACHE_NONE); |
|
|
|
shell_print(sh, "Mapped 0x%lx to 0x%lx\n", phys_addr, addr); |
|
#else |
|
addr = phys_addr; |
|
#endif /* defined(CONFIG_MMU) || defined(CONFIG_PCIE) */ |
|
|
|
for (; size > 0; |
|
addr += SHELL_HEXDUMP_BYTES_IN_LINE, size -= MIN(size, SHELL_HEXDUMP_BYTES_IN_LINE)) { |
|
for (data_offset = 0; |
|
size >= vsize && data_offset + vsize <= SHELL_HEXDUMP_BYTES_IN_LINE; |
|
data_offset += vsize) { |
|
switch (width) { |
|
case 8: |
|
value = sys_read8(addr + data_offset); |
|
hex_data[data_offset] = value; |
|
break; |
|
case 16: |
|
value = sys_le16_to_cpu(sys_read16(addr + data_offset)); |
|
hex_data[data_offset] = (uint8_t)value; |
|
value >>= 8; |
|
hex_data[data_offset + 1] = (uint8_t)value; |
|
break; |
|
case 32: |
|
value = sys_le32_to_cpu(sys_read32(addr + data_offset)); |
|
hex_data[data_offset] = (uint8_t)value; |
|
value >>= 8; |
|
hex_data[data_offset + 1] = (uint8_t)value; |
|
value >>= 8; |
|
hex_data[data_offset + 2] = (uint8_t)value; |
|
value >>= 8; |
|
hex_data[data_offset + 3] = (uint8_t)value; |
|
break; |
|
default: |
|
shell_fprintf(sh, SHELL_NORMAL, "Incorrect data width\n"); |
|
return -EINVAL; |
|
} |
|
} |
|
|
|
shell_hexdump_line(sh, addr, hex_data, MIN(size, SHELL_HEXDUMP_BYTES_IN_LINE)); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int cmd_dump(const struct shell *sh, size_t argc, char **argv) |
|
{ |
|
int rv; |
|
int err = 0; |
|
size_t size = -1; |
|
size_t width = 32; |
|
mem_addr_t addr = -1; |
|
|
|
optind = 1; |
|
#ifndef CONFIG_NATIVE_LIBC |
|
getopt_init(); |
|
#endif |
|
while ((rv = getopt(argc, argv, "a:s:w:")) != -1) { |
|
switch (rv) { |
|
case 'a': |
|
addr = (mem_addr_t)shell_strtoul(optarg, 16, &err); |
|
if (err != 0) { |
|
shell_error(sh, "invalid addr '%s'", optarg); |
|
return -EINVAL; |
|
} |
|
break; |
|
case 's': |
|
size = (size_t)shell_strtoul(optarg, 0, &err); |
|
if (err != 0) { |
|
shell_error(sh, "invalid size '%s'", optarg); |
|
return -EINVAL; |
|
} |
|
break; |
|
case 'w': |
|
width = (size_t)shell_strtoul(optarg, 0, &err); |
|
if (err != 0) { |
|
shell_error(sh, "invalid width '%s'", optarg); |
|
return -EINVAL; |
|
} |
|
break; |
|
case '?': |
|
default: |
|
return -EINVAL; |
|
} |
|
} |
|
|
|
if (addr == -1) { |
|
shell_error(sh, "'-a <address>' is mandatory"); |
|
return -EINVAL; |
|
} |
|
|
|
if (size == -1) { |
|
shell_error(sh, "'-s <size>' is mandatory"); |
|
return -EINVAL; |
|
} |
|
|
|
return memory_dump(sh, addr, size, width); |
|
} |
|
|
|
static int set_bypass(const struct shell *sh, shell_bypass_cb_t bypass) |
|
{ |
|
static bool in_use; |
|
|
|
if (bypass && in_use) { |
|
shell_error(sh, "devmem load supports setting bypass on a single instance."); |
|
|
|
return -EBUSY; |
|
} |
|
|
|
in_use = !in_use; |
|
if (in_use) { |
|
shell_print(sh, "Loading...\npress ctrl-x ctrl-q to escape"); |
|
in_use = true; |
|
} |
|
|
|
shell_set_bypass(sh, bypass); |
|
|
|
return 0; |
|
} |
|
|
|
static void bypass_cb(const struct shell *sh, uint8_t *recv, size_t len) |
|
{ |
|
bool escape = false; |
|
static uint8_t tail; |
|
uint8_t byte; |
|
|
|
if (tail == CHAR_CAN && recv[0] == CHAR_DC1) { |
|
escape = true; |
|
} else { |
|
for (int i = 0; i < (len - 1); i++) { |
|
if (recv[i] == CHAR_CAN && recv[i + 1] == CHAR_DC1) { |
|
escape = true; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if (escape) { |
|
shell_print(sh, "Number of bytes read: %d", sum); |
|
set_bypass(sh, NULL); |
|
|
|
if (!littleendian) { |
|
while (sum > 4) { |
|
*data = BSWAP_32(*data); |
|
data++; |
|
sum = sum - 4; |
|
} |
|
if (sum % 4 == 0) { |
|
*data = BSWAP_32(*data); |
|
} else if (sum % 4 == 2) { |
|
*data = BSWAP_16(*data); |
|
} else if (sum % 4 == 3) { |
|
*data = BSWAP_24(*data); |
|
} |
|
} |
|
return; |
|
} |
|
|
|
tail = recv[len - 1]; |
|
|
|
if (is_ascii(*recv)) { |
|
chunk[chunk_element] = *recv; |
|
chunk_element++; |
|
} |
|
|
|
if (chunk_element == 2) { |
|
byte = (uint8_t)strtoul(chunk, NULL, 16); |
|
*bytes = byte; |
|
bytes++; |
|
sum++; |
|
chunk_element = 0; |
|
} |
|
} |
|
|
|
static int cmd_load(const struct shell *sh, size_t argc, char **argv) |
|
{ |
|
littleendian = false; |
|
char *arg; |
|
|
|
chunk_element = 0; |
|
sum = 0; |
|
|
|
while (argc >= 2) { |
|
arg = argv[1] + (!strncmp(argv[1], "--", 2) && argv[1][2]); |
|
if (!strncmp(arg, "-e", 2)) { |
|
littleendian = true; |
|
} else if (!strcmp(arg, "--")) { |
|
argv++; |
|
argc--; |
|
break; |
|
} else if (arg[0] == '-' && arg[1]) { |
|
shell_print(sh, "Unknown option \"%s\"", arg); |
|
} else { |
|
break; |
|
} |
|
argv++; |
|
argc--; |
|
} |
|
|
|
bytes = (unsigned char *)strtoul(argv[1], NULL, 0); |
|
data = (uint32_t *)strtoul(argv[1], NULL, 0); |
|
|
|
set_bypass(sh, bypass_cb); |
|
return 0; |
|
} |
|
|
|
static int memory_read(const struct shell *sh, mem_addr_t addr, uint8_t width) |
|
{ |
|
uint32_t value; |
|
int err = 0; |
|
|
|
switch (width) { |
|
case 8: |
|
value = sys_read8(addr); |
|
break; |
|
case 16: |
|
value = sys_read16(addr); |
|
break; |
|
case 32: |
|
value = sys_read32(addr); |
|
break; |
|
default: |
|
shell_fprintf(sh, SHELL_NORMAL, "Incorrect data width\n"); |
|
err = -EINVAL; |
|
break; |
|
} |
|
|
|
if (err == 0) { |
|
shell_fprintf(sh, SHELL_NORMAL, "Read value 0x%x\n", value); |
|
} |
|
|
|
return err; |
|
} |
|
|
|
static int memory_write(const struct shell *sh, mem_addr_t addr, uint8_t width, uint64_t value) |
|
{ |
|
int err = 0; |
|
|
|
switch (width) { |
|
case 8: |
|
sys_write8(value, addr); |
|
break; |
|
case 16: |
|
sys_write16(value, addr); |
|
break; |
|
case 32: |
|
sys_write32(value, addr); |
|
break; |
|
default: |
|
shell_fprintf(sh, SHELL_NORMAL, "Incorrect data width\n"); |
|
err = -EINVAL; |
|
break; |
|
} |
|
|
|
return err; |
|
} |
|
|
|
/* The syntax of the command is similar to busybox's devmem */ |
|
static int cmd_devmem(const struct shell *sh, size_t argc, char **argv) |
|
{ |
|
mem_addr_t phys_addr, addr; |
|
uint32_t value = 0; |
|
uint8_t width; |
|
|
|
phys_addr = strtoul(argv[1], NULL, 16); |
|
|
|
#if defined(CONFIG_MMU) || defined(CONFIG_PCIE) |
|
device_map((mm_reg_t *)&addr, phys_addr, 0x100, K_MEM_CACHE_NONE); |
|
|
|
shell_print(sh, "Mapped 0x%lx to 0x%lx\n", phys_addr, addr); |
|
#else |
|
addr = phys_addr; |
|
#endif /* defined(CONFIG_MMU) || defined(CONFIG_PCIE) */ |
|
|
|
if (argc < 3) { |
|
width = 32; |
|
} else { |
|
width = strtoul(argv[2], NULL, 10); |
|
} |
|
|
|
shell_fprintf(sh, SHELL_NORMAL, "Using data width %d\n", width); |
|
|
|
if (argc <= 3) { |
|
return memory_read(sh, addr, width); |
|
} |
|
|
|
/* If there are more then 3 arguments, that means we are going to write |
|
* this value at the address provided |
|
*/ |
|
|
|
value = strtoul(argv[3], NULL, 16); |
|
|
|
shell_fprintf(sh, SHELL_NORMAL, "Writing value 0x%x\n", value); |
|
|
|
return memory_write(sh, addr, width, value); |
|
} |
|
|
|
SHELL_STATIC_SUBCMD_SET_CREATE(sub_devmem, |
|
SHELL_CMD_ARG(dump, NULL, |
|
"Usage:\n" |
|
"devmem dump -a <address> -s <size> [-w <width>]\n", |
|
cmd_dump, 5, 2), |
|
SHELL_CMD_ARG(load, NULL, |
|
"Usage:\n" |
|
"devmem load [options] [address]\n" |
|
"Options:\n" |
|
"-e\tlittle-endian parse", |
|
cmd_load, 2, 1), |
|
SHELL_SUBCMD_SET_END); |
|
|
|
SHELL_CMD_ARG_REGISTER(devmem, &sub_devmem, |
|
"Read/write physical memory\n" |
|
"Usage:\n" |
|
"Read memory at address with optional width:\n" |
|
"devmem <address> [<width>]\n" |
|
"Write memory at address with mandatory width and value:\n" |
|
"devmem <address> <width> <value>", |
|
cmd_devmem, 2, 2);
|
|
|