zephyr/drivers/gpio/gpio_shell.c
Martí Bolívar 48a14dc17e gpio: add support for node label based lookup in the shell
Use some new kernel features to make the experience of finding and
dealing with GPIO devices much more ergonomic by allowing the use of
devicetree node labels to identify GPIO devices by default.

Users who wish to avoid the associated footprint penalty can set
CONFIG_DEVICE_DT_METADATA=n by hand, but I think the convenience is
worth the price as a default. If we're running a shell, then we've
already paid a heavy footprint penalty.

Example output for qemu_cortex_m3:

    uart:~$ gpio devices
    Device           Other names
    gpio@40004000    gpio0
    gpio@40005000    gpio1
    gpio@40006000    gpio2
    gpio@40007000    gpio3
    gpio@40024000    gpio4
    gpio@40025000    gpio5
    gpio@40026000    gpio6

Signed-off-by: Martí Bolívar <mbolivar@amperecomputing.com>
2024-06-12 18:49:54 +02:00

690 lines
18 KiB
C

/*
* Copyright (c) 2018 Intel Corporation
* Copyright (c) 2021 Dennis Ruffer <daruffer@gmail.com>
* Copyright (c) 2023 Nick Ward <nix.ward@gmail.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/drivers/gpio.h>
#include <zephyr/shell/shell.h>
#include <stdio.h>
#define ARGV_DEV 1
#define ARGV_PIN 2
#define ARGV_CONF 3
#define ARGV_VALUE 3
#define ARGV_VENDOR_SPECIFIC 4
#define NGPIOS_UNKNOWN -1
#define PIN_NOT_FOUND UINT8_MAX
/* Pin syntax maximum length */
#define PIN_SYNTAX_MAX 32
#define PIN_NUM_MAX 4
struct gpio_ctrl {
const struct device *dev;
int8_t ngpios;
gpio_port_pins_t reserved_mask;
const char **line_names;
uint8_t line_names_len;
const union shell_cmd_entry *subcmd;
};
struct sh_gpio {
const struct device *dev;
gpio_pin_t pin;
};
/*
* Find idx-th pin reference from the set of non reserved
* pin numbers and provided line names.
*/
static void port_pin_get(gpio_port_pins_t reserved_mask, const char **line_names,
uint8_t line_names_len, size_t idx, struct shell_static_entry *entry)
{
static char pin_syntax[PIN_SYNTAX_MAX];
static char pin_num[PIN_NUM_MAX];
const char *name;
gpio_pin_t pin;
bool reserved;
entry->handler = NULL;
/* Find allowed numeric pin reference */
for (pin = 0; pin < GPIO_MAX_PINS_PER_PORT; pin++) {
reserved = ((BIT64(pin) & reserved_mask) != 0);
if (!reserved) {
if (idx == 0) {
break;
}
idx--;
}
}
if (pin < GPIO_MAX_PINS_PER_PORT) {
sprintf(pin_num, "%u", pin);
if ((pin < line_names_len) && (strlen(line_names[pin]) > 0)) {
/* pin can be specified by line name */
name = line_names[pin];
for (int i = 0; i < (sizeof(pin_syntax) - 1); i++) {
/*
* For line-name tab completion to work replace any
* space characters with '_'.
*/
pin_syntax[i] = (name[i] != ' ') ? name[i] : '_';
if (name[i] == '\0') {
break;
}
}
pin_syntax[sizeof(pin_syntax) - 1] = '\0';
entry->syntax = pin_syntax;
entry->help = pin_num;
} else {
/* fallback to pin specified by pin number */
entry->syntax = pin_num;
entry->help = NULL;
}
} else {
/* No more pins */
entry->syntax = NULL;
entry->help = NULL;
}
}
#define GPIO_DT_RESERVED_RANGES_NGPIOS_SHELL(node_id) \
COND_CODE_1(DT_NODE_HAS_PROP(node_id, ngpios), \
(GPIO_DT_RESERVED_RANGES_NGPIOS(node_id, DT_PROP(node_id, ngpios))), \
(GPIO_MAX_PINS_PER_PORT))
#define GPIO_CTRL_PIN_GET_FN(node_id) \
static const char *node_id##line_names[] = DT_PROP_OR(node_id, gpio_line_names, {NULL}); \
\
static void node_id##cmd_gpio_pin_get(size_t idx, struct shell_static_entry *entry); \
\
SHELL_DYNAMIC_CMD_CREATE(node_id##sub_gpio_pin, node_id##cmd_gpio_pin_get); \
\
static void node_id##cmd_gpio_pin_get(size_t idx, struct shell_static_entry *entry) \
{ \
gpio_port_pins_t reserved_mask = GPIO_DT_RESERVED_RANGES_NGPIOS_SHELL(node_id); \
uint8_t line_names_len = DT_PROP_LEN_OR(node_id, gpio_line_names, 0); \
\
port_pin_get(reserved_mask, node_id##line_names, line_names_len, idx, entry); \
entry->subcmd = NULL; \
}
#define IS_GPIO_CTRL_PIN_GET(node_id) \
COND_CODE_1(DT_PROP(node_id, gpio_controller), (GPIO_CTRL_PIN_GET_FN(node_id)), ())
DT_FOREACH_STATUS_OKAY_NODE(IS_GPIO_CTRL_PIN_GET)
#define GPIO_CTRL_LIST_ENTRY(node_id) \
{ \
.dev = DEVICE_DT_GET(node_id), \
.ngpios = DT_PROP_OR(node_id, ngpios, NGPIOS_UNKNOWN), \
.reserved_mask = GPIO_DT_RESERVED_RANGES_NGPIOS_SHELL(node_id), \
.line_names = node_id##line_names, \
.line_names_len = DT_PROP_LEN_OR(node_id, gpio_line_names, 0), \
.subcmd = &node_id##sub_gpio_pin, \
},
#define IS_GPIO_CTRL_LIST(node_id) \
COND_CODE_1(DT_PROP(node_id, gpio_controller), (GPIO_CTRL_LIST_ENTRY(node_id)), ())
static const struct gpio_ctrl gpio_list[] = {DT_FOREACH_STATUS_OKAY_NODE(IS_GPIO_CTRL_LIST)};
static const struct gpio_ctrl *get_gpio_ctrl_helper(const struct device *dev)
{
size_t i;
if (dev == NULL) {
return NULL;
}
for (i = 0; i < ARRAY_SIZE(gpio_list); i++) {
if (gpio_list[i].dev == dev) {
return &gpio_list[i];
}
}
return NULL;
}
/* Look up a device by some human-readable string identifier. We
* always search among device names. If the feature is available, we
* search by node label as well.
*/
static const struct gpio_ctrl *get_gpio_ctrl(char *id)
{
const struct gpio_ctrl *ctrl;
ctrl = get_gpio_ctrl_helper(device_get_binding(id));
if (ctrl != NULL) {
return ctrl;
}
#ifdef CONFIG_DEVICE_DT_METADATA
ctrl = get_gpio_ctrl_helper(device_get_by_dt_nodelabel(id));
if (ctrl != NULL) {
return ctrl;
}
#endif /* CONFIG_DEVICE_DT_METADATA */
return NULL;
}
int line_cmp(const char *input, const char *line_name)
{
int i = 0;
while (true) {
if ((input[i] == '_') && (line_name[i] == ' ')) {
/* Allow input underscore to match line_name space */
} else if (input[i] != line_name[i]) {
return (input[i] > line_name[i]) ? 1 : -1;
} else if (line_name[i] == '\0') {
return 0;
}
i++;
}
}
static int get_gpio_pin(const struct shell *sh, const struct gpio_ctrl *ctrl, char *line_name)
{
gpio_pin_t pin = PIN_NOT_FOUND;
gpio_pin_t i;
int result;
for (i = 0; i < ctrl->ngpios; i++) {
result = line_cmp(line_name, ctrl->line_names[i]);
if (result == 0) {
if ((BIT64(i) & ctrl->reserved_mask) != 0) {
shell_error(sh, "Reserved pin");
return -EACCES;
} else if (pin == PIN_NOT_FOUND) {
pin = i;
} else {
shell_error(sh, "Line name ambiguous");
return -EFAULT;
}
}
}
if (pin == PIN_NOT_FOUND) {
shell_error(sh, "Line name not found: '%s'", line_name);
return -ENOENT;
}
return pin;
}
static int get_sh_gpio(const struct shell *sh, char **argv, struct sh_gpio *gpio)
{
const struct gpio_ctrl *ctrl;
int ret = 0;
int pin;
ctrl = get_gpio_ctrl(argv[ARGV_DEV]);
if (ctrl == NULL) {
shell_error(sh, "unknown gpio controller: %s", argv[ARGV_DEV]);
return -EINVAL;
}
gpio->dev = ctrl->dev;
pin = shell_strtoul(argv[ARGV_PIN], 0, &ret);
if (ret != 0) {
pin = get_gpio_pin(sh, ctrl, argv[ARGV_PIN]);
if (pin < 0) {
return pin;
}
} else if ((BIT64(pin) & ctrl->reserved_mask) != 0) {
shell_error(sh, "Reserved pin");
return -EACCES;
}
gpio->pin = pin;
return 0;
}
static int cmd_gpio_conf(const struct shell *sh, size_t argc, char **argv, void *data)
{
gpio_flags_t flags = 0;
gpio_flags_t vendor_specific;
struct sh_gpio gpio;
int ret = 0;
ret = get_sh_gpio(sh, argv, &gpio);
if (ret != 0) {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
for (int i = 0; i < strlen(argv[ARGV_CONF]); i++) {
switch (argv[ARGV_CONF][i]) {
case 'i':
flags |= GPIO_INPUT;
break;
case 'o':
flags |= GPIO_OUTPUT;
break;
case 'u':
flags |= GPIO_PULL_UP;
break;
case 'd':
flags |= GPIO_PULL_DOWN;
break;
case 'h':
flags |= GPIO_ACTIVE_HIGH;
break;
case 'l':
flags |= GPIO_ACTIVE_LOW;
break;
case '0':
flags |= GPIO_OUTPUT_INIT_LOGICAL | GPIO_OUTPUT_INIT_LOW;
break;
case '1':
flags |= GPIO_OUTPUT_INIT_LOGICAL | GPIO_OUTPUT_INIT_HIGH;
break;
default:
shell_error(sh, "Unknown: '%c'", argv[ARGV_CONF][i]);
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
}
if (((flags & GPIO_INPUT) != 0) == ((flags & GPIO_OUTPUT) != 0)) {
shell_error(sh, "must be either input or output");
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
if (((flags & GPIO_PULL_UP) != 0) && ((flags & GPIO_PULL_DOWN) != 0)) {
shell_error(sh, "cannot be pull up and pull down");
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
if (((flags & GPIO_ACTIVE_LOW) != 0) && ((flags & GPIO_ACTIVE_HIGH) != 0)) {
shell_error(sh, "cannot be active low and active high");
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
if ((flags & GPIO_OUTPUT) != 0) {
/* Default to active high if not specified */
if ((flags & (GPIO_ACTIVE_LOW | GPIO_ACTIVE_HIGH)) == 0) {
flags |= GPIO_ACTIVE_HIGH;
}
/* Default to initialisation to logic 0 if not specified */
if ((flags & GPIO_OUTPUT_INIT_LOGICAL) == 0) {
flags |= GPIO_OUTPUT_INIT_LOGICAL | GPIO_OUTPUT_INIT_LOW;
}
}
if (((flags & GPIO_INPUT) != 0) && ((flags & GPIO_OUTPUT_INIT_LOGICAL) != 0)) {
shell_error(sh, "an input cannot be initialised to a logic level");
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
if (((flags & GPIO_OUTPUT_INIT_LOW) != 0) && ((flags & GPIO_OUTPUT_INIT_HIGH) != 0)) {
shell_error(sh, "cannot initialise to logic 0 and logic 1");
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
if (argc == 5) {
vendor_specific = shell_strtoul(argv[ARGV_VENDOR_SPECIFIC], 0, &ret);
if ((ret == 0) && ((vendor_specific & ~(0xFF00U)) == 0)) {
flags |= vendor_specific;
} else {
/*
* See include/zephyr/dt-bindings/gpio/ for the
* available flags for your vendor.
*/
shell_error(sh, "vendor specific flags must be within "
"the mask 0xFF00");
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
}
ret = gpio_pin_configure(gpio.dev, gpio.pin, flags);
if (ret != 0) {
shell_error(sh, "error: %d", ret);
return ret;
}
return 0;
}
static int cmd_gpio_get(const struct shell *sh, size_t argc, char **argv)
{
struct sh_gpio gpio;
int value;
int ret;
ret = get_sh_gpio(sh, argv, &gpio);
if (ret != 0) {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
value = gpio_pin_get(gpio.dev, gpio.pin);
if (value >= 0) {
shell_print(sh, "%u", value);
} else {
shell_error(sh, "error: %d", value);
return value;
}
return 0;
}
static int cmd_gpio_set(const struct shell *sh, size_t argc, char **argv)
{
struct sh_gpio gpio;
unsigned long value;
int ret = 0;
ret = get_sh_gpio(sh, argv, &gpio);
if (ret != 0) {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
value = shell_strtoul(argv[ARGV_VALUE], 0, &ret);
if (ret != 0) {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
ret = gpio_pin_set(gpio.dev, gpio.pin, value != 0);
if (ret != 0) {
shell_error(sh, "error: %d", ret);
return ret;
}
return 0;
}
static int cmd_gpio_toggle(const struct shell *sh, size_t argc, char **argv)
{
struct sh_gpio gpio;
int ret = 0;
ret = get_sh_gpio(sh, argv, &gpio);
if (ret != 0) {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
ret = gpio_pin_toggle(gpio.dev, gpio.pin);
if (ret != 0) {
shell_error(sh, "error: %d", ret);
return ret;
}
return 0;
}
static int cmd_gpio_devices(const struct shell *sh, size_t argc, char **argv)
{
size_t i;
shell_fprintf(sh, SHELL_NORMAL, "%-16s Other names\n", "Device");
for (i = 0; i < ARRAY_SIZE(gpio_list); i++) {
const struct device *dev = gpio_list[i].dev;
shell_fprintf(sh, SHELL_NORMAL, "%-16s", dev->name);
#ifdef CONFIG_DEVICE_DT_METADATA
const struct device_dt_nodelabels *nl = device_get_dt_nodelabels(dev);
if (nl->num_nodelabels > 0) {
for (size_t j = 0; j < nl->num_nodelabels; j++) {
const char *nodelabel = nl->nodelabels[j];
shell_fprintf(sh, SHELL_NORMAL, " %s", nodelabel);
}
}
#endif
shell_fprintf(sh, SHELL_NORMAL, "\n");
}
return 0;
}
/* 500 msec = 1/2 sec */
#define SLEEP_TIME_MS 500
static int cmd_gpio_blink(const struct shell *sh, size_t argc, char **argv)
{
bool msg_one_shot = true;
struct sh_gpio gpio;
size_t count;
char data;
int ret;
ret = get_sh_gpio(sh, argv, &gpio);
if (ret != 0) {
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
/* dummy read to clear any pending input */
(void)sh->iface->api->read(sh->iface, &data, sizeof(data), &count);
while (true) {
(void)sh->iface->api->read(sh->iface, &data, sizeof(data), &count);
if (count != 0) {
break;
}
ret = gpio_pin_toggle(gpio.dev, gpio.pin);
if (ret != 0) {
shell_error(sh, "%d", ret);
break;
} else if (msg_one_shot) {
msg_one_shot = false;
shell_print(sh, "Hit any key to exit");
}
k_msleep(SLEEP_TIME_MS);
}
return 0;
}
static void device_name_get(size_t idx, struct shell_static_entry *entry)
{
if (idx >= ARRAY_SIZE(gpio_list)) {
entry->syntax = NULL;
return;
}
entry->syntax = gpio_list[idx].dev->name;
entry->handler = NULL;
entry->help = "Device";
entry->subcmd = gpio_list[idx].subcmd;
}
SHELL_DYNAMIC_CMD_CREATE(sub_gpio_dev, device_name_get);
struct pin_info {
const struct device *dev;
bool reserved;
gpio_pin_t pin;
const char *line_name;
};
struct pin_order_user_data {
const struct shell *sh;
struct pin_info prev;
struct pin_info next;
};
typedef void (*pin_foreach_func_t)(const struct pin_info *info, void *user_data);
static void print_gpio_ctrl_info(const struct shell *sh, const struct gpio_ctrl *ctrl)
{
gpio_pin_t pin;
bool reserved;
shell_print(sh, " ngpios: %u", ctrl->ngpios);
shell_print(sh, " Reserved pin mask: 0x%08X", ctrl->reserved_mask);
shell_print(sh, "");
shell_print(sh, " Reserved Pin Line Name");
for (pin = 0; pin < GPIO_MAX_PINS_PER_PORT; pin++) {
if ((pin >= ctrl->ngpios) && (pin >= ctrl->line_names_len)) {
/* Out of info */
break;
}
reserved = (BIT64(pin) & ctrl->reserved_mask) != 0;
shell_print(sh, " %c %2u %s", reserved ? '*' : ' ',
pin, ctrl->line_names[pin]);
}
}
static void foreach_pin(pin_foreach_func_t func, void *user_data)
{
gpio_port_pins_t reserved_mask;
struct pin_info info;
gpio_pin_t pin;
size_t i;
for (i = 0; i < ARRAY_SIZE(gpio_list); i++) {
for (pin = 0; pin < gpio_list[i].ngpios; pin++) {
info.dev = gpio_list[i].dev;
reserved_mask = gpio_list[i].reserved_mask;
info.reserved = (BIT64(pin) & reserved_mask) != 0;
info.pin = pin;
if (pin < gpio_list[i].line_names_len) {
info.line_name = gpio_list[i].line_names[pin];
} else {
info.line_name = "";
}
func(&info, user_data);
}
}
}
static int pin_cmp(const struct pin_info *a, const struct pin_info *b)
{
int result = strcmp(a->line_name, b->line_name);
if (result != 0) {
return result;
}
result = strcmp(a->dev->name, b->dev->name);
if (result != 0) {
return result;
}
result = (int)a->pin - (int)b->pin;
return result;
}
static void pin_get_next(const struct pin_info *info, void *user_data)
{
struct pin_order_user_data *data = user_data;
int result;
if (data->prev.line_name != NULL) {
result = pin_cmp(info, &data->prev);
} else {
result = 1;
}
if (result > 0) {
if (data->next.line_name == NULL) {
data->next = *info;
return;
}
result = pin_cmp(info, &data->next);
if (result < 0) {
data->next = *info;
}
}
}
static void pin_ordered(const struct pin_info *info, void *user_data)
{
struct pin_order_user_data *data = user_data;
ARG_UNUSED(info);
foreach_pin(pin_get_next, data);
shell_print(data->sh, " %-12s %-8c %-16s %2u",
data->next.line_name,
data->next.reserved ? '*' : ' ',
data->next.dev->name,
data->next.pin);
data->prev = data->next;
data->next.line_name = NULL;
}
static void print_ordered_info(const struct shell *sh)
{
struct pin_order_user_data data = {0};
data.sh = sh;
shell_print(sh, " %-12s %-8s %-16s %-3s",
"Line", "Reserved", "Device", "Pin");
foreach_pin(pin_ordered, &data);
}
static int cmd_gpio_info(const struct shell *sh, size_t argc, char **argv)
{
const struct gpio_ctrl *ctrl = get_gpio_ctrl(argv[ARGV_DEV]);
if (ctrl == NULL) {
/* No device specified */
print_ordered_info(sh);
return 0;
}
print_gpio_ctrl_info(sh, ctrl);
return 0;
}
SHELL_STATIC_SUBCMD_SET_CREATE(sub_gpio,
SHELL_CMD_ARG(conf, &sub_gpio_dev,
"Configure GPIO pin\n"
"Usage: gpio conf <device> <pin> <configuration <i|o>[u|d][h|l][0|1]> [vendor specific]\n"
"<i|o> - input|output\n"
"[u|d] - pull up|pull down, otherwise open\n"
"[h|l] - active high|active low, otherwise defaults to active high\n"
"[0|1] - initialise to logic 0|logic 1, otherwise defaults to logic 0\n"
"[vendor specific] - configuration flags within the mask 0xFF00\n"
" see include/zephyr/dt-bindings/gpio/",
cmd_gpio_conf, 4, 1),
SHELL_CMD_ARG(get, &sub_gpio_dev,
"Get GPIO pin value\n"
"Usage: gpio get <device> <pin>", cmd_gpio_get, 3, 0),
SHELL_CMD_ARG(set, &sub_gpio_dev,
"Set GPIO pin value\n"
"Usage: gpio set <device> <pin> <level 0|1>", cmd_gpio_set, 4, 0),
SHELL_COND_CMD_ARG(CONFIG_GPIO_SHELL_TOGGLE_CMD, toggle, &sub_gpio_dev,
"Toggle GPIO pin\n"
"Usage: gpio toggle <device> <pin>", cmd_gpio_toggle, 3, 0),
SHELL_CMD(devices, NULL,
"List all GPIO devices\n"
"Usage: gpio devices", cmd_gpio_devices),
SHELL_COND_CMD_ARG(CONFIG_GPIO_SHELL_BLINK_CMD, blink, &sub_gpio_dev,
"Blink GPIO pin\n"
"Usage: gpio blink <device> <pin>", cmd_gpio_blink, 3, 0),
SHELL_COND_CMD_ARG(CONFIG_GPIO_SHELL_INFO_CMD, info, &sub_gpio_dev,
"GPIO Information\n"
"Usage: gpio info [device]", cmd_gpio_info, 1, 1),
SHELL_SUBCMD_SET_END /* Array terminated. */
);
SHELL_CMD_REGISTER(gpio, &sub_gpio, "GPIO commands", NULL);