blob: b463fb790b38c095079221495790aa8b02b358bd [file] [log] [blame]
/*
* Copyright 2024 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/modem/ubx.h>
#include <string.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(modem_ubx, CONFIG_MODEM_MODULES_LOG_LEVEL);
#define MODEM_UBX_STATE_ATTACHED_BIT 0
static int modem_ubx_validate_frame_size(uint16_t ubx_frame_size, uint8_t msg_cls, uint8_t msg_id,
uint16_t payload_size)
{
if (ubx_frame_size > UBX_FRM_SZ_MAX ||
ubx_frame_size < UBX_FRM_SZ_WITHOUT_PAYLOAD ||
ubx_frame_size < UBX_FRM_SZ_WITHOUT_PAYLOAD + payload_size) {
return -1;
}
return 0;
}
int modem_ubx_create_frame(uint8_t *ubx_frame, uint16_t ubx_frame_size, uint8_t msg_cls,
uint8_t msg_id, const void *payload, uint16_t payload_size)
{
if (modem_ubx_validate_frame_size(ubx_frame_size, msg_cls, msg_id, payload_size)) {
return -1;
}
struct ubx_frame *frame = (struct ubx_frame *) ubx_frame;
frame->preamble_sync_char_1 = UBX_PREAMBLE_SYNC_CHAR_1;
frame->preamble_sync_char_2 = UBX_PREAMBLE_SYNC_CHAR_2;
frame->message_class = msg_cls;
frame->message_id = msg_id;
frame->payload_size_low = payload_size;
frame->payload_size_high = payload_size >> 8;
memcpy(frame->payload_and_checksum, payload, payload_size);
uint16_t ubx_frame_len = payload_size + UBX_FRM_SZ_WITHOUT_PAYLOAD;
uint8_t ckA = 0, ckB = 0;
for (unsigned int i = UBX_FRM_CHECKSUM_START_IDX;
i < (UBX_FRM_CHECKSUM_STOP_IDX(ubx_frame_len)); i++) {
ckA += ubx_frame[i];
ckB += ckA;
}
frame->payload_and_checksum[payload_size] = ckA;
frame->payload_and_checksum[payload_size + 1] = ckB;
return ubx_frame_len;
}
static void modem_ubx_reset_received_ubx_preamble_sync_chars(struct modem_ubx *ubx)
{
ubx->ubx_preamble_sync_chars_received = false;
}
static void modem_ubx_reset_parser(struct modem_ubx *ubx)
{
modem_ubx_reset_received_ubx_preamble_sync_chars(ubx);
}
static int modem_ubx_get_payload_length(struct ubx_frame *frame)
{
uint16_t payload_len = frame->payload_size_high;
payload_len = payload_len << 8;
return payload_len | frame->payload_size_low;
}
static int modem_ubx_get_frame_length(struct ubx_frame *frame)
{
return modem_ubx_get_payload_length(frame) + UBX_FRM_SZ_WITHOUT_PAYLOAD;
}
static bool modem_ubx_match_frame_type(struct ubx_frame *frame_1, struct ubx_frame *frame_2)
{
if (frame_1->message_class == frame_2->message_class
&& frame_1->message_id == frame_2->message_id) {
return true;
} else {
return false;
}
}
static bool modem_ubx_match_frame_full(struct ubx_frame *frame_1, struct ubx_frame *frame_2)
{
if (modem_ubx_get_frame_length(frame_1) != modem_ubx_get_frame_length(frame_2)) {
return false;
}
if (memcmp(frame_1, frame_2, modem_ubx_get_frame_length(frame_1)) == 0) {
return true;
} else {
return false;
}
}
static void modem_ubx_script_init(struct modem_ubx *ubx, const struct modem_ubx_script *script)
{
ubx->script = script;
}
static int modem_ubx_run_script_helper(struct modem_ubx *ubx, const struct modem_ubx_script *script)
{
int ret;
if (ubx->pipe == NULL) {
return -EPERM;
}
k_sem_reset(&ubx->script_stopped_sem);
modem_ubx_reset_parser(ubx);
k_work_submit(&ubx->send_work);
if (ubx->script->match == NULL) {
return 0;
}
ret = k_sem_take(&ubx->script_stopped_sem, script->timeout);
if (ret < 0) {
return ret;
}
return 0;
}
int modem_ubx_run_script(struct modem_ubx *ubx, const struct modem_ubx_script *script)
{
int ret, attempt;
if (modem_ubx_get_frame_length(script->request) > UBX_FRM_SZ_MAX) {
return -EFBIG;
}
if (atomic_test_bit(&ubx->state, MODEM_UBX_STATE_ATTACHED_BIT) == false) {
return -EPERM;
}
ret = k_sem_take(&ubx->script_running_sem, K_FOREVER);
if (ret < 0) {
return ret;
}
modem_ubx_script_init(ubx, script);
for (attempt = 0; attempt < script->retry_count; ++attempt) {
ret = modem_ubx_run_script_helper(ubx, script);
if (ret > -1) {
LOG_INF("Successfully executed script on attempt: %d.", attempt);
break;
} else if (ret == -EPERM) {
break;
}
}
if (ret < 0) {
LOG_ERR("Failed to execute script successfully. Attempts: %d.", attempt);
goto unlock;
}
unlock:
k_sem_give(&ubx->script_running_sem);
return ret;
}
static void modem_ubx_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_event event,
void *user_data)
{
struct modem_ubx *ubx = (struct modem_ubx *)user_data;
if (event == MODEM_PIPE_EVENT_RECEIVE_READY) {
k_work_submit(&ubx->process_work);
}
}
static void modem_ubx_send_handler(struct k_work *item)
{
struct modem_ubx *ubx = CONTAINER_OF(item, struct modem_ubx, send_work);
int ret, tx_frame_len;
tx_frame_len = modem_ubx_get_frame_length(ubx->script->request);
ret = modem_pipe_transmit(ubx->pipe, (const uint8_t *) ubx->script->request, tx_frame_len);
if (ret < tx_frame_len) {
LOG_ERR("Ubx frame transmission failed. Returned %d.", ret);
return;
}
}
static int modem_ubx_process_received_ubx_frame(struct modem_ubx *ubx)
{
int ret;
struct ubx_frame *received = (struct ubx_frame *) ubx->work_buf;
if (modem_ubx_match_frame_full(received, ubx->script->match) == true) {
/* Frame matched successfully. Terminate the script. */
k_sem_give(&ubx->script_stopped_sem);
ret = 0;
} else if (modem_ubx_match_frame_type(received, ubx->script->request) == true) {
/* Response received successfully. Script not ended. */
memcpy(ubx->script->response, ubx->work_buf, ubx->work_buf_len);
ret = -1;
} else {
/* Ignore the received frame. The device may automatically send periodic frames.
* These frames are not relevant for our script's execution and must be ignored.
*/
ret = -1;
}
modem_ubx_reset_parser(ubx);
return ret;
}
static int modem_ubx_process_received_byte(struct modem_ubx *ubx, uint8_t byte)
{
static uint8_t prev_byte;
static uint16_t rx_ubx_frame_len;
if (ubx->ubx_preamble_sync_chars_received == false) {
if (prev_byte == UBX_PREAMBLE_SYNC_CHAR_1 && byte == UBX_PREAMBLE_SYNC_CHAR_2) {
ubx->ubx_preamble_sync_chars_received = true;
ubx->work_buf[0] = UBX_PREAMBLE_SYNC_CHAR_1;
ubx->work_buf[1] = UBX_PREAMBLE_SYNC_CHAR_2;
ubx->work_buf_len = 2;
}
} else {
ubx->work_buf[ubx->work_buf_len] = byte;
++ubx->work_buf_len;
if (ubx->work_buf_len == UBX_FRM_HEADER_SZ) {
uint16_t rx_ubx_payload_len = ubx->work_buf[UBX_FRM_PAYLOAD_SZ_H_IDX];
rx_ubx_payload_len = ubx->work_buf[UBX_FRM_PAYLOAD_SZ_H_IDX] << 8;
rx_ubx_payload_len |= ubx->work_buf[UBX_FRM_PAYLOAD_SZ_L_IDX];
rx_ubx_frame_len = rx_ubx_payload_len + UBX_FRM_SZ_WITHOUT_PAYLOAD;
}
if (ubx->work_buf_len == rx_ubx_frame_len) {
return modem_ubx_process_received_ubx_frame(ubx);
}
}
prev_byte = byte;
return -1;
}
static void modem_ubx_process_handler(struct k_work *item)
{
struct modem_ubx *ubx = CONTAINER_OF(item, struct modem_ubx, process_work);
int ret;
ret = modem_pipe_receive(ubx->pipe, ubx->receive_buf, ubx->receive_buf_size);
if (ret < 1) {
return;
}
const size_t length = ret;
for (int i = 0; i < length; i++) {
ret = modem_ubx_process_received_byte(ubx, ubx->receive_buf[i]);
if (ret == 0) { /* Frame matched successfully. Terminate the script. */
break;
}
}
k_work_submit(&ubx->process_work);
}
int modem_ubx_attach(struct modem_ubx *ubx, struct modem_pipe *pipe)
{
if (atomic_test_and_set_bit(&ubx->state, MODEM_UBX_STATE_ATTACHED_BIT) == true) {
return 0;
}
ubx->pipe = pipe;
modem_pipe_attach(ubx->pipe, modem_ubx_pipe_callback, ubx);
k_sem_give(&ubx->script_running_sem);
return 0;
}
void modem_ubx_release(struct modem_ubx *ubx)
{
struct k_work_sync sync;
if (atomic_test_and_clear_bit(&ubx->state, MODEM_UBX_STATE_ATTACHED_BIT) == false) {
return;
}
modem_pipe_release(ubx->pipe);
k_work_cancel_sync(&ubx->send_work, &sync);
k_work_cancel_sync(&ubx->process_work, &sync);
k_sem_reset(&ubx->script_stopped_sem);
k_sem_reset(&ubx->script_running_sem);
ubx->work_buf_len = 0;
modem_ubx_reset_parser(ubx);
ubx->pipe = NULL;
}
int modem_ubx_init(struct modem_ubx *ubx, const struct modem_ubx_config *config)
{
__ASSERT_NO_MSG(ubx != 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->work_buf != NULL);
__ASSERT_NO_MSG(config->work_buf_size > 0);
memset(ubx, 0x00, sizeof(*ubx));
ubx->user_data = config->user_data;
ubx->receive_buf = config->receive_buf;
ubx->receive_buf_size = config->receive_buf_size;
ubx->work_buf = config->work_buf;
ubx->work_buf_size = config->work_buf_size;
ubx->pipe = NULL;
k_work_init(&ubx->send_work, modem_ubx_send_handler);
k_work_init(&ubx->process_work, modem_ubx_process_handler);
k_sem_init(&ubx->script_stopped_sem, 0, 1);
k_sem_init(&ubx->script_running_sem, 1, 1);
return 0;
}