blob: cca1738f117e6311da4b164f6a4ff2a8fd89d81a [file] [log] [blame]
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/** SMP - Simple Management Client Protocol. */
#include <string.h>
#include <zcbor_common.h>
#include <zcbor_decode.h>
#include <zcbor_encode.h>
#include <zephyr/kernel.h>
#include <zephyr/init.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/net/buf.h>
#include <zephyr/mgmt/mcumgr/mgmt/mgmt.h>
#include <zephyr/mgmt/mcumgr/smp/smp.h>
#include <zephyr/mgmt/mcumgr/smp/smp_client.h>
#include <zephyr/mgmt/mcumgr/transport/smp.h>
#include <mgmt/mcumgr/transport/smp_internal.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(mcumgr_smp_client, CONFIG_MCUMGR_SMP_CLIENT_LOG_LEVEL);
struct smp_client_cmd_req {
sys_snode_t node;
struct net_buf *nb;
struct smp_client_object *smp_client;
void *user_data;
smp_client_res_fn cb;
int64_t timestamp;
int retry_cnt;
};
struct smp_client_data_base {
struct k_work_delayable work_delay;
sys_slist_t cmd_free_list;
sys_slist_t cmd_list;
};
static struct smp_client_cmd_req smp_cmd_req_bufs[CONFIG_SMP_CLIENT_CMD_MAX];
static struct smp_client_data_base smp_client_data;
static void smp_read_hdr(const struct net_buf *nb, struct smp_hdr *dst_hdr);
static void smp_client_cmd_req_free(struct smp_client_cmd_req *cmd_req);
/**
* Send all SMP client request packets.
*/
static void smp_client_handle_reqs(struct k_work *work)
{
struct smp_client_object *smp_client;
struct smp_transport *smpt;
struct net_buf *nb;
smp_client = (void *)work;
smpt = smp_client->smpt;
while ((nb = net_buf_get(&smp_client->tx_fifo, K_NO_WAIT)) != NULL) {
smpt->functions.output(nb);
}
}
static void smp_header_init(struct smp_hdr *header, uint16_t group, uint8_t id, uint8_t op,
uint16_t payload_len, uint8_t seq, enum smp_mcumgr_version_t version)
{
/* Pre config SMP header structure */
memset(header, 0, sizeof(struct smp_hdr));
header->nh_version = version;
header->nh_op = op;
header->nh_len = sys_cpu_to_be16(payload_len);
header->nh_group = sys_cpu_to_be16(group);
header->nh_id = id;
header->nh_seq = seq;
}
static void smp_client_transport_work_fn(struct k_work *work)
{
struct smp_client_cmd_req *entry, *tmp;
smp_client_res_fn cb;
void *user_data;
int backoff_ms = CONFIG_SMP_CMD_RETRY_TIME;
int64_t time_stamp_cmp;
int64_t time_stamp_ref;
int64_t time_stamp_delta;
ARG_UNUSED(work);
if (sys_slist_is_empty(&smp_client_data.cmd_list)) {
/* No more packet for Transport */
return;
}
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&smp_client_data.cmd_list, entry, tmp, node) {
time_stamp_ref = entry->timestamp;
/* Check Time delta and get current time to reference */
time_stamp_delta = k_uptime_delta(&time_stamp_ref);
if (time_stamp_delta < 0) {
time_stamp_cmp = entry->timestamp - time_stamp_ref;
if (time_stamp_cmp < CONFIG_SMP_CMD_RETRY_TIME &&
time_stamp_cmp < backoff_ms) {
/* Update new shorter shedule */
backoff_ms = time_stamp_cmp;
}
continue;
} else if (entry->retry_cnt) {
/* Increment reference for re-transmission */
entry->nb = net_buf_ref(entry->nb);
entry->retry_cnt--;
entry->timestamp = time_stamp_ref + CONFIG_SMP_CMD_RETRY_TIME;
net_buf_put(&entry->smp_client->tx_fifo, entry->nb);
smp_tx_req(&entry->smp_client->work);
continue;
}
cb = entry->cb;
user_data = entry->user_data;
smp_client_cmd_req_free(entry);
if (cb) {
cb(NULL, user_data);
}
}
if (!sys_slist_is_empty(&smp_client_data.cmd_list)) {
/* Re-schedule new timeout to next */
k_work_reschedule(&smp_client_data.work_delay, K_MSEC(backoff_ms));
}
}
static int smp_client_init(void)
{
k_work_init_delayable(&smp_client_data.work_delay, smp_client_transport_work_fn);
sys_slist_init(&smp_client_data.cmd_list);
sys_slist_init(&smp_client_data.cmd_free_list);
for (int i = 0; i < CONFIG_SMP_CLIENT_CMD_MAX; i++) {
sys_slist_append(&smp_client_data.cmd_free_list, &smp_cmd_req_bufs[i].node);
}
return 0;
}
static struct smp_client_cmd_req *smp_client_cmd_req_allocate(void)
{
sys_snode_t *cmd_node;
struct smp_client_cmd_req *req;
cmd_node = sys_slist_get(&smp_client_data.cmd_free_list);
if (!cmd_node) {
return NULL;
}
req = SYS_SLIST_CONTAINER(cmd_node, req, node);
return req;
}
static void smp_cmd_add_to_list(struct smp_client_cmd_req *cmd_req)
{
if (sys_slist_is_empty(&smp_client_data.cmd_list)) {
/* Enable timer */
k_work_reschedule(&smp_client_data.work_delay, K_MSEC(CONFIG_SMP_CMD_RETRY_TIME));
}
sys_slist_append(&smp_client_data.cmd_list, &cmd_req->node);
}
static void smp_client_cmd_req_free(struct smp_client_cmd_req *cmd_req)
{
smp_client_buf_free(cmd_req->nb);
cmd_req->nb = NULL;
sys_slist_find_and_remove(&smp_client_data.cmd_list, &cmd_req->node);
/* Add to free list */
sys_slist_append(&smp_client_data.cmd_free_list, &cmd_req->node);
if (sys_slist_is_empty(&smp_client_data.cmd_list)) {
/* cancel delay */
k_work_cancel_delayable(&smp_client_data.work_delay);
}
}
static struct smp_client_cmd_req *smp_client_response_discover(const struct smp_hdr *res_hdr)
{
struct smp_hdr smp_header;
enum mcumgr_op_t response;
struct smp_client_cmd_req *cmd_req;
if (sys_slist_is_empty(&smp_client_data.cmd_list)) {
return NULL;
}
SYS_SLIST_FOR_EACH_CONTAINER(&smp_client_data.cmd_list, cmd_req, node) {
smp_read_hdr(cmd_req->nb, &smp_header);
if (smp_header.nh_op == MGMT_OP_READ) {
response = MGMT_OP_READ_RSP;
} else {
response = MGMT_OP_WRITE_RSP;
}
if (smp_header.nh_seq != res_hdr->nh_seq) {
continue;
} else if (res_hdr->nh_op != response) {
continue;
}
return cmd_req;
}
return NULL;
}
int smp_client_object_init(struct smp_client_object *smp_client, int smp_type)
{
smp_client->smpt = smp_client_transport_get(smp_type);
if (!smp_client->smpt) {
return MGMT_ERR_EINVAL;
}
/* Init TX FIFO */
k_work_init(&smp_client->work, smp_client_handle_reqs);
k_fifo_init(&smp_client->tx_fifo);
return MGMT_ERR_EOK;
}
int smp_client_single_response(struct net_buf *nb, const struct smp_hdr *res_hdr)
{
struct smp_client_cmd_req *cmd_req;
smp_client_res_fn cb;
void *user_data;
/* Discover request for incoming response */
cmd_req = smp_client_response_discover(res_hdr);
LOG_DBG("Response Header len %d, flags %d OP: %d group %d id %d seq %d", res_hdr->nh_len,
res_hdr->nh_flags, res_hdr->nh_op, res_hdr->nh_group, res_hdr->nh_id,
res_hdr->nh_seq);
if (cmd_req) {
cb = cmd_req->cb;
user_data = cmd_req->user_data;
smp_client_cmd_req_free(cmd_req);
if (cb) {
cb(nb, user_data);
return MGMT_ERR_EOK;
}
}
return MGMT_ERR_ENOENT;
}
struct net_buf *smp_client_buf_allocation(struct smp_client_object *smp_client, uint16_t group,
uint8_t command_id, uint8_t op,
enum smp_mcumgr_version_t version)
{
struct net_buf *nb;
struct smp_hdr smp_header;
nb = smp_packet_alloc();
if (nb) {
/* Write SMP header with payload length 0 */
smp_header_init(&smp_header, group, command_id, op, 0, smp_client->smp_seq++,
version);
memcpy(nb->data, &smp_header, sizeof(smp_header));
nb->len = sizeof(smp_header);
}
return nb;
}
void smp_client_buf_free(struct net_buf *nb)
{
smp_packet_free(nb);
}
static void smp_read_hdr(const struct net_buf *nb, struct smp_hdr *dst_hdr)
{
memcpy(dst_hdr, nb->data, sizeof(*dst_hdr));
dst_hdr->nh_len = sys_be16_to_cpu(dst_hdr->nh_len);
dst_hdr->nh_group = sys_be16_to_cpu(dst_hdr->nh_group);
}
int smp_client_send_cmd(struct smp_client_object *smp_client, struct net_buf *nb,
smp_client_res_fn cb, void *user_data, int timeout_in_sec)
{
struct smp_hdr smp_header;
struct smp_client_cmd_req *cmd_req;
if (timeout_in_sec > 30) {
LOG_ERR("Command timeout can't be over 30 seconds");
return MGMT_ERR_EINVAL;
}
if (timeout_in_sec == 0) {
timeout_in_sec = CONFIG_SMP_CMD_DEFAULT_LIFE_TIME;
}
smp_read_hdr(nb, &smp_header);
if (nb->len < sizeof(smp_header)) {
return MGMT_ERR_EINVAL;
}
/* Update Length */
smp_header.nh_len = sys_cpu_to_be16(nb->len - sizeof(smp_header));
smp_header.nh_group = sys_cpu_to_be16(smp_header.nh_group),
memcpy(nb->data, &smp_header, sizeof(smp_header));
cmd_req = smp_client_cmd_req_allocate();
if (!cmd_req) {
return MGMT_ERR_ENOMEM;
}
LOG_DBG("Command send Header flags %d OP: %d group %d id %d seq %d", smp_header.nh_flags,
smp_header.nh_op, sys_be16_to_cpu(smp_header.nh_group), smp_header.nh_id,
smp_header.nh_seq);
cmd_req->nb = nb;
cmd_req->cb = cb;
cmd_req->smp_client = smp_client;
cmd_req->user_data = user_data;
cmd_req->retry_cnt = timeout_in_sec * (1000 / CONFIG_SMP_CMD_RETRY_TIME);
cmd_req->timestamp = k_uptime_get() + CONFIG_SMP_CMD_RETRY_TIME;
/* Increment reference for re-transmission and read smp header */
nb = net_buf_ref(nb);
smp_cmd_add_to_list(cmd_req);
net_buf_put(&smp_client->tx_fifo, nb);
smp_tx_req(&smp_client->work);
return MGMT_ERR_EOK;
}
SYS_INIT(smp_client_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);