1 /*
2 * Copyright (c) 2022 Trackunit Corporation
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/logging/log.h>
8 LOG_MODULE_REGISTER(modem_chat, CONFIG_MODEM_MODULES_LOG_LEVEL);
9
10 #include <zephyr/kernel.h>
11 #include <string.h>
12
13 #include <zephyr/modem/chat.h>
14
15 #define MODEM_CHAT_MATCHES_INDEX_RESPONSE (0)
16 #define MODEM_CHAT_MATCHES_INDEX_ABORT (1)
17 #define MODEM_CHAT_MATCHES_INDEX_UNSOL (2)
18
19 #define MODEM_CHAT_SCRIPT_STATE_RUNNING_BIT (0)
20
21 #if defined(CONFIG_LOG) && (CONFIG_MODEM_MODULES_LOG_LEVEL == LOG_LEVEL_DBG)
22
23 static char log_buffer[CONFIG_MODEM_CHAT_LOG_BUFFER];
24
modem_chat_log_received_command(struct modem_chat * chat)25 static void modem_chat_log_received_command(struct modem_chat *chat)
26 {
27 uint16_t log_buffer_pos = 0;
28 uint16_t argv_len;
29
30 for (uint16_t i = 0; i < chat->argc; i++) {
31 argv_len = (uint16_t)strlen(chat->argv[i]);
32
33 /* Validate argument fits in log buffer including termination */
34 if (sizeof(log_buffer) < (log_buffer_pos + argv_len + 1)) {
35 LOG_WRN("log buffer overrun");
36 break;
37 }
38
39 /* Copy argument and append space */
40 memcpy(&log_buffer[log_buffer_pos], chat->argv[i], argv_len);
41 log_buffer_pos += argv_len;
42 log_buffer[log_buffer_pos] = ' ';
43 log_buffer_pos++;
44 }
45
46 /* Terminate line after last argument, overwriting trailing space */
47 log_buffer_pos = log_buffer_pos == 0 ? log_buffer_pos : log_buffer_pos - 1;
48 log_buffer[log_buffer_pos] = '\0';
49
50 LOG_DBG("%s", log_buffer);
51 }
52
53 #else
54
modem_chat_log_received_command(struct modem_chat * chat)55 static void modem_chat_log_received_command(struct modem_chat *chat)
56 {
57 }
58
59 #endif
60
modem_chat_script_stop(struct modem_chat * chat,enum modem_chat_script_result result)61 static void modem_chat_script_stop(struct modem_chat *chat, enum modem_chat_script_result result)
62 {
63 if ((chat == NULL) || (chat->script == NULL)) {
64 return;
65 }
66
67 /* Handle result */
68 if (result == MODEM_CHAT_SCRIPT_RESULT_SUCCESS) {
69 LOG_DBG("%s: complete", chat->script->name);
70 } else if (result == MODEM_CHAT_SCRIPT_RESULT_ABORT) {
71 LOG_WRN("%s: aborted", chat->script->name);
72 } else {
73 LOG_WRN("%s: timed out", chat->script->name);
74 }
75
76 /* Call back with result */
77 if (chat->script->callback != NULL) {
78 chat->script->callback(chat, result, chat->user_data);
79 }
80
81 /* Clear parse_match in case it is stored in the script being stopped */
82 if ((chat->parse_match != NULL) &&
83 ((chat->parse_match_type == MODEM_CHAT_MATCHES_INDEX_ABORT) ||
84 (chat->parse_match_type == MODEM_CHAT_MATCHES_INDEX_RESPONSE))) {
85 chat->parse_match = NULL;
86 chat->parse_match_len = 0;
87 }
88
89 /* Clear reference to script */
90 chat->script = NULL;
91
92 /* Clear response and abort commands */
93 chat->matches[MODEM_CHAT_MATCHES_INDEX_ABORT] = NULL;
94 chat->matches_size[MODEM_CHAT_MATCHES_INDEX_ABORT] = 0;
95 chat->matches[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = NULL;
96 chat->matches_size[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = 0;
97
98 /* Cancel work */
99 k_work_cancel_delayable(&chat->script_timeout_work);
100 k_work_cancel(&chat->script_send_work);
101 k_work_cancel_delayable(&chat->script_send_timeout_work);
102
103 /* Clear script running state */
104 atomic_clear_bit(&chat->script_state, MODEM_CHAT_SCRIPT_STATE_RUNNING_BIT);
105
106 /* Store result of script for script stoppted indication */
107 chat->script_result = result;
108
109 /* Indicate script stopped */
110 k_sem_give(&chat->script_stopped_sem);
111 }
112
modem_chat_set_script_send_state(struct modem_chat * chat,enum modem_chat_script_send_state state)113 static void modem_chat_set_script_send_state(struct modem_chat *chat,
114 enum modem_chat_script_send_state state)
115 {
116 chat->script_send_pos = 0;
117 chat->script_send_state = state;
118 }
119
modem_chat_script_send(struct modem_chat * chat)120 static void modem_chat_script_send(struct modem_chat *chat)
121 {
122 modem_chat_set_script_send_state(chat, MODEM_CHAT_SCRIPT_SEND_STATE_REQUEST);
123 k_work_submit(&chat->script_send_work);
124 }
125
modem_chat_script_set_response_matches(struct modem_chat * chat)126 static void modem_chat_script_set_response_matches(struct modem_chat *chat)
127 {
128 const struct modem_chat_script_chat *script_chat =
129 &chat->script->script_chats[chat->script_chat_it];
130
131 chat->matches[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = script_chat->response_matches;
132 chat->matches_size[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = script_chat->response_matches_size;
133 }
134
modem_chat_script_clear_response_matches(struct modem_chat * chat)135 static void modem_chat_script_clear_response_matches(struct modem_chat *chat)
136 {
137 chat->matches[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = NULL;
138 chat->matches_size[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = 0;
139 }
140
modem_chat_script_next(struct modem_chat * chat,bool initial)141 static void modem_chat_script_next(struct modem_chat *chat, bool initial)
142 {
143 const struct modem_chat_script_chat *script_chat;
144
145 /* Advance iterator if not initial */
146 if (initial == true) {
147 /* Reset iterator */
148 chat->script_chat_it = 0;
149 } else {
150 /* Advance iterator */
151 chat->script_chat_it++;
152 }
153
154 /* Check if end of script reached */
155 if (chat->script_chat_it == chat->script->script_chats_size) {
156 modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_SUCCESS);
157
158 return;
159 }
160
161 LOG_DBG("%s: step: %u", chat->script->name, chat->script_chat_it);
162
163 script_chat = &chat->script->script_chats[chat->script_chat_it];
164
165 /* Check if request must be sent */
166 if (script_chat->request_size > 0) {
167 LOG_DBG("sending: %.*s", script_chat->request_size, script_chat->request);
168 modem_chat_script_clear_response_matches(chat);
169 modem_chat_script_send(chat);
170 } else {
171 modem_chat_script_set_response_matches(chat);
172 }
173 }
174
modem_chat_script_start(struct modem_chat * chat,const struct modem_chat_script * script)175 static void modem_chat_script_start(struct modem_chat *chat, const struct modem_chat_script *script)
176 {
177 /* Save script */
178 chat->script = script;
179
180 /* Set abort matches */
181 chat->matches[MODEM_CHAT_MATCHES_INDEX_ABORT] = script->abort_matches;
182 chat->matches_size[MODEM_CHAT_MATCHES_INDEX_ABORT] = script->abort_matches_size;
183
184 LOG_DBG("running script: %s", chat->script->name);
185
186 /* Set first script command */
187 modem_chat_script_next(chat, true);
188
189 /* Start timeout work if script started */
190 if (chat->script != NULL) {
191 k_work_schedule(&chat->script_timeout_work, K_SECONDS(chat->script->timeout));
192 }
193 }
194
modem_chat_script_run_handler(struct k_work * item)195 static void modem_chat_script_run_handler(struct k_work *item)
196 {
197 struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, script_run_work);
198
199 /* Start script */
200 modem_chat_script_start(chat, chat->pending_script);
201 }
202
modem_chat_script_timeout_handler(struct k_work * item)203 static void modem_chat_script_timeout_handler(struct k_work *item)
204 {
205 struct k_work_delayable *dwork = k_work_delayable_from_work(item);
206 struct modem_chat *chat = CONTAINER_OF(dwork, struct modem_chat, script_timeout_work);
207
208 /* Abort script */
209 modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_TIMEOUT);
210 }
211
modem_chat_script_abort_handler(struct k_work * item)212 static void modem_chat_script_abort_handler(struct k_work *item)
213 {
214 struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, script_abort_work);
215
216 /* Validate script is currently running */
217 if (chat->script == NULL) {
218 return;
219 }
220
221 /* Abort script */
222 modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_ABORT);
223 }
224
modem_chat_script_chat_is_no_response(struct modem_chat * chat)225 static bool modem_chat_script_chat_is_no_response(struct modem_chat *chat)
226 {
227 const struct modem_chat_script_chat *script_chat =
228 &chat->script->script_chats[chat->script_chat_it];
229
230 return (script_chat->response_matches_size == 0) ? true : false;
231 }
232
modem_chat_script_chat_get_send_timeout(struct modem_chat * chat)233 static uint16_t modem_chat_script_chat_get_send_timeout(struct modem_chat *chat)
234 {
235 const struct modem_chat_script_chat *script_chat =
236 &chat->script->script_chats[chat->script_chat_it];
237
238 return script_chat->timeout;
239 }
240
241 /* Returns true when request part has been sent */
modem_chat_send_script_request_part(struct modem_chat * chat)242 static bool modem_chat_send_script_request_part(struct modem_chat *chat)
243 {
244 const struct modem_chat_script_chat *script_chat =
245 &chat->script->script_chats[chat->script_chat_it];
246
247 uint8_t *request_part;
248 uint16_t request_size;
249 uint16_t request_part_size;
250 int ret;
251
252 switch (chat->script_send_state) {
253 case MODEM_CHAT_SCRIPT_SEND_STATE_REQUEST:
254 request_part = (uint8_t *)(&script_chat->request[chat->script_send_pos]);
255 request_size = script_chat->request_size;
256 break;
257
258 case MODEM_CHAT_SCRIPT_SEND_STATE_DELIMITER:
259 request_part = (uint8_t *)(&chat->delimiter[chat->script_send_pos]);
260 request_size = chat->delimiter_size;
261 break;
262
263 default:
264 return false;
265 }
266
267 request_part_size = request_size - chat->script_send_pos;
268 ret = modem_pipe_transmit(chat->pipe, request_part, request_part_size);
269 if (ret < 1) {
270 return false;
271 }
272
273 chat->script_send_pos += (uint16_t)ret;
274
275 /* Return true if all data was sent */
276 return request_size <= chat->script_send_pos;
277 }
278
modem_chat_script_send_handler(struct k_work * item)279 static void modem_chat_script_send_handler(struct k_work *item)
280 {
281 struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, script_send_work);
282 uint16_t timeout;
283
284 if (chat->script == NULL) {
285 return;
286 }
287
288 switch (chat->script_send_state) {
289 case MODEM_CHAT_SCRIPT_SEND_STATE_IDLE:
290 return;
291
292 case MODEM_CHAT_SCRIPT_SEND_STATE_REQUEST:
293 if (!modem_chat_send_script_request_part(chat)) {
294 return;
295 }
296
297 modem_chat_set_script_send_state(chat, MODEM_CHAT_SCRIPT_SEND_STATE_DELIMITER);
298 __fallthrough;
299
300 case MODEM_CHAT_SCRIPT_SEND_STATE_DELIMITER:
301 if (!modem_chat_send_script_request_part(chat)) {
302 return;
303 }
304
305 modem_chat_set_script_send_state(chat, MODEM_CHAT_SCRIPT_SEND_STATE_IDLE);
306 break;
307 }
308
309 if (modem_chat_script_chat_is_no_response(chat)) {
310 timeout = modem_chat_script_chat_get_send_timeout(chat);
311 if (timeout == 0) {
312 modem_chat_script_next(chat, false);
313 } else {
314 k_work_schedule(&chat->script_send_timeout_work, K_MSEC(timeout));
315 }
316 } else {
317 modem_chat_script_set_response_matches(chat);
318 }
319 }
320
modem_chat_script_send_timeout_handler(struct k_work * item)321 static void modem_chat_script_send_timeout_handler(struct k_work *item)
322 {
323 struct k_work_delayable *dwork = k_work_delayable_from_work(item);
324 struct modem_chat *chat = CONTAINER_OF(dwork, struct modem_chat, script_send_timeout_work);
325
326 /* Validate script is currently running */
327 if (chat->script == NULL) {
328 return;
329 }
330
331 modem_chat_script_next(chat, false);
332 }
333
modem_chat_parse_reset(struct modem_chat * chat)334 static void modem_chat_parse_reset(struct modem_chat *chat)
335 {
336 /* Reset parameters used for parsing */
337 chat->receive_buf_len = 0;
338 chat->delimiter_match_len = 0;
339 chat->argc = 0;
340 chat->parse_match = NULL;
341 }
342
343 /* Exact match is stored at end of receive buffer */
modem_chat_parse_save_match(struct modem_chat * chat)344 static void modem_chat_parse_save_match(struct modem_chat *chat)
345 {
346 uint8_t *argv;
347
348 /* Store length of match including NULL to avoid overwriting it if buffer overruns */
349 chat->parse_match_len = chat->receive_buf_len + 1;
350
351 /* Copy match to end of receive buffer */
352 argv = &chat->receive_buf[chat->receive_buf_size - chat->parse_match_len];
353
354 /* Copy match to end of receive buffer (excluding NULL) */
355 memcpy(argv, &chat->receive_buf[0], chat->parse_match_len - 1);
356
357 /* Save match */
358 chat->argv[chat->argc] = argv;
359
360 /* Terminate match */
361 chat->receive_buf[chat->receive_buf_size - 1] = '\0';
362
363 /* Increment argument count */
364 chat->argc++;
365 }
366
modem_chat_match_matches_received(struct modem_chat * chat,const struct modem_chat_match * match)367 static bool modem_chat_match_matches_received(struct modem_chat *chat,
368 const struct modem_chat_match *match)
369 {
370 for (uint16_t i = 0; i < match->match_size; i++) {
371 if ((match->match[i] == chat->receive_buf[i]) ||
372 (match->wildcards == true && match->match[i] == '?')) {
373 continue;
374 }
375
376 return false;
377 }
378
379 return true;
380 }
381
modem_chat_parse_find_match(struct modem_chat * chat)382 static bool modem_chat_parse_find_match(struct modem_chat *chat)
383 {
384 /* Find in all matches types */
385 for (uint16_t i = 0; i < ARRAY_SIZE(chat->matches); i++) {
386 /* Find in all matches of matches type */
387 for (uint16_t u = 0; u < chat->matches_size[i]; u++) {
388 /* Validate match size matches received data length */
389 if (chat->matches[i][u].match_size != chat->receive_buf_len) {
390 continue;
391 }
392
393 /* Validate match */
394 if (modem_chat_match_matches_received(chat, &chat->matches[i][u]) ==
395 false) {
396 continue;
397 }
398
399 /* Complete match found */
400 chat->parse_match = &chat->matches[i][u];
401 chat->parse_match_type = i;
402 return true;
403 }
404 }
405
406 return false;
407 }
408
modem_chat_parse_is_separator(struct modem_chat * chat)409 static bool modem_chat_parse_is_separator(struct modem_chat *chat)
410 {
411 for (uint16_t i = 0; i < chat->parse_match->separators_size; i++) {
412 if ((chat->parse_match->separators[i]) ==
413 (chat->receive_buf[chat->receive_buf_len - 1])) {
414 return true;
415 }
416 }
417
418 return false;
419 }
420
modem_chat_parse_end_del_start(struct modem_chat * chat)421 static bool modem_chat_parse_end_del_start(struct modem_chat *chat)
422 {
423 for (uint8_t i = 0; i < chat->delimiter_size; i++) {
424 if (chat->receive_buf[chat->receive_buf_len - 1] == chat->delimiter[i]) {
425 return true;
426 }
427 }
428
429 return false;
430 }
431
modem_chat_parse_end_del_complete(struct modem_chat * chat)432 static bool modem_chat_parse_end_del_complete(struct modem_chat *chat)
433 {
434 /* Validate length of end delimiter */
435 if (chat->receive_buf_len < chat->delimiter_size) {
436 return false;
437 }
438
439 /* Compare end delimiter with receive buffer content */
440 return (memcmp(&chat->receive_buf[chat->receive_buf_len - chat->delimiter_size],
441 chat->delimiter, chat->delimiter_size) == 0)
442 ? true
443 : false;
444 }
445
modem_chat_on_command_received_unsol(struct modem_chat * chat)446 static void modem_chat_on_command_received_unsol(struct modem_chat *chat)
447 {
448 /* Callback */
449 if (chat->parse_match->callback != NULL) {
450 chat->parse_match->callback(chat, (char **)chat->argv, chat->argc, chat->user_data);
451 }
452 }
453
modem_chat_on_command_received_abort(struct modem_chat * chat)454 static void modem_chat_on_command_received_abort(struct modem_chat *chat)
455 {
456 /* Callback */
457 if (chat->parse_match->callback != NULL) {
458 chat->parse_match->callback(chat, (char **)chat->argv, chat->argc, chat->user_data);
459 }
460
461 /* Abort script */
462 modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_ABORT);
463 }
464
modem_chat_on_command_received_resp(struct modem_chat * chat)465 static void modem_chat_on_command_received_resp(struct modem_chat *chat)
466 {
467 /* Callback */
468 if (chat->parse_match->callback != NULL) {
469 chat->parse_match->callback(chat, (char **)chat->argv, chat->argc, chat->user_data);
470 }
471
472 /* Validate response command is not partial */
473 if (chat->parse_match->partial) {
474 return;
475 }
476
477 /* Advance script */
478 modem_chat_script_next(chat, false);
479 }
480
modem_chat_parse_find_catch_all_match(struct modem_chat * chat)481 static bool modem_chat_parse_find_catch_all_match(struct modem_chat *chat)
482 {
483 /* Find in all matches types */
484 for (uint16_t i = 0; i < ARRAY_SIZE(chat->matches); i++) {
485 /* Find in all matches of matches type */
486 for (uint16_t u = 0; u < chat->matches_size[i]; u++) {
487 /* Validate match config is matching previous bytes */
488 if (chat->matches[i][u].match_size == 0) {
489 chat->parse_match = &chat->matches[i][u];
490 chat->parse_match_type = i;
491 return true;
492 }
493 }
494 }
495
496 return false;
497 }
498
modem_chat_on_command_received(struct modem_chat * chat)499 static void modem_chat_on_command_received(struct modem_chat *chat)
500 {
501 modem_chat_log_received_command(chat);
502
503 switch (chat->parse_match_type) {
504 case MODEM_CHAT_MATCHES_INDEX_UNSOL:
505 modem_chat_on_command_received_unsol(chat);
506 break;
507
508 case MODEM_CHAT_MATCHES_INDEX_ABORT:
509 modem_chat_on_command_received_abort(chat);
510 break;
511
512 case MODEM_CHAT_MATCHES_INDEX_RESPONSE:
513 modem_chat_on_command_received_resp(chat);
514 break;
515 }
516 }
517
modem_chat_on_unknown_command_received(struct modem_chat * chat)518 static void modem_chat_on_unknown_command_received(struct modem_chat *chat)
519 {
520 /* Terminate received command */
521 chat->receive_buf[chat->receive_buf_len - chat->delimiter_size] = '\0';
522
523 /* Try to find catch all match */
524 if (modem_chat_parse_find_catch_all_match(chat) == false) {
525 LOG_DBG("%s", chat->receive_buf);
526 return;
527 }
528
529 /* Parse command */
530 chat->argv[0] = "";
531 chat->argv[1] = chat->receive_buf;
532 chat->argc = 2;
533
534 modem_chat_on_command_received(chat);
535 }
536
modem_chat_process_byte(struct modem_chat * chat,uint8_t byte)537 static void modem_chat_process_byte(struct modem_chat *chat, uint8_t byte)
538 {
539 /* Validate receive buffer not overrun */
540 if (chat->receive_buf_size == chat->receive_buf_len) {
541 LOG_WRN("receive buffer overrun");
542 modem_chat_parse_reset(chat);
543 return;
544 }
545
546 /* Validate argv buffer not overrun */
547 if (chat->argc == chat->argv_size) {
548 LOG_WRN("argv buffer overrun");
549 modem_chat_parse_reset(chat);
550 return;
551 }
552
553 /* Copy byte to receive buffer */
554 chat->receive_buf[chat->receive_buf_len] = byte;
555 chat->receive_buf_len++;
556
557 /* Validate end delimiter not complete */
558 if (modem_chat_parse_end_del_complete(chat) == true) {
559 /* Filter out empty lines */
560 if (chat->receive_buf_len == chat->delimiter_size) {
561 /* Reset parser */
562 modem_chat_parse_reset(chat);
563 return;
564 }
565
566 /* Check if match exists */
567 if (chat->parse_match == NULL) {
568 /* Handle unknown command */
569 modem_chat_on_unknown_command_received(chat);
570
571 /* Reset parser */
572 modem_chat_parse_reset(chat);
573 return;
574 }
575
576 /* Check if trailing argument exists */
577 if (chat->parse_arg_len > 0) {
578 chat->argv[chat->argc] =
579 &chat->receive_buf[chat->receive_buf_len - chat->delimiter_size -
580 chat->parse_arg_len];
581 chat->receive_buf[chat->receive_buf_len - chat->delimiter_size] = '\0';
582 chat->argc++;
583 }
584
585 /* Handle received command */
586 modem_chat_on_command_received(chat);
587
588 /* Reset parser */
589 modem_chat_parse_reset(chat);
590 return;
591 }
592
593 /* Validate end delimiter not started */
594 if (modem_chat_parse_end_del_start(chat) == true) {
595 return;
596 }
597
598 /* Find matching command if missing */
599 if (chat->parse_match == NULL) {
600 /* Find matching command */
601 if (modem_chat_parse_find_match(chat) == false) {
602 return;
603 }
604
605 /* Save match */
606 modem_chat_parse_save_match(chat);
607
608 /* Prepare argument parser */
609 chat->parse_arg_len = 0;
610 return;
611 }
612
613 /* Check if separator reached */
614 if (modem_chat_parse_is_separator(chat) == true) {
615 /* Check if argument is empty */
616 if (chat->parse_arg_len == 0) {
617 /* Save empty argument */
618 chat->argv[chat->argc] = "";
619 } else {
620 /* Save pointer to start of argument */
621 chat->argv[chat->argc] =
622 &chat->receive_buf[chat->receive_buf_len - chat->parse_arg_len - 1];
623
624 /* Replace separator with string terminator */
625 chat->receive_buf[chat->receive_buf_len - 1] = '\0';
626 }
627
628 /* Increment argument count */
629 chat->argc++;
630
631 /* Reset parse argument length */
632 chat->parse_arg_len = 0;
633 return;
634 }
635
636 /* Increment argument length */
637 chat->parse_arg_len++;
638 }
639
modem_chat_discard_byte(struct modem_chat * chat,uint8_t byte)640 static bool modem_chat_discard_byte(struct modem_chat *chat, uint8_t byte)
641 {
642 for (uint8_t i = 0; i < chat->filter_size; i++) {
643 if (byte == chat->filter[i]) {
644 return true;
645 }
646 }
647
648 return false;
649 }
650
651 /* Process chunk of received bytes */
modem_chat_process_bytes(struct modem_chat * chat)652 static void modem_chat_process_bytes(struct modem_chat *chat)
653 {
654 for (uint16_t i = 0; i < chat->work_buf_len; i++) {
655 if (modem_chat_discard_byte(chat, chat->work_buf[i])) {
656 continue;
657 }
658
659 modem_chat_process_byte(chat, chat->work_buf[i]);
660 }
661 }
662
modem_chat_process_handler(struct k_work * item)663 static void modem_chat_process_handler(struct k_work *item)
664 {
665 struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, receive_work);
666 int ret;
667
668 /* Fill work buffer */
669 ret = modem_pipe_receive(chat->pipe, chat->work_buf, sizeof(chat->work_buf));
670 if (ret < 1) {
671 return;
672 }
673
674 /* Save received data length */
675 chat->work_buf_len = (size_t)ret;
676
677 /* Process data */
678 modem_chat_process_bytes(chat);
679 k_work_submit(&chat->receive_work);
680 }
681
modem_chat_pipe_callback(struct modem_pipe * pipe,enum modem_pipe_event event,void * user_data)682 static void modem_chat_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_event event,
683 void *user_data)
684 {
685 struct modem_chat *chat = (struct modem_chat *)user_data;
686
687 switch (event) {
688 case MODEM_PIPE_EVENT_RECEIVE_READY:
689 k_work_submit(&chat->receive_work);
690 break;
691
692 case MODEM_PIPE_EVENT_TRANSMIT_IDLE:
693 k_work_submit(&chat->script_send_work);
694 break;
695
696 default:
697 break;
698 }
699 }
700
modem_chat_init(struct modem_chat * chat,const struct modem_chat_config * config)701 int modem_chat_init(struct modem_chat *chat, const struct modem_chat_config *config)
702 {
703 __ASSERT_NO_MSG(chat != NULL);
704 __ASSERT_NO_MSG(config != NULL);
705 __ASSERT_NO_MSG(config->receive_buf != NULL);
706 __ASSERT_NO_MSG(config->receive_buf_size > 0);
707 __ASSERT_NO_MSG(config->argv != NULL);
708 __ASSERT_NO_MSG(config->argv_size > 0);
709 __ASSERT_NO_MSG(config->delimiter != NULL);
710 __ASSERT_NO_MSG(config->delimiter_size > 0);
711 __ASSERT_NO_MSG(!((config->filter == NULL) && (config->filter > 0)));
712 __ASSERT_NO_MSG(!((config->unsol_matches == NULL) && (config->unsol_matches_size > 0)));
713
714 memset(chat, 0x00, sizeof(*chat));
715 chat->pipe = NULL;
716 chat->user_data = config->user_data;
717 chat->receive_buf = config->receive_buf;
718 chat->receive_buf_size = config->receive_buf_size;
719 chat->argv = config->argv;
720 chat->argv_size = config->argv_size;
721 chat->delimiter = config->delimiter;
722 chat->delimiter_size = config->delimiter_size;
723 chat->filter = config->filter;
724 chat->filter_size = config->filter_size;
725 chat->matches[MODEM_CHAT_MATCHES_INDEX_UNSOL] = config->unsol_matches;
726 chat->matches_size[MODEM_CHAT_MATCHES_INDEX_UNSOL] = config->unsol_matches_size;
727 atomic_set(&chat->script_state, 0);
728 k_sem_init(&chat->script_stopped_sem, 0, 1);
729 k_work_init(&chat->receive_work, modem_chat_process_handler);
730 k_work_init(&chat->script_run_work, modem_chat_script_run_handler);
731 k_work_init_delayable(&chat->script_timeout_work, modem_chat_script_timeout_handler);
732 k_work_init(&chat->script_abort_work, modem_chat_script_abort_handler);
733 k_work_init(&chat->script_send_work, modem_chat_script_send_handler);
734 k_work_init_delayable(&chat->script_send_timeout_work,
735 modem_chat_script_send_timeout_handler);
736
737 return 0;
738 }
739
modem_chat_attach(struct modem_chat * chat,struct modem_pipe * pipe)740 int modem_chat_attach(struct modem_chat *chat, struct modem_pipe *pipe)
741 {
742 chat->pipe = pipe;
743 modem_chat_parse_reset(chat);
744 modem_pipe_attach(chat->pipe, modem_chat_pipe_callback, chat);
745 return 0;
746 }
747
modem_chat_run_script_async(struct modem_chat * chat,const struct modem_chat_script * script)748 int modem_chat_run_script_async(struct modem_chat *chat, const struct modem_chat_script *script)
749 {
750 bool script_is_running;
751
752 if (chat->pipe == NULL) {
753 return -EPERM;
754 }
755
756 /* Validate script */
757 if ((script->script_chats == NULL) || (script->script_chats_size == 0) ||
758 ((script->abort_matches != NULL) && (script->abort_matches_size == 0))) {
759 return -EINVAL;
760 }
761
762 /* Validate script commands */
763 for (uint16_t i = 0; i < script->script_chats_size; i++) {
764 if ((script->script_chats[i].request_size == 0) &&
765 (script->script_chats[i].response_matches_size == 0)) {
766 return -EINVAL;
767 }
768 }
769
770 script_is_running =
771 atomic_test_and_set_bit(&chat->script_state, MODEM_CHAT_SCRIPT_STATE_RUNNING_BIT);
772
773 if (script_is_running == true) {
774 return -EBUSY;
775 }
776
777 chat->pending_script = script;
778 k_work_submit(&chat->script_run_work);
779 return 0;
780 }
781
modem_chat_run_script(struct modem_chat * chat,const struct modem_chat_script * script)782 int modem_chat_run_script(struct modem_chat *chat, const struct modem_chat_script *script)
783 {
784 int ret;
785
786 k_sem_reset(&chat->script_stopped_sem);
787
788 ret = modem_chat_run_script_async(chat, script);
789 if (ret < 0) {
790 return ret;
791 }
792
793 ret = k_sem_take(&chat->script_stopped_sem, K_FOREVER);
794 if (ret < 0) {
795 return ret;
796 }
797
798 return chat->script_result == MODEM_CHAT_SCRIPT_RESULT_SUCCESS ? 0 : -EAGAIN;
799 }
800
modem_chat_script_abort(struct modem_chat * chat)801 void modem_chat_script_abort(struct modem_chat *chat)
802 {
803 k_work_submit(&chat->script_abort_work);
804 }
805
modem_chat_release(struct modem_chat * chat)806 void modem_chat_release(struct modem_chat *chat)
807 {
808 struct k_work_sync sync;
809
810 if (chat->pipe) {
811 modem_pipe_release(chat->pipe);
812 }
813
814 k_work_cancel_sync(&chat->script_run_work, &sync);
815 k_work_cancel_sync(&chat->script_abort_work, &sync);
816 k_work_cancel_sync(&chat->receive_work, &sync);
817 k_work_cancel_sync(&chat->script_send_work, &sync);
818
819 chat->pipe = NULL;
820 chat->receive_buf_len = 0;
821 chat->work_buf_len = 0;
822 chat->argc = 0;
823 chat->script = NULL;
824 chat->script_chat_it = 0;
825 atomic_set(&chat->script_state, 0);
826 chat->script_result = MODEM_CHAT_SCRIPT_RESULT_ABORT;
827 k_sem_reset(&chat->script_stopped_sem);
828 chat->script_send_state = MODEM_CHAT_SCRIPT_SEND_STATE_IDLE;
829 chat->script_send_pos = 0;
830 chat->parse_match = NULL;
831 chat->parse_match_len = 0;
832 chat->parse_arg_len = 0;
833 chat->matches[MODEM_CHAT_MATCHES_INDEX_ABORT] = NULL;
834 chat->matches_size[MODEM_CHAT_MATCHES_INDEX_ABORT] = 0;
835 chat->matches[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = NULL;
836 chat->matches_size[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = 0;
837 }
838