1 /*
2 * Copyright (c) 2023 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 /** SMP - Simple Management Client Protocol. */
8
9 #include <string.h>
10 #include <zcbor_common.h>
11 #include <zcbor_decode.h>
12 #include <zcbor_encode.h>
13 #include <zephyr/kernel.h>
14 #include <zephyr/init.h>
15 #include <zephyr/sys/byteorder.h>
16 #include <zephyr/net/buf.h>
17
18 #include <zephyr/mgmt/mcumgr/mgmt/mgmt.h>
19 #include <zephyr/mgmt/mcumgr/smp/smp.h>
20 #include <zephyr/mgmt/mcumgr/smp/smp_client.h>
21 #include <zephyr/mgmt/mcumgr/transport/smp.h>
22 #include <mgmt/mcumgr/transport/smp_internal.h>
23
24 #include <zephyr/logging/log.h>
25 LOG_MODULE_REGISTER(mcumgr_smp_client, CONFIG_MCUMGR_SMP_CLIENT_LOG_LEVEL);
26
27 struct smp_client_cmd_req {
28 sys_snode_t node;
29 struct net_buf *nb;
30 struct smp_client_object *smp_client;
31 void *user_data;
32 smp_client_res_fn cb;
33 int64_t timestamp;
34 int retry_cnt;
35 };
36
37 struct smp_client_data_base {
38 struct k_work_delayable work_delay;
39 sys_slist_t cmd_free_list;
40 sys_slist_t cmd_list;
41 };
42
43 static struct smp_client_cmd_req smp_cmd_req_bufs[CONFIG_SMP_CLIENT_CMD_MAX];
44 static struct smp_client_data_base smp_client_data;
45
46 static void smp_read_hdr(const struct net_buf *nb, struct smp_hdr *dst_hdr);
47 static void smp_client_cmd_req_free(struct smp_client_cmd_req *cmd_req);
48
49 /**
50 * Send all SMP client request packets.
51 */
smp_client_handle_reqs(struct k_work * work)52 static void smp_client_handle_reqs(struct k_work *work)
53 {
54 struct smp_client_object *smp_client;
55 struct smp_transport *smpt;
56 struct net_buf *nb;
57
58 smp_client = (void *)work;
59 smpt = smp_client->smpt;
60
61 while ((nb = net_buf_get(&smp_client->tx_fifo, K_NO_WAIT)) != NULL) {
62 smpt->functions.output(nb);
63 }
64 }
65
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)66 static void smp_header_init(struct smp_hdr *header, uint16_t group, uint8_t id, uint8_t op,
67 uint16_t payload_len, uint8_t seq, enum smp_mcumgr_version_t version)
68 {
69 /* Pre config SMP header structure */
70 memset(header, 0, sizeof(struct smp_hdr));
71 header->nh_version = version;
72 header->nh_op = op;
73 header->nh_len = sys_cpu_to_be16(payload_len);
74 header->nh_group = sys_cpu_to_be16(group);
75 header->nh_id = id;
76 header->nh_seq = seq;
77 }
78
smp_client_transport_work_fn(struct k_work * work)79 static void smp_client_transport_work_fn(struct k_work *work)
80 {
81 struct smp_client_cmd_req *entry, *tmp;
82 smp_client_res_fn cb;
83 void *user_data;
84 int backoff_ms = CONFIG_SMP_CMD_RETRY_TIME;
85 int64_t time_stamp_cmp;
86 int64_t time_stamp_ref;
87 int64_t time_stamp_delta;
88
89 ARG_UNUSED(work);
90
91 if (sys_slist_is_empty(&smp_client_data.cmd_list)) {
92 /* No more packet for Transport */
93 return;
94 }
95
96 SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&smp_client_data.cmd_list, entry, tmp, node) {
97 time_stamp_ref = entry->timestamp;
98 /* Check Time delta and get current time to reference */
99 time_stamp_delta = k_uptime_delta(&time_stamp_ref);
100
101 if (time_stamp_delta < 0) {
102 time_stamp_cmp = entry->timestamp - time_stamp_ref;
103 if (time_stamp_cmp < CONFIG_SMP_CMD_RETRY_TIME &&
104 time_stamp_cmp < backoff_ms) {
105 /* Update new shorter shedule */
106 backoff_ms = time_stamp_cmp;
107 }
108 continue;
109 } else if (entry->retry_cnt) {
110 /* Increment reference for re-transmission */
111 entry->nb = net_buf_ref(entry->nb);
112 entry->retry_cnt--;
113 entry->timestamp = time_stamp_ref + CONFIG_SMP_CMD_RETRY_TIME;
114 net_buf_put(&entry->smp_client->tx_fifo, entry->nb);
115 smp_tx_req(&entry->smp_client->work);
116 continue;
117 }
118
119 cb = entry->cb;
120 user_data = entry->user_data;
121 smp_client_cmd_req_free(entry);
122 if (cb) {
123 cb(NULL, user_data);
124 }
125 }
126
127 if (!sys_slist_is_empty(&smp_client_data.cmd_list)) {
128 /* Re-schedule new timeout to next */
129 k_work_reschedule(&smp_client_data.work_delay, K_MSEC(backoff_ms));
130 }
131 }
132
smp_client_init(void)133 static int smp_client_init(void)
134 {
135 k_work_init_delayable(&smp_client_data.work_delay, smp_client_transport_work_fn);
136 sys_slist_init(&smp_client_data.cmd_list);
137 sys_slist_init(&smp_client_data.cmd_free_list);
138 for (int i = 0; i < CONFIG_SMP_CLIENT_CMD_MAX; i++) {
139 sys_slist_append(&smp_client_data.cmd_free_list, &smp_cmd_req_bufs[i].node);
140 }
141 return 0;
142 }
143
smp_client_cmd_req_allocate(void)144 static struct smp_client_cmd_req *smp_client_cmd_req_allocate(void)
145 {
146 sys_snode_t *cmd_node;
147 struct smp_client_cmd_req *req;
148
149 cmd_node = sys_slist_get(&smp_client_data.cmd_free_list);
150 if (!cmd_node) {
151 return NULL;
152 }
153
154 req = SYS_SLIST_CONTAINER(cmd_node, req, node);
155
156 return req;
157 }
158
smp_cmd_add_to_list(struct smp_client_cmd_req * cmd_req)159 static void smp_cmd_add_to_list(struct smp_client_cmd_req *cmd_req)
160 {
161 if (sys_slist_is_empty(&smp_client_data.cmd_list)) {
162 /* Enable timer */
163 k_work_reschedule(&smp_client_data.work_delay, K_MSEC(CONFIG_SMP_CMD_RETRY_TIME));
164 }
165 sys_slist_append(&smp_client_data.cmd_list, &cmd_req->node);
166 }
167
smp_client_cmd_req_free(struct smp_client_cmd_req * cmd_req)168 static void smp_client_cmd_req_free(struct smp_client_cmd_req *cmd_req)
169 {
170 smp_client_buf_free(cmd_req->nb);
171 cmd_req->nb = NULL;
172 sys_slist_find_and_remove(&smp_client_data.cmd_list, &cmd_req->node);
173 /* Add to free list */
174 sys_slist_append(&smp_client_data.cmd_free_list, &cmd_req->node);
175
176 if (sys_slist_is_empty(&smp_client_data.cmd_list)) {
177 /* cancel delay */
178 k_work_cancel_delayable(&smp_client_data.work_delay);
179 }
180 }
181
smp_client_response_discover(const struct smp_hdr * res_hdr)182 static struct smp_client_cmd_req *smp_client_response_discover(const struct smp_hdr *res_hdr)
183 {
184 struct smp_hdr smp_header;
185 enum mcumgr_op_t response;
186 struct smp_client_cmd_req *cmd_req;
187
188 if (sys_slist_is_empty(&smp_client_data.cmd_list)) {
189 return NULL;
190 }
191
192 SYS_SLIST_FOR_EACH_CONTAINER(&smp_client_data.cmd_list, cmd_req, node) {
193 smp_read_hdr(cmd_req->nb, &smp_header);
194 if (smp_header.nh_op == MGMT_OP_READ) {
195 response = MGMT_OP_READ_RSP;
196 } else {
197 response = MGMT_OP_WRITE_RSP;
198 }
199
200 if (smp_header.nh_seq != res_hdr->nh_seq) {
201 continue;
202 } else if (res_hdr->nh_op != response) {
203 continue;
204 }
205
206 return cmd_req;
207 }
208 return NULL;
209 }
210
smp_client_object_init(struct smp_client_object * smp_client,int smp_type)211 int smp_client_object_init(struct smp_client_object *smp_client, int smp_type)
212 {
213 smp_client->smpt = smp_client_transport_get(smp_type);
214 if (!smp_client->smpt) {
215 return MGMT_ERR_EINVAL;
216 }
217
218 /* Init TX FIFO */
219 k_work_init(&smp_client->work, smp_client_handle_reqs);
220 k_fifo_init(&smp_client->tx_fifo);
221
222 return MGMT_ERR_EOK;
223 }
224
smp_client_single_response(struct net_buf * nb,const struct smp_hdr * res_hdr)225 int smp_client_single_response(struct net_buf *nb, const struct smp_hdr *res_hdr)
226 {
227 struct smp_client_cmd_req *cmd_req;
228 smp_client_res_fn cb;
229 void *user_data;
230
231 /* Discover request for incoming response */
232 cmd_req = smp_client_response_discover(res_hdr);
233 LOG_DBG("Response Header len %d, flags %d OP: %d group %d id %d seq %d", res_hdr->nh_len,
234 res_hdr->nh_flags, res_hdr->nh_op, res_hdr->nh_group, res_hdr->nh_id,
235 res_hdr->nh_seq);
236
237 if (cmd_req) {
238 cb = cmd_req->cb;
239 user_data = cmd_req->user_data;
240 smp_client_cmd_req_free(cmd_req);
241 if (cb) {
242 cb(nb, user_data);
243 return MGMT_ERR_EOK;
244 }
245 }
246
247 return MGMT_ERR_ENOENT;
248 }
249
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)250 struct net_buf *smp_client_buf_allocation(struct smp_client_object *smp_client, uint16_t group,
251 uint8_t command_id, uint8_t op,
252 enum smp_mcumgr_version_t version)
253 {
254 struct net_buf *nb;
255 struct smp_hdr smp_header;
256
257 nb = smp_packet_alloc();
258
259 if (nb) {
260 /* Write SMP header with payload length 0 */
261 smp_header_init(&smp_header, group, command_id, op, 0, smp_client->smp_seq++,
262 version);
263 memcpy(nb->data, &smp_header, sizeof(smp_header));
264 nb->len = sizeof(smp_header);
265 }
266 return nb;
267 }
268
smp_client_buf_free(struct net_buf * nb)269 void smp_client_buf_free(struct net_buf *nb)
270 {
271 smp_packet_free(nb);
272 }
273
smp_read_hdr(const struct net_buf * nb,struct smp_hdr * dst_hdr)274 static void smp_read_hdr(const struct net_buf *nb, struct smp_hdr *dst_hdr)
275 {
276 memcpy(dst_hdr, nb->data, sizeof(*dst_hdr));
277 dst_hdr->nh_len = sys_be16_to_cpu(dst_hdr->nh_len);
278 dst_hdr->nh_group = sys_be16_to_cpu(dst_hdr->nh_group);
279 }
280
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)281 int smp_client_send_cmd(struct smp_client_object *smp_client, struct net_buf *nb,
282 smp_client_res_fn cb, void *user_data, int timeout_in_sec)
283 {
284 struct smp_hdr smp_header;
285 struct smp_client_cmd_req *cmd_req;
286
287 if (timeout_in_sec > 30) {
288 LOG_ERR("Command timeout can't be over 30 seconds");
289 return MGMT_ERR_EINVAL;
290 }
291
292 if (timeout_in_sec == 0) {
293 timeout_in_sec = CONFIG_SMP_CMD_DEFAULT_LIFE_TIME;
294 }
295
296 smp_read_hdr(nb, &smp_header);
297 if (nb->len < sizeof(smp_header)) {
298 return MGMT_ERR_EINVAL;
299 }
300 /* Update Length */
301 smp_header.nh_len = sys_cpu_to_be16(nb->len - sizeof(smp_header));
302 smp_header.nh_group = sys_cpu_to_be16(smp_header.nh_group),
303 memcpy(nb->data, &smp_header, sizeof(smp_header));
304
305 cmd_req = smp_client_cmd_req_allocate();
306 if (!cmd_req) {
307 return MGMT_ERR_ENOMEM;
308 }
309
310 LOG_DBG("Command send Header flags %d OP: %d group %d id %d seq %d", smp_header.nh_flags,
311 smp_header.nh_op, sys_be16_to_cpu(smp_header.nh_group), smp_header.nh_id,
312 smp_header.nh_seq);
313 cmd_req->nb = nb;
314 cmd_req->cb = cb;
315 cmd_req->smp_client = smp_client;
316 cmd_req->user_data = user_data;
317 cmd_req->retry_cnt = timeout_in_sec * (1000 / CONFIG_SMP_CMD_RETRY_TIME);
318 cmd_req->timestamp = k_uptime_get() + CONFIG_SMP_CMD_RETRY_TIME;
319 /* Increment reference for re-transmission and read smp header */
320 nb = net_buf_ref(nb);
321 smp_cmd_add_to_list(cmd_req);
322 net_buf_put(&smp_client->tx_fifo, nb);
323 smp_tx_req(&smp_client->work);
324 return MGMT_ERR_EOK;
325 }
326
327 SYS_INIT(smp_client_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
328