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