zephyr/drivers/can/can_shell.c
Henrik Brix Andersen e397b85eb8 drivers: can: calculate a default SJW value
Change the CAN timing calculation APIs to automatically calculate a default
(Re-)Synchronization Jump Width (SJW) value. The calculated value can be
overwritten by the caller if desired.

This allows automatically scaling the SJW according to the number of Time
Quanta (TQ) used for phase segment 2 instead of relying on a compile-time
fallback value defined in devicetree.

This reduces the can_set_timing()/can_set_timing_data() API functions to
simple setters (with validation).

Fixes: #63033

Signed-off-by: Henrik Brix Andersen <hebad@vestas.com>
2023-09-28 16:28:56 +02:00

957 lines
26 KiB
C

/*
* Copyright (c) 2022 Vestas Wind Systems A/S
* Copyright (c) 2019 Alexander Wachter
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <stdio.h>
#include <zephyr/device.h>
#include <zephyr/drivers/can.h>
#include <zephyr/logging/log.h>
#include <zephyr/shell/shell.h>
LOG_MODULE_REGISTER(can_shell, CONFIG_CAN_LOG_LEVEL);
struct can_shell_tx_event {
unsigned int frame_no;
int error;
};
struct can_shell_mode_mapping {
const char *name;
can_mode_t mode;
};
#define CAN_SHELL_MODE_MAPPING(_name, _mode) { .name = _name, .mode = _mode }
static const struct can_shell_mode_mapping can_shell_mode_map[] = {
/* Array sorted alphabetically based on name */
CAN_SHELL_MODE_MAPPING("fd", CAN_MODE_FD),
CAN_SHELL_MODE_MAPPING("listen-only", CAN_MODE_LISTENONLY),
CAN_SHELL_MODE_MAPPING("loopback", CAN_MODE_LOOPBACK),
CAN_SHELL_MODE_MAPPING("normal", CAN_MODE_NORMAL),
CAN_SHELL_MODE_MAPPING("one-shot", CAN_MODE_ONE_SHOT),
CAN_SHELL_MODE_MAPPING("triple-sampling", CAN_MODE_3_SAMPLES),
};
K_MSGQ_DEFINE(can_shell_tx_msgq, sizeof(struct can_shell_tx_event),
CONFIG_CAN_SHELL_TX_QUEUE_SIZE, 4);
const struct shell *can_shell_tx_msgq_sh;
static struct k_work_poll can_shell_tx_msgq_work;
static struct k_poll_event can_shell_tx_msgq_events[] = {
K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_MSGQ_DATA_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&can_shell_tx_msgq, 0)
};
CAN_MSGQ_DEFINE(can_shell_rx_msgq, CONFIG_CAN_SHELL_RX_QUEUE_SIZE);
const struct shell *can_shell_rx_msgq_sh;
static struct k_work_poll can_shell_rx_msgq_work;
static struct k_poll_event can_shell_rx_msgq_events[] = {
K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_MSGQ_DATA_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&can_shell_rx_msgq, 0)
};
/* Forward declarations */
static void can_shell_tx_msgq_triggered_work_handler(struct k_work *work);
static void can_shell_rx_msgq_triggered_work_handler(struct k_work *work);
static void can_shell_print_frame(const struct shell *sh, const struct can_frame *frame)
{
uint8_t nbytes = can_dlc_to_bytes(frame->dlc);
int i;
#ifdef CONFIG_CAN_RX_TIMESTAMP
/* Timestamp */
shell_fprintf(sh, SHELL_NORMAL, "(%05d) ", frame->timestamp);
#endif /* CONFIG_CAN_RX_TIMESTAMP */
#ifdef CONFIG_CAN_FD_MODE
/* Flags */
shell_fprintf(sh, SHELL_NORMAL, "%c%c ",
(frame->flags & CAN_FRAME_BRS) == 0 ? '-' : 'B',
(frame->flags & CAN_FRAME_ESI) == 0 ? '-' : 'P');
#endif /* CONFIG_CAN_FD_MODE */
/* CAN ID */
shell_fprintf(sh, SHELL_NORMAL, "%*s%0*x ",
(frame->flags & CAN_FRAME_IDE) != 0 ? 0 : 5, "",
(frame->flags & CAN_FRAME_IDE) != 0 ? 8 : 3,
(frame->flags & CAN_FRAME_IDE) != 0 ?
frame->id & CAN_EXT_ID_MASK : frame->id & CAN_STD_ID_MASK);
/* DLC as number of bytes */
shell_fprintf(sh, SHELL_NORMAL, "%s[%0*d] ",
(frame->flags & CAN_FRAME_FDF) != 0 ? "" : " ",
(frame->flags & CAN_FRAME_FDF) != 0 ? 2 : 1,
nbytes);
/* Data payload */
if ((frame->flags & CAN_FRAME_RTR) != 0) {
shell_fprintf(sh, SHELL_NORMAL, "remote transmission request");
} else {
for (i = 0; i < nbytes; i++) {
shell_fprintf(sh, SHELL_NORMAL, "%02x ", frame->data[i]);
}
}
shell_fprintf(sh, SHELL_NORMAL, "\n");
}
static int can_shell_tx_msgq_poll_submit(const struct shell *sh)
{
int err;
if (can_shell_tx_msgq_sh == NULL) {
can_shell_tx_msgq_sh = sh;
k_work_poll_init(&can_shell_tx_msgq_work, can_shell_tx_msgq_triggered_work_handler);
}
err = k_work_poll_submit(&can_shell_tx_msgq_work, can_shell_tx_msgq_events,
ARRAY_SIZE(can_shell_tx_msgq_events), K_FOREVER);
if (err != 0) {
shell_error(can_shell_tx_msgq_sh, "failed to submit tx msgq polling (err %d)",
err);
}
return err;
}
static void can_shell_tx_msgq_triggered_work_handler(struct k_work *work)
{
struct can_shell_tx_event event;
while (k_msgq_get(&can_shell_tx_msgq, &event, K_NO_WAIT) == 0) {
if (event.error == 0) {
shell_print(can_shell_tx_msgq_sh, "CAN frame #%u successfully sent",
event.frame_no);
} else {
shell_error(can_shell_tx_msgq_sh, "failed to send CAN frame #%u (err %d)",
event.frame_no, event.error);
}
}
(void)can_shell_tx_msgq_poll_submit(can_shell_tx_msgq_sh);
}
static void can_shell_tx_callback(const struct device *dev, int error, void *user_data)
{
struct can_shell_tx_event event;
int err;
ARG_UNUSED(dev);
event.frame_no = POINTER_TO_UINT(user_data);
event.error = error;
err = k_msgq_put(&can_shell_tx_msgq, &event, K_NO_WAIT);
if (err != 0) {
LOG_ERR("CAN shell tx event queue full");
}
}
static int can_shell_rx_msgq_poll_submit(const struct shell *sh)
{
int err;
if (can_shell_rx_msgq_sh == NULL) {
can_shell_rx_msgq_sh = sh;
k_work_poll_init(&can_shell_rx_msgq_work, can_shell_rx_msgq_triggered_work_handler);
}
err = k_work_poll_submit(&can_shell_rx_msgq_work, can_shell_rx_msgq_events,
ARRAY_SIZE(can_shell_rx_msgq_events), K_FOREVER);
if (err != 0) {
shell_error(can_shell_rx_msgq_sh, "failed to submit rx msgq polling (err %d)",
err);
}
return err;
}
static void can_shell_rx_msgq_triggered_work_handler(struct k_work *work)
{
struct can_frame frame;
while (k_msgq_get(&can_shell_rx_msgq, &frame, K_NO_WAIT) == 0) {
can_shell_print_frame(can_shell_rx_msgq_sh, &frame);
}
(void)can_shell_rx_msgq_poll_submit(can_shell_rx_msgq_sh);
}
static const char *can_shell_state_to_string(enum can_state state)
{
switch (state) {
case CAN_STATE_ERROR_ACTIVE:
return "error-active";
case CAN_STATE_ERROR_WARNING:
return "error-warning";
case CAN_STATE_ERROR_PASSIVE:
return "error-passive";
case CAN_STATE_BUS_OFF:
return "bus-off";
case CAN_STATE_STOPPED:
return "stopped";
default:
return "unknown";
}
}
static void can_shell_print_capabilities(const struct shell *sh, can_mode_t cap)
{
int bit;
int i;
for (bit = 0; bit < sizeof(cap) * 8; bit++) {
/* Skip unset bits */
if ((cap & BIT(bit)) == 0) {
continue;
}
/* Lookup symbolic mode name */
for (i = 0; i < ARRAY_SIZE(can_shell_mode_map); i++) {
if (BIT(bit) == can_shell_mode_map[i].mode) {
shell_fprintf(sh, SHELL_NORMAL, "%s ", can_shell_mode_map[i].name);
break;
}
}
if (i == ARRAY_SIZE(can_shell_mode_map)) {
/* Symbolic name not found, use raw mode */
shell_fprintf(sh, SHELL_NORMAL, "0x%08x ", (can_mode_t)BIT(bit));
}
}
}
static int cmd_can_start(const struct shell *sh, size_t argc, char **argv)
{
const struct device *dev = device_get_binding(argv[1]);
int err;
if (!device_is_ready(dev)) {
shell_error(sh, "device %s not ready", argv[1]);
return -ENODEV;
}
shell_print(sh, "starting %s", argv[1]);
err = can_start(dev);
if (err != 0) {
shell_error(sh, "failed to start CAN controller (err %d)", err);
return err;
}
return 0;
}
static int cmd_can_stop(const struct shell *sh, size_t argc, char **argv)
{
const struct device *dev = device_get_binding(argv[1]);
int err;
if (!device_is_ready(dev)) {
shell_error(sh, "device %s not ready", argv[1]);
return -ENODEV;
}
shell_print(sh, "stopping %s", argv[1]);
err = can_stop(dev);
if (err != 0) {
shell_error(sh, "failed to stop CAN controller (err %d)", err);
return err;
}
return 0;
}
static int cmd_can_show(const struct shell *sh, size_t argc, char **argv)
{
const struct device *dev = device_get_binding(argv[1]);
const struct can_timing *timing_min;
const struct can_timing *timing_max;
struct can_bus_err_cnt err_cnt;
enum can_state state;
uint32_t max_bitrate = 0;
int max_std_filters = 0;
int max_ext_filters = 0;
uint32_t core_clock;
can_mode_t cap;
int err;
if (!device_is_ready(dev)) {
shell_error(sh, "device %s not ready", argv[1]);
return -ENODEV;
}
err = can_get_core_clock(dev, &core_clock);
if (err != 0) {
shell_error(sh, "failed to get CAN core clock (err %d)", err);
return err;
}
err = can_get_max_bitrate(dev, &max_bitrate);
if (err != 0 && err != -ENOSYS) {
shell_error(sh, "failed to get maximum bitrate (err %d)", err);
return err;
}
max_std_filters = can_get_max_filters(dev, false);
if (max_std_filters < 0 && max_std_filters != -ENOSYS) {
shell_error(sh, "failed to get maximum standard (11-bit) filters (err %d)", err);
return err;
}
max_ext_filters = can_get_max_filters(dev, true);
if (max_ext_filters < 0 && max_ext_filters != -ENOSYS) {
shell_error(sh, "failed to get maximum extended (29-bit) filters (err %d)", err);
return err;
}
err = can_get_capabilities(dev, &cap);
if (err != 0) {
shell_error(sh, "failed to get CAN controller capabilities (err %d)", err);
return err;
}
err = can_get_state(dev, &state, &err_cnt);
if (err != 0) {
shell_error(sh, "failed to get CAN controller state (%d)", err);
return err;
}
shell_print(sh, "core clock: %d Hz", core_clock);
shell_print(sh, "max bitrate: %d bps", max_bitrate);
shell_print(sh, "max std filters: %d", max_std_filters);
shell_print(sh, "max ext filters: %d", max_ext_filters);
shell_fprintf(sh, SHELL_NORMAL, "capabilities: normal ");
can_shell_print_capabilities(sh, cap);
shell_fprintf(sh, SHELL_NORMAL, "\n");
shell_print(sh, "state: %s", can_shell_state_to_string(state));
shell_print(sh, "rx errors: %d", err_cnt.rx_err_cnt);
shell_print(sh, "tx errors: %d", err_cnt.tx_err_cnt);
timing_min = can_get_timing_min(dev);
timing_max = can_get_timing_max(dev);
shell_print(sh, "timing: sjw %u..%u, prop_seg %u..%u, "
"phase_seg1 %u..%u, phase_seg2 %u..%u, prescaler %u..%u",
timing_min->sjw, timing_max->sjw,
timing_min->prop_seg, timing_max->prop_seg,
timing_min->phase_seg1, timing_max->phase_seg1,
timing_min->phase_seg2, timing_max->phase_seg2,
timing_min->prescaler, timing_max->prescaler);
if (IS_ENABLED(CONFIG_CAN_FD_MODE) && (cap & CAN_MODE_FD) != 0) {
timing_min = can_get_timing_data_min(dev);
timing_max = can_get_timing_data_max(dev);
shell_print(sh, "timing data: sjw %u..%u, prop_seg %u..%u, "
"phase_seg1 %u..%u, phase_seg2 %u..%u, prescaler %u..%u",
timing_min->sjw, timing_max->sjw,
timing_min->prop_seg, timing_max->prop_seg,
timing_min->phase_seg1, timing_max->phase_seg1,
timing_min->phase_seg2, timing_max->phase_seg2,
timing_min->prescaler, timing_max->prescaler);
}
return 0;
}
static int cmd_can_bitrate_set(const struct shell *sh, size_t argc, char **argv)
{
const struct device *dev = device_get_binding(argv[1]);
struct can_timing timing = { 0 };
uint16_t sample_pnt;
uint32_t bitrate;
char *endptr;
int err;
if (!device_is_ready(dev)) {
shell_error(sh, "device %s not ready", argv[1]);
return -ENODEV;
}
bitrate = (uint32_t)strtoul(argv[2], &endptr, 10);
if (*endptr != '\0') {
shell_error(sh, "failed to parse bitrate");
return -EINVAL;
}
if (argc >= 4) {
sample_pnt = (uint32_t)strtoul(argv[3], &endptr, 10);
if (*endptr != '\0') {
shell_error(sh, "failed to parse sample point");
return -EINVAL;
}
err = can_calc_timing(dev, &timing, bitrate, sample_pnt);
if (err < 0) {
shell_error(sh, "failed to calculate timing for "
"bitrate %d bps, sample point %d.%d%% (err %d)",
bitrate, sample_pnt / 10, sample_pnt % 10, err);
return err;
}
if (argc >= 5) {
/* Overwrite calculated default SJW with user-provided value */
timing.sjw = (uint16_t)strtoul(argv[4], &endptr, 10);
if (*endptr != '\0') {
shell_error(sh, "failed to parse SJW");
return -EINVAL;
}
}
shell_print(sh, "setting bitrate to %d bps, sample point %d.%d%% "
"(+/- %d.%d%%), sjw %d",
bitrate, sample_pnt / 10, sample_pnt % 10, err / 10, err % 10,
timing.sjw);
LOG_DBG("sjw %u, prop_seg %u, phase_seg1 %u, phase_seg2 %u, prescaler %u",
timing.sjw, timing.prop_seg, timing.phase_seg1, timing.phase_seg2,
timing.prescaler);
err = can_set_timing(dev, &timing);
if (err != 0) {
shell_error(sh, "failed to set timing (err %d)", err);
return err;
}
} else {
shell_print(sh, "setting bitrate to %d bps", bitrate);
err = can_set_bitrate(dev, bitrate);
if (err != 0) {
shell_error(sh, "failed to set bitrate (err %d)", err);
return err;
}
}
return 0;
}
static int cmd_can_dbitrate_set(const struct shell *sh, size_t argc, char **argv)
{
const struct device *dev = device_get_binding(argv[1]);
struct can_timing timing = { 0 };
uint16_t sample_pnt;
uint32_t bitrate;
char *endptr;
int err;
if (!device_is_ready(dev)) {
shell_error(sh, "device %s not ready", argv[1]);
return -ENODEV;
}
bitrate = (uint32_t)strtoul(argv[2], &endptr, 10);
if (*endptr != '\0') {
shell_error(sh, "failed to parse data bitrate");
return -EINVAL;
}
if (argc >= 4) {
sample_pnt = (uint32_t)strtoul(argv[3], &endptr, 10);
if (*endptr != '\0') {
shell_error(sh, "failed to parse sample point");
return -EINVAL;
}
err = can_calc_timing_data(dev, &timing, bitrate, sample_pnt);
if (err < 0) {
shell_error(sh, "failed to calculate timing for "
"data bitrate %d bps, sample point %d.%d%% (err %d)",
bitrate, sample_pnt / 10, sample_pnt % 10, err);
return err;
}
if (argc >= 5) {
/* Overwrite calculated default SJW with user-provided value */
timing.sjw = (uint16_t)strtoul(argv[4], &endptr, 10);
if (*endptr != '\0') {
shell_error(sh, "failed to parse SJW");
return -EINVAL;
}
}
shell_print(sh, "setting data bitrate to %d bps, sample point %d.%d%% "
"(+/- %d.%d%%), sjw %d",
bitrate, sample_pnt / 10, sample_pnt % 10, err / 10, err % 10,
timing.sjw);
LOG_DBG("sjw %u, prop_seg %u, phase_seg1 %u, phase_seg2 %u, prescaler %u",
timing.sjw, timing.prop_seg, timing.phase_seg1, timing.phase_seg2,
timing.prescaler);
err = can_set_timing_data(dev, &timing);
if (err != 0) {
shell_error(sh, "failed to set data timing (err %d)", err);
return err;
}
} else {
shell_print(sh, "setting data bitrate to %d bps", bitrate);
err = can_set_bitrate_data(dev, bitrate);
if (err != 0) {
shell_error(sh, "failed to set data bitrate (err %d)", err);
return err;
}
}
return 0;
}
static int cmd_can_mode_set(const struct shell *sh, size_t argc, char **argv)
{
const struct device *dev = device_get_binding(argv[1]);
can_mode_t mode = CAN_MODE_NORMAL;
can_mode_t raw;
char *endptr;
int err;
int i;
int j;
if (!device_is_ready(dev)) {
shell_error(sh, "device %s not ready", argv[1]);
return -ENODEV;
}
for (i = 2; i < argc; i++) {
/* Lookup symbolic mode name */
for (j = 0; j < ARRAY_SIZE(can_shell_mode_map); j++) {
if (strcmp(argv[i], can_shell_mode_map[j].name) == 0) {
mode |= can_shell_mode_map[j].mode;
break;
}
}
if (j == ARRAY_SIZE(can_shell_mode_map)) {
/* Symbolic name not found, use raw mode if hex number */
raw = (can_mode_t)strtoul(argv[i], &endptr, 16);
if (*endptr == '\0') {
mode |= raw;
continue;
}
shell_error(sh, "failed to parse mode");
return -EINVAL;
}
}
shell_print(sh, "setting mode 0x%08x", mode);
err = can_set_mode(dev, mode);
if (err != 0) {
shell_error(sh, "failed to set mode 0x%08x (err %d)", mode, err);
return err;
}
return 0;
}
static int cmd_can_send(const struct shell *sh, size_t argc, char **argv)
{
const struct device *dev = device_get_binding(argv[1]);
static unsigned int frame_counter;
unsigned int frame_no;
struct can_frame frame;
uint32_t max_id;
int argidx = 2;
uint32_t val;
char *endptr;
int nbytes;
int err;
int i;
if (!device_is_ready(dev)) {
shell_error(sh, "device %s not ready", argv[1]);
return -ENODEV;
}
/* Defaults */
max_id = CAN_MAX_STD_ID;
frame.flags = 0;
frame.dlc = 0;
/* Parse options */
while (argidx < argc && strncmp(argv[argidx], "-", 1) == 0) {
if (strcmp(argv[argidx], "--") == 0) {
argidx++;
break;
} else if (strcmp(argv[argidx], "-e") == 0) {
frame.flags |= CAN_FRAME_IDE;
max_id = CAN_MAX_EXT_ID;
argidx++;
} else if (strcmp(argv[argidx], "-r") == 0) {
frame.flags |= CAN_FRAME_RTR;
argidx++;
} else if (strcmp(argv[argidx], "-f") == 0) {
frame.flags |= CAN_FRAME_FDF;
argidx++;
} else if (strcmp(argv[argidx], "-b") == 0) {
frame.flags |= CAN_FRAME_BRS;
argidx++;
} else {
shell_error(sh, "unsupported option %s", argv[argidx]);
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
}
/* Parse CAN ID */
if (argidx >= argc) {
shell_error(sh, "missing CAN ID parameter");
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
val = (uint32_t)strtoul(argv[argidx++], &endptr, 16);
if (*endptr != '\0') {
shell_error(sh, "failed to parse CAN ID");
return -EINVAL;
}
if (val > max_id) {
shell_error(sh, "CAN ID 0x%0*x out of range",
(frame.flags & CAN_FRAME_IDE) != 0 ? 8 : 3,
val);
return -EINVAL;
}
frame.id = val;
nbytes = argc - argidx;
if (nbytes > ARRAY_SIZE(frame.data)) {
shell_error(sh, "excessive amount of data (%d bytes)", nbytes);
return -EINVAL;
}
frame.dlc = can_bytes_to_dlc(nbytes);
/* Parse data */
for (i = 0; i < nbytes; i++) {
val = (uint32_t)strtoul(argv[argidx++], &endptr, 16);
if (*endptr != '\0') {
shell_error(sh, "failed to parse data %s", argv[argidx++]);
return -EINVAL;
}
if (val > 0xff) {
shell_error(sh, "data 0x%x out of range", val);
return -EINVAL;
}
frame.data[i] = val;
}
err = can_shell_tx_msgq_poll_submit(sh);
if (err != 0) {
return err;
}
frame_no = frame_counter++;
shell_print(sh, "enqueuing CAN frame #%u with %s (%d-bit) CAN ID 0x%0*x, "
"RTR %d, CAN-FD %d, BRS %d, DLC %d", frame_no,
(frame.flags & CAN_FRAME_IDE) != 0 ? "extended" : "standard",
(frame.flags & CAN_FRAME_IDE) != 0 ? 29 : 11,
(frame.flags & CAN_FRAME_IDE) != 0 ? 8 : 3, frame.id,
(frame.flags & CAN_FRAME_RTR) != 0 ? 1 : 0,
(frame.flags & CAN_FRAME_FDF) != 0 ? 1 : 0,
(frame.flags & CAN_FRAME_BRS) != 0 ? 1 : 0,
can_dlc_to_bytes(frame.dlc));
err = can_send(dev, &frame, K_NO_WAIT, can_shell_tx_callback, UINT_TO_POINTER(frame_no));
if (err != 0) {
shell_error(sh, "failed to enqueue CAN frame #%u (err %d)", frame_no, err);
return err;
}
return 0;
}
static int cmd_can_filter_add(const struct shell *sh, size_t argc, char **argv)
{
const struct device *dev = device_get_binding(argv[1]);
struct can_filter filter;
uint32_t max_id;
int argidx = 2;
uint32_t val;
char *endptr;
int err;
if (!device_is_ready(dev)) {
shell_error(sh, "device %s not ready", argv[1]);
return -ENODEV;
}
/* Defaults */
max_id = CAN_MAX_STD_ID;
filter.flags = CAN_FILTER_DATA;
/* Parse options */
while (argidx < argc && strncmp(argv[argidx], "-", 1) == 0) {
if (strcmp(argv[argidx], "--") == 0) {
argidx++;
break;
} else if (strcmp(argv[argidx], "-e") == 0) {
filter.flags |= CAN_FILTER_IDE;
max_id = CAN_MAX_EXT_ID;
argidx++;
} else if (strcmp(argv[argidx], "-f") == 0) {
filter.flags |= CAN_FILTER_FDF;
argidx++;
} else if (strcmp(argv[argidx], "-r") == 0) {
filter.flags |= CAN_FILTER_RTR;
argidx++;
} else if (strcmp(argv[argidx], "-R") == 0) {
filter.flags &= ~(CAN_FILTER_DATA);
filter.flags |= CAN_FILTER_RTR;
argidx++;
} else {
shell_error(sh, "unsupported argument %s", argv[argidx]);
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
}
/* Parse CAN ID */
if (argidx >= argc) {
shell_error(sh, "missing CAN ID parameter");
shell_help(sh);
return SHELL_CMD_HELP_PRINTED;
}
val = (uint32_t)strtoul(argv[argidx++], &endptr, 16);
if (*endptr != '\0') {
shell_error(sh, "failed to parse CAN ID");
return -EINVAL;
}
if (val > max_id) {
shell_error(sh, "CAN ID 0x%0*x out of range",
(filter.flags & CAN_FILTER_IDE) != 0 ? 8 : 3,
val);
return -EINVAL;
}
filter.id = val;
if (argidx < argc) {
/* Parse CAN ID mask */
val = (uint32_t)strtoul(argv[argidx++], &endptr, 16);
if (*endptr != '\0') {
shell_error(sh, "failed to parse CAN ID mask");
return -EINVAL;
}
if (val > max_id) {
shell_error(sh, "CAN ID mask 0x%0*x out of range",
(filter.flags & CAN_FILTER_IDE) != 0 ? 8 : 3,
val);
return -EINVAL;
}
} else {
val = max_id;
}
filter.mask = val;
err = can_shell_rx_msgq_poll_submit(sh);
if (err != 0) {
return err;
}
shell_print(sh, "adding filter with %s (%d-bit) CAN ID 0x%0*x, "
"CAN ID mask 0x%0*x, data frames %d, RTR frames %d, CAN-FD frames %d",
(filter.flags & CAN_FILTER_IDE) != 0 ? "extended" : "standard",
(filter.flags & CAN_FILTER_IDE) != 0 ? 29 : 11,
(filter.flags & CAN_FILTER_IDE) != 0 ? 8 : 3, filter.id,
(filter.flags & CAN_FILTER_IDE) != 0 ? 8 : 3, filter.mask,
(filter.flags & CAN_FILTER_DATA) != 0 ? 1 : 0,
(filter.flags & CAN_FILTER_RTR) != 0 ? 1 : 0,
(filter.flags & CAN_FILTER_FDF) != 0 ? 1 : 0);
err = can_add_rx_filter_msgq(dev, &can_shell_rx_msgq, &filter);
if (err < 0) {
shell_error(sh, "failed to add filter (err %d)", err);
return err;
}
shell_print(sh, "filter ID: %d", err);
return 0;
}
static int cmd_can_filter_remove(const struct shell *sh, size_t argc, char **argv)
{
const struct device *dev = device_get_binding(argv[1]);
int filter_id;
char *endptr;
if (!device_is_ready(dev)) {
shell_error(sh, "device %s not ready", argv[1]);
return -ENODEV;
}
/* Parse filter ID */
filter_id = (int)strtol(argv[2], &endptr, 10);
if (*endptr != '\0') {
shell_error(sh, "failed to parse filter ID");
return -EINVAL;
}
shell_print(sh, "removing filter with ID %d", filter_id);
can_remove_rx_filter(dev, filter_id);
return 0;
}
static int cmd_can_recover(const struct shell *sh, size_t argc, char **argv)
{
const struct device *dev = device_get_binding(argv[1]);
k_timeout_t timeout = K_FOREVER;
int millisec;
char *endptr;
int err;
if (!device_is_ready(dev)) {
shell_error(sh, "device %s not ready", argv[1]);
return -ENODEV;
}
if (argc >= 3) {
/* Parse timeout */
millisec = (int)strtol(argv[2], &endptr, 10);
if (*endptr != '\0') {
shell_error(sh, "failed to parse timeout");
return -EINVAL;
}
timeout = K_MSEC(millisec);
shell_print(sh, "recovering, timeout %d ms", millisec);
} else {
shell_print(sh, "recovering, no timeout");
}
err = can_recover(dev, timeout);
if (err != 0) {
shell_error(sh, "failed to recover CAN controller from bus-off (err %d)", err);
return err;
}
return 0;
}
static void cmd_can_device_name(size_t idx, struct shell_static_entry *entry)
{
const struct device *dev = shell_device_lookup(idx, NULL);
entry->syntax = (dev != NULL) ? dev->name : NULL;
entry->handler = NULL;
entry->help = NULL;
entry->subcmd = NULL;
}
SHELL_DYNAMIC_CMD_CREATE(dsub_can_device_name, cmd_can_device_name);
static void cmd_can_mode(size_t idx, struct shell_static_entry *entry);
SHELL_DYNAMIC_CMD_CREATE(dsub_can_mode, cmd_can_mode);
static void cmd_can_mode(size_t idx, struct shell_static_entry *entry)
{
if (idx < ARRAY_SIZE(can_shell_mode_map)) {
entry->syntax = can_shell_mode_map[idx].name;
} else {
entry->syntax = NULL;
}
entry->handler = NULL;
entry->help = NULL;
entry->subcmd = &dsub_can_mode;
}
static void cmd_can_device_name_mode(size_t idx, struct shell_static_entry *entry)
{
const struct device *dev = shell_device_lookup(idx, NULL);
entry->syntax = (dev != NULL) ? dev->name : NULL;
entry->handler = NULL;
entry->help = NULL;
entry->subcmd = &dsub_can_mode;
}
SHELL_DYNAMIC_CMD_CREATE(dsub_can_device_name_mode, cmd_can_device_name_mode);
SHELL_STATIC_SUBCMD_SET_CREATE(sub_can_filter_cmds,
SHELL_CMD_ARG(add, &dsub_can_device_name,
"Add rx filter\n"
"Usage: can filter add <device> [-e] [-f] [-r] [-R] <CAN ID> [CAN ID mask]\n"
"-e use extended (29-bit) CAN ID/CAN ID mask\n"
"-f match CAN-FD format frames\n"
"-r also match Remote Transmission Request (RTR) frames\n"
"-R only match Remote Transmission Request (RTR) frames",
cmd_can_filter_add, 3, 5),
SHELL_CMD_ARG(remove, &dsub_can_device_name,
"Remove rx filter\n"
"Usage: can filter remove <device> <filter_id>",
cmd_can_filter_remove, 3, 0),
SHELL_SUBCMD_SET_END
);
SHELL_STATIC_SUBCMD_SET_CREATE(sub_can_cmds,
SHELL_CMD_ARG(start, &dsub_can_device_name,
"Start CAN controller\n"
"Usage: can start <device>",
cmd_can_start, 2, 0),
SHELL_CMD_ARG(stop, &dsub_can_device_name,
"Stop CAN controller\n"
"Usage: can stop <device>",
cmd_can_stop, 2, 0),
SHELL_CMD_ARG(show, &dsub_can_device_name,
"Show CAN controller information\n"
"Usage: can show <device>",
cmd_can_show, 2, 0),
SHELL_CMD_ARG(bitrate, &dsub_can_device_name,
"Set CAN controller bitrate (sample point and SJW optional)\n"
"Usage: can bitrate <device> <bitrate> [sample point] [sjw]",
cmd_can_bitrate_set, 3, 2),
SHELL_COND_CMD_ARG(CONFIG_CAN_FD_MODE,
dbitrate, &dsub_can_device_name,
"Set CAN controller data phase bitrate (sample point and SJW optional)\n"
"Usage: can dbitrate <device> <data phase bitrate> [sample point] [sjw]",
cmd_can_dbitrate_set, 3, 2),
SHELL_CMD_ARG(mode, &dsub_can_device_name_mode,
"Set CAN controller mode\n"
"Usage: can mode <device> <mode> [mode] [mode] [...]",
cmd_can_mode_set, 3, SHELL_OPT_ARG_CHECK_SKIP),
SHELL_CMD_ARG(send, &dsub_can_device_name,
"Enqueue a CAN frame for sending\n"
"Usage: can send <device> [-e] [-r] [-f] [-b] <CAN ID> [data] [...]\n"
"-e use extended (29-bit) CAN ID\n"
"-r send Remote Transmission Request (RTR) frame\n"
"-f use CAN-FD frame format\n"
"-b use CAN-FD Bit Rate Switching (BRS)",
cmd_can_send, 3, SHELL_OPT_ARG_CHECK_SKIP),
SHELL_CMD(filter, &sub_can_filter_cmds,
"CAN rx filter commands\n"
"Usage: can filter <add|remove> <device> ...",
NULL),
SHELL_EXPR_CMD_ARG(!IS_ENABLED(CONFIG_CAN_AUTO_BUS_OFF_RECOVERY),
recover, &dsub_can_device_name,
"Recover CAN controller from bus-off state\n"
"Usage: can recover <device> [timeout ms]",
cmd_can_recover, 2, 1),
SHELL_SUBCMD_SET_END
);
SHELL_CMD_REGISTER(can, &sub_can_cmds, "CAN controller commands", NULL);