zephyr/subsys/modem/modem_chat.c
Bjarki Arge Andreasen 494fab8ea4 modem: chat: patch unintended behavior in modem_chat_run_script()
Trying to start a chat script using either modem_chat_run_script()
or modem_chat_run_script_async() should result in returning -EBUSY
without affecting the currently running script and thread waiting
on the current script to stop.

The current behavior causes the thread waiting for the current
script to stop to return with error -EAGAIN, and the thread trying
to start the new script to return with error -EBUSY.

This commit moves the reset of the sem the current thread is
waiting on, to after the check of whether a script is currently
running, leaving the current thread unaffected as is intended.

Signed-off-by: Bjarki Arge Andreasen <bjarki@arge-andreasen.me>
2024-06-13 17:49:23 +02:00

1079 lines
28 KiB
C

/*
* Copyright (c) 2022 Trackunit Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(modem_chat, CONFIG_MODEM_MODULES_LOG_LEVEL);
#include <zephyr/kernel.h>
#include <string.h>
#include <zephyr/modem/chat.h>
const struct modem_chat_match modem_chat_any_match = MODEM_CHAT_MATCH("", "", NULL);
const struct modem_chat_match modem_chat_empty_matches[0];
const struct modem_chat_script_chat modem_chat_empty_script_chats[0];
#define MODEM_CHAT_MATCHES_INDEX_RESPONSE (0)
#define MODEM_CHAT_MATCHES_INDEX_ABORT (1)
#define MODEM_CHAT_MATCHES_INDEX_UNSOL (2)
#define MODEM_CHAT_SCRIPT_STATE_RUNNING_BIT (0)
#if defined(CONFIG_LOG) && (CONFIG_MODEM_MODULES_LOG_LEVEL == LOG_LEVEL_DBG)
static char log_buffer[CONFIG_MODEM_CHAT_LOG_BUFFER_SIZE];
static void modem_chat_log_received_command(struct modem_chat *chat)
{
uint16_t log_buffer_pos = 0;
uint16_t argv_len;
for (uint16_t i = 0; i < chat->argc; i++) {
argv_len = (uint16_t)strlen(chat->argv[i]);
/* Validate argument fits in log buffer including termination */
if (sizeof(log_buffer) < (log_buffer_pos + argv_len + 1)) {
LOG_WRN("log buffer overrun");
break;
}
/* Copy argument and append space */
memcpy(&log_buffer[log_buffer_pos], chat->argv[i], argv_len);
log_buffer_pos += argv_len;
log_buffer[log_buffer_pos] = ' ';
log_buffer_pos++;
}
/* Terminate line after last argument, overwriting trailing space */
log_buffer_pos = log_buffer_pos == 0 ? log_buffer_pos : log_buffer_pos - 1;
log_buffer[log_buffer_pos] = '\0';
LOG_DBG("%s", log_buffer);
}
#else
static void modem_chat_log_received_command(struct modem_chat *chat)
{
}
#endif
static void modem_chat_script_stop(struct modem_chat *chat, enum modem_chat_script_result result)
{
if ((chat == NULL) || (chat->script == NULL)) {
return;
}
/* Handle result */
if (result == MODEM_CHAT_SCRIPT_RESULT_SUCCESS) {
LOG_DBG("%s: complete", chat->script->name);
} else if (result == MODEM_CHAT_SCRIPT_RESULT_ABORT) {
LOG_WRN("%s: aborted", chat->script->name);
} else {
LOG_WRN("%s: timed out", chat->script->name);
}
/* Call back with result */
if (chat->script->callback != NULL) {
chat->script->callback(chat, result, chat->user_data);
}
/* Clear parse_match in case it is stored in the script being stopped */
if ((chat->parse_match != NULL) &&
((chat->parse_match_type == MODEM_CHAT_MATCHES_INDEX_ABORT) ||
(chat->parse_match_type == MODEM_CHAT_MATCHES_INDEX_RESPONSE))) {
chat->parse_match = NULL;
chat->parse_match_len = 0;
}
/* Clear reference to script */
chat->script = NULL;
/* Clear response and abort commands */
chat->matches[MODEM_CHAT_MATCHES_INDEX_ABORT] = NULL;
chat->matches_size[MODEM_CHAT_MATCHES_INDEX_ABORT] = 0;
chat->matches[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = NULL;
chat->matches_size[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = 0;
/* Cancel work */
k_work_cancel_delayable(&chat->script_timeout_work);
k_work_cancel(&chat->script_send_work);
k_work_cancel_delayable(&chat->script_send_timeout_work);
/* Clear script running state */
atomic_clear_bit(&chat->script_state, MODEM_CHAT_SCRIPT_STATE_RUNNING_BIT);
/* Store result of script for script stoppted indication */
chat->script_result = result;
/* Indicate script stopped */
k_sem_give(&chat->script_stopped_sem);
}
static void modem_chat_set_script_send_state(struct modem_chat *chat,
enum modem_chat_script_send_state state)
{
chat->script_send_pos = 0;
chat->script_send_state = state;
}
static void modem_chat_script_send(struct modem_chat *chat)
{
modem_chat_set_script_send_state(chat, MODEM_CHAT_SCRIPT_SEND_STATE_REQUEST);
k_work_submit(&chat->script_send_work);
}
static void modem_chat_script_set_response_matches(struct modem_chat *chat)
{
const struct modem_chat_script_chat *script_chat =
&chat->script->script_chats[chat->script_chat_it];
chat->matches[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = script_chat->response_matches;
chat->matches_size[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = script_chat->response_matches_size;
}
static void modem_chat_script_clear_response_matches(struct modem_chat *chat)
{
chat->matches[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = NULL;
chat->matches_size[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = 0;
}
static bool modem_chat_script_chat_has_request(struct modem_chat *chat)
{
const struct modem_chat_script_chat *script_chat =
&chat->script->script_chats[chat->script_chat_it];
return script_chat->request_size > 0;
}
static bool modem_chat_script_chat_has_matches(struct modem_chat *chat)
{
const struct modem_chat_script_chat *script_chat =
&chat->script->script_chats[chat->script_chat_it];
return script_chat->response_matches_size > 0;
}
static uint16_t modem_chat_script_chat_get_send_timeout(struct modem_chat *chat)
{
const struct modem_chat_script_chat *script_chat =
&chat->script->script_chats[chat->script_chat_it];
return script_chat->timeout;
}
static bool modem_chat_script_chat_has_send_timeout(struct modem_chat *chat)
{
return modem_chat_script_chat_get_send_timeout(chat) > 0;
}
static void modem_chat_script_chat_schedule_send_timeout(struct modem_chat *chat)
{
uint16_t timeout = modem_chat_script_chat_get_send_timeout(chat);
k_work_schedule(&chat->script_send_timeout_work, K_MSEC(timeout));
}
static void modem_chat_script_next(struct modem_chat *chat, bool initial)
{
const struct modem_chat_script_chat *script_chat;
/* Advance iterator if not initial */
if (initial == true) {
/* Reset iterator */
chat->script_chat_it = 0;
} else {
/* Advance iterator */
chat->script_chat_it++;
}
/* Check if end of script reached */
if (chat->script_chat_it == chat->script->script_chats_size) {
modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_SUCCESS);
return;
}
LOG_DBG("%s: step: %u", chat->script->name, chat->script_chat_it);
script_chat = &chat->script->script_chats[chat->script_chat_it];
/* Continue script */
if (modem_chat_script_chat_has_request(chat)) {
LOG_DBG("sending: %.*s", script_chat->request_size, script_chat->request);
modem_chat_script_clear_response_matches(chat);
modem_chat_script_send(chat);
} else if (modem_chat_script_chat_has_matches(chat)) {
modem_chat_script_set_response_matches(chat);
} else {
modem_chat_script_chat_schedule_send_timeout(chat);
}
}
static void modem_chat_script_start(struct modem_chat *chat, const struct modem_chat_script *script)
{
/* Save script */
chat->script = script;
/* Set abort matches */
chat->matches[MODEM_CHAT_MATCHES_INDEX_ABORT] = script->abort_matches;
chat->matches_size[MODEM_CHAT_MATCHES_INDEX_ABORT] = script->abort_matches_size;
LOG_DBG("running script: %s", chat->script->name);
/* Set first script command */
modem_chat_script_next(chat, true);
/* Start timeout work if script started */
if (chat->script != NULL) {
k_work_schedule(&chat->script_timeout_work, K_SECONDS(chat->script->timeout));
}
}
static void modem_chat_script_run_handler(struct k_work *item)
{
struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, script_run_work);
/* Start script */
modem_chat_script_start(chat, chat->pending_script);
}
static void modem_chat_script_timeout_handler(struct k_work *item)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(item);
struct modem_chat *chat = CONTAINER_OF(dwork, struct modem_chat, script_timeout_work);
/* Abort script */
modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_TIMEOUT);
}
static void modem_chat_script_abort_handler(struct k_work *item)
{
struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, script_abort_work);
/* Validate script is currently running */
if (chat->script == NULL) {
return;
}
/* Abort script */
modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_ABORT);
}
/* Returns true when request part has been sent */
static bool modem_chat_send_script_request_part(struct modem_chat *chat)
{
const struct modem_chat_script_chat *script_chat =
&chat->script->script_chats[chat->script_chat_it];
uint8_t *request_part;
uint16_t request_size;
uint16_t request_part_size;
int ret;
switch (chat->script_send_state) {
case MODEM_CHAT_SCRIPT_SEND_STATE_REQUEST:
request_part = (uint8_t *)(&script_chat->request[chat->script_send_pos]);
request_size = script_chat->request_size;
break;
case MODEM_CHAT_SCRIPT_SEND_STATE_DELIMITER:
request_part = (uint8_t *)(&chat->delimiter[chat->script_send_pos]);
request_size = chat->delimiter_size;
break;
default:
return false;
}
request_part_size = request_size - chat->script_send_pos;
ret = modem_pipe_transmit(chat->pipe, request_part, request_part_size);
if (ret < 1) {
if (ret < 0) {
LOG_ERR("Failed to %s %u bytes. (%d)", "transmit", request_part_size, ret);
}
return false;
}
chat->script_send_pos += (uint16_t)ret;
/* Return true if all data was sent */
return request_size <= chat->script_send_pos;
}
static void modem_chat_script_send_handler(struct k_work *item)
{
struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, script_send_work);
if (chat->script == NULL) {
return;
}
switch (chat->script_send_state) {
case MODEM_CHAT_SCRIPT_SEND_STATE_IDLE:
return;
case MODEM_CHAT_SCRIPT_SEND_STATE_REQUEST:
if (!modem_chat_send_script_request_part(chat)) {
return;
}
modem_chat_set_script_send_state(chat, MODEM_CHAT_SCRIPT_SEND_STATE_DELIMITER);
__fallthrough;
case MODEM_CHAT_SCRIPT_SEND_STATE_DELIMITER:
if (!modem_chat_send_script_request_part(chat)) {
return;
}
modem_chat_set_script_send_state(chat, MODEM_CHAT_SCRIPT_SEND_STATE_IDLE);
break;
}
if (modem_chat_script_chat_has_matches(chat)) {
modem_chat_script_set_response_matches(chat);
} else if (modem_chat_script_chat_has_send_timeout(chat)) {
modem_chat_script_chat_schedule_send_timeout(chat);
} else {
modem_chat_script_next(chat, false);
}
}
static void modem_chat_script_send_timeout_handler(struct k_work *item)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(item);
struct modem_chat *chat = CONTAINER_OF(dwork, struct modem_chat, script_send_timeout_work);
/* Validate script is currently running */
if (chat->script == NULL) {
return;
}
modem_chat_script_next(chat, false);
}
#if CONFIG_MODEM_STATS
static uint32_t get_receive_buf_length(struct modem_chat *chat)
{
return chat->receive_buf_len;
}
static void advertise_receive_buf_stats(struct modem_chat *chat)
{
uint32_t length;
length = get_receive_buf_length(chat);
modem_stats_buffer_advertise_length(&chat->receive_buf_stats, length);
}
#endif
static void modem_chat_parse_reset(struct modem_chat *chat)
{
#if CONFIG_MODEM_STATS
advertise_receive_buf_stats(chat);
#endif
/* Reset parameters used for parsing */
chat->receive_buf_len = 0;
chat->delimiter_match_len = 0;
chat->argc = 0;
chat->parse_match = NULL;
}
/* Exact match is stored at end of receive buffer */
static void modem_chat_parse_save_match(struct modem_chat *chat)
{
uint8_t *argv;
/* Store length of match including NULL to avoid overwriting it if buffer overruns */
chat->parse_match_len = chat->receive_buf_len + 1;
/* Copy match to end of receive buffer */
argv = &chat->receive_buf[chat->receive_buf_size - chat->parse_match_len];
/* Copy match to end of receive buffer (excluding NULL) */
memcpy(argv, &chat->receive_buf[0], chat->parse_match_len - 1);
/* Save match */
chat->argv[chat->argc] = argv;
/* Terminate match */
chat->receive_buf[chat->receive_buf_size - 1] = '\0';
/* Increment argument count */
chat->argc++;
}
static bool modem_chat_match_matches_received(struct modem_chat *chat,
const struct modem_chat_match *match)
{
for (uint16_t i = 0; i < match->match_size; i++) {
if ((match->match[i] == chat->receive_buf[i]) ||
(match->wildcards == true && match->match[i] == '?')) {
continue;
}
return false;
}
return true;
}
static bool modem_chat_parse_find_match(struct modem_chat *chat)
{
/* Find in all matches types */
for (uint16_t i = 0; i < ARRAY_SIZE(chat->matches); i++) {
/* Find in all matches of matches type */
for (uint16_t u = 0; u < chat->matches_size[i]; u++) {
/* Validate match size matches received data length */
if (chat->matches[i][u].match_size != chat->receive_buf_len) {
continue;
}
/* Validate match */
if (modem_chat_match_matches_received(chat, &chat->matches[i][u]) ==
false) {
continue;
}
/* Complete match found */
chat->parse_match = &chat->matches[i][u];
chat->parse_match_type = i;
return true;
}
}
return false;
}
static bool modem_chat_parse_is_separator(struct modem_chat *chat)
{
for (uint16_t i = 0; i < chat->parse_match->separators_size; i++) {
if ((chat->parse_match->separators[i]) ==
(chat->receive_buf[chat->receive_buf_len - 1])) {
return true;
}
}
return false;
}
static bool modem_chat_parse_end_del_start(struct modem_chat *chat)
{
for (uint8_t i = 0; i < chat->delimiter_size; i++) {
if (chat->receive_buf[chat->receive_buf_len - 1] == chat->delimiter[i]) {
return true;
}
}
return false;
}
static bool modem_chat_parse_end_del_complete(struct modem_chat *chat)
{
/* Validate length of end delimiter */
if (chat->receive_buf_len < chat->delimiter_size) {
return false;
}
/* Compare end delimiter with receive buffer content */
return (memcmp(&chat->receive_buf[chat->receive_buf_len - chat->delimiter_size],
chat->delimiter, chat->delimiter_size) == 0)
? true
: false;
}
static void modem_chat_on_command_received_unsol(struct modem_chat *chat)
{
/* Callback */
if (chat->parse_match->callback != NULL) {
chat->parse_match->callback(chat, (char **)chat->argv, chat->argc, chat->user_data);
}
}
static void modem_chat_on_command_received_abort(struct modem_chat *chat)
{
/* Callback */
if (chat->parse_match->callback != NULL) {
chat->parse_match->callback(chat, (char **)chat->argv, chat->argc, chat->user_data);
}
/* Abort script */
modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_ABORT);
}
static void modem_chat_on_command_received_resp(struct modem_chat *chat)
{
/* Callback */
if (chat->parse_match->callback != NULL) {
chat->parse_match->callback(chat, (char **)chat->argv, chat->argc, chat->user_data);
}
/* Validate response command is not partial */
if (chat->parse_match->partial) {
return;
}
/* Advance script */
modem_chat_script_next(chat, false);
}
static bool modem_chat_parse_find_catch_all_match(struct modem_chat *chat)
{
/* Find in all matches types */
for (uint16_t i = 0; i < ARRAY_SIZE(chat->matches); i++) {
/* Find in all matches of matches type */
for (uint16_t u = 0; u < chat->matches_size[i]; u++) {
/* Validate match config is matching previous bytes */
if (chat->matches[i][u].match_size == 0) {
chat->parse_match = &chat->matches[i][u];
chat->parse_match_type = i;
return true;
}
}
}
return false;
}
static void modem_chat_on_command_received(struct modem_chat *chat)
{
modem_chat_log_received_command(chat);
switch (chat->parse_match_type) {
case MODEM_CHAT_MATCHES_INDEX_UNSOL:
modem_chat_on_command_received_unsol(chat);
break;
case MODEM_CHAT_MATCHES_INDEX_ABORT:
modem_chat_on_command_received_abort(chat);
break;
case MODEM_CHAT_MATCHES_INDEX_RESPONSE:
modem_chat_on_command_received_resp(chat);
break;
}
}
static void modem_chat_on_unknown_command_received(struct modem_chat *chat)
{
/* Terminate received command */
chat->receive_buf[chat->receive_buf_len - chat->delimiter_size] = '\0';
/* Try to find catch all match */
if (modem_chat_parse_find_catch_all_match(chat) == false) {
LOG_DBG("%s", chat->receive_buf);
return;
}
/* Parse command */
chat->argv[0] = "";
chat->argv[1] = chat->receive_buf;
chat->argc = 2;
modem_chat_on_command_received(chat);
}
static void modem_chat_process_byte(struct modem_chat *chat, uint8_t byte)
{
/* Validate receive buffer not overrun */
if (chat->receive_buf_size == chat->receive_buf_len) {
LOG_WRN("receive buffer overrun");
modem_chat_parse_reset(chat);
return;
}
/* Validate argv buffer not overrun */
if (chat->argc == chat->argv_size) {
LOG_WRN("argv buffer overrun");
modem_chat_parse_reset(chat);
return;
}
/* Copy byte to receive buffer */
chat->receive_buf[chat->receive_buf_len] = byte;
chat->receive_buf_len++;
/* Validate end delimiter not complete */
if (modem_chat_parse_end_del_complete(chat) == true) {
/* Filter out empty lines */
if (chat->receive_buf_len == chat->delimiter_size) {
/* Reset parser */
modem_chat_parse_reset(chat);
return;
}
/* Check if match exists */
if (chat->parse_match == NULL) {
/* Handle unknown command */
modem_chat_on_unknown_command_received(chat);
/* Reset parser */
modem_chat_parse_reset(chat);
return;
}
/* Check if trailing argument exists */
if (chat->parse_arg_len > 0) {
chat->argv[chat->argc] =
&chat->receive_buf[chat->receive_buf_len - chat->delimiter_size -
chat->parse_arg_len];
chat->receive_buf[chat->receive_buf_len - chat->delimiter_size] = '\0';
chat->argc++;
}
/* Handle received command */
modem_chat_on_command_received(chat);
/* Reset parser */
modem_chat_parse_reset(chat);
return;
}
/* Validate end delimiter not started */
if (modem_chat_parse_end_del_start(chat) == true) {
return;
}
/* Find matching command if missing */
if (chat->parse_match == NULL) {
/* Find matching command */
if (modem_chat_parse_find_match(chat) == false) {
return;
}
/* Save match */
modem_chat_parse_save_match(chat);
/* Prepare argument parser */
chat->parse_arg_len = 0;
return;
}
/* Check if separator reached */
if (modem_chat_parse_is_separator(chat) == true) {
/* Check if argument is empty */
if (chat->parse_arg_len == 0) {
/* Save empty argument */
chat->argv[chat->argc] = "";
} else {
/* Save pointer to start of argument */
chat->argv[chat->argc] =
&chat->receive_buf[chat->receive_buf_len - chat->parse_arg_len - 1];
/* Replace separator with string terminator */
chat->receive_buf[chat->receive_buf_len - 1] = '\0';
}
/* Increment argument count */
chat->argc++;
/* Reset parse argument length */
chat->parse_arg_len = 0;
return;
}
/* Increment argument length */
chat->parse_arg_len++;
}
static bool modem_chat_discard_byte(struct modem_chat *chat, uint8_t byte)
{
for (uint8_t i = 0; i < chat->filter_size; i++) {
if (byte == chat->filter[i]) {
return true;
}
}
return false;
}
/* Process chunk of received bytes */
static void modem_chat_process_bytes(struct modem_chat *chat)
{
for (uint16_t i = 0; i < chat->work_buf_len; i++) {
if (modem_chat_discard_byte(chat, chat->work_buf[i])) {
continue;
}
modem_chat_process_byte(chat, chat->work_buf[i]);
}
}
#if CONFIG_MODEM_STATS
static uint32_t get_work_buf_length(struct modem_chat *chat)
{
return chat->work_buf_len;
}
static void advertise_work_buf_stats(struct modem_chat *chat)
{
uint32_t length;
length = get_work_buf_length(chat);
modem_stats_buffer_advertise_length(&chat->work_buf_stats, length);
}
#endif
static void modem_chat_process_handler(struct k_work *item)
{
struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, receive_work);
int ret;
/* Fill work buffer */
ret = modem_pipe_receive(chat->pipe, chat->work_buf, sizeof(chat->work_buf));
if (ret < 1) {
return;
}
/* Save received data length */
chat->work_buf_len = (size_t)ret;
#if CONFIG_MODEM_STATS
advertise_work_buf_stats(chat);
#endif
/* Process data */
modem_chat_process_bytes(chat);
k_work_submit(&chat->receive_work);
}
static void modem_chat_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_event event,
void *user_data)
{
struct modem_chat *chat = (struct modem_chat *)user_data;
switch (event) {
case MODEM_PIPE_EVENT_RECEIVE_READY:
k_work_submit(&chat->receive_work);
break;
case MODEM_PIPE_EVENT_TRANSMIT_IDLE:
k_work_submit(&chat->script_send_work);
break;
default:
break;
}
}
static bool modem_chat_validate_array(const void *array, size_t size)
{
return ((array == NULL) && (size == 0)) ||
((array != NULL) && (size > 0));
}
#if CONFIG_MODEM_STATS
static uint32_t get_receive_buf_size(struct modem_chat *chat)
{
return chat->receive_buf_size;
}
static uint32_t get_work_buf_size(struct modem_chat *chat)
{
return sizeof(chat->work_buf);
}
static void init_buf_stats(struct modem_chat *chat)
{
uint32_t size;
size = get_receive_buf_size(chat);
modem_stats_buffer_init(&chat->receive_buf_stats, "chat_rx", size);
size = get_work_buf_size(chat);
modem_stats_buffer_init(&chat->work_buf_stats, "chat_work", size);
}
#endif
int modem_chat_init(struct modem_chat *chat, const struct modem_chat_config *config)
{
__ASSERT_NO_MSG(chat != NULL);
__ASSERT_NO_MSG(config != NULL);
__ASSERT_NO_MSG(config->receive_buf != NULL);
__ASSERT_NO_MSG(config->receive_buf_size > 0);
__ASSERT_NO_MSG(config->argv != NULL);
__ASSERT_NO_MSG(config->argv_size > 0);
__ASSERT_NO_MSG(config->delimiter != NULL);
__ASSERT_NO_MSG(config->delimiter_size > 0);
__ASSERT_NO_MSG(!((config->filter == NULL) && (config->filter_size > 0)));
__ASSERT_NO_MSG(!((config->unsol_matches == NULL) && (config->unsol_matches_size > 0)));
memset(chat, 0x00, sizeof(*chat));
chat->pipe = NULL;
chat->user_data = config->user_data;
chat->receive_buf = config->receive_buf;
chat->receive_buf_size = config->receive_buf_size;
chat->argv = config->argv;
chat->argv_size = config->argv_size;
chat->delimiter = config->delimiter;
chat->delimiter_size = config->delimiter_size;
chat->filter = config->filter;
chat->filter_size = config->filter_size;
chat->matches[MODEM_CHAT_MATCHES_INDEX_UNSOL] = config->unsol_matches;
chat->matches_size[MODEM_CHAT_MATCHES_INDEX_UNSOL] = config->unsol_matches_size;
atomic_set(&chat->script_state, 0);
k_sem_init(&chat->script_stopped_sem, 0, 1);
k_work_init(&chat->receive_work, modem_chat_process_handler);
k_work_init(&chat->script_run_work, modem_chat_script_run_handler);
k_work_init_delayable(&chat->script_timeout_work, modem_chat_script_timeout_handler);
k_work_init(&chat->script_abort_work, modem_chat_script_abort_handler);
k_work_init(&chat->script_send_work, modem_chat_script_send_handler);
k_work_init_delayable(&chat->script_send_timeout_work,
modem_chat_script_send_timeout_handler);
#if CONFIG_MODEM_STATS
init_buf_stats(chat);
#endif
return 0;
}
int modem_chat_attach(struct modem_chat *chat, struct modem_pipe *pipe)
{
chat->pipe = pipe;
modem_chat_parse_reset(chat);
modem_pipe_attach(chat->pipe, modem_chat_pipe_callback, chat);
return 0;
}
int modem_chat_run_script_async(struct modem_chat *chat, const struct modem_chat_script *script)
{
bool script_is_running;
if (chat->pipe == NULL) {
return -EPERM;
}
/* Validate script */
if (script->script_chats == NULL ||
(script->script_chats_size == 0
&& script->script_chats != modem_chat_empty_script_chats) ||
(script->abort_matches_size == 0
&& script->abort_matches != NULL
&& script->abort_matches != modem_chat_empty_matches)) {
return -EINVAL;
}
/* Validate script commands */
for (uint16_t i = 0; i < script->script_chats_size; i++) {
if ((script->script_chats[i].request_size == 0) &&
(script->script_chats[i].response_matches_size == 0) &&
(script->script_chats[i].timeout == 0)) {
return -EINVAL;
}
}
script_is_running =
atomic_test_and_set_bit(&chat->script_state, MODEM_CHAT_SCRIPT_STATE_RUNNING_BIT);
if (script_is_running == true) {
return -EBUSY;
}
k_sem_reset(&chat->script_stopped_sem);
chat->pending_script = script;
k_work_submit(&chat->script_run_work);
return 0;
}
int modem_chat_run_script(struct modem_chat *chat, const struct modem_chat_script *script)
{
int ret;
ret = modem_chat_run_script_async(chat, script);
if (ret < 0) {
return ret;
}
ret = k_sem_take(&chat->script_stopped_sem, K_FOREVER);
if (ret < 0) {
return ret;
}
return (chat->script_result == MODEM_CHAT_SCRIPT_RESULT_SUCCESS) ? 0 : -EAGAIN;
}
void modem_chat_script_abort(struct modem_chat *chat)
{
k_work_submit(&chat->script_abort_work);
}
void modem_chat_release(struct modem_chat *chat)
{
struct k_work_sync sync;
if (chat->pipe) {
modem_pipe_release(chat->pipe);
}
k_work_cancel_sync(&chat->script_run_work, &sync);
k_work_cancel_sync(&chat->script_abort_work, &sync);
k_work_cancel_sync(&chat->receive_work, &sync);
k_work_cancel_sync(&chat->script_send_work, &sync);
chat->pipe = NULL;
chat->receive_buf_len = 0;
chat->work_buf_len = 0;
chat->argc = 0;
chat->script = NULL;
chat->script_chat_it = 0;
atomic_set(&chat->script_state, 0);
chat->script_result = MODEM_CHAT_SCRIPT_RESULT_ABORT;
k_sem_reset(&chat->script_stopped_sem);
chat->script_send_state = MODEM_CHAT_SCRIPT_SEND_STATE_IDLE;
chat->script_send_pos = 0;
chat->parse_match = NULL;
chat->parse_match_len = 0;
chat->parse_arg_len = 0;
chat->matches[MODEM_CHAT_MATCHES_INDEX_ABORT] = NULL;
chat->matches_size[MODEM_CHAT_MATCHES_INDEX_ABORT] = 0;
chat->matches[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = NULL;
chat->matches_size[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = 0;
}
void modem_chat_match_init(struct modem_chat_match *chat_match)
{
memset(chat_match, 0, sizeof(struct modem_chat_match));
}
int modem_chat_match_set_match(struct modem_chat_match *chat_match, const char *match)
{
size_t size;
size = strnlen(match, UINT8_MAX + 1);
if (size == (UINT8_MAX + 1)) {
return -ENOMEM;
}
chat_match->match = match;
chat_match->match_size = (uint8_t)size;
return 0;
}
int modem_chat_match_set_separators(struct modem_chat_match *chat_match, const char *separators)
{
size_t size;
size = strnlen(separators, UINT8_MAX + 1);
if (size == (UINT8_MAX + 1)) {
return -ENOMEM;
}
chat_match->separators = separators;
chat_match->separators_size = (uint8_t)size;
return 0;
}
void modem_chat_match_set_callback(struct modem_chat_match *match,
modem_chat_match_callback callback)
{
match->callback = callback;
}
void modem_chat_match_set_partial(struct modem_chat_match *match, bool partial)
{
match->partial = partial;
}
void modem_chat_match_enable_wildcards(struct modem_chat_match *match, bool enable)
{
match->wildcards = enable;
}
void modem_chat_script_chat_init(struct modem_chat_script_chat *script_chat)
{
memset(script_chat, 0, sizeof(struct modem_chat_script_chat));
}
int modem_chat_script_chat_set_request(struct modem_chat_script_chat *script_chat,
const char *request)
{
size_t size;
size = strnlen(request, UINT16_MAX + 1);
if (size == (UINT16_MAX + 1)) {
return -ENOMEM;
}
script_chat->request = request;
script_chat->request_size = (uint16_t)size;
return 0;
}
int modem_chat_script_chat_set_response_matches(struct modem_chat_script_chat *script_chat,
const struct modem_chat_match *response_matches,
uint16_t response_matches_size)
{
if (!modem_chat_validate_array(response_matches, response_matches_size)) {
return -EINVAL;
}
script_chat->response_matches = response_matches;
script_chat->response_matches_size = response_matches_size;
return 0;
}
void modem_chat_script_chat_set_timeout(struct modem_chat_script_chat *script_chat,
uint16_t timeout)
{
script_chat->timeout = timeout;
}
void modem_chat_script_init(struct modem_chat_script *script)
{
memset(script, 0, sizeof(struct modem_chat_script));
script->name = "";
}
void modem_chat_script_set_name(struct modem_chat_script *script, const char *name)
{
script->name = name;
}
int modem_chat_script_set_script_chats(struct modem_chat_script *script,
const struct modem_chat_script_chat *script_chats,
uint16_t script_chats_size)
{
if (!modem_chat_validate_array(script_chats, script_chats_size)) {
return -EINVAL;
}
script->script_chats = script_chats;
script->script_chats_size = script_chats_size;
return 0;
}
int modem_chat_script_set_abort_matches(struct modem_chat_script *script,
const struct modem_chat_match *abort_matches,
uint16_t abort_matches_size)
{
if (!modem_chat_validate_array(abort_matches, abort_matches_size)) {
return -EINVAL;
}
script->abort_matches = abort_matches;
script->abort_matches_size = abort_matches_size;
return 0;
}
void modem_chat_script_set_callback(struct modem_chat_script *script,
modem_chat_script_callback callback)
{
script->callback = callback;
}
void modem_chat_script_set_timeout(struct modem_chat_script *script, uint32_t timeout_s)
{
script->timeout = timeout_s;
}