1 /** @file
2 * @brief Modem command handler for modem context driver
3 *
4 * Text-based command handler implementation for modem context driver.
5 */
6
7 /*
8 * Copyright (c) 2019-2020 Foundries.io
9 *
10 * SPDX-License-Identifier: Apache-2.0
11 */
12
13 #include <zephyr/logging/log.h>
14 LOG_MODULE_REGISTER(modem_cmd_handler, CONFIG_MODEM_LOG_LEVEL);
15
16 #include <zephyr/kernel.h>
17 #include <stddef.h>
18 #include <zephyr/net_buf.h>
19
20 #include "modem_context.h"
21 #include "modem_cmd_handler.h"
22
23
24 /*
25 * Parsing Functions
26 */
27
is_crlf(uint8_t c)28 static bool is_crlf(uint8_t c)
29 {
30 if (c == '\n' || c == '\r') {
31 return true;
32 } else {
33 return false;
34 }
35 }
36
skipcrlf(struct modem_cmd_handler_data * data)37 static void skipcrlf(struct modem_cmd_handler_data *data)
38 {
39 while (data->rx_buf && data->rx_buf->len &&
40 is_crlf(*data->rx_buf->data)) {
41 net_buf_pull_u8(data->rx_buf);
42 if (!data->rx_buf->len) {
43 data->rx_buf = net_buf_frag_del(NULL, data->rx_buf);
44 }
45 }
46 }
47
findcrlf(struct modem_cmd_handler_data * data,struct net_buf ** frag,uint16_t * offset)48 static uint16_t findcrlf(struct modem_cmd_handler_data *data,
49 struct net_buf **frag, uint16_t *offset)
50 {
51 struct net_buf *buf = data->rx_buf;
52 uint16_t len = 0U, pos = 0U;
53
54 while (buf && buf->len && !is_crlf(*(buf->data + pos))) {
55 if (pos + 1 >= buf->len) {
56 len += buf->len;
57 buf = buf->frags;
58 pos = 0U;
59 } else {
60 pos++;
61 }
62 }
63
64 if (buf && buf->len && is_crlf(*(buf->data + pos))) {
65 len += pos;
66 *offset = pos;
67 *frag = buf;
68 return len;
69 }
70
71 return 0;
72 }
73
starts_with(struct net_buf * buf,const char * str)74 static bool starts_with(struct net_buf *buf, const char *str)
75 {
76 int pos = 0;
77
78 while (buf && buf->len && *str) {
79 if (*(buf->data + pos) == *str) {
80 str++;
81 pos++;
82 if (pos >= buf->len) {
83 buf = buf->frags;
84 pos = 0;
85 }
86 } else {
87 return false;
88 }
89 }
90
91 if (*str == 0) {
92 return true;
93 }
94
95 return false;
96 }
97
98 /*
99 * Cmd Handler Functions
100 */
101
102 /* return scanned length for params */
parse_params(struct modem_cmd_handler_data * data,size_t match_len,const struct modem_cmd * cmd,uint8_t ** argv,size_t argv_len,uint16_t * argc)103 static int parse_params(struct modem_cmd_handler_data *data, size_t match_len,
104 const struct modem_cmd *cmd,
105 uint8_t **argv, size_t argv_len, uint16_t *argc)
106 {
107 int count = 0;
108 size_t delim_len, begin, end, i;
109 bool quoted = false;
110
111 if (!data || !data->match_buf || !match_len || !cmd || !argv || !argc) {
112 return -EINVAL;
113 }
114
115 begin = cmd->cmd_len;
116 end = cmd->cmd_len;
117 delim_len = strlen(cmd->delim);
118 while (end < match_len) {
119 /* Don't look for delimiters in the middle of a quoted parameter */
120 if (data->match_buf[end] == '"') {
121 quoted = !quoted;
122 }
123 if (quoted) {
124 end++;
125 continue;
126 }
127 /* Look for delimiter characters */
128 for (i = 0; i < delim_len; i++) {
129 if (data->match_buf[end] == cmd->delim[i]) {
130 /* mark a parameter beginning */
131 argv[*argc] = &data->match_buf[begin];
132 /* end parameter with NUL char */
133 data->match_buf[end] = '\0';
134 /* bump begin */
135 begin = end + 1;
136 count += 1;
137 (*argc)++;
138 break;
139 }
140 }
141
142 if (count >= cmd->arg_count_max) {
143 break;
144 }
145
146 if (*argc == argv_len) {
147 break;
148 }
149
150 end++;
151 }
152
153 /* consider the ending portion a param if end > begin */
154 if (end > begin) {
155 /* mark a parameter beginning */
156 argv[*argc] = &data->match_buf[begin];
157 /* end parameter with NUL char
158 * NOTE: if this is at the end of match_len will probably
159 * be overwriting a NUL that's already there
160 */
161 data->match_buf[end] = '\0';
162 (*argc)++;
163 }
164
165 /* missing arguments */
166 if (*argc < cmd->arg_count_min) {
167 /* Do not return -EAGAIN here as there is no way new argument
168 * can be parsed later because match_len is computed to be
169 * the minimum of the distance to the first CRLF and the size
170 * of the buffer.
171 * Therefore, waiting more data on the interface won't change
172 * match_len value, which mean there is no point in waiting
173 * for more arguments, this will just end in a infinite loop
174 * parsing data and finding that some arguments are missing.
175 */
176 return -EINVAL;
177 }
178
179 /*
180 * return the beginning of the next unfinished param so we don't
181 * "skip" any data that could be parsed later.
182 */
183 return begin - cmd->cmd_len;
184 }
185
186 /* process a "matched" command */
process_cmd(const struct modem_cmd * cmd,size_t match_len,struct modem_cmd_handler_data * data)187 static int process_cmd(const struct modem_cmd *cmd, size_t match_len,
188 struct modem_cmd_handler_data *data)
189 {
190 int parsed_len = 0, ret = 0;
191 uint8_t *argv[CONFIG_MODEM_CMD_HANDLER_MAX_PARAM_COUNT];
192 uint16_t argc = 0U;
193
194 /* reset params */
195 memset(argv, 0, sizeof(argv[0]) * ARRAY_SIZE(argv));
196
197 /* do we need to parse arguments? */
198 if (cmd->arg_count_max > 0U) {
199 /* returns < 0 on error and > 0 for parsed len */
200 parsed_len = parse_params(data, match_len, cmd,
201 argv, ARRAY_SIZE(argv), &argc);
202 if (parsed_len < 0) {
203 return parsed_len;
204 }
205 }
206
207 /* skip cmd_len + parsed len */
208 data->rx_buf = net_buf_skip(data->rx_buf, cmd->cmd_len + parsed_len);
209
210 /* call handler */
211 if (cmd->func) {
212 ret = cmd->func(data, match_len - cmd->cmd_len - parsed_len,
213 argv, argc);
214 if (ret == -EAGAIN) {
215 /* wait for more data */
216 net_buf_push(data->rx_buf, cmd->cmd_len + parsed_len);
217 }
218 }
219
220 return ret;
221 }
222
223 /*
224 * check 3 arrays of commands for a match in match_buf:
225 * - response handlers[0]
226 * - unsolicited handlers[1]
227 * - current assigned handlers[2]
228 */
find_cmd_match(struct modem_cmd_handler_data * data)229 static const struct modem_cmd *find_cmd_match(
230 struct modem_cmd_handler_data *data)
231 {
232 int j;
233 size_t i;
234
235 for (j = 0; j < ARRAY_SIZE(data->cmds); j++) {
236 if (!data->cmds[j] || data->cmds_len[j] == 0U) {
237 continue;
238 }
239
240 for (i = 0; i < data->cmds_len[j]; i++) {
241 /* match on "empty" cmd */
242 if (strlen(data->cmds[j][i].cmd) == 0 ||
243 strncmp(data->match_buf, data->cmds[j][i].cmd,
244 data->cmds[j][i].cmd_len) == 0) {
245 return &data->cmds[j][i];
246 }
247 }
248 }
249
250 return NULL;
251 }
252
find_cmd_direct_match(struct modem_cmd_handler_data * data)253 static const struct modem_cmd *find_cmd_direct_match(
254 struct modem_cmd_handler_data *data)
255 {
256 size_t j, i;
257
258 for (j = 0; j < ARRAY_SIZE(data->cmds); j++) {
259 if (!data->cmds[j] || data->cmds_len[j] == 0U) {
260 continue;
261 }
262
263 for (i = 0; i < data->cmds_len[j]; i++) {
264 /* match start of cmd */
265 if (data->cmds[j][i].direct &&
266 (data->cmds[j][i].cmd[0] == '\0' ||
267 starts_with(data->rx_buf, data->cmds[j][i].cmd))) {
268 return &data->cmds[j][i];
269 }
270 }
271 }
272
273 return NULL;
274 }
275
cmd_handler_process_iface_data(struct modem_cmd_handler_data * data,struct modem_iface * iface)276 static int cmd_handler_process_iface_data(struct modem_cmd_handler_data *data,
277 struct modem_iface *iface)
278 {
279 struct net_buf *last;
280 size_t bytes_read = 0;
281 int ret;
282
283 if (!data->rx_buf) {
284 data->rx_buf = net_buf_alloc(data->buf_pool,
285 data->alloc_timeout);
286 if (!data->rx_buf) {
287 /* there is potentially more data waiting */
288 return -ENOMEM;
289 }
290 }
291
292 last = net_buf_frag_last(data->rx_buf);
293
294 /* read all of the data from modem iface */
295 while (true) {
296 struct net_buf *frag = last;
297 size_t frag_room = net_buf_tailroom(frag);
298
299 if (!frag_room) {
300 frag = net_buf_alloc(data->buf_pool,
301 data->alloc_timeout);
302 if (!frag) {
303 /* there is potentially more data waiting */
304 return -ENOMEM;
305 }
306
307 net_buf_frag_insert(last, frag);
308 last = frag;
309
310 frag_room = net_buf_tailroom(frag);
311 }
312
313 ret = iface->read(iface, net_buf_tail(frag), frag_room,
314 &bytes_read);
315 if (ret < 0 || bytes_read == 0) {
316 /* modem context buffer is empty */
317 return 0;
318 }
319
320 net_buf_add(frag, bytes_read);
321 }
322 }
323
cmd_handler_process_rx_buf(struct modem_cmd_handler_data * data)324 static void cmd_handler_process_rx_buf(struct modem_cmd_handler_data *data)
325 {
326 const struct modem_cmd *cmd;
327 struct net_buf *frag = NULL;
328 size_t match_len;
329 int ret;
330 uint16_t offset, len;
331
332 /* process all of the data in the net_buf */
333 while (data->rx_buf && data->rx_buf->len) {
334 skipcrlf(data);
335 if (!data->rx_buf || !data->rx_buf->len) {
336 break;
337 }
338
339 cmd = find_cmd_direct_match(data);
340 if (cmd && cmd->func) {
341 ret = cmd->func(data, cmd->cmd_len, NULL, 0);
342 if (ret == -EAGAIN) {
343 /* Wait for more data */
344 break;
345 } else if (ret > 0) {
346 LOG_DBG("match direct cmd [%s] (ret:%d)",
347 cmd->cmd, ret);
348 data->rx_buf = net_buf_skip(data->rx_buf, ret);
349 }
350
351 continue;
352 }
353
354 frag = NULL;
355 /* locate next CR/LF */
356 len = findcrlf(data, &frag, &offset);
357 if (!frag) {
358 /*
359 * No CR/LF found. Let's exit and leave any data
360 * for next time
361 */
362 break;
363 }
364
365 /* load match_buf with content up to the next CR/LF */
366 /* NOTE: keep room in match_buf for ending NUL char */
367 match_len = net_buf_linearize(data->match_buf,
368 data->match_buf_len - 1,
369 data->rx_buf, 0, len);
370 if ((data->match_buf_len - 1) < match_len) {
371 LOG_ERR("Match buffer size (%zu) is too small for "
372 "incoming command size: %zu! Truncating!",
373 data->match_buf_len - 1, match_len);
374 }
375
376 #if defined(CONFIG_MODEM_CONTEXT_VERBOSE_DEBUG)
377 LOG_HEXDUMP_DBG(data->match_buf, match_len, "RECV");
378 #endif
379
380 k_sem_take(&data->sem_parse_lock, K_FOREVER);
381
382 cmd = find_cmd_match(data);
383 if (cmd) {
384 LOG_DBG("match cmd [%s] (len:%zu)",
385 cmd->cmd, match_len);
386
387 ret = process_cmd(cmd, match_len, data);
388 if (ret == -EAGAIN) {
389 k_sem_give(&data->sem_parse_lock);
390 break;
391 } else if (ret < 0) {
392 LOG_ERR("process cmd [%s] (len:%zu, ret:%d)",
393 cmd->cmd, match_len, ret);
394 }
395
396 /*
397 * make sure we didn't run out of data during
398 * command processing
399 */
400 if (!data->rx_buf) {
401 /* we're out of data, exit early */
402 k_sem_give(&data->sem_parse_lock);
403 break;
404 }
405
406 frag = NULL;
407 /*
408 * We've handled the current line.
409 * Let's skip any "extra" data in that
410 * line, and look for the next CR/LF.
411 * This leaves us ready for the next
412 * handler search.
413 * Ignore the length returned.
414 */
415 (void)findcrlf(data, &frag, &offset);
416 }
417
418 k_sem_give(&data->sem_parse_lock);
419
420 if (frag && data->rx_buf) {
421 /* clear out processed line (net_buf's) */
422 while (frag && data->rx_buf != frag) {
423 data->rx_buf = net_buf_frag_del(NULL,
424 data->rx_buf);
425 }
426
427 net_buf_pull(data->rx_buf, offset);
428 }
429 }
430 }
431
cmd_handler_process(struct modem_cmd_handler * cmd_handler,struct modem_iface * iface)432 static void cmd_handler_process(struct modem_cmd_handler *cmd_handler,
433 struct modem_iface *iface)
434 {
435 struct modem_cmd_handler_data *data;
436 int err;
437
438 if (!cmd_handler || !cmd_handler->cmd_handler_data ||
439 !iface || !iface->read) {
440 return;
441 }
442
443 data = (struct modem_cmd_handler_data *)(cmd_handler->cmd_handler_data);
444
445 do {
446 err = cmd_handler_process_iface_data(data, iface);
447 cmd_handler_process_rx_buf(data);
448 } while (err);
449 }
450
modem_cmd_handler_get_error(struct modem_cmd_handler_data * data)451 int modem_cmd_handler_get_error(struct modem_cmd_handler_data *data)
452 {
453 if (!data) {
454 return -EINVAL;
455 }
456
457 return data->last_error;
458 }
459
modem_cmd_handler_set_error(struct modem_cmd_handler_data * data,int error_code)460 int modem_cmd_handler_set_error(struct modem_cmd_handler_data *data,
461 int error_code)
462 {
463 if (!data) {
464 return -EINVAL;
465 }
466
467 data->last_error = error_code;
468 return 0;
469 }
470
modem_cmd_handler_update_cmds(struct modem_cmd_handler_data * data,const struct modem_cmd * handler_cmds,size_t handler_cmds_len,bool reset_error_flag)471 int modem_cmd_handler_update_cmds(struct modem_cmd_handler_data *data,
472 const struct modem_cmd *handler_cmds,
473 size_t handler_cmds_len,
474 bool reset_error_flag)
475 {
476 if (!data) {
477 return -EINVAL;
478 }
479
480 data->cmds[CMD_HANDLER] = handler_cmds;
481 data->cmds_len[CMD_HANDLER] = handler_cmds_len;
482 if (reset_error_flag) {
483 data->last_error = 0;
484 }
485
486 return 0;
487 }
488
modem_cmd_send_ext(struct modem_iface * iface,struct modem_cmd_handler * handler,const struct modem_cmd * handler_cmds,size_t handler_cmds_len,const uint8_t * buf,struct k_sem * sem,k_timeout_t timeout,int flags)489 int modem_cmd_send_ext(struct modem_iface *iface,
490 struct modem_cmd_handler *handler,
491 const struct modem_cmd *handler_cmds,
492 size_t handler_cmds_len, const uint8_t *buf,
493 struct k_sem *sem, k_timeout_t timeout, int flags)
494 {
495 struct modem_cmd_handler_data *data;
496 int ret = 0;
497
498 if (!iface || !handler || !handler->cmd_handler_data || !buf) {
499 return -EINVAL;
500 }
501
502 if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) {
503 /* semaphore is not needed if there is no timeout */
504 sem = NULL;
505 } else if (!sem) {
506 /* cannot respect timeout without semaphore */
507 return -EINVAL;
508 }
509
510 data = (struct modem_cmd_handler_data *)(handler->cmd_handler_data);
511 if (!(flags & MODEM_NO_TX_LOCK)) {
512 k_sem_take(&data->sem_tx_lock, K_FOREVER);
513 }
514
515 if (!(flags & MODEM_NO_SET_CMDS)) {
516 ret = modem_cmd_handler_update_cmds(data, handler_cmds,
517 handler_cmds_len, true);
518 if (ret < 0) {
519 goto unlock_tx_lock;
520 }
521 }
522
523 #if defined(CONFIG_MODEM_CONTEXT_VERBOSE_DEBUG)
524 LOG_HEXDUMP_DBG(buf, strlen(buf), "SENT DATA");
525
526 if (data->eol_len > 0) {
527 if (data->eol[0] != '\r') {
528 /* Print the EOL only if it is not \r, otherwise there
529 * is just too much printing.
530 */
531 LOG_HEXDUMP_DBG(data->eol, data->eol_len, "SENT EOL");
532 }
533 } else {
534 LOG_DBG("EOL not set!!!");
535 }
536 #endif
537 if (sem) {
538 k_sem_reset(sem);
539 }
540
541 iface->write(iface, buf, strlen(buf));
542 iface->write(iface, data->eol, data->eol_len);
543
544 if (sem) {
545 ret = k_sem_take(sem, timeout);
546
547 if (ret == 0) {
548 ret = data->last_error;
549 } else if (ret == -EAGAIN) {
550 ret = -ETIMEDOUT;
551 }
552 }
553
554 if (!(flags & MODEM_NO_UNSET_CMDS)) {
555 /* unset handlers and ignore any errors */
556 (void)modem_cmd_handler_update_cmds(data, NULL, 0U, false);
557 }
558
559 unlock_tx_lock:
560 if (!(flags & MODEM_NO_TX_LOCK)) {
561 k_sem_give(&data->sem_tx_lock);
562 }
563
564 return ret;
565 }
566
567 /* run a set of AT commands */
modem_cmd_handler_setup_cmds(struct modem_iface * iface,struct modem_cmd_handler * handler,const struct setup_cmd * cmds,size_t cmds_len,struct k_sem * sem,k_timeout_t timeout)568 int modem_cmd_handler_setup_cmds(struct modem_iface *iface,
569 struct modem_cmd_handler *handler,
570 const struct setup_cmd *cmds, size_t cmds_len,
571 struct k_sem *sem, k_timeout_t timeout)
572 {
573 int ret = 0;
574 size_t i;
575
576 for (i = 0; i < cmds_len; i++) {
577
578 if (cmds[i].handle_cmd.cmd && cmds[i].handle_cmd.func) {
579 ret = modem_cmd_send(iface, handler,
580 &cmds[i].handle_cmd, 1U,
581 cmds[i].send_cmd,
582 sem, timeout);
583 } else {
584 ret = modem_cmd_send(iface, handler,
585 NULL, 0, cmds[i].send_cmd,
586 sem, timeout);
587 }
588
589 k_sleep(K_MSEC(50));
590
591 if (ret < 0) {
592 LOG_ERR("command %s ret:%d",
593 cmds[i].send_cmd, ret);
594 break;
595 }
596 }
597
598 return ret;
599 }
600
601 /* run a set of AT commands, without lock */
modem_cmd_handler_setup_cmds_nolock(struct modem_iface * iface,struct modem_cmd_handler * handler,const struct setup_cmd * cmds,size_t cmds_len,struct k_sem * sem,k_timeout_t timeout)602 int modem_cmd_handler_setup_cmds_nolock(struct modem_iface *iface,
603 struct modem_cmd_handler *handler,
604 const struct setup_cmd *cmds,
605 size_t cmds_len, struct k_sem *sem,
606 k_timeout_t timeout)
607 {
608 int ret = 0;
609 size_t i;
610
611 for (i = 0; i < cmds_len; i++) {
612
613 if (cmds[i].handle_cmd.cmd && cmds[i].handle_cmd.func) {
614 ret = modem_cmd_send_nolock(iface, handler,
615 &cmds[i].handle_cmd, 1U,
616 cmds[i].send_cmd,
617 sem, timeout);
618 } else {
619 ret = modem_cmd_send_nolock(iface, handler,
620 NULL, 0, cmds[i].send_cmd,
621 sem, timeout);
622 }
623
624 k_sleep(K_MSEC(50));
625
626 if (ret < 0) {
627 LOG_ERR("command %s ret:%d",
628 cmds[i].send_cmd, ret);
629 break;
630 }
631 }
632
633 return ret;
634 }
635
modem_cmd_handler_tx_lock(struct modem_cmd_handler * handler,k_timeout_t timeout)636 int modem_cmd_handler_tx_lock(struct modem_cmd_handler *handler,
637 k_timeout_t timeout)
638 {
639 struct modem_cmd_handler_data *data;
640 data = (struct modem_cmd_handler_data *)(handler->cmd_handler_data);
641
642 return k_sem_take(&data->sem_tx_lock, timeout);
643 }
644
modem_cmd_handler_tx_unlock(struct modem_cmd_handler * handler)645 void modem_cmd_handler_tx_unlock(struct modem_cmd_handler *handler)
646 {
647 struct modem_cmd_handler_data *data;
648 data = (struct modem_cmd_handler_data *)(handler->cmd_handler_data);
649
650 k_sem_give(&data->sem_tx_lock);
651 }
652
modem_cmd_handler_init(struct modem_cmd_handler * handler,struct modem_cmd_handler_data * data,const struct modem_cmd_handler_config * config)653 int modem_cmd_handler_init(struct modem_cmd_handler *handler,
654 struct modem_cmd_handler_data *data,
655 const struct modem_cmd_handler_config *config)
656 {
657 /* Verify arguments */
658 if (handler == NULL || data == NULL || config == NULL) {
659 return -EINVAL;
660 }
661
662 /* Verify config */
663 if ((config->match_buf == NULL) ||
664 (config->match_buf_len == 0) ||
665 (config->buf_pool == NULL) ||
666 (NULL != config->response_cmds && 0 == config->response_cmds_len) ||
667 (NULL != config->unsol_cmds && 0 == config->unsol_cmds_len)) {
668 return -EINVAL;
669 }
670
671 /* Assign data to command handler */
672 handler->cmd_handler_data = data;
673
674 /* Assign command process implementation to command handler */
675 handler->process = cmd_handler_process;
676
677 /* Store arguments */
678 data->match_buf = config->match_buf;
679 data->match_buf_len = config->match_buf_len;
680 data->buf_pool = config->buf_pool;
681 data->alloc_timeout = config->alloc_timeout;
682 data->eol = config->eol;
683 data->cmds[CMD_RESP] = config->response_cmds;
684 data->cmds_len[CMD_RESP] = config->response_cmds_len;
685 data->cmds[CMD_UNSOL] = config->unsol_cmds;
686 data->cmds_len[CMD_UNSOL] = config->unsol_cmds_len;
687
688 /* Process end of line */
689 data->eol_len = data->eol == NULL ? 0 : strlen(data->eol);
690
691 /* Store optional user data */
692 data->user_data = config->user_data;
693
694 /* Initialize command handler data members */
695 k_sem_init(&data->sem_tx_lock, 1, 1);
696 k_sem_init(&data->sem_parse_lock, 1, 1);
697
698 return 0;
699 }
700