1 /*
2 * Copyright Runtime.io 2018. All rights reserved.
3 * Copyright (c) 2021-2022 Nordic Semiconductor ASA
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include <assert.h>
9 #include <zephyr/kernel.h>
10 #include <zephyr/device.h>
11 #include <zephyr/net/buf.h>
12 #include <zephyr/mgmt/mcumgr/mgmt/mgmt.h>
13 #include <zephyr/mgmt/mcumgr/smp/smp.h>
14 #include <zephyr/mgmt/mcumgr/transport/smp.h>
15
16 #include <mgmt/mcumgr/transport/smp_reassembly.h>
17
18 #include <zephyr/logging/log.h>
19 LOG_MODULE_REGISTER(mcumgr_smp, CONFIG_MCUMGR_TRANSPORT_LOG_LEVEL);
20
21 /* To be able to unit test some callers some functions need to be
22 * demoted to allow overriding them.
23 */
24 #ifdef CONFIG_ZTEST
25 #define WEAK __weak
26 #else
27 #define WEAK
28 #endif
29
30 K_THREAD_STACK_DEFINE(smp_work_queue_stack, CONFIG_MCUMGR_TRANSPORT_WORKQUEUE_STACK_SIZE);
31
32 static struct k_work_q smp_work_queue;
33
34 #ifdef CONFIG_SMP_CLIENT
35 static sys_slist_t smp_transport_clients;
36 #endif
37
38 static const struct k_work_queue_config smp_work_queue_config = {
39 .name = "mcumgr smp"
40 };
41
42 NET_BUF_POOL_DEFINE(pkt_pool, CONFIG_MCUMGR_TRANSPORT_NETBUF_COUNT,
43 CONFIG_MCUMGR_TRANSPORT_NETBUF_SIZE,
44 CONFIG_MCUMGR_TRANSPORT_NETBUF_USER_DATA_SIZE, NULL);
45
smp_packet_alloc(void)46 struct net_buf *smp_packet_alloc(void)
47 {
48 return net_buf_alloc(&pkt_pool, K_NO_WAIT);
49 }
50
smp_packet_free(struct net_buf * nb)51 void smp_packet_free(struct net_buf *nb)
52 {
53 net_buf_unref(nb);
54 }
55
56 /**
57 * @brief Allocates a response buffer.
58 *
59 * If a source buf is provided, its user data is copied into the new buffer.
60 *
61 * @param req An optional source buffer to copy user data from.
62 * @param arg The streamer providing the callback.
63 *
64 * @return Newly-allocated buffer on success
65 * NULL on failure.
66 */
smp_alloc_rsp(const void * req,void * arg)67 void *smp_alloc_rsp(const void *req, void *arg)
68 {
69 const struct net_buf *req_nb;
70 struct net_buf *rsp_nb;
71 struct smp_transport *smpt = arg;
72
73 req_nb = req;
74
75 rsp_nb = smp_packet_alloc();
76 if (rsp_nb == NULL) {
77 return NULL;
78 }
79
80 if (smpt->functions.ud_copy) {
81 smpt->functions.ud_copy(rsp_nb, req_nb);
82 } else {
83 memcpy(net_buf_user_data(rsp_nb),
84 net_buf_user_data((void *)req_nb),
85 req_nb->user_data_size);
86 }
87
88 return rsp_nb;
89 }
90
smp_free_buf(void * buf,void * arg)91 void smp_free_buf(void *buf, void *arg)
92 {
93 struct smp_transport *smpt = arg;
94
95 if (!buf) {
96 return;
97 }
98
99 if (smpt->functions.ud_free) {
100 smpt->functions.ud_free(net_buf_user_data((struct net_buf *)buf));
101 }
102
103 smp_packet_free(buf);
104 }
105
106 /**
107 * Processes a single SMP packet and sends the corresponding response(s).
108 */
109 static int
smp_process_packet(struct smp_transport * smpt,struct net_buf * nb)110 smp_process_packet(struct smp_transport *smpt, struct net_buf *nb)
111 {
112 struct cbor_nb_reader reader;
113 struct cbor_nb_writer writer;
114 struct smp_streamer streamer;
115 int rc;
116
117 streamer = (struct smp_streamer) {
118 .reader = &reader,
119 .writer = &writer,
120 .smpt = smpt,
121 };
122
123 rc = smp_process_request_packet(&streamer, nb);
124 return rc;
125 }
126
127 /**
128 * Processes all received SNP request packets.
129 */
130 static void
smp_handle_reqs(struct k_work * work)131 smp_handle_reqs(struct k_work *work)
132 {
133 struct smp_transport *smpt;
134 struct net_buf *nb;
135
136 smpt = (void *)work;
137
138 /* Read and handle received messages */
139 while ((nb = net_buf_get(&smpt->fifo, K_NO_WAIT)) != NULL) {
140 smp_process_packet(smpt, nb);
141 }
142 }
143
smp_transport_init(struct smp_transport * smpt)144 int smp_transport_init(struct smp_transport *smpt)
145 {
146 __ASSERT((smpt->functions.output != NULL),
147 "Required transport output function pointer cannot be NULL");
148
149 if (smpt->functions.output == NULL) {
150 return -EINVAL;
151 }
152
153 #ifdef CONFIG_MCUMGR_TRANSPORT_REASSEMBLY
154 smp_reassembly_init(smpt);
155 #endif
156
157 k_work_init(&smpt->work, smp_handle_reqs);
158 k_fifo_init(&smpt->fifo);
159
160 return 0;
161 }
162
163 #ifdef CONFIG_SMP_CLIENT
smp_client_transport_get(int smpt_type)164 struct smp_transport *smp_client_transport_get(int smpt_type)
165 {
166 struct smp_client_transport_entry *entry;
167
168 SYS_SLIST_FOR_EACH_CONTAINER(&smp_transport_clients, entry, node) {
169 if (entry->smpt_type == smpt_type) {
170 return entry->smpt;
171 }
172 }
173
174 return NULL;
175 }
176
smp_client_transport_register(struct smp_client_transport_entry * entry)177 void smp_client_transport_register(struct smp_client_transport_entry *entry)
178 {
179 if (smp_client_transport_get(entry->smpt_type)) {
180 /* Already in list */
181 return;
182 }
183
184 sys_slist_append(&smp_transport_clients, &entry->node);
185
186 }
187
188 #endif /* CONFIG_SMP_CLIENT */
189
190 /**
191 * @brief Enqueues an incoming SMP request packet for processing.
192 *
193 * This function always consumes the supplied net_buf.
194 *
195 * @param smpt The transport to use to send the corresponding
196 * response(s).
197 * @param nb The request packet to process.
198 */
199 WEAK void
smp_rx_req(struct smp_transport * smpt,struct net_buf * nb)200 smp_rx_req(struct smp_transport *smpt, struct net_buf *nb)
201 {
202 net_buf_put(&smpt->fifo, nb);
203 k_work_submit_to_queue(&smp_work_queue, &smpt->work);
204 }
205
206 #ifdef CONFIG_SMP_CLIENT
smp_tx_req(struct k_work * work)207 void smp_tx_req(struct k_work *work)
208 {
209 k_work_submit_to_queue(&smp_work_queue, work);
210 }
211 #endif
212
smp_rx_remove_invalid(struct smp_transport * zst,void * arg)213 void smp_rx_remove_invalid(struct smp_transport *zst, void *arg)
214 {
215 struct net_buf *nb;
216 struct k_fifo temp_fifo;
217
218 if (zst->functions.query_valid_check == NULL) {
219 /* No check check function registered, abort check */
220 return;
221 }
222
223 /* Cancel current work-queue if ongoing */
224 if (k_work_busy_get(&zst->work) & (K_WORK_RUNNING | K_WORK_QUEUED)) {
225 k_work_cancel(&zst->work);
226 }
227
228 /* Run callback function and remove all buffers that are no longer needed. Store those
229 * that are in a temporary FIFO
230 */
231 k_fifo_init(&temp_fifo);
232
233 while ((nb = net_buf_get(&zst->fifo, K_NO_WAIT)) != NULL) {
234 if (!zst->functions.query_valid_check(nb, arg)) {
235 smp_free_buf(nb, zst);
236 } else {
237 net_buf_put(&temp_fifo, nb);
238 }
239 }
240
241 /* Re-insert the remaining queued operations into the original FIFO */
242 while ((nb = net_buf_get(&temp_fifo, K_NO_WAIT)) != NULL) {
243 net_buf_put(&zst->fifo, nb);
244 }
245
246 /* If at least one entry remains, queue the workqueue for running */
247 if (!k_fifo_is_empty(&zst->fifo)) {
248 k_work_submit_to_queue(&smp_work_queue, &zst->work);
249 }
250 }
251
smp_rx_clear(struct smp_transport * zst)252 void smp_rx_clear(struct smp_transport *zst)
253 {
254 struct net_buf *nb;
255
256 /* Cancel current work-queue if ongoing */
257 if (k_work_busy_get(&zst->work) & (K_WORK_RUNNING | K_WORK_QUEUED)) {
258 k_work_cancel(&zst->work);
259 }
260
261 /* Drain the FIFO of all entries without re-adding any */
262 while ((nb = net_buf_get(&zst->fifo, K_NO_WAIT)) != NULL) {
263 smp_free_buf(nb, zst);
264 }
265 }
266
smp_init(void)267 static int smp_init(void)
268 {
269 #ifdef CONFIG_SMP_CLIENT
270 sys_slist_init(&smp_transport_clients);
271 #endif
272
273 k_work_queue_init(&smp_work_queue);
274
275 k_work_queue_start(&smp_work_queue, smp_work_queue_stack,
276 K_THREAD_STACK_SIZEOF(smp_work_queue_stack),
277 CONFIG_MCUMGR_TRANSPORT_WORKQUEUE_THREAD_PRIO, &smp_work_queue_config);
278
279 return 0;
280 }
281
282 SYS_INIT(smp_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
283