shell: add devmem load command

This commit adds a devmem load command for shell that allows
users to easily load arbitrary data into the device memory.

Signed-off-by: Mateusz Sierszulski <msierszulski@internships.antmicro.com>
Signed-off-by: Tomasz Gorochowik <tgorochowik@antmicro.com>
This commit is contained in:
Mateusz Sierszulski 2021-03-18 18:00:38 +01:00 committed by Christopher Friedt
commit 9d62f8d8ac
6 changed files with 217 additions and 5 deletions

View file

@ -0,0 +1,8 @@
# Copyright (c) 2021 Antmicro <www.antmicro.com>
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.13.1)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(devmem_load)
target_sources(app PRIVATE src/main.c)

View file

@ -0,0 +1,53 @@
# Zephyr devmem load command
This module add a `devmem load` command that allows data to be loaded into device memory.
The `devmem load` command is supported by every transport the shell can run on.
After using a command in the Zephyr shell, the device reads all transferred data and writes to an address in the memory.
The transfer ends when the user presses `ctrl-x + ctrl-q`.
## Usage
Note: when using the devmem load command over UART it is recommended to use interrupts whenever possible.
If this is not possible, reduce the baud rate to 9600.
If you use poll you should also use `prj_poll.conf` instead of `prj.conf`.
## Building
The sample can be built for several platforms, the following commands build and run the application with a shell for the FRDM-K64F board.
```bash
west build -b frdm_k64f samples/subsys/shell/devmem_load
west flash
```
Building for boards without UART interrupt support:
```bash
west build -b quick_feather -- -DOVERLAY_CONFIG=prj_poll.conf samples/subsys/shell/devmem_load
west flash
```
## Running
After connecting to the UART console you should see the following output:
```bash
uart:~$
```
The `devmem load` command can now be used (`devmem load [option] [address]`):
```bash
uart:~$ devmem load 0x20020000
Loading...
press ctrl-x ctrl-q to escape
```
Now, the `devmem load` is waiting for data.
You can either type it directly from the console or send it from the host PC (replace `ttyX` with the appropriate one for your UART console):
```bash
xxd -p data > /dev/ttyX
```
(It is important to use plain-style hex dump)
Once the data is transferred, use `ctrl-x + ctrl-q` to quit the loader.
It will print the number of the bytes read and return to the shell:
```bash
Number of bytes read: 3442
uart:~$
```
## Options
Currently, the `devmem load` command supports the following argument:
* `-e` little endian parse e.g. `0xDEADBEFF -> 0xFFBEADDE`

View file

@ -0,0 +1,2 @@
CONFIG_BOOT_BANNER=n
CONFIG_SHELL=y

View file

@ -0,0 +1,4 @@
CONFIG_SHELL=y
CONFIG_SHELL_BACKEND_SERIAL_INTERRUPT_DRIVEN=n
CONFIG_SHELL_BACKEND_SERIAL_RX_POLL_PERIOD=1
CONFIG_BOOT_BANNER=n

View file

@ -0,0 +1,10 @@
/*
* Copyright (c) 2021 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
void main(void)
{
};

View file

@ -1,12 +1,137 @@
/*
* Copyright (c) 2020 Intel Corporation
* Copyright (c) 2021 Antmicro <www.antmicro.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <device.h>
#include <shell/shell.h>
#include <stdlib.h>
#include <sys/byteorder.h>
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 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 *)strtol(argv[1], NULL, 0);
data = (uint32_t *)strtol(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)
{
@ -36,8 +161,7 @@ static int memory_read(const struct shell *sh, mem_addr_t addr, uint8_t width)
return err;
}
static int memory_write(const struct shell *sh, mem_addr_t addr,
uint8_t width, uint64_t value)
static int memory_write(const struct shell *sh, mem_addr_t addr, uint8_t width, uint64_t value)
{
int err = 0;
@ -104,5 +228,16 @@ static int cmd_devmem(const struct shell *sh, size_t argc, char **argv)
return memory_write(sh, addr, width, value);
}
SHELL_CMD_REGISTER(devmem, NULL, "Read/write physical memory\""
"devmem address [width [value]]", cmd_devmem);
SHELL_STATIC_SUBCMD_SET_CREATE(sub_devmem,
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_REGISTER(devmem, &sub_devmem,
"Read/write physical memory\""
"devmem address [width [value]]",
cmd_devmem);