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