zephyr/drivers/modem/hl7800.c
Ryan Erickson 8deaf18d8b drivers: modem: hl7800: fix low power modes
Do not allow the modem to sleep if the driver is busy.
Fix CTS filtering. Ignore small pulses on the CTS line.

Fix socket restoration. Restored sockets could be mismatched
with a wrong type.
UDP sockets could be duplicated during restore.

Improve IO debug mode. Use warning message for IO debug mode
to easily see IO transitions color coded in a terminal.

Ensure the UART is enabled whenever the driver needs to send
commands to the modem.

Ensure DNS resolver is re-initialized after the modem is powered off.

PROD-307

Signed-off-by: Ryan Erickson <ryan.erickson@lairdconnect.com>
2023-12-08 10:26:04 +00:00

6560 lines
165 KiB
C

/*
* Copyright (c) 2020 Laird Connectivity
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT swir_hl7800
#include <zephyr/logging/log.h>
#include <zephyr/logging/log_ctrl.h>
#define LOG_MODULE_NAME modem_hl7800
LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_MODEM_LOG_LEVEL);
#include <zephyr/types.h>
#include <stddef.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <zephyr/pm/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/sys/util.h>
#include <zephyr/net/net_context.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_offload.h>
#include <zephyr/net/net_pkt.h>
#include <zephyr/net/dns_resolve.h>
#include <zephyr/net/offloaded_netdev.h>
#if defined(CONFIG_NET_IPV6)
#include "ipv6.h"
#endif
#if defined(CONFIG_NET_IPV4)
#include "ipv4.h"
#endif
#if defined(CONFIG_NET_UDP)
#include "udp_internal.h"
#endif
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
#include <zephyr/fs/fs.h>
#endif
#include "modem_receiver.h"
#include <zephyr/drivers/modem/hl7800.h>
#define PREFIXED_SWITCH_CASE_RETURN_STRING(prefix, val) \
case prefix##_##val: { \
return #val; \
}
/* Uncomment the #define below to enable a hexdump of all incoming
* data from the modem receiver
*/
/* #define HL7800_ENABLE_VERBOSE_MODEM_RECV_HEXDUMP 1 */
#define HL7800_LOG_UNHANDLED_RX_MSGS 1
/* Uncomment the #define(s) below to enable extra debugging */
/* #define HL7800_RX_LOCK_LOG 1 */
/* #define HL7800_TX_LOCK_LOG 1 */
/* #define HL7800_IO_LOG 1 */
#define HL7800_RX_LOCK_DBG_LOG(fmt, ...) \
do { \
if (IS_ENABLED(HL7800_RX_LOCK_LOG)) { \
LOG_DBG(fmt, ##__VA_ARGS__); \
} \
} while (false)
#define HL7800_TX_LOCK_DBG_LOG(fmt, ...) \
do { \
if (IS_ENABLED(HL7800_TX_LOCK_LOG)) { \
LOG_DBG(fmt, ##__VA_ARGS__); \
} \
} while (false)
#define HL7800_IO_DBG_LOG(fmt, ...) \
do { \
if (IS_ENABLED(HL7800_IO_LOG)) { \
LOG_WRN(fmt, ##__VA_ARGS__); \
} \
} while (false)
#if ((LOG_LEVEL == LOG_LEVEL_DBG) && \
defined(CONFIG_MODEM_HL7800_LOW_POWER_MODE))
#define PRINT_AWAKE_MSG LOG_WRN("awake")
#define PRINT_NOT_AWAKE_MSG LOG_WRN("NOT awake")
#else
#define PRINT_AWAKE_MSG
#define PRINT_NOT_AWAKE_MSG
#endif
enum tcp_notif {
HL7800_TCP_NET_ERR,
HL7800_TCP_NO_SOCKS,
HL7800_TCP_MEM,
HL7800_TCP_DNS,
HL7800_TCP_DISCON,
HL7800_TCP_CONN,
HL7800_TCP_ERR,
HL7800_TCP_CLIENT_REQ,
HL7800_TCP_DATA_SND,
HL7800_TCP_ID,
HL7800_TCP_RUNNING,
HL7800_TCP_ALL_USED,
HL7800_TCP_TIMEOUT,
HL7800_TCP_SSL_CONN,
HL7800_TCP_SSL_INIT
};
enum udp_notif {
HL7800_UDP_NET_ERR = 0,
HL7800_UDP_NO_SOCKS = 1,
HL7800_UDP_MEM = 2,
HL7800_UDP_DNS = 3,
HL7800_UDP_CONN = 5,
HL7800_UDP_ERR = 6,
HL7800_UDP_DATA_SND = 8, /* this matches TCP_DATA_SND */
HL7800_UDP_ID = 9,
HL7800_UDP_RUNNING = 10,
HL7800_UDP_ALL_USED = 11
};
enum socket_state {
SOCK_IDLE,
SOCK_RX,
SOCK_TX,
SOCK_CONNECTED,
};
enum hl7800_lpm {
HL7800_LPM_NONE,
HL7800_LPM_EDRX,
HL7800_LPM_PSM,
};
/* pin settings */
enum mdm_control_pins {
MDM_RESET = 0,
MDM_WAKE,
MDM_PWR_ON,
MDM_FAST_SHUTD,
MDM_VGPIO,
MDM_UART_DSR,
MDM_UART_CTS,
MDM_GPIO6,
MAX_MDM_CONTROL_PINS,
};
enum net_operator_status { NO_OPERATOR, REGISTERED };
enum device_service_indications {
WDSI_PKG_DOWNLOADED = 3,
};
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
enum XMODEM_CONTROL_CHARACTERS {
XM_SOH = 0x01,
XM_SOH_1K = 0x02,
XM_EOT = 0x04,
XM_ACK = 0x06, /* 'R' */
XM_NACK = 0x15, /* 'N' */
XM_ETB = 0x17,
XM_CAN = 0x18,
XM_C = 0x43
};
#define XMODEM_DATA_SIZE 1024
#define XMODEM_PACKET_SIZE (XMODEM_DATA_SIZE + 4)
#define XMODEM_PAD_VALUE 26
struct xmodem_packet {
uint8_t preamble;
uint8_t id;
uint8_t id_complement;
uint8_t data[XMODEM_DATA_SIZE];
uint8_t crc;
};
#endif
#define MDM_UART_DEV DEVICE_DT_GET(DT_INST_BUS(0))
#define MDM_SEND_OK_ENABLED 0
#define MDM_SEND_OK_DISABLED 1
#define MDM_CMD_SEND_TIMEOUT K_SECONDS(6)
#define MDM_IP_SEND_RX_TIMEOUT K_SECONDS(62)
#define MDM_SOCK_NOTIF_DELAY K_MSEC(150)
#define MDM_CMD_CONN_TIMEOUT K_SECONDS(31)
#define MDM_MAX_DATA_LENGTH 1500
#define MDM_MTU 1500
#define MDM_MAX_RESP_SIZE 128
#define MDM_IP_INFO_RESP_SIZE 256
#define MDM_EID_LENGTH 33
#define MDM_CCID_RESP_MAX_SIZE (MDM_HL7800_ICCID_MAX_SIZE + MDM_EID_LENGTH)
#define MDM_HANDLER_MATCH_MAX_LEN 100
#define MDM_MAX_SOCKETS 6
/* Special value used to indicate that a socket is being created
* and that its actual ID hasn't been assigned yet.
*/
#define MDM_CREATE_SOCKET_ID (MDM_MAX_SOCKETS + 1)
#define MDM_INVALID_SOCKET_ID -1
#define BUF_ALLOC_TIMEOUT K_SECONDS(1)
#define SIZE_OF_NUL 1
#define SIZE_WITHOUT_NUL(v) (sizeof(v) - SIZE_OF_NUL)
#define CMD_HANDLER(cmd_, cb_) \
{ \
.cmd = cmd_, .cmd_len = (uint16_t)sizeof(cmd_) - 1, \
.func = on_cmd_##cb_ \
}
#define MDM_MANUFACTURER_LENGTH 16
#define MDM_MODEL_LENGTH 7
#define MDM_SN_RESPONSE_LENGTH (MDM_HL7800_SERIAL_NUMBER_SIZE + 7)
#define MDM_NETWORK_STATUS_LENGTH 45
#define MDM_TOP_BAND_SIZE 4
#define MDM_MIDDLE_BAND_SIZE 8
#define MDM_BOTTOM_BAND_SIZE 8
#define MDM_TOP_BAND_START_POSITION 2
#define MDM_MIDDLE_BAND_START_POSITION 6
#define MDM_BOTTOM_BAND_START_POSITION 14
#define MDM_BAND_BITMAP_STR_LENGTH_MAX \
(MDM_TOP_BAND_SIZE + MDM_MIDDLE_BAND_SIZE + MDM_BOTTOM_BAND_SIZE)
#define MDM_BAND_BITMAP_STR_LENGTH_MIN 1
#define MDM_DEFAULT_AT_CMD_RETRIES 3
#define MDM_WAKEUP_TIME K_SECONDS(12)
#define MDM_BOOT_TIME K_SECONDS(12)
#define MDM_WAKE_TO_CHECK_CTS_DELAY_MS K_MSEC(20)
#define MDM_WAIT_FOR_DATA_TIME K_MSEC(50)
#define MDM_RESET_LOW_TIME K_MSEC(50)
#define MDM_RESET_HIGH_TIME K_MSEC(10)
#define MDM_WAIT_FOR_DATA_RETRIES 3
#define RSSI_UNKNOWN -999
#define DNS_WORK_DELAY_SECS 1
#define IFACE_WORK_DELAY K_MSEC(500)
#define SOCKET_CLEANUP_WORK_DELAY K_MSEC(100)
#define WAIT_FOR_KSUP_RETRIES 5
#define CGCONTRDP_RESPONSE_NUM_DELIMS 7
#define COPS_RESPONSE_NUM_DELIMS 2
#define KCELLMEAS_RESPONSE_NUM_DELIMS 4
#define PROFILE_LINE_1 \
"E1 Q0 V1 X4 &C1 &D1 &R1 &S0 +IFC=2,2 &K3 +IPR=115200 +FCLASS0\r\n"
#define PROFILE_LINE_2 \
"S00:255 S01:255 S03:255 S04:255 S05:255 S07:255 S08:255 S10:255\r\n"
#define ADDRESS_FAMILY_IP "IP"
#define ADDRESS_FAMILY_IPV4 "IPV4"
#if defined(CONFIG_MODEM_HL7800_ADDRESS_FAMILY_IPV4V6)
#define MODEM_HL7800_ADDRESS_FAMILY "IPV4V6"
#elif defined(CONFIG_MODEM_HL7800_ADDRESS_FAMILY_IPV4)
#define MODEM_HL7800_ADDRESS_FAMILY ADDRESS_FAMILY_IPV4
#else
#define MODEM_HL7800_ADDRESS_FAMILY "IPV6"
#endif
#define MDM_HL7800_SOCKET_AF_IPV4 0
#define MDM_HL7800_SOCKET_AF_IPV6 1
#define SET_RAT_M1_CMD_LEGACY "AT+KSRAT=0"
#define SET_RAT_NB1_CMD_LEGACY "AT+KSRAT=1"
#define SET_RAT_M1_CMD "AT+KSRAT=0,1"
#define SET_RAT_NB1_CMD "AT+KSRAT=1,1"
#define NEW_RAT_CMD_MIN_VERSION "HL7800.4.5.4.0"
#define HL7800_VERSION_FORMAT "HL7800.%zu.%zu.%zu.%zu"
#define MAX_PROFILE_LINE_LENGTH \
MAX(sizeof(PROFILE_LINE_1), sizeof(PROFILE_LINE_2))
#define IPV6_ADDR_FORMAT "####:####:####:####:####:####:####:####"
#define HL7800_IPV6_ADDR_LEN \
sizeof("a01.a02.a03.a04.a05.a06.a07.a08.a09.a10.a11.a12.a13.a14.a15.a16")
#define MDM_ADDR_FAM_MAX_LEN sizeof("IPV4V6")
/* The ? can be a + or - */
static const char TIME_STRING_FORMAT[] = "\"yy/MM/dd,hh:mm:ss?zz\"";
#define TIME_STRING_DIGIT_STRLEN 2
#define TIME_STRING_SEPARATOR_STRLEN 1
#define TIME_STRING_PLUS_MINUS_INDEX (6 * 3)
#define TIME_STRING_FIRST_SEPARATOR_INDEX 0
#define TIME_STRING_FIRST_DIGIT_INDEX 1
#define TIME_STRING_TO_TM_STRUCT_YEAR_OFFSET (2000 - 1900)
/* Time structure min, max */
#define TM_YEAR_RANGE 0, 99
#define TM_MONTH_RANGE_PLUS_1 1, 12
#define TM_DAY_RANGE 1, 31
#define TM_HOUR_RANGE 0, 23
#define TM_MIN_RANGE 0, 59
#define TM_SEC_RANGE 0, 60 /* leap second */
#define QUARTER_HOUR_RANGE 0, 96
#define SECONDS_PER_QUARTER_HOUR (15 * 60)
#define SEND_AT_CMD_ONCE_EXPECT_OK(c) \
do { \
ret = send_at_cmd(NULL, (c), MDM_CMD_SEND_TIMEOUT, 0, false); \
if (ret < 0) { \
LOG_ERR("%s result:%d", (c), ret); \
goto error; \
} \
} while (false)
#define SEND_AT_CMD_IGNORE_ERROR(c) \
do { \
ret = send_at_cmd(NULL, (c), MDM_CMD_SEND_TIMEOUT, 0, false); \
if (ret < 0) { \
LOG_ERR("%s result:%d", (c), ret); \
} \
} while (false)
#define SEND_AT_CMD_EXPECT_OK(c) \
do { \
ret = send_at_cmd(NULL, (c), MDM_CMD_SEND_TIMEOUT, \
MDM_DEFAULT_AT_CMD_RETRIES, false); \
if (ret < 0) { \
LOG_ERR("%s result:%d", (c), ret); \
goto error; \
} \
} while (false)
/* Complex has "no_id_resp" set to true because the sending command
* is the command used to process the response
*/
#define SEND_COMPLEX_AT_CMD(c) \
do { \
ret = send_at_cmd(NULL, (c), MDM_CMD_SEND_TIMEOUT, \
MDM_DEFAULT_AT_CMD_RETRIES, true); \
if (ret < 0) { \
LOG_ERR("%s result:%d", (c), ret); \
goto error; \
} \
} while (false)
NET_BUF_POOL_DEFINE(mdm_recv_pool, CONFIG_MODEM_HL7800_RECV_BUF_CNT,
CONFIG_MODEM_HL7800_RECV_BUF_SIZE, 0, NULL);
static uint8_t mdm_recv_buf[MDM_MAX_DATA_LENGTH];
static K_SEM_DEFINE(hl7800_RX_lock_sem, 1, 1);
static K_SEM_DEFINE(hl7800_TX_lock_sem, 1, 1);
static K_SEM_DEFINE(cb_lock, 1, 1);
/* RX thread structures */
K_THREAD_STACK_DEFINE(hl7800_rx_stack, CONFIG_MODEM_HL7800_RX_STACK_SIZE);
struct k_thread hl7800_rx_thread;
#define RX_THREAD_PRIORITY K_PRIO_COOP(7)
/* RX thread work queue */
K_THREAD_STACK_DEFINE(hl7800_workq_stack,
CONFIG_MODEM_HL7800_RX_WORKQ_STACK_SIZE);
static struct k_work_q hl7800_workq;
#define WORKQ_PRIORITY K_PRIO_COOP(7)
static const char EOF_PATTERN[] = "--EOF--Pattern--";
static const char CONNECT_STRING[] = "CONNECT";
static const char OK_STRING[] = "OK";
struct hl7800_socket {
struct net_context *context;
sa_family_t family;
enum net_sock_type type;
enum net_ip_protocol ip_proto;
struct sockaddr src;
struct sockaddr dst;
bool created;
bool reconfig;
int socket_id;
int rx_size;
int error;
enum socket_state state;
/** semaphore */
struct k_sem sock_send_sem;
/** socket callbacks */
struct k_work recv_cb_work;
struct k_work rx_data_work;
struct k_work_delayable notif_work;
net_context_recv_cb_t recv_cb;
struct net_pkt *recv_pkt;
void *recv_user_data;
};
struct stale_socket {
int reserved; /* first word of queue data item reserved for the kernel */
enum net_sock_type type;
uint8_t id;
bool allocated;
};
#define NO_ID_RESP_CMD_MAX_LENGTH 32
struct hl7800_config {
struct gpio_dt_spec gpio[MAX_MDM_CONTROL_PINS];
};
struct hl7800_iface_ctx {
struct net_if *iface;
uint8_t mac_addr[6];
struct in_addr ipv4Addr, subnet, gateway, dns_v4;
#ifdef CONFIG_NET_IPV6
struct in6_addr ipv6Addr, dns_v6;
char dns_v6_string[HL7800_IPV6_ADDR_LEN];
#endif
bool restarting;
bool initialized;
bool wait_for_KSUP;
uint32_t wait_for_KSUP_tries;
bool reconfig_IP_connection;
char dns_v4_string[NET_IPV4_ADDR_LEN];
char no_id_resp_cmd[NO_ID_RESP_CMD_MAX_LENGTH];
bool search_no_id_resp;
/* GPIO PORT devices */
struct gpio_callback mdm_vgpio_cb;
struct gpio_callback mdm_uart_dsr_cb;
struct gpio_callback mdm_gpio6_cb;
struct gpio_callback mdm_uart_cts_cb;
int vgpio_state;
int dsr_state;
int gpio6_state;
int cts_state;
int last_cts_state;
int last_cts_time;
/* RX specific attributes */
struct mdm_receiver_context mdm_ctx;
/* socket data */
struct hl7800_socket sockets[MDM_MAX_SOCKETS];
int last_socket_id;
int last_error;
struct stale_socket stale_sockets[MDM_MAX_SOCKETS];
struct k_queue stale_socket_queue;
/* semaphores */
struct k_sem response_sem;
struct k_sem mdm_awake;
/* work */
struct k_work_delayable rssi_query_work;
struct k_work_delayable iface_status_work;
struct k_work_delayable dns_work;
struct k_work mdm_vgpio_work;
struct k_work_delayable mdm_reset_work;
struct k_work_delayable allow_sleep_work;
struct k_work_delayable delete_untracked_socket_work;
struct k_work mdm_pwr_off_work;
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
/* firmware update */
enum mdm_hl7800_fota_state fw_update_state;
struct fs_file_t fw_update_file;
struct xmodem_packet fw_packet;
uint32_t fw_packet_count;
int file_pos;
struct k_work finish_fw_update_work;
bool fw_updated;
#endif
/* modem info */
/* NOTE: make sure length is +1 for null char */
char mdm_manufacturer[MDM_MANUFACTURER_LENGTH];
char mdm_model[MDM_MODEL_LENGTH];
char mdm_revision[MDM_HL7800_REVISION_MAX_SIZE];
char mdm_imei[MDM_HL7800_IMEI_SIZE];
char mdm_sn[MDM_HL7800_SERIAL_NUMBER_SIZE];
char mdm_network_status[MDM_NETWORK_STATUS_LENGTH];
char mdm_iccid[MDM_HL7800_ICCID_MAX_SIZE];
enum mdm_hl7800_startup_state mdm_startup_state;
enum mdm_hl7800_radio_mode mdm_rat;
char mdm_active_bands_string[MDM_HL7800_LTE_BAND_STR_SIZE];
char mdm_bands_string[MDM_HL7800_LTE_BAND_STR_SIZE];
char mdm_imsi[MDM_HL7800_IMSI_MAX_STR_SIZE];
int mdm_rssi;
uint16_t mdm_bands_top;
uint32_t mdm_bands_middle;
uint32_t mdm_bands_bottom;
int32_t mdm_sinr;
bool mdm_echo_is_on;
struct mdm_hl7800_apn mdm_apn;
bool mdm_startup_reporting_on;
int device_services_ind;
bool new_rat_cmd_support;
uint8_t operator_index;
enum mdm_hl7800_functionality functionality;
char mdm_pdp_addr_fam[MDM_ADDR_FAM_MAX_LEN];
/* modem state */
bool busy;
bool socket_cmd;
bool allow_sleep;
enum mdm_hl7800_sleep desired_sleep_level;
enum mdm_hl7800_sleep sleep_state;
enum hl7800_lpm low_power_mode;
enum mdm_hl7800_network_state network_state;
bool network_dropped;
bool dns_ready;
enum net_operator_status operator_status;
struct tm local_time;
int32_t local_time_offset;
bool local_time_valid;
bool configured;
bool off;
void (*wake_up_callback)(int state);
void (*gpio6_callback)(int state);
void (*cts_callback)(int state);
#ifdef CONFIG_MODEM_HL7800_GPS
struct k_work_delayable gps_work;
uint32_t gps_query_location_rate_seconds;
#endif
};
struct cmd_handler {
const char *cmd;
uint16_t cmd_len;
bool (*func)(struct net_buf **buf, uint16_t len);
};
static sys_slist_t hl7800_event_callback_list =
SYS_SLIST_STATIC_INIT(&hl7800_event_callback_list);
const static struct hl7800_config hl7800_cfg = {
.gpio = {
GPIO_DT_SPEC_INST_GET(0, mdm_reset_gpios),
GPIO_DT_SPEC_INST_GET(0, mdm_wake_gpios),
GPIO_DT_SPEC_INST_GET(0, mdm_pwr_on_gpios),
GPIO_DT_SPEC_INST_GET(0, mdm_fast_shutd_gpios),
GPIO_DT_SPEC_INST_GET(0, mdm_vgpio_gpios),
GPIO_DT_SPEC_INST_GET(0, mdm_uart_dsr_gpios),
GPIO_DT_SPEC_INST_GET(0, mdm_uart_cts_gpios),
GPIO_DT_SPEC_INST_GET(0, mdm_gpio6_gpios),
},
};
static struct hl7800_iface_ctx iface_ctx;
static size_t hl7800_read_rx(struct net_buf **buf);
static char *get_network_state_string(enum mdm_hl7800_network_state state);
static char *get_startup_state_string(enum mdm_hl7800_startup_state state);
static char *get_sleep_state_string(enum mdm_hl7800_sleep state);
static void set_network_state(enum mdm_hl7800_network_state state);
static void set_startup_state(enum mdm_hl7800_startup_state state);
static void set_sleep_state(enum mdm_hl7800_sleep state);
static void generate_network_state_event(void);
static void generate_startup_state_event(void);
static void generate_sleep_state_event(void);
static int modem_boot_handler(char *reason);
static void mdm_vgpio_work_cb(struct k_work *item);
static void mdm_reset_work_callback(struct k_work *item);
static void mdm_power_off_work_callback(struct k_work *item);
static int write_apn(char *access_point_name);
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
static void mark_sockets_for_reconfig(void);
#endif
static void hl7800_build_mac(struct hl7800_iface_ctx *ictx);
static void rssi_query(void);
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
static void initialize_sleep_level(void);
static int set_sleep_level(void);
#endif
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
static char *get_fota_state_string(enum mdm_hl7800_fota_state state);
static void set_fota_state(enum mdm_hl7800_fota_state state);
static void generate_fota_state_event(void);
static void generate_fota_count_event(void);
#endif
static struct stale_socket *alloc_stale_socket(void)
{
struct stale_socket *sock = NULL;
for (int i = 0; i < MDM_MAX_SOCKETS; i++) {
if (!iface_ctx.stale_sockets[i].allocated) {
sock = &iface_ctx.stale_sockets[i];
sock->allocated = true;
break;
}
}
return sock;
}
static void free_stale_socket(struct stale_socket *sock)
{
if (sock != NULL) {
sock->allocated = false;
}
}
static int queue_stale_socket(enum net_sock_type type, uint8_t id)
{
int ret = 0;
struct stale_socket *sock = NULL;
sock = alloc_stale_socket();
if (sock != NULL) {
LOG_DBG("Queueing stale socket %d", id);
sock->type = type;
sock->id = id;
k_queue_append(&iface_ctx.stale_socket_queue, (void *)sock);
} else {
LOG_ERR("Could not alloc stale socket");
ret = -ENOMEM;
}
return ret;
}
static struct stale_socket *dequeue_stale_socket(void)
{
struct stale_socket *sock = NULL;
sock = (struct stale_socket *)k_queue_get(&iface_ctx.stale_socket_queue, K_NO_WAIT);
return sock;
}
static bool convert_time_string_to_struct(struct tm *tm, int32_t *offset,
char *time_string);
static int modem_reset_and_configure(void);
static int read_pin(int default_state, const struct gpio_dt_spec *spec)
{
int state = gpio_pin_get_raw(spec->port, spec->pin);
if (state < 0) {
LOG_ERR("Unable to read port: %s pin: %d status: %d",
spec->port->name, spec->pin, state);
state = default_state;
}
return state;
}
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
static bool is_cmd_ready(void)
{
iface_ctx.vgpio_state = read_pin(0, &hl7800_cfg.gpio[MDM_VGPIO]);
iface_ctx.gpio6_state = read_pin(0, &hl7800_cfg.gpio[MDM_GPIO6]);
iface_ctx.cts_state = read_pin(1, &hl7800_cfg.gpio[MDM_UART_CTS]);
return iface_ctx.vgpio_state && iface_ctx.gpio6_state && !iface_ctx.cts_state;
}
#endif
/**
* The definition of awake is that the HL7800
* is ready to receive AT commands successfully
*/
static void check_hl7800_awake(void)
{
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
bool is_cmd_rdy = is_cmd_ready();
if (is_cmd_rdy && (iface_ctx.sleep_state != HL7800_SLEEP_AWAKE) &&
!iface_ctx.allow_sleep && !iface_ctx.wait_for_KSUP) {
PRINT_AWAKE_MSG;
set_sleep_state(HL7800_SLEEP_AWAKE);
k_sem_give(&iface_ctx.mdm_awake);
} else if (!is_cmd_rdy && iface_ctx.sleep_state == HL7800_SLEEP_AWAKE &&
iface_ctx.allow_sleep) {
PRINT_NOT_AWAKE_MSG;
if (iface_ctx.desired_sleep_level == HL7800_SLEEP_HIBERNATE ||
iface_ctx.desired_sleep_level == HL7800_SLEEP_LITE_HIBERNATE) {
/* If the device is sleeping (not ready to receive commands)
* then the device may send +KSUP when waking up.
* We should wait for it.
*/
iface_ctx.wait_for_KSUP = true;
iface_ctx.wait_for_KSUP_tries = 0;
set_sleep_state(iface_ctx.desired_sleep_level);
} else if (iface_ctx.desired_sleep_level == HL7800_SLEEP_SLEEP) {
set_sleep_state(HL7800_SLEEP_SLEEP);
}
}
#endif
}
static int hl7800_RX_lock(void)
{
HL7800_RX_LOCK_DBG_LOG("Locking RX [%p]...", k_current_get());
int rc = k_sem_take(&hl7800_RX_lock_sem, K_FOREVER);
if (rc != 0) {
LOG_ERR("Unable to lock hl7800 (%d)", rc);
} else {
HL7800_RX_LOCK_DBG_LOG("Locked RX [%p]", k_current_get());
}
return rc;
}
static void hl7800_RX_unlock(void)
{
HL7800_RX_LOCK_DBG_LOG("UNLocking RX [%p]...", k_current_get());
k_sem_give(&hl7800_RX_lock_sem);
HL7800_RX_LOCK_DBG_LOG("UNLocked RX [%p]", k_current_get());
}
static bool hl7800_RX_locked(void)
{
if (k_sem_count_get(&hl7800_RX_lock_sem) == 0) {
return true;
} else {
return false;
}
}
static int hl7800_TX_lock(void)
{
HL7800_TX_LOCK_DBG_LOG("Locking TX [%p]...", k_current_get());
int rc = k_sem_take(&hl7800_TX_lock_sem, K_FOREVER);
if (rc != 0) {
LOG_ERR("Unable to lock hl7800 (%d)", rc);
} else {
HL7800_TX_LOCK_DBG_LOG("Locked TX [%p]", k_current_get());
}
return rc;
}
static void hl7800_TX_unlock(void)
{
HL7800_TX_LOCK_DBG_LOG("UNLocking TX [%p]...", k_current_get());
k_sem_give(&hl7800_TX_lock_sem);
HL7800_TX_LOCK_DBG_LOG("UNLocked TX [%p]", k_current_get());
}
static bool hl7800_TX_locked(void)
{
if (k_sem_count_get(&hl7800_TX_lock_sem) == 0) {
return true;
} else {
return false;
}
}
static void hl7800_lock(void)
{
hl7800_TX_lock();
hl7800_RX_lock();
}
static void hl7800_unlock(void)
{
hl7800_RX_unlock();
hl7800_TX_unlock();
}
static struct hl7800_socket *socket_get(void)
{
int i;
struct hl7800_socket *sock = NULL;
for (i = 0; i < MDM_MAX_SOCKETS; i++) {
if (!iface_ctx.sockets[i].context) {
sock = &iface_ctx.sockets[i];
break;
}
}
return sock;
}
static struct hl7800_socket *socket_from_id(int socket_id)
{
int i;
struct hl7800_socket *sock = NULL;
if (socket_id < 1) {
return NULL;
}
for (i = 0; i < MDM_MAX_SOCKETS; i++) {
if (iface_ctx.sockets[i].socket_id == socket_id) {
sock = &iface_ctx.sockets[i];
break;
}
}
return sock;
}
static inline void set_busy(bool busy)
{
iface_ctx.busy = busy;
}
static void socket_put(struct hl7800_socket *sock)
{
if (!sock) {
return;
}
sock->context = NULL;
sock->socket_id = MDM_INVALID_SOCKET_ID;
sock->created = false;
sock->reconfig = false;
sock->error = 0;
sock->rx_size = 0;
sock->state = SOCK_IDLE;
(void)memset(&sock->src, 0, sizeof(struct sockaddr));
(void)memset(&sock->dst, 0, sizeof(struct sockaddr));
}
char *hl7800_sprint_ip_addr(const struct sockaddr *addr)
{
static char buf[NET_IPV6_ADDR_LEN];
#if defined(CONFIG_NET_IPV6)
if (addr->sa_family == AF_INET6) {
return net_addr_ntop(AF_INET6, &net_sin6(addr)->sin6_addr, buf,
sizeof(buf));
} else
#endif
#if defined(CONFIG_NET_IPV4)
if (addr->sa_family == AF_INET) {
return net_addr_ntop(AF_INET, &net_sin(addr)->sin_addr, buf,
sizeof(buf));
} else
#endif
{
LOG_ERR("Unknown IP address family:%d", addr->sa_family);
return NULL;
}
}
void mdm_hl7800_register_wake_test_point_callback(void (*func)(int state))
{
iface_ctx.wake_up_callback = func;
}
void mdm_hl7800_register_gpio6_callback(void (*func)(int state))
{
iface_ctx.gpio6_callback = func;
}
void mdm_hl7800_register_cts_callback(void (*func)(int state))
{
iface_ctx.cts_callback = func;
}
static void modem_assert_reset(bool assert)
{
if (assert) {
HL7800_IO_DBG_LOG("MDM_RESET -> ASSERTED");
gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_RESET], 1);
} else {
HL7800_IO_DBG_LOG("MDM_RESET -> NOT_ASSERTED");
gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_RESET], 0);
}
}
static void modem_assert_wake(bool assert)
{
int state;
if (assert) {
HL7800_IO_DBG_LOG("MDM_WAKE_PIN -> ASSERTED");
state = 1;
} else {
HL7800_IO_DBG_LOG("MDM_WAKE_PIN -> NOT_ASSERTED");
state = 0;
}
gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_WAKE], state);
if (iface_ctx.wake_up_callback != NULL) {
iface_ctx.wake_up_callback(state);
}
}
static void modem_assert_pwr_on(bool assert)
{
if (assert) {
HL7800_IO_DBG_LOG("MDM_PWR_ON -> ASSERTED");
gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_PWR_ON], 1);
} else {
HL7800_IO_DBG_LOG("MDM_PWR_ON -> NOT_ASSERTED");
gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_PWR_ON], 0);
}
}
static void modem_assert_fast_shutd(bool assert)
{
if (assert) {
HL7800_IO_DBG_LOG("MDM_FAST_SHUTD -> ASSERTED");
gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_FAST_SHUTD], 1);
} else {
HL7800_IO_DBG_LOG("MDM_FAST_SHUTD -> NOT_ASSERTED");
gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_FAST_SHUTD], 0);
}
}
static void allow_sleep_work_callback(struct k_work *item)
{
ARG_UNUSED(item);
if (!iface_ctx.busy) {
LOG_DBG("Allow sleep");
iface_ctx.allow_sleep = true;
set_sleep_state(iface_ctx.desired_sleep_level);
modem_assert_wake(false);
} else {
k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.allow_sleep_work,
K_MSEC(CONFIG_MODEM_HL7800_ALLOW_SLEEP_DELAY_MS));
}
}
static void allow_sleep(bool allow)
{
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
if (allow) {
if (!iface_ctx.restarting && !iface_ctx.busy) {
k_work_reschedule_for_queue(
&hl7800_workq, &iface_ctx.allow_sleep_work,
K_MSEC(CONFIG_MODEM_HL7800_ALLOW_SLEEP_DELAY_MS));
} else {
k_work_cancel_delayable(&iface_ctx.allow_sleep_work);
}
} else {
LOG_DBG("Keep awake");
k_work_cancel_delayable(&iface_ctx.allow_sleep_work);
iface_ctx.allow_sleep = false;
modem_assert_wake(true);
}
#endif
}
static void event_handler(enum mdm_hl7800_event event, void *event_data)
{
sys_snode_t *node;
struct mdm_hl7800_callback_agent *agent;
k_sem_take(&cb_lock, K_FOREVER);
SYS_SLIST_FOR_EACH_NODE(&hl7800_event_callback_list, node) {
agent = CONTAINER_OF(node, struct mdm_hl7800_callback_agent, node);
if (agent->event_callback != NULL) {
agent->event_callback(event, event_data);
}
}
k_sem_give(&cb_lock);
}
void mdm_hl7800_get_signal_quality(int *rsrp, int *sinr)
{
if (CONFIG_MODEM_HL7800_RSSI_RATE_SECONDS == 0) {
rssi_query();
}
*rsrp = iface_ctx.mdm_rssi;
*sinr = iface_ctx.mdm_sinr;
}
void mdm_hl7800_wakeup(bool wakeup)
{
allow_sleep(!wakeup);
}
/* Send an AT command with a series of response handlers */
static int send_at_cmd(struct hl7800_socket *sock, const uint8_t *data,
k_timeout_t timeout, int retries, bool no_id_resp)
{
int ret = 0;
iface_ctx.last_error = 0;
do {
if (!sock) {
k_sem_reset(&iface_ctx.response_sem);
iface_ctx.last_socket_id = 0;
iface_ctx.socket_cmd = false;
} else {
sock->error = 0;
iface_ctx.socket_cmd = true;
k_sem_reset(&sock->sock_send_sem);
iface_ctx.last_socket_id = sock->socket_id;
}
if (no_id_resp) {
strncpy(iface_ctx.no_id_resp_cmd, data,
sizeof(iface_ctx.no_id_resp_cmd) - 1);
iface_ctx.search_no_id_resp = true;
}
LOG_DBG("OUT: [%s]", (char *)data);
mdm_receiver_send(&iface_ctx.mdm_ctx, data, strlen(data));
mdm_receiver_send(&iface_ctx.mdm_ctx, "\r", 1);
if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) {
goto done;
}
if (!sock) {
ret = k_sem_take(&iface_ctx.response_sem, timeout);
} else {
ret = k_sem_take(&sock->sock_send_sem, timeout);
}
if (ret == 0) {
if (sock) {
ret = sock->error;
} else {
ret = iface_ctx.last_error;
}
} else if (ret == -EAGAIN) {
ret = -ETIMEDOUT;
}
retries--;
if (retries < 0) {
retries = 0;
}
} while (ret != 0 && retries > 0);
done:
iface_ctx.search_no_id_resp = false;
return ret;
}
static int wakeup_hl7800(void)
{
set_busy(true);
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
int ret;
allow_sleep(false);
/* If modem is in sleep mode (not hibernate),
* then it can respond in ~10 ms.
*/
if (iface_ctx.desired_sleep_level == HL7800_SLEEP_SLEEP) {
k_sleep(MDM_WAKE_TO_CHECK_CTS_DELAY_MS);
}
if (!is_cmd_ready()) {
LOG_DBG("Waiting to wakeup");
ret = k_sem_take(&iface_ctx.mdm_awake, MDM_WAKEUP_TIME);
if (ret) {
LOG_DBG("Err waiting for wakeup: %d", ret);
}
}
#endif
return 0;
}
int32_t mdm_hl7800_send_at_cmd(const uint8_t *data)
{
int ret;
if (!data) {
return -EINVAL;
}
hl7800_lock();
wakeup_hl7800();
iface_ctx.last_socket_id = 0;
ret = send_at_cmd(NULL, data, MDM_CMD_SEND_TIMEOUT, 0, false);
set_busy(false);
allow_sleep(true);
hl7800_unlock();
return ret;
}
/* The access point name (and username and password) are stored in the modem's
* non-volatile memory.
*/
int32_t mdm_hl7800_update_apn(char *access_point_name)
{
int ret = -EINVAL;
hl7800_lock();
wakeup_hl7800();
iface_ctx.last_socket_id = 0;
ret = write_apn(access_point_name);
set_busy(false);
allow_sleep(true);
hl7800_unlock();
if (ret >= 0) {
/* After a reset the APN will be re-read from the modem
* and an event will be generated.
*/
k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.mdm_reset_work,
K_NO_WAIT);
}
return ret;
}
bool mdm_hl7800_valid_rat(uint8_t value)
{
if ((value == MDM_RAT_CAT_M1) || (value == MDM_RAT_CAT_NB1)) {
return true;
}
return false;
}
int32_t mdm_hl7800_update_rat(enum mdm_hl7800_radio_mode value)
{
int ret = -EINVAL;
if (value == iface_ctx.mdm_rat) {
/* The set command will fail (in the modem)
* if the RAT isn't different.
*/
return 0;
} else if (!mdm_hl7800_valid_rat(value)) {
return ret;
}
hl7800_lock();
wakeup_hl7800();
iface_ctx.last_socket_id = 0;
if (value == MDM_RAT_CAT_M1) {
if (iface_ctx.new_rat_cmd_support) {
SEND_AT_CMD_ONCE_EXPECT_OK(SET_RAT_M1_CMD);
} else {
SEND_AT_CMD_ONCE_EXPECT_OK(SET_RAT_M1_CMD_LEGACY);
}
} else { /* MDM_RAT_CAT_NB1 */
if (iface_ctx.new_rat_cmd_support) {
SEND_AT_CMD_ONCE_EXPECT_OK(SET_RAT_NB1_CMD);
} else {
SEND_AT_CMD_ONCE_EXPECT_OK(SET_RAT_NB1_CMD_LEGACY);
}
}
error:
set_busy(false);
allow_sleep(true);
hl7800_unlock();
/* Changing the RAT causes the modem to reset.
* A reset and reconfigure ensures the modem configuration and
* state are valid.
*/
if (ret >= 0) {
k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.mdm_reset_work, K_NO_WAIT);
}
return ret;
}
int32_t mdm_hl7800_get_local_time(struct tm *tm, int32_t *offset)
{
int ret;
iface_ctx.local_time_valid = false;
hl7800_lock();
wakeup_hl7800();
iface_ctx.last_socket_id = 0;
ret = send_at_cmd(NULL, "AT+CCLK?", MDM_CMD_SEND_TIMEOUT, 0, false);
set_busy(false);
allow_sleep(true);
if (iface_ctx.local_time_valid) {
memcpy(tm, &iface_ctx.local_time, sizeof(struct tm));
memcpy(offset, &iface_ctx.local_time_offset, sizeof(*offset));
} else {
ret = -EIO;
}
hl7800_unlock();
return ret;
}
int32_t mdm_hl7800_get_operator_index(void)
{
int ret;
hl7800_lock();
wakeup_hl7800();
iface_ctx.last_socket_id = 0;
ret = send_at_cmd(NULL, "AT+KCARRIERCFG?", MDM_CMD_SEND_TIMEOUT, 0,
false);
set_busy(false);
allow_sleep(true);
hl7800_unlock();
if (ret < 0) {
return ret;
} else {
return iface_ctx.operator_index;
}
}
int32_t mdm_hl7800_get_functionality(void)
{
int ret;
hl7800_lock();
wakeup_hl7800();
iface_ctx.last_socket_id = 0;
ret = send_at_cmd(NULL, "AT+CFUN?", MDM_CMD_SEND_TIMEOUT, 0, false);
set_busy(false);
allow_sleep(true);
hl7800_unlock();
if (ret < 0) {
return ret;
} else {
return iface_ctx.functionality;
}
}
int32_t mdm_hl7800_set_functionality(enum mdm_hl7800_functionality mode)
{
int ret;
char buf[sizeof("AT+CFUN=###,0")] = { 0 };
hl7800_lock();
wakeup_hl7800();
snprintk(buf, sizeof(buf), "AT+CFUN=%u,0", mode);
iface_ctx.last_socket_id = 0;
ret = send_at_cmd(NULL, buf, MDM_CMD_SEND_TIMEOUT,
MDM_DEFAULT_AT_CMD_RETRIES, false);
set_busy(false);
allow_sleep(true);
hl7800_unlock();
return ret;
}
#ifdef CONFIG_MODEM_HL7800_GPS
int32_t mdm_hl7800_set_gps_rate(uint32_t rate)
{
int ret = -1;
hl7800_lock();
wakeup_hl7800();
iface_ctx.gps_query_location_rate_seconds = rate;
/* Stopping first allows changing the rate between two non-zero values.
* Ignore error if GNSS isn't running.
*/
SEND_AT_CMD_IGNORE_ERROR("AT+GNSSSTOP");
if (rate == 0) {
SEND_AT_CMD_EXPECT_OK("AT+CFUN=1,0");
} else {
/* Navigation doesn't work when LTE is on. */
SEND_AT_CMD_EXPECT_OK("AT+CFUN=4,0");
SEND_AT_CMD_EXPECT_OK("AT+GNSSCONF=1,1");
if (IS_ENABLED(CONFIG_MODEM_HL7800_USE_GLONASS)) {
SEND_AT_CMD_EXPECT_OK("AT+GNSSCONF=10,1");
}
/* Enable all NMEA sentences */
SEND_AT_CMD_EXPECT_OK("AT+GNSSNMEA=0,1000,0,1FF");
/* Enable GPS */
SEND_AT_CMD_EXPECT_OK("AT+GNSSSTART=0");
}
error:
if (rate && ret == 0) {
k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.gps_work,
K_SECONDS(iface_ctx.gps_query_location_rate_seconds));
} else {
k_work_cancel_delayable(&iface_ctx.gps_work);
}
LOG_DBG("GPS status: %d rate: %u", ret, rate);
set_busy(false);
allow_sleep(true);
hl7800_unlock();
return ret;
}
#endif /* CONFIG_MODEM_HL7800_GPS */
#ifdef CONFIG_MODEM_HL7800_POLTE
int32_t mdm_hl7800_polte_register(void)
{
int ret = -1;
hl7800_lock();
wakeup_hl7800();
/* register for events */
SEND_AT_CMD_EXPECT_OK("AT%POLTEEV=\"REGISTER\",1");
SEND_AT_CMD_EXPECT_OK("AT%POLTEEV=\"LOCATION\",1");
/* register with polte.io */
SEND_AT_CMD_EXPECT_OK("AT%POLTECMD=\"REGISTER\"");
error:
LOG_DBG("PoLTE register status: %d", ret);
set_busy(false);
allow_sleep(true);
hl7800_unlock();
return ret;
}
int32_t mdm_hl7800_polte_enable(char *user, char *password)
{
int ret = -1;
char buf[sizeof(MDM_HL7800_SET_POLTE_USER_AND_PASSWORD_FMT_STR) +
MDM_HL7800_MAX_POLTE_USER_ID_SIZE + MDM_HL7800_MAX_POLTE_PASSWORD_SIZE] = { 0 };
hl7800_lock();
wakeup_hl7800();
/* register for events */
SEND_AT_CMD_EXPECT_OK("AT%POLTEEV=\"REGISTER\",1");
SEND_AT_CMD_EXPECT_OK("AT%POLTEEV=\"LOCATION\",1");
/* restore user and password (not saved in NV by modem) */
snprintk(buf, sizeof(buf), MDM_HL7800_SET_POLTE_USER_AND_PASSWORD_FMT_STR, user, password);
ret = send_at_cmd(NULL, buf, MDM_CMD_SEND_TIMEOUT, MDM_DEFAULT_AT_CMD_RETRIES, false);
error:
LOG_DBG("PoLTE register status: %d", ret);
set_busy(false);
allow_sleep(true);
hl7800_unlock();
return ret;
}
int32_t mdm_hl7800_polte_locate(void)
{
int ret = -1;
hl7800_lock();
wakeup_hl7800();
SEND_AT_CMD_EXPECT_OK("AT%POLTECMD=\"LOCATE\",2,1");
error:
LOG_DBG("PoLTE locate status: %d", ret);
set_busy(false);
allow_sleep(true);
hl7800_unlock();
return ret;
}
#endif /* CONFIG_MODEM_HL7800_POLTE */
/**
* @brief Perform a site survey.
*
*/
int32_t mdm_hl7800_perform_site_survey(void)
{
int ret;
hl7800_lock();
wakeup_hl7800();
ret = send_at_cmd(NULL, "at%meas=\"97\"", MDM_CMD_SEND_TIMEOUT, 0, false);
set_busy(false);
allow_sleep(true);
hl7800_unlock();
return ret;
}
void mdm_hl7800_generate_status_events(void)
{
hl7800_lock();
generate_startup_state_event();
generate_network_state_event();
generate_sleep_state_event();
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
generate_fota_state_event();
#endif
event_handler(HL7800_EVENT_RSSI, &iface_ctx.mdm_rssi);
event_handler(HL7800_EVENT_SINR, &iface_ctx.mdm_sinr);
event_handler(HL7800_EVENT_APN_UPDATE, &iface_ctx.mdm_apn);
event_handler(HL7800_EVENT_RAT, &iface_ctx.mdm_rat);
event_handler(HL7800_EVENT_BANDS, iface_ctx.mdm_bands_string);
event_handler(HL7800_EVENT_ACTIVE_BANDS, iface_ctx.mdm_active_bands_string);
event_handler(HL7800_EVENT_REVISION, iface_ctx.mdm_revision);
hl7800_unlock();
}
uint32_t mdm_hl7800_log_filter_set(uint32_t level)
{
uint32_t new_log_level = 0;
#ifdef CONFIG_LOG_RUNTIME_FILTERING
new_log_level =
log_filter_set(NULL, Z_LOG_LOCAL_DOMAIN_ID,
log_source_id_get(STRINGIFY(LOG_MODULE_NAME)),
level);
#endif
return new_log_level;
}
static int send_data(struct hl7800_socket *sock, struct net_pkt *pkt)
{
int ret;
struct net_buf *frag;
char dst_addr[NET_IPV6_ADDR_LEN];
char buf[sizeof("AT+KUDPSND=##,\"" IPV6_ADDR_FORMAT "\",#####,####")];
size_t send_len, actual_send_len;
send_len = 0, actual_send_len = 0;
if (!sock) {
return -EINVAL;
}
sock->error = 0;
sock->state = SOCK_TX;
frag = pkt->frags;
send_len = net_buf_frags_len(frag);
/* start sending data */
k_sem_reset(&sock->sock_send_sem);
if (sock->type == SOCK_STREAM) {
snprintk(buf, sizeof(buf), "AT+KTCPSND=%d,%zu", sock->socket_id,
send_len);
} else {
if (!net_addr_ntop(sock->family, &net_sin(&sock->dst)->sin_addr,
dst_addr, sizeof(dst_addr))) {
LOG_ERR("Invalid dst addr");
return -EINVAL;
}
snprintk(buf, sizeof(buf), "AT+KUDPSND=%d,\"%s\",%u,%zu",
sock->socket_id, dst_addr,
net_sin(&sock->dst)->sin_port, send_len);
}
send_at_cmd(sock, buf, K_NO_WAIT, 0, false);
/* wait for CONNECT or error */
ret = k_sem_take(&sock->sock_send_sem, MDM_IP_SEND_RX_TIMEOUT);
if (ret) {
LOG_ERR("Err waiting for CONNECT (%d)", ret);
goto done;
}
/* check for error */
if (sock->error != 0) {
ret = sock->error;
LOG_ERR("AT+K**PSND (%d)", ret);
goto done;
}
/* Loop through packet data and send */
while (frag) {
actual_send_len += frag->len;
mdm_receiver_send(&iface_ctx.mdm_ctx, frag->data, frag->len);
frag = frag->frags;
}
if (actual_send_len != send_len) {
LOG_WRN("AT+K**PSND act: %zd exp: %zd", actual_send_len,
send_len);
}
LOG_DBG("Sent %zu bytes", actual_send_len);
/* Send EOF pattern to terminate data */
k_sem_reset(&sock->sock_send_sem);
mdm_receiver_send(&iface_ctx.mdm_ctx, EOF_PATTERN, strlen(EOF_PATTERN));
ret = k_sem_take(&sock->sock_send_sem, MDM_IP_SEND_RX_TIMEOUT);
if (ret == 0) {
ret = sock->error;
} else if (ret == -EAGAIN) {
ret = -ETIMEDOUT;
}
done:
if (sock->type == SOCK_STREAM) {
if (sock->error == 0) {
sock->state = SOCK_CONNECTED;
}
} else {
sock->state = SOCK_IDLE;
}
return ret;
}
/*** NET_BUF HELPERS ***/
static bool is_crlf(uint8_t c)
{
if (c == '\n' || c == '\r') {
return true;
} else {
return false;
}
}
static void net_buf_skipcrlf(struct net_buf **buf)
{
/* chop off any /n or /r */
while (*buf && is_crlf(*(*buf)->data)) {
net_buf_pull_u8(*buf);
if (!(*buf)->len) {
*buf = net_buf_frag_del(NULL, *buf);
}
}
}
static uint16_t net_buf_findcrlf(struct net_buf *buf, struct net_buf **frag)
{
uint16_t len = 0U, pos = 0U;
while (buf && !is_crlf(*(buf->data + pos))) {
if (pos + 1 >= buf->len) {
len += buf->len;
buf = buf->frags;
pos = 0U;
} else {
pos++;
}
}
if (buf && is_crlf(*(buf->data + pos))) {
len += pos;
*frag = buf;
return len;
}
return 0;
}
static uint8_t net_buf_get_u8(struct net_buf **buf)
{
uint8_t val = net_buf_pull_u8(*buf);
if (!(*buf)->len) {
*buf = net_buf_frag_del(NULL, *buf);
}
return val;
}
static uint32_t net_buf_remove(struct net_buf **buf, uint32_t len)
{
uint32_t to_remove;
uint32_t removed = 0;
while (*buf && len > 0) {
to_remove = (*buf)->len;
if (to_remove > len) {
to_remove = len;
}
net_buf_pull(*buf, to_remove);
removed += to_remove;
len -= to_remove;
if (!(*buf)->len) {
*buf = net_buf_frag_del(NULL, *buf);
}
}
return removed;
}
/*** UDP / TCP Helper Function ***/
/* Setup IP header data to be used by some network applications.
* While much is dummy data, some fields such as dst, port and family are
* important.
* Return the IP + protocol header length.
*/
static int pkt_setup_ip_data(struct net_pkt *pkt, struct hl7800_socket *sock)
{
int hdr_len = 0;
uint16_t src_port = 0U, dst_port = 0U;
#if defined(CONFIG_NET_TCP)
struct net_tcp_hdr *tcp;
#endif
#if defined(CONFIG_NET_IPV6)
if (net_pkt_family(pkt) == AF_INET6) {
if (net_ipv6_create(
pkt,
&((struct sockaddr_in6 *)&sock->dst)->sin6_addr,
&((struct sockaddr_in6 *)&sock->src)->sin6_addr)) {
return -1;
}
src_port = ntohs(net_sin6(&sock->src)->sin6_port);
dst_port = ntohs(net_sin6(&sock->dst)->sin6_port);
hdr_len = sizeof(struct net_ipv6_hdr);
}
#endif
#if defined(CONFIG_NET_IPV4)
if (net_pkt_family(pkt) == AF_INET) {
if (net_ipv4_create(
pkt, &((struct sockaddr_in *)&sock->dst)->sin_addr,
&((struct sockaddr_in *)&sock->src)->sin_addr)) {
return -1;
}
src_port = ntohs(net_sin(&sock->src)->sin_port);
dst_port = ntohs(net_sin(&sock->dst)->sin_port);
hdr_len = sizeof(struct net_ipv4_hdr);
}
#endif
#if defined(CONFIG_NET_UDP)
if (sock->ip_proto == IPPROTO_UDP) {
if (net_udp_create(pkt, dst_port, src_port)) {
return -1;
}
hdr_len += NET_UDPH_LEN;
}
#endif
#if defined(CONFIG_NET_TCP)
if (sock->ip_proto == IPPROTO_TCP) {
NET_PKT_DATA_ACCESS_DEFINE(tcp_access, struct net_tcp_hdr);
tcp = (struct net_tcp_hdr *)net_pkt_get_data(pkt, &tcp_access);
if (!tcp) {
return -1;
}
(void)memset(tcp, 0, NET_TCPH_LEN);
/* Setup TCP header */
tcp->src_port = dst_port;
tcp->dst_port = src_port;
if (net_pkt_set_data(pkt, &tcp_access)) {
return -1;
}
hdr_len += NET_TCPH_LEN;
}
#endif /* CONFIG_NET_TCP */
return hdr_len;
}
/*** MODEM RESPONSE HANDLERS ***/
static uint32_t wait_for_modem_data(struct net_buf **buf, uint32_t current_len,
uint32_t expected_len)
{
uint32_t waitForDataTries = 0;
while ((current_len < expected_len) &&
(waitForDataTries < MDM_WAIT_FOR_DATA_RETRIES)) {
LOG_DBG("cur:%d, exp:%d", current_len, expected_len);
k_sleep(MDM_WAIT_FOR_DATA_TIME);
current_len += hl7800_read_rx(buf);
waitForDataTries++;
}
return current_len;
}
static uint32_t wait_for_modem_data_and_newline(struct net_buf **buf,
uint32_t current_len,
uint32_t expected_len)
{
return wait_for_modem_data(buf, current_len,
(expected_len + strlen("\r\n")));
}
/* Handler: AT+CGMI */
static bool on_cmd_atcmdinfo_manufacturer(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
size_t out_len;
int len_no_null = MDM_MANUFACTURER_LENGTH - 1;
/* make sure revision data is received
* waiting for: Sierra Wireless\r\n
*/
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
MDM_MANUFACTURER_LENGTH);
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find mfg end");
goto done;
}
if (len < len_no_null) {
LOG_WRN("mfg too short (len:%d)", len);
} else if (len > len_no_null) {
LOG_WRN("mfg too long (len:%d)", len);
len = MDM_MANUFACTURER_LENGTH;
}
out_len = net_buf_linearize(iface_ctx.mdm_manufacturer,
sizeof(iface_ctx.mdm_manufacturer) - 1, *buf, 0,
len);
iface_ctx.mdm_manufacturer[out_len] = 0;
LOG_INF("Manufacturer: %s", iface_ctx.mdm_manufacturer);
done:
return true;
}
/* Handler: AT+CGMM */
static bool on_cmd_atcmdinfo_model(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
size_t out_len;
int len_no_null = MDM_MODEL_LENGTH - 1;
/* make sure model data is received
* waiting for: HL7800\r\n
*/
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
MDM_MODEL_LENGTH);
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find model end");
goto done;
}
if (len < len_no_null) {
LOG_WRN("model too short (len:%d)", len);
} else if (len > len_no_null) {
LOG_WRN("model too long (len:%d)", len);
len = MDM_MODEL_LENGTH;
}
out_len = net_buf_linearize(iface_ctx.mdm_model, sizeof(iface_ctx.mdm_model) - 1,
*buf, 0, len);
iface_ctx.mdm_model[out_len] = 0;
LOG_INF("Model: %s", iface_ctx.mdm_model);
done:
return true;
}
/* Handler: AT+CGMR */
static bool on_cmd_atcmdinfo_revision(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
size_t out_len;
/* make sure revision data is received
* waiting for something like: AHL7800.1.2.3.1.20171211\r\n
*/
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
MDM_HL7800_REVISION_MAX_SIZE);
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find rev end");
goto done;
}
if (len == 0) {
LOG_WRN("revision not found");
} else if (len > MDM_HL7800_REVISION_MAX_STRLEN) {
LOG_WRN("revision too long (len:%d)", len);
len = MDM_HL7800_REVISION_MAX_STRLEN;
}
out_len = net_buf_linearize(
iface_ctx.mdm_revision, sizeof(iface_ctx.mdm_revision) - 1, *buf, 0, len);
iface_ctx.mdm_revision[out_len] = 0;
LOG_INF("Revision: %s", iface_ctx.mdm_revision);
event_handler(HL7800_EVENT_REVISION, iface_ctx.mdm_revision);
done:
return true;
}
/* Handler: AT+CGSN */
static bool on_cmd_atcmdinfo_imei(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
size_t out_len;
/* make sure IMEI data is received
* waiting for: ###############\r\n
*/
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
MDM_HL7800_IMEI_SIZE);
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find IMEI end");
goto done;
}
if (len < MDM_HL7800_IMEI_STRLEN) {
LOG_WRN("IMEI too short (len:%d)", len);
} else if (len > MDM_HL7800_IMEI_STRLEN) {
LOG_WRN("IMEI too long (len:%d)", len);
len = MDM_HL7800_IMEI_STRLEN;
}
out_len = net_buf_linearize(iface_ctx.mdm_imei, sizeof(iface_ctx.mdm_imei) - 1,
*buf, 0, len);
iface_ctx.mdm_imei[out_len] = 0;
LOG_INF("IMEI: %s", iface_ctx.mdm_imei);
done:
return true;
}
/* Handler: +CCID: <ICCID>,<EID>
* NOTE: EID will only be present for eSIM
*/
static bool on_cmd_atcmdinfo_iccid(struct net_buf **buf, uint16_t len)
{
char value[MDM_CCID_RESP_MAX_SIZE];
char *delim;
int iccid_len;
size_t out_len;
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
value[out_len] = 0;
LOG_DBG("+CCID: %s", value);
if (len > MDM_HL7800_ICCID_MAX_STRLEN) {
delim = strchr(value, ',');
if (!delim) {
LOG_ERR("Could not process +CCID");
return true;
}
/* Replace ',' with null so value contains two null terminated strings */
*delim = 0;
LOG_INF("EID: %s", delim + 1);
}
iccid_len = strlen(value);
strncpy(iface_ctx.mdm_iccid, value, sizeof(iface_ctx.mdm_iccid));
len = MIN(iccid_len, sizeof(iface_ctx.mdm_iccid) - 1);
iface_ctx.mdm_iccid[len] = '\0';
if (iccid_len > len) {
LOG_WRN("ICCID too long: %d (max %d)", iccid_len, len);
}
LOG_INF("ICCID: %s", iface_ctx.mdm_iccid);
return true;
}
static bool on_cmd_atcmdinfo_imsi(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
size_t out_len;
/* The handler for the IMSI is based on the command.
* waiting for: <IMSI>\r\n
*/
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
MDM_HL7800_IMSI_MIN_STR_SIZE);
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find IMSI end");
goto done;
}
if (len > MDM_HL7800_IMSI_MAX_STRLEN) {
LOG_WRN("IMSI too long (len:%d)", len);
len = MDM_HL7800_IMSI_MAX_STRLEN;
}
out_len = net_buf_linearize(iface_ctx.mdm_imsi, MDM_HL7800_IMSI_MAX_STR_SIZE,
*buf, 0, len);
iface_ctx.mdm_imsi[out_len] = 0;
if (strstr(iface_ctx.mdm_imsi, "ERROR") != NULL) {
LOG_ERR("Unable to read IMSI");
memset(iface_ctx.mdm_imsi, 0, sizeof(iface_ctx.mdm_imsi));
}
LOG_INF("IMSI: %s", iface_ctx.mdm_imsi);
done:
return true;
}
static void dns_work_cb(struct k_work *work)
{
#if defined(CONFIG_DNS_RESOLVER) && !defined(CONFIG_DNS_SERVER_IP_ADDRESSES)
int ret;
struct dns_resolve_context *dnsCtx;
struct sockaddr temp_addr;
bool valid_address = false;
bool retry = false;
static const char *const dns_servers_str[] = {
#ifdef CONFIG_NET_IPV6
iface_ctx.dns_v6_string,
#endif
#ifdef CONFIG_NET_IPV4
iface_ctx.dns_v4_string,
#endif
NULL};
#ifdef CONFIG_NET_IPV6
valid_address =
net_ipaddr_parse(iface_ctx.dns_v6_string, strlen(iface_ctx.dns_v6_string),
&temp_addr);
if (!valid_address && IS_ENABLED(CONFIG_NET_IPV4)) {
/* IPv6 DNS string is not valid, replace it with IPv4 address and recheck */
strncpy(iface_ctx.dns_v6_string, iface_ctx.dns_v4_string,
strlen(iface_ctx.dns_v4_string));
valid_address = net_ipaddr_parse(iface_ctx.dns_v6_string,
strlen(iface_ctx.dns_v6_string),
&temp_addr);
}
#else
valid_address =
net_ipaddr_parse(iface_ctx.dns_v4_string, strlen(iface_ctx.dns_v4_string),
&temp_addr);
#endif
if (!valid_address) {
LOG_WRN("No valid DNS address!");
} else if (iface_ctx.iface && net_if_is_up(iface_ctx.iface) && !iface_ctx.dns_ready) {
/* set new DNS addr in DNS resolver */
LOG_DBG("Refresh DNS resolver");
dnsCtx = dns_resolve_get_default();
if (dnsCtx->state == DNS_RESOLVE_CONTEXT_INACTIVE) {
LOG_DBG("Initializing DNS resolver");
ret = dns_resolve_init(dnsCtx, (const char **)dns_servers_str, NULL);
if (ret < 0) {
LOG_ERR("dns_resolve_init fail (%d)", ret);
retry = true;
}
} else {
LOG_DBG("Reconfiguring DNS resolver");
ret = dns_resolve_reconfigure(dnsCtx, (const char **)dns_servers_str, NULL);
if (ret < 0) {
LOG_ERR("dns_resolve_reconfigure fail (%d)", ret);
retry = true;
}
}
if (!retry) {
LOG_DBG("DNS ready");
iface_ctx.dns_ready = true;
} else {
LOG_DBG("DNS not ready, schedule a retry");
k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.dns_work,
K_SECONDS(DNS_WORK_DELAY_SECS * 2));
}
}
#endif
}
char *mdm_hl7800_get_iccid(void)
{
return iface_ctx.mdm_iccid;
}
char *mdm_hl7800_get_sn(void)
{
return iface_ctx.mdm_sn;
}
char *mdm_hl7800_get_imei(void)
{
return iface_ctx.mdm_imei;
}
char *mdm_hl7800_get_fw_version(void)
{
return iface_ctx.mdm_revision;
}
char *mdm_hl7800_get_imsi(void)
{
return iface_ctx.mdm_imsi;
}
/* Convert HL7800 IPv6 address string in format
* a01.a02.a03.a04.a05.a06.a07.a08.a09.a10.a11.a12.a13.a14.a15.a16 to
* an IPv6 address.
*/
static int hl7800_net_addr6_pton(const char *src, struct in6_addr *dst)
{
int num_sections = 8;
int i, len;
uint16_t ipv6_section;
len = strlen(src);
for (i = 0; i < len; i++) {
if (!(src[i] >= '0' && src[i] <= '9') && src[i] != '.') {
return -EINVAL;
}
}
for (i = 0; i < num_sections; i++) {
if (!src || *src == '\0') {
return -EINVAL;
}
ipv6_section = (uint16_t)strtol(src, NULL, 10);
src = strchr(src, '.');
src++;
if (!src || *src == '\0') {
return -EINVAL;
}
ipv6_section = (ipv6_section << 8) | (uint16_t)strtol(src, NULL, 10);
UNALIGNED_PUT(htons(ipv6_section), &dst->s6_addr16[i]);
src = strchr(src, '.');
if (src) {
src++;
} else {
if (i < num_sections - 1) {
return -EINVAL;
}
}
}
return 0;
}
/* Handler: +CGCONTRDP: <cid>,<bearer_id>,<apn>,<local_addr and subnet_mask>,
* <gw_addr>,<DNS_prim_addr>,<DNS_sec_addr>
*/
static bool on_cmd_atcmdinfo_ipaddr(struct net_buf **buf, uint16_t len)
{
int ret;
int num_delims = CGCONTRDP_RESPONSE_NUM_DELIMS;
char *delims[CGCONTRDP_RESPONSE_NUM_DELIMS];
size_t out_len;
char value[MDM_IP_INFO_RESP_SIZE];
char *search_start, *addr_start, *sm_start;
struct in_addr new_ipv4_addr;
struct in6_addr new_ipv6_addr;
bool is_ipv4;
int addr_len;
char temp_addr_str[HL7800_IPV6_ADDR_LEN];
k_timeout_t delay;
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
value[out_len] = 0;
search_start = value;
LOG_DBG("IP info: %s", value);
/* find all delimiters (,) */
for (int i = 0; i < num_delims; i++) {
delims[i] = strchr(search_start, ',');
if (!delims[i]) {
LOG_ERR("Could not find delim %d, val: %s", i,
value);
return true;
}
/* Start next search after current delim location */
search_start = delims[i] + 1;
}
/* determine if IPv4 or IPv6 by checking length of ip address plus
* gateway string.
*/
is_ipv4 = false;
addr_len = delims[3] - delims[2];
LOG_DBG("IP string len: %d", addr_len);
if (addr_len <= (NET_IPV4_ADDR_LEN * 2)) {
is_ipv4 = true;
}
/* Find start of subnet mask */
addr_start = delims[2] + 1;
if (is_ipv4) {
num_delims = 4;
} else {
num_delims = 16;
}
search_start = addr_start;
sm_start = addr_start;
for (int i = 0; i < num_delims; i++) {
sm_start = strchr(search_start, '.');
if (!sm_start) {
LOG_ERR("Could not find submask start");
return true;
}
/* Start next search after current delim location */
search_start = sm_start + 1;
}
/* get new IP addr */
addr_len = sm_start - addr_start;
strncpy(temp_addr_str, addr_start, addr_len);
temp_addr_str[addr_len] = 0;
LOG_DBG("IP addr: %s", temp_addr_str);
if (is_ipv4) {
ret = net_addr_pton(AF_INET, temp_addr_str, &new_ipv4_addr);
} else {
ret = hl7800_net_addr6_pton(temp_addr_str, &new_ipv6_addr);
}
if (ret < 0) {
LOG_ERR("Invalid IP addr");
return true;
}
if (is_ipv4) {
/* move past the '.' */
sm_start += 1;
/* store new subnet mask */
addr_len = delims[3] - sm_start;
strncpy(temp_addr_str, sm_start, addr_len);
temp_addr_str[addr_len] = 0;
ret = net_addr_pton(AF_INET, temp_addr_str, &iface_ctx.subnet);
if (ret < 0) {
LOG_ERR("Invalid subnet");
return true;
}
/* store new gateway */
addr_start = delims[3] + 1;
addr_len = delims[4] - addr_start;
strncpy(temp_addr_str, addr_start, addr_len);
temp_addr_str[addr_len] = 0;
ret = net_addr_pton(AF_INET, temp_addr_str, &iface_ctx.gateway);
if (ret < 0) {
LOG_ERR("Invalid gateway");
return true;
}
}
/* store new dns */
addr_start = delims[4] + 1;
addr_len = delims[5] - addr_start;
strncpy(temp_addr_str, addr_start, addr_len);
temp_addr_str[addr_len] = 0;
if (is_ipv4) {
ret = strncmp(temp_addr_str, iface_ctx.dns_v4_string, addr_len);
if (ret != 0) {
iface_ctx.dns_ready = false;
}
strncpy(iface_ctx.dns_v4_string, addr_start, addr_len);
iface_ctx.dns_v4_string[addr_len] = 0;
ret = net_addr_pton(AF_INET, iface_ctx.dns_v4_string, &iface_ctx.dns_v4);
LOG_DBG("IPv4 DNS addr: %s", iface_ctx.dns_v4_string);
}
#ifdef CONFIG_NET_IPV6
else {
ret = strncmp(temp_addr_str, iface_ctx.dns_v6_string, addr_len);
if (ret != 0) {
iface_ctx.dns_ready = false;
}
/* store HL7800 formatted IPv6 DNS string temporarily */
strncpy(iface_ctx.dns_v6_string, addr_start, addr_len);
ret = hl7800_net_addr6_pton(iface_ctx.dns_v6_string, &iface_ctx.dns_v6);
net_addr_ntop(AF_INET6, &iface_ctx.dns_v6, iface_ctx.dns_v6_string,
sizeof(iface_ctx.dns_v6_string));
LOG_DBG("IPv6 DNS addr: %s", iface_ctx.dns_v6_string);
}
#endif
if (ret < 0) {
LOG_ERR("Invalid dns");
return true;
}
if (iface_ctx.iface) {
if (is_ipv4) {
#ifdef CONFIG_NET_IPV4
/* remove the current IPv4 addr before adding a new one.
* We dont care if it is successful or not.
*/
net_if_ipv4_addr_rm(iface_ctx.iface, &iface_ctx.ipv4Addr);
if (!net_if_ipv4_addr_add(iface_ctx.iface, &new_ipv4_addr,
NET_ADDR_DHCP, 0)) {
LOG_ERR("Cannot set iface IPv4 addr");
}
net_if_ipv4_set_netmask(iface_ctx.iface, &iface_ctx.subnet);
net_if_ipv4_set_gw(iface_ctx.iface, &iface_ctx.gateway);
#endif
/* store the new IP addr */
net_ipaddr_copy(&iface_ctx.ipv4Addr, &new_ipv4_addr);
} else {
#if CONFIG_NET_IPV6
net_if_ipv6_addr_rm(iface_ctx.iface, &iface_ctx.ipv6Addr);
if (!net_if_ipv6_addr_add(iface_ctx.iface, &new_ipv6_addr,
NET_ADDR_AUTOCONF, 0)) {
LOG_ERR("Cannot set iface IPv6 addr");
}
#endif
}
/* start DNS update work */
delay = K_NO_WAIT;
if (!iface_ctx.initialized) {
/* Delay this in case the network
* stack is still starting up
*/
delay = K_SECONDS(DNS_WORK_DELAY_SECS);
}
k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.dns_work,
delay);
} else {
LOG_ERR("iface NULL");
}
return true;
}
/* Handler1: +COPS: <mode>[,<format>,<oper>[,<AcT>]]
*
* Handler2:
* +COPS: [list of supported (<stat>, long alphanumeric <oper>, short
* alphanumeric <oper>, numeric <oper>[,< AcT>])s][,,
* (list of supported <mode>s),(list of supported <format>s)]
*/
static bool on_cmd_atcmdinfo_operator_status(struct net_buf **buf, uint16_t len)
{
size_t out_len;
char value[MDM_MAX_RESP_SIZE];
int num_delims = COPS_RESPONSE_NUM_DELIMS;
char *delims[COPS_RESPONSE_NUM_DELIMS];
char *search_start;
int i;
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
value[out_len] = 0;
/* For AT+COPS=?, result is most likely longer than size of log string */
if (strchr(value, '(') != NULL) {
LOG_HEXDUMP_DBG(value, out_len, "Operator: ");
goto done;
} else {
LOG_INF("Operator: %s", value);
}
/* Process AT+COPS? */
if (len == 1) {
/* only mode was returned, there is no operator info */
iface_ctx.operator_status = NO_OPERATOR;
goto done;
}
search_start = value;
/* find all delimiters (,) */
for (i = 0; i < num_delims; i++) {
delims[i] = strchr(search_start, ',');
if (!delims[i]) {
LOG_ERR("Could not find delim %d, val: %s", i, value);
goto done;
}
/* Start next search after current delim location */
search_start = delims[i] + 1;
}
/* we found both delimiters, that means we have an operator */
iface_ctx.operator_status = REGISTERED;
done:
return true;
}
/* Handler: +KGSN: T5640400011101 */
static bool on_cmd_atcmdinfo_serial_number(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
char value[MDM_SN_RESPONSE_LENGTH];
size_t out_len;
int sn_len;
char *val_start;
/* make sure SN# data is received.
* we are waiting for: +KGSN: ##############\r\n
*/
wait_for_modem_data(buf, net_buf_frags_len(*buf),
MDM_SN_RESPONSE_LENGTH);
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find sn end");
goto done;
}
/* get msg data */
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
value[out_len] = 0;
/* find ':' */
val_start = strchr(value, ':');
if (!val_start) {
LOG_ERR("Unable to find sn ':'");
goto done;
}
/* Remove ": " chars */
val_start += 2;
sn_len = len - (val_start - value);
if (sn_len < MDM_HL7800_SERIAL_NUMBER_STRLEN) {
LOG_WRN("sn too short (len:%d)", sn_len);
} else if (sn_len > MDM_HL7800_SERIAL_NUMBER_STRLEN) {
LOG_WRN("sn too long (len:%d)", sn_len);
sn_len = MDM_HL7800_SERIAL_NUMBER_STRLEN;
}
strncpy(iface_ctx.mdm_sn, val_start, sn_len);
iface_ctx.mdm_sn[sn_len] = 0;
LOG_INF("Serial #: %s", iface_ctx.mdm_sn);
done:
return true;
}
/* Handler: +KSRAT: # */
static bool on_cmd_radio_tech_status(struct net_buf **buf, uint16_t len)
{
size_t out_len;
char value[MDM_MAX_RESP_SIZE];
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
value[out_len] = 0;
iface_ctx.mdm_rat = strtol(value, NULL, 10);
LOG_INF("+KSRAT: %d", iface_ctx.mdm_rat);
event_handler(HL7800_EVENT_RAT, &iface_ctx.mdm_rat);
return true;
}
/* Handler: +KBNDCFG: #,####################### */
static bool on_cmd_radio_band_configuration(struct net_buf **buf, uint16_t len)
{
size_t out_len;
char value[MDM_MAX_RESP_SIZE];
char n_tmp[sizeof("#########")];
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
value[out_len] = 0;
if (value[0] != (iface_ctx.mdm_rat == MDM_RAT_CAT_M1 ? '0' : '1')) {
/* Invalid RAT */
return true;
} else if (strlen(value) < sizeof("#,###################")) {
/* String size too short */
return true;
}
memcpy(iface_ctx.mdm_bands_string, &value[MDM_TOP_BAND_START_POSITION],
MDM_HL7800_LTE_BAND_STRLEN);
memcpy(n_tmp, &value[MDM_TOP_BAND_START_POSITION], MDM_TOP_BAND_SIZE);
n_tmp[MDM_TOP_BAND_SIZE] = 0;
iface_ctx.mdm_bands_top = strtoul(n_tmp, NULL, 16);
memcpy(n_tmp, &value[MDM_MIDDLE_BAND_START_POSITION],
MDM_MIDDLE_BAND_SIZE);
n_tmp[MDM_MIDDLE_BAND_SIZE] = 0;
iface_ctx.mdm_bands_middle = strtoul(n_tmp, NULL, 16);
memcpy(n_tmp, &value[MDM_BOTTOM_BAND_START_POSITION],
MDM_BOTTOM_BAND_SIZE);
n_tmp[MDM_BOTTOM_BAND_SIZE] = 0;
iface_ctx.mdm_bands_bottom = strtoul(n_tmp, NULL, 16);
LOG_INF("Current band configuration: %04x %08x %08x",
iface_ctx.mdm_bands_top, iface_ctx.mdm_bands_middle,
iface_ctx.mdm_bands_bottom);
event_handler(HL7800_EVENT_BANDS, iface_ctx.mdm_bands_string);
return true;
}
/* Handler: +KBND: #,####################### */
static bool on_cmd_radio_active_bands(struct net_buf **buf, uint16_t len)
{
size_t out_len;
char value[MDM_MAX_RESP_SIZE];
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
value[out_len] = 0;
if (strlen(value) < sizeof("#,###################")) {
/* String size too short */
return true;
}
memcpy(iface_ctx.mdm_active_bands_string,
&value[MDM_TOP_BAND_START_POSITION], MDM_HL7800_LTE_BAND_STRLEN);
event_handler(HL7800_EVENT_ACTIVE_BANDS, iface_ctx.mdm_active_bands_string);
return true;
}
static char *get_startup_state_string(enum mdm_hl7800_startup_state state)
{
/* clang-format off */
switch (state) {
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, READY);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, WAITING_FOR_ACCESS_CODE);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, SIM_NOT_PRESENT);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, SIMLOCK);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, UNRECOVERABLE_ERROR);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, UNKNOWN);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, INACTIVE_SIM);
default:
return "UNKNOWN";
}
/* clang-format on */
}
static void set_startup_state(enum mdm_hl7800_startup_state state)
{
iface_ctx.mdm_startup_state = state;
generate_startup_state_event();
}
static void generate_startup_state_event(void)
{
struct mdm_hl7800_compound_event event;
event.code = iface_ctx.mdm_startup_state;
event.string = get_startup_state_string(iface_ctx.mdm_startup_state);
LOG_INF("Startup State: %s", event.string);
event_handler(HL7800_EVENT_STARTUP_STATE_CHANGE, &event);
}
int mdm_hl7800_set_desired_sleep_level(enum mdm_hl7800_sleep level)
{
int r = -EPERM;
#if CONFIG_MODEM_HL7800_LOW_POWER_MODE
switch (level) {
case HL7800_SLEEP_AWAKE:
case HL7800_SLEEP_HIBERNATE:
case HL7800_SLEEP_LITE_HIBERNATE:
case HL7800_SLEEP_SLEEP:
iface_ctx.desired_sleep_level = level;
r = 0;
break;
default:
r = -EINVAL;
}
if (r == 0) {
hl7800_lock();
wakeup_hl7800();
r = set_sleep_level();
set_busy(false);
allow_sleep(true);
hl7800_unlock();
}
#endif
return r;
}
static void initialize_sleep_level(void)
{
if (iface_ctx.desired_sleep_level == HL7800_SLEEP_UNINITIALIZED) {
if (IS_ENABLED(CONFIG_MODEM_HL7800_SLEEP_LEVEL_HIBERNATE)) {
iface_ctx.desired_sleep_level = HL7800_SLEEP_HIBERNATE;
} else if (IS_ENABLED(CONFIG_MODEM_HL7800_SLEEP_LEVEL_LITE_HIBERNATE)) {
iface_ctx.desired_sleep_level = HL7800_SLEEP_LITE_HIBERNATE;
} else if (IS_ENABLED(CONFIG_MODEM_HL7800_SLEEP_LEVEL_SLEEP)) {
iface_ctx.desired_sleep_level = HL7800_SLEEP_SLEEP;
} else {
iface_ctx.desired_sleep_level = HL7800_SLEEP_AWAKE;
}
}
}
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
static int set_sleep_level(void)
{
char cmd[sizeof("AT+KSLEEP=#,#,##")];
static const char SLEEP_CMD_FMT[] = "AT+KSLEEP=%d,%d,%d";
int delay = CONFIG_MODEM_HL7800_SLEEP_DELAY_AFTER_REBOOT;
int ret = 0;
/* AT+KSLEEP= <management>[,<level>[,<delay to sleep after reboot>]]
* management 1 means the HL7800 decides when it enters sleep mode
*/
switch (iface_ctx.desired_sleep_level) {
case HL7800_SLEEP_HIBERNATE:
snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 1, 2, delay);
break;
case HL7800_SLEEP_LITE_HIBERNATE:
snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 1, 1, delay);
break;
case HL7800_SLEEP_SLEEP:
snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 1, 0, delay);
break;
default:
/* don't sleep */
snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 2, 0, delay);
break;
}
SEND_AT_CMD_EXPECT_OK(cmd);
error:
return ret;
}
#endif /* CONFIG_MODEM_HL7800_LOW_POWER_MODE */
static char *get_sleep_state_string(enum mdm_hl7800_sleep state)
{
/* clang-format off */
switch (state) {
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, UNINITIALIZED);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, HIBERNATE);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, LITE_HIBERNATE);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, SLEEP);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, AWAKE);
default:
return "UNKNOWN";
}
/* clang-format on */
}
static void set_sleep_state(enum mdm_hl7800_sleep state)
{
iface_ctx.sleep_state = state;
if (iface_ctx.sleep_state != HL7800_SLEEP_AWAKE) {
k_sem_reset(&iface_ctx.mdm_awake);
}
generate_sleep_state_event();
}
static void generate_sleep_state_event(void)
{
struct mdm_hl7800_compound_event event;
event.code = iface_ctx.sleep_state;
event.string = get_sleep_state_string(iface_ctx.sleep_state);
LOG_INF("Sleep State: %s", event.string);
event_handler(HL7800_EVENT_SLEEP_STATE_CHANGE, &event);
}
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
static char *get_fota_state_string(enum mdm_hl7800_fota_state state)
{
/* clang-format off */
switch (state) {
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, IDLE);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, START);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, WIP);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, PAD);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, SEND_EOT);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, FILE_ERROR);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, INSTALL);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, REBOOT_AND_RECONFIGURE);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, COMPLETE);
default:
return "UNKNOWN";
}
/* clang-format on */
}
static void set_fota_state(enum mdm_hl7800_fota_state state)
{
LOG_INF("FOTA state: %s->%s",
get_fota_state_string(iface_ctx.fw_update_state),
get_fota_state_string(state));
iface_ctx.fw_update_state = state;
generate_fota_state_event();
}
static void generate_fota_state_event(void)
{
struct mdm_hl7800_compound_event event;
event.code = iface_ctx.fw_update_state;
event.string = get_fota_state_string(iface_ctx.fw_update_state);
event_handler(HL7800_EVENT_FOTA_STATE, &event);
}
static void generate_fota_count_event(void)
{
uint32_t count = iface_ctx.fw_packet_count * XMODEM_DATA_SIZE;
event_handler(HL7800_EVENT_FOTA_COUNT, &count);
}
#endif
/* Handler: +KSUP: # */
static bool on_cmd_startup_report(struct net_buf **buf, uint16_t len)
{
size_t out_len;
char value[MDM_MAX_RESP_SIZE];
memset(value, 0, sizeof(value));
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
if (out_len > 0) {
set_startup_state(strtol(value, NULL, 10));
} else {
set_startup_state(HL7800_STARTUP_STATE_UNKNOWN);
}
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
if (iface_ctx.fw_updated) {
iface_ctx.fw_updated = false;
set_fota_state(HL7800_FOTA_REBOOT_AND_RECONFIGURE);
/* issue reset after a firmware update to reconfigure modem state */
k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.mdm_reset_work,
K_NO_WAIT);
} else
#endif
{
PRINT_AWAKE_MSG;
iface_ctx.wait_for_KSUP = false;
iface_ctx.mdm_startup_reporting_on = true;
iface_ctx.reconfig_IP_connection = true;
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
mark_sockets_for_reconfig();
#endif
set_sleep_state(HL7800_SLEEP_AWAKE);
k_sem_give(&iface_ctx.mdm_awake);
}
return true;
}
static bool profile_handler(struct net_buf **buf, uint16_t len,
bool active_profile)
{
uint32_t size;
int echo_state = -1;
struct net_buf *frag = NULL;
uint16_t line_length;
char line[MAX_PROFILE_LINE_LENGTH];
size_t output_length;
/* Prepare net buffer for this parser. */
net_buf_remove(buf, len);
net_buf_skipcrlf(buf);
size = wait_for_modem_data(buf, net_buf_frags_len(*buf),
sizeof(PROFILE_LINE_1));
net_buf_skipcrlf(buf); /* remove any \r\n that are in the front */
/* Parse configuration data to determine if echo is on/off. */
line_length = net_buf_findcrlf(*buf, &frag);
if (line_length) {
memset(line, 0, sizeof(line));
output_length = net_buf_linearize(line, SIZE_WITHOUT_NUL(line),
*buf, 0, line_length);
LOG_DBG("length: %u: %s", line_length, line);
/* Echo on off is the first thing on the line: E0, E1 */
if (output_length >= SIZE_WITHOUT_NUL("E?")) {
echo_state = (line[1] == '1') ? 1 : 0;
}
}
LOG_DBG("echo: %d", echo_state);
net_buf_remove(buf, line_length);
net_buf_skipcrlf(buf);
if (active_profile) {
iface_ctx.mdm_echo_is_on = (echo_state != 0);
}
/* Discard next line. This waits for the longest possible response even
* though most registers won't have the value 0xFF. */
size = wait_for_modem_data(buf, net_buf_frags_len(*buf),
sizeof(PROFILE_LINE_2));
net_buf_skipcrlf(buf);
len = net_buf_findcrlf(*buf, &frag);
net_buf_remove(buf, len);
net_buf_skipcrlf(buf);
return false;
}
static bool on_cmd_atcmdinfo_active_profile(struct net_buf **buf, uint16_t len)
{
return profile_handler(buf, len, true);
}
static bool on_cmd_atcmdinfo_stored_profile0(struct net_buf **buf, uint16_t len)
{
return profile_handler(buf, len, false);
}
static bool on_cmd_atcmdinfo_stored_profile1(struct net_buf **buf, uint16_t len)
{
return profile_handler(buf, len, false);
}
/* +WPPP: 1,1,"username","password" */
static bool on_cmd_atcmdinfo_pdp_authentication_cfg(struct net_buf **buf,
uint16_t len)
{
struct net_buf *frag = NULL;
uint16_t line_length;
char line[MDM_HL7800_APN_CMD_MAX_SIZE];
size_t output_length;
size_t i;
char *p;
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
MDM_HL7800_APN_CMD_MAX_SIZE);
line_length = net_buf_findcrlf(*buf, &frag);
if (line_length) {
memset(line, 0, sizeof(line));
output_length = net_buf_linearize(line, SIZE_WITHOUT_NUL(line),
*buf, 0, line_length);
LOG_DBG("length: %u: %s", line_length, line);
if (output_length > 0) {
memset(iface_ctx.mdm_apn.username, 0,
sizeof(iface_ctx.mdm_apn.username));
memset(iface_ctx.mdm_apn.password, 0,
sizeof(iface_ctx.mdm_apn.password));
i = 0;
p = strchr(line, '"');
if (p != NULL) {
p += 1;
i = 0;
while ((p != NULL) && (*p != '"') &&
(i <
MDM_HL7800_APN_USERNAME_MAX_STRLEN)) {
iface_ctx.mdm_apn.username[i++] = *p++;
}
}
LOG_INF("APN Username: %s",
iface_ctx.mdm_apn.username);
p = strchr(p + 1, '"');
if (p != NULL) {
p += 1;
i = 0;
while ((p != NULL) && (*p != '"') &&
(i <
MDM_HL7800_APN_PASSWORD_MAX_STRLEN)) {
iface_ctx.mdm_apn.password[i++] = *p++;
}
}
LOG_INF("APN Password: %s",
iface_ctx.mdm_apn.password);
}
}
net_buf_remove(buf, line_length);
net_buf_skipcrlf(buf);
return false;
}
/* Only context 1 is used. Other contexts are unhandled.
*
* +CGDCONT: 1,"IP","access point name",,0,0,0,0,0,,0,,,,,
*/
static bool on_cmd_atcmdinfo_pdp_context(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
uint16_t line_length;
char line[MDM_HL7800_APN_CMD_MAX_SIZE];
size_t output_length;
char *p;
size_t i;
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
MDM_HL7800_APN_CMD_MAX_SIZE);
line_length = net_buf_findcrlf(*buf, &frag);
if (line_length) {
memset(line, 0, sizeof(line));
output_length = net_buf_linearize(line, SIZE_WITHOUT_NUL(line),
*buf, 0, line_length);
LOG_DBG("length: %u: %s", line_length, line);
if (output_length > 0) {
memset(iface_ctx.mdm_apn.value, 0, sizeof(iface_ctx.mdm_apn.value));
memset(iface_ctx.mdm_pdp_addr_fam, 0, MDM_ADDR_FAM_MAX_LEN);
/* Address family after first , */
p = strchr(line, ',');
if (p == NULL) {
LOG_WRN("Issue parsing APN response");
goto done;
}
p += 2;
i = 0;
while ((p != NULL) && (*p != '"') && (i < MDM_ADDR_FAM_MAX_LEN)) {
iface_ctx.mdm_pdp_addr_fam[i++] = *p++;
}
if (strcmp(iface_ctx.mdm_pdp_addr_fam, ADDRESS_FAMILY_IP) == 0) {
snprintk(iface_ctx.mdm_pdp_addr_fam,
sizeof(iface_ctx.mdm_pdp_addr_fam), "%s",
ADDRESS_FAMILY_IPV4);
}
LOG_DBG("PDP address family: %s", iface_ctx.mdm_pdp_addr_fam);
/* APN after second , " */
p = strchr(p, ',');
if (p == NULL) {
LOG_WRN("Issue parsing APN response");
goto done;
}
p++;
if (*p == ',') {
/* APN is blank */
goto done;
}
if (*p == '"') {
p++;
i = 0;
while ((p != NULL) && (*p != '"') &&
(i < MDM_HL7800_APN_MAX_STRLEN)) {
iface_ctx.mdm_apn.value[i++] = *p++;
}
}
LOG_INF("APN: %s", iface_ctx.mdm_apn.value);
}
}
done:
net_buf_remove(buf, line_length);
net_buf_skipcrlf(buf);
return false;
}
static int hl7800_query_rssi(void)
{
int ret;
ret = send_at_cmd(NULL, "AT+KCELLMEAS=0", MDM_CMD_SEND_TIMEOUT, 1,
false);
if (ret < 0) {
LOG_ERR("AT+KCELLMEAS ret:%d", ret);
}
return ret;
}
static void hl7800_start_rssi_work(void)
{
/* Rate is not checked here to allow one reading
* when going from network down->up
*/
k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.rssi_query_work,
K_NO_WAIT);
}
static void hl7800_stop_rssi_work(void)
{
int rc;
rc = k_work_cancel_delayable(&iface_ctx.rssi_query_work);
if (rc != 0) {
LOG_ERR("Could not cancel RSSI work [%d]", rc);
}
}
static void rssi_query(void)
{
hl7800_lock();
wakeup_hl7800();
hl7800_query_rssi();
set_busy(false);
allow_sleep(true);
hl7800_unlock();
}
static void hl7800_rssi_query_work(struct k_work *work)
{
rssi_query();
/* re-start RSSI query work */
if (CONFIG_MODEM_HL7800_RSSI_RATE_SECONDS > 0) {
k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.rssi_query_work,
K_SECONDS(CONFIG_MODEM_HL7800_RSSI_RATE_SECONDS));
}
}
#ifdef CONFIG_MODEM_HL7800_GPS
/* Unsolicited notification
* Handler: +GNSSEV: <eventType>,<eventStatus>
*/
static bool on_cmd_gps_event(struct net_buf **buf, uint16_t len)
{
size_t out_len;
char value[MDM_MAX_RESP_SIZE];
char *start = NULL;
char *end = NULL;
int8_t event = -1;
int8_t status = -1;
memset(value, 0, sizeof(value));
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
if (out_len > 0) {
start = value;
event = strtol(start, &end, 10);
if (end == strchr(value, ',')) {
start = end + 1;
status = strtol(start, &end, 10);
}
}
LOG_INF("GPS event: %d status: %d", event, status);
if (event == HL7800_GNSS_EVENT_POSITION) {
event_handler(HL7800_EVENT_GPS_POSITION_STATUS, &status);
}
return true;
}
static void gps_work_callback(struct k_work *work)
{
ARG_UNUSED(work);
int r;
hl7800_lock();
wakeup_hl7800();
r = send_at_cmd(NULL, "AT+GNSSLOC?", MDM_CMD_SEND_TIMEOUT, 1, false);
set_busy(false);
allow_sleep(true);
hl7800_unlock();
LOG_DBG("GPS location request status: %d", r);
if (iface_ctx.gps_query_location_rate_seconds) {
k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.gps_work,
K_SECONDS(iface_ctx.gps_query_location_rate_seconds));
}
}
/* The AT+GNSSLOC? command returns 1 of 2 things:
*
* +GNSSLOC:
* Latitude: "49 Deg 10 Min 21.49 Sec N"
* Longitude: "123 Deg 4 Min 14.76 Sec W"
* GpsTime: "yyyy mm dd hh:mm:ss"
* FixType: "2D" or "3D"
* HEPE: "8.485 m" (Horizontal Estimated Position Error)
* Altitude: "-1 m"
* AltUnc: "3.0 m"
* Direction: "0.0 deg"
* HorSpeed: "0.0 m/s"
* VerSpeed: "0.0 m/s"
* OK
*
* OR
*
* +GNSSLOC:
* FIX NOT AVAILABLE
* OK
*
* Since each response is on its own line, the command handler is used
* to handle each one as an individual response.
*/
static bool gps_handler(struct net_buf **buf, uint16_t len,
enum mdm_hl7800_gps_string_types str_type)
{
struct mdm_hl7800_compound_event event;
char gps_str[MDM_HL7800_MAX_GPS_STR_SIZE];
size_t gps_len = sizeof(gps_str) - 1;
struct net_buf *frag = NULL;
size_t out_len;
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), sizeof(gps_str));
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find end");
goto done;
}
if (len > gps_len) {
LOG_WRN("GPS string too long (len:%d)", len);
len = gps_len;
}
out_len = net_buf_linearize(gps_str, gps_len, *buf, 0, len);
gps_str[out_len] = 0;
event.code = str_type;
event.string = gps_str;
event_handler(HL7800_EVENT_GPS, &event);
done:
return true;
}
static bool on_cmd_latitude(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_LATITUDE);
}
static bool on_cmd_longitude(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_LONGITUDE);
}
static bool on_cmd_gps_time(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_GPS_TIME);
}
static bool on_cmd_fix_type(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_FIX_TYPE);
}
static bool on_cmd_hepe(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_HEPE);
}
static bool on_cmd_altitude(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_ALTITUDE);
}
static bool on_cmd_alt_unc(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_ALT_UNC);
}
static bool on_cmd_direction(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_DIRECTION);
}
static bool on_cmd_hor_speed(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_HOR_SPEED);
}
static bool on_cmd_ver_speed(struct net_buf **buf, uint16_t len)
{
return gps_handler(buf, len, HL7800_GPS_STR_VER_SPEED);
}
#endif /* CONFIG_MODEM_HL7800_GPS */
#ifdef CONFIG_MODEM_HL7800_POLTE
/* Handler: %POLTEEVU: "REGISTER",0, <mqttAuthUser>, <mqttAuthPassword> */
static bool on_cmd_polte_registration(struct net_buf **buf, uint16_t len)
{
char rsp[MDM_MAX_RESP_SIZE] = { 0 };
size_t rsp_len = sizeof(rsp) - 1;
char *rsp_end = rsp + rsp_len;
struct mdm_hl7800_polte_registration_event_data data;
struct net_buf *frag = NULL;
size_t out_len;
char *location;
bool parsed;
memset(&data, 0, sizeof(data));
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), sizeof(rsp));
location = rsp;
parsed = false;
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
do {
if (!frag) {
LOG_ERR("Unable to find end");
break;
}
if (len > rsp_len) {
LOG_WRN("string too long (len:%d)", len);
len = rsp_len;
}
out_len = net_buf_linearize(rsp, rsp_len, *buf, 0, len);
rsp[out_len] = 0;
/* Command handler looks for string up to the user field */
location = strstr(location, "\"");
if (location != NULL && location < rsp_end) {
location += 1;
if (location >= rsp_end) {
break;
}
data.user = location;
} else {
break;
}
/* Find end of user field and null terminate string */
location = strstr(location, "\"");
if (location != NULL && location < rsp_end) {
*location = 0;
location += 1;
if (location >= rsp_end) {
break;
}
} else {
break;
}
location = strstr(location, ",\"");
if (location != NULL && location < rsp_end) {
location += 2;
if (location >= rsp_end) {
break;
}
data.password = location;
} else {
break;
}
location = strstr(location, "\"");
if (location != NULL && location < rsp_end) {
*location = 0;
} else {
break;
}
parsed = true;
} while (false);
if (parsed && data.user && data.password) {
data.status = 0;
} else {
data.status = -1;
LOG_ERR("Unable to parse PoLTE registration");
}
event_handler(HL7800_EVENT_POLTE_REGISTRATION, &data);
return true;
}
/* Handler: %POLTECMD: "LOCATE",<res> */
static bool on_cmd_polte_locate_cmd_rsp(struct net_buf **buf, uint16_t len)
{
char rsp[sizeof("99")] = { 0 };
size_t rsp_len = sizeof(rsp) - 1;
size_t out_len;
struct net_buf *frag = NULL;
struct mdm_hl7800_polte_location_data data;
memset(&data, 0, sizeof(data));
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), sizeof(rsp));
data.status = -1;
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
do {
if (!frag) {
LOG_ERR("Unable to find end");
break;
}
if (len > rsp_len) {
LOG_WRN("string too long (len:%d)", len);
len = rsp_len;
}
out_len = net_buf_linearize(rsp, rsp_len, *buf, 0, len);
rsp[out_len] = 0;
data.status = (uint32_t)strtoul(rsp, NULL, 10);
} while (false);
event_handler(HL7800_EVENT_POLTE_LOCATE_STATUS, &data);
return true;
}
/* Handler:
* %POLTEEVU: "LOCATION",<stat>[,<latitude>,<longitude>,<time>,<confidence>]
*/
static bool on_cmd_polte_location(struct net_buf **buf, uint16_t len)
{
char rsp[MDM_MAX_RESP_SIZE] = { 0 };
size_t rsp_len = sizeof(rsp) - 1;
char *rsp_end = rsp + rsp_len;
struct net_buf *frag = NULL;
size_t out_len = 0;
char *start;
char *end;
bool parsed;
struct mdm_hl7800_polte_location_data data;
static const char POLTE_LOC_DELIMITER[] = "\",\"";
memset(&data, 0, sizeof(data));
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), sizeof(rsp));
parsed = false;
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
do {
if (!frag) {
LOG_ERR("Unable to find end");
break;
}
if (len > rsp_len) {
LOG_WRN("string too long (len:%d)", len);
len = rsp_len;
}
out_len = net_buf_linearize(rsp, rsp_len, *buf, 0, len);
rsp[out_len] = 0;
data.status = -1;
start = rsp;
end = "";
/* Comma isn't present when there is an error. */
start = strstr(start, ",");
if (start != NULL && start < rsp_end) {
*start = ' ';
start += 1;
}
data.status = (uint32_t)strtoul(rsp, &end, 10);
if (data.status != 0) {
LOG_WRN("Response not received from PoLTE server: %d", data.status);
data.status = MDM_HL7800_POLTE_SERVER_ERROR;
parsed = true;
break;
} else if (start >= rsp_end) {
break;
}
start = strstr(start, "\"") + 1;
end = strstr(start, POLTE_LOC_DELIMITER);
if (start > rsp && start < rsp_end && end < rsp_end && end > start) {
memcpy(data.latitude, start, MIN(end - start, sizeof(data.latitude) - 1));
} else {
break;
}
start = end + strlen(POLTE_LOC_DELIMITER);
end = strstr(start, POLTE_LOC_DELIMITER);
if (start > rsp && start < rsp_end && end < rsp_end && end > start) {
memcpy(data.longitude, start, MIN(end - start, sizeof(data.longitude) - 1));
} else {
break;
}
start = end + strlen(POLTE_LOC_DELIMITER);
end = strstr(start, POLTE_LOC_DELIMITER);
if (start > rsp && start < rsp_end && end < rsp_end && end > start) {
data.timestamp = (uint32_t)strtoul(start, NULL, 10);
} else {
break;
}
start = end + strlen(POLTE_LOC_DELIMITER);
end = strstr(start, "\"");
if (start > rsp && start < rsp_end && end < rsp_end && end > start) {
memcpy(data.confidence_in_meters, start,
MIN(end - start, sizeof(data.confidence_in_meters) - 1));
} else {
break;
}
parsed = true;
} while (false);
if (!parsed) {
LOG_HEXDUMP_ERR(rsp, out_len, "Unable to parse PoLTE location");
} else {
LOG_HEXDUMP_DBG(rsp, out_len, "PoLTE Location");
}
event_handler(HL7800_EVENT_POLTE, &data);
return true;
}
#endif /* CONFIG_MODEM_HL7800_POLTE */
static void notify_all_tcp_sockets_closed(void)
{
int i;
struct hl7800_socket *sock = NULL;
for (i = 0; i < MDM_MAX_SOCKETS; i++) {
sock = &iface_ctx.sockets[i];
if ((sock->context != NULL) && (sock->type == SOCK_STREAM)) {
LOG_DBG("Sock %d closed", sock->socket_id);
/* signal RX callback with null packet */
if (sock->recv_cb) {
sock->recv_cb(sock->context, sock->recv_pkt,
NULL, NULL, 0,
sock->recv_user_data);
}
}
}
}
static void iface_status_work_cb(struct k_work *work)
{
int ret;
hl7800_lock();
enum mdm_hl7800_network_state state;
if (iface_ctx.off) {
goto done;
} else if (!iface_ctx.initialized && iface_ctx.restarting) {
LOG_DBG("Wait for driver init, process network state later");
/* we are not ready to process this yet, try again later */
k_work_reschedule_for_queue(&hl7800_workq,
&iface_ctx.iface_status_work,
IFACE_WORK_DELAY);
goto done;
} else if (iface_ctx.wait_for_KSUP &&
iface_ctx.wait_for_KSUP_tries < WAIT_FOR_KSUP_RETRIES) {
LOG_DBG("Wait for +KSUP before updating network state");
iface_ctx.wait_for_KSUP_tries++;
/* we have not received +KSUP yet, lets wait more time to receive +KSUP */
k_work_reschedule_for_queue(&hl7800_workq,
&iface_ctx.iface_status_work,
IFACE_WORK_DELAY);
goto done;
} else if (iface_ctx.wait_for_KSUP &&
iface_ctx.wait_for_KSUP_tries >= WAIT_FOR_KSUP_RETRIES) {
/* give up waiting for KSUP */
LOG_DBG("Give up waiting for");
iface_ctx.wait_for_KSUP = false;
check_hl7800_awake();
}
wakeup_hl7800();
LOG_DBG("Updating network state...");
state = iface_ctx.network_state;
/* Ensure we bring the network interface down and then re-check the current state */
if (iface_ctx.network_dropped) {
iface_ctx.network_dropped = false;
state = HL7800_OUT_OF_COVERAGE;
k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.iface_status_work,
IFACE_WORK_DELAY);
}
/* Query operator selection */
ret = send_at_cmd(NULL, "AT+COPS?", MDM_CMD_SEND_TIMEOUT, 0, false);
if (ret < 0) {
LOG_ERR("AT+COPS ret:%d", ret);
}
/* bring iface up/down */
switch (state) {
case HL7800_HOME_NETWORK:
case HL7800_ROAMING:
if (iface_ctx.iface) {
LOG_DBG("HL7800 iface UP");
net_if_carrier_on(iface_ctx.iface);
}
break;
case HL7800_OUT_OF_COVERAGE:
default:
if (iface_ctx.iface && (iface_ctx.low_power_mode != HL7800_LPM_PSM)) {
LOG_DBG("HL7800 iface DOWN");
iface_ctx.dns_ready = false;
net_if_carrier_off(iface_ctx.iface);
}
break;
}
if ((iface_ctx.iface && !net_if_is_up(iface_ctx.iface)) ||
(iface_ctx.low_power_mode == HL7800_LPM_PSM && state == HL7800_OUT_OF_COVERAGE)) {
hl7800_stop_rssi_work();
notify_all_tcp_sockets_closed();
} else if (iface_ctx.iface && net_if_is_up(iface_ctx.iface)) {
hl7800_start_rssi_work();
/* get IP address info */
(void)send_at_cmd(NULL, "AT+CGCONTRDP=1", MDM_CMD_SEND_TIMEOUT,
CONFIG_MODEM_HL7800_GET_IP_ADDR_INFO_ATTEMPTS, false);
/* get active bands */
SEND_AT_CMD_IGNORE_ERROR("AT+KBND?");
}
LOG_DBG("Network state updated");
set_busy(false);
allow_sleep(true);
done:
hl7800_unlock();
}
static char *get_network_state_string(enum mdm_hl7800_network_state state)
{
switch (state) {
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800, NOT_REGISTERED);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800, HOME_NETWORK);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800, SEARCHING);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800, REGISTRATION_DENIED);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800, OUT_OF_COVERAGE);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800, ROAMING);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800, EMERGENCY);
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800, UNABLE_TO_CONFIGURE);
default:
return "UNKNOWN";
}
}
static void set_network_state(enum mdm_hl7800_network_state state)
{
iface_ctx.network_state = state;
generate_network_state_event();
}
static void generate_network_state_event(void)
{
struct mdm_hl7800_compound_event event;
event.code = iface_ctx.network_state;
event.string = get_network_state_string(iface_ctx.network_state);
LOG_INF("Network State: %d %s", iface_ctx.network_state, event.string);
event_handler(HL7800_EVENT_NETWORK_STATE_CHANGE, &event);
}
/* Handler: +CEREG: <n>,<stat>[,[<lac>],[<ci>],[<AcT>]
* [,[<cause_type>],[<reject_cause>] [,[<Active-Time>],[<Periodic-TAU>]]]]
*/
static bool on_cmd_network_report_query(struct net_buf **buf, uint16_t len)
{
size_t out_len;
char value[MDM_MAX_RESP_SIZE];
char *pos;
int l;
char val[MDM_MAX_RESP_SIZE];
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
pos = strchr(value, ',');
if (pos) {
l = (value + out_len) - pos;
strncpy(val, pos + 1, l);
val[l] = 0;
set_network_state(strtol(val, NULL, 0));
/* start work to adjust iface */
k_work_reschedule_for_queue(&hl7800_workq,
&iface_ctx.iface_status_work,
IFACE_WORK_DELAY);
}
return true;
}
static bool on_cmd_operator_index_query(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
char carrier[MDM_HL7800_OPERATOR_INDEX_SIZE];
size_t out_len;
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
MDM_HL7800_OPERATOR_INDEX_SIZE);
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find end of operator index response");
goto done;
}
out_len = net_buf_linearize(carrier, MDM_HL7800_OPERATOR_INDEX_STRLEN,
*buf, 0, len);
carrier[out_len] = 0;
iface_ctx.operator_index = (uint8_t)strtol(carrier, NULL, 10);
LOG_INF("Operator Index: %u", iface_ctx.operator_index);
done:
return true;
}
static bool on_cmd_modem_functionality(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag;
size_t out_len;
char rsp[MDM_HL7800_MODEM_FUNCTIONALITY_SIZE];
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
MDM_HL7800_MODEM_FUNCTIONALITY_SIZE);
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find end of response");
goto done;
}
out_len = net_buf_linearize(rsp, MDM_HL7800_MODEM_FUNCTIONALITY_STRLEN,
*buf, 0, len);
rsp[out_len] = 0;
iface_ctx.functionality = strtol(rsp, NULL, 10);
LOG_INF("Modem Functionality: %u", iface_ctx.functionality);
done:
return true;
}
/* There can be multiple responses from a single command.
* %MEAS: EARFCN=5826, CellID=420, RSRP=-99, RSRQ=-15
* %MEAS: EARFCN=6400, CellID=201, RSRP=-93, RSRQ=-21
*/
static bool on_cmd_survey_status(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
char response[sizeof("EARFCN=XXXXXXXXXXX, CellID=XXXXXXXXXXX, RSRP=-XXX, RSRQ=-XXX")];
char *key;
size_t out_len;
char *value;
struct mdm_hl7800_site_survey site_survey;
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
sizeof(response));
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_ERR("Unable to find end");
goto done;
}
out_len = net_buf_linearize(response, sizeof(response), *buf, 0, len);
LOG_HEXDUMP_DBG(response, out_len, "Site Survey");
key = "EARFCN=";
value = strstr(response, key);
if (value == NULL) {
goto done;
} else {
value += strlen(key);
site_survey.earfcn = strtoul(value, NULL, 10);
}
key = "CellID=";
value = strstr(response, key);
if (value == NULL) {
goto done;
} else {
value += strlen(key);
site_survey.cell_id = strtoul(value, NULL, 10);
}
key = "RSRP=";
value = strstr(response, key);
if (value == NULL) {
goto done;
} else {
value += strlen(key);
site_survey.rsrp = strtol(value, NULL, 10);
}
key = "RSRQ=";
value = strstr(response, key);
if (value == NULL) {
goto done;
} else {
value += strlen(key);
site_survey.rsrq = strtol(value, NULL, 10);
}
event_handler(HL7800_EVENT_SITE_SURVEY, &site_survey);
done:
return true;
}
/* Handler: +CCLK: "yy/MM/dd,hh:mm:ss±zz" */
static bool on_cmd_rtc_query(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
size_t str_len = sizeof(TIME_STRING_FORMAT) - 1;
char rtc_string[sizeof(TIME_STRING_FORMAT)];
memset(rtc_string, 0, sizeof(rtc_string));
iface_ctx.local_time_valid = false;
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
sizeof(TIME_STRING_FORMAT));
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
goto done;
}
if (len != str_len) {
LOG_WRN("Unexpected length for RTC string %d (expected:%zu)",
len, str_len);
} else {
net_buf_linearize(rtc_string, str_len, *buf, 0, str_len);
LOG_INF("RTC string: '%s'", rtc_string);
iface_ctx.local_time_valid = convert_time_string_to_struct(
&iface_ctx.local_time, &iface_ctx.local_time_offset, rtc_string);
}
done:
return true;
}
static bool valid_time_string(const char *time_string)
{
size_t offset, i;
/* Ensure the all the expected delimiters are present */
offset = TIME_STRING_DIGIT_STRLEN + TIME_STRING_SEPARATOR_STRLEN;
i = TIME_STRING_FIRST_SEPARATOR_INDEX;
for (; i < TIME_STRING_PLUS_MINUS_INDEX; i += offset) {
if (time_string[i] != TIME_STRING_FORMAT[i]) {
return false;
}
}
/* The last character is the offset from UTC and can be either
* positive or negative. The last " is also handled here.
*/
if ((time_string[i] == '+' || time_string[i] == '-') &&
(time_string[i + offset] == '"')) {
return true;
}
return false;
}
int get_next_time_string_digit(int *failure_cnt, char **pp, int min, int max)
{
char digits[TIME_STRING_DIGIT_STRLEN + SIZE_OF_NUL];
int result;
memset(digits, 0, sizeof(digits));
memcpy(digits, *pp, TIME_STRING_DIGIT_STRLEN);
*pp += TIME_STRING_DIGIT_STRLEN + TIME_STRING_SEPARATOR_STRLEN;
result = strtol(digits, NULL, 10);
if (result > max) {
*failure_cnt += 1;
return max;
} else if (result < min) {
*failure_cnt += 1;
return min;
} else {
return result;
}
}
static bool convert_time_string_to_struct(struct tm *tm, int32_t *offset,
char *time_string)
{
int fc = 0;
char *ptr = time_string;
if (!valid_time_string(ptr)) {
return false;
}
ptr = &ptr[TIME_STRING_FIRST_DIGIT_INDEX];
tm->tm_year = TIME_STRING_TO_TM_STRUCT_YEAR_OFFSET +
get_next_time_string_digit(&fc, &ptr, TM_YEAR_RANGE);
tm->tm_mon =
get_next_time_string_digit(&fc, &ptr, TM_MONTH_RANGE_PLUS_1) -
1;
tm->tm_mday = get_next_time_string_digit(&fc, &ptr, TM_DAY_RANGE);
tm->tm_hour = get_next_time_string_digit(&fc, &ptr, TM_HOUR_RANGE);
tm->tm_min = get_next_time_string_digit(&fc, &ptr, TM_MIN_RANGE);
tm->tm_sec = get_next_time_string_digit(&fc, &ptr, TM_SEC_RANGE);
tm->tm_isdst = 0;
*offset = (int32_t)get_next_time_string_digit(&fc, &ptr,
QUARTER_HOUR_RANGE) *
SECONDS_PER_QUARTER_HOUR;
if (time_string[TIME_STRING_PLUS_MINUS_INDEX] == '-') {
*offset *= -1;
}
return (fc == 0);
}
/* Handler: +CEREG: <stat>[,[<lac>],[<ci>],[<AcT>]
* [,[<cause_type>],[<reject_cause>] [,[<Active-Time>],[<Periodic-TAU>]]]]
*/
static bool on_cmd_network_report(struct net_buf **buf, uint16_t len)
{
size_t out_len;
char *pos;
int l;
char val[MDM_MAX_RESP_SIZE];
out_len = net_buf_linearize(iface_ctx.mdm_network_status,
sizeof(iface_ctx.mdm_network_status) - 1, *buf,
0, len);
iface_ctx.mdm_network_status[out_len] = 0;
LOG_DBG("Network status: %s", iface_ctx.mdm_network_status);
pos = strchr(iface_ctx.mdm_network_status, ',');
if (pos) {
l = pos - iface_ctx.mdm_network_status;
strncpy(val, iface_ctx.mdm_network_status, l);
val[l] = 0;
set_network_state(strtol(val, NULL, 0));
} else {
set_network_state(strtol(iface_ctx.mdm_network_status, NULL, 0));
}
/* keep HL7800 awake because we want to process the network state soon */
set_busy(true);
allow_sleep(false);
/* start work to adjust iface */
k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.iface_status_work,
IFACE_WORK_DELAY);
return true;
}
/* Handler: +KCELLMEAS: <RSRP>,<Downlink Path Loss>,<PUSCH Tx Power>,
* <PUCCH Tx Power>,<SiNR>
*/
static bool on_cmd_atcmdinfo_rssi(struct net_buf **buf, uint16_t len)
{
/* number of ',' delimiters in this response */
int num_delims = KCELLMEAS_RESPONSE_NUM_DELIMS;
char *delims[KCELLMEAS_RESPONSE_NUM_DELIMS];
size_t out_len;
char value[MDM_MAX_RESP_SIZE];
char *search_start;
int i;
out_len = net_buf_linearize(value, len, *buf, 0, len);
value[out_len] = 0;
search_start = value;
/* find all delimiters */
for (i = 0; i < num_delims; i++) {
delims[i] = strchr(search_start, ',');
if (!delims[i]) {
LOG_ERR("Could not find delim %d, val: %s", i,
value);
goto done;
}
/* Start next search after current delim location */
search_start = delims[i] + 1;
}
/* the first value in the message is the RSRP */
iface_ctx.mdm_rssi = strtol(value, NULL, 10);
/* the 4th ',' (last in the msg) is the start of the SINR */
iface_ctx.mdm_sinr = strtol(delims[3] + 1, NULL, 10);
if ((delims[1] - delims[0]) == 1) {
/* there is no value between the first and second
* delimiter, signal is unknown
*/
LOG_INF("RSSI (RSRP): UNKNOWN");
} else {
LOG_INF("RSSI (RSRP): %d SINR: %d", iface_ctx.mdm_rssi,
iface_ctx.mdm_sinr);
event_handler(HL7800_EVENT_RSSI, &iface_ctx.mdm_rssi);
event_handler(HL7800_EVENT_SINR, &iface_ctx.mdm_sinr);
}
done:
return true;
}
/* Handle the "OK" response from an AT command or a socket call */
static bool on_cmd_sockok(struct net_buf **buf, uint16_t len)
{
struct hl7800_socket *sock = NULL;
sock = socket_from_id(iface_ctx.last_socket_id);
if (!sock || !iface_ctx.socket_cmd) {
iface_ctx.last_error = 0;
k_sem_give(&iface_ctx.response_sem);
} else {
sock->error = 0;
k_sem_give(&sock->sock_send_sem);
}
return true;
}
/* Handler: +KTCP_IND/+KUDP_IND */
static bool on_cmd_sock_ind(struct net_buf **buf, uint16_t len, const char *const type)
{
struct hl7800_socket *sock = NULL;
char *delim;
char value[MDM_MAX_RESP_SIZE];
size_t out_len;
int id;
iface_ctx.last_error = 0;
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
value[out_len] = 0;
/* find ',' because this is the format we expect */
delim = strchr(value, ',');
if (!delim) {
LOG_ERR("%s could not find ','", type);
goto done;
}
id = strtol(value, NULL, 10);
LOG_DBG("%s ID: %d", type, id);
sock = socket_from_id(id);
if (sock) {
sock->error = 0;
k_sem_give(&sock->sock_send_sem);
}
done:
return true;
}
static bool on_cmd_ktcp_ind(struct net_buf **buf, uint16_t len)
{
return on_cmd_sock_ind(buf, len, "+KTCP_IND");
}
static bool on_cmd_kudp_ind(struct net_buf **buf, uint16_t len)
{
return on_cmd_sock_ind(buf, len, "+KUDP_IND");
}
/* Handler: ERROR */
static bool on_cmd_sockerror(struct net_buf **buf, uint16_t len)
{
struct hl7800_socket *sock = NULL;
char string[MDM_MAX_RESP_SIZE];
if (len > 0) {
memset(string, 0, sizeof(string));
net_buf_linearize(string, sizeof(string), *buf, 0, len);
LOG_ERR("'%s'", string);
}
sock = socket_from_id(iface_ctx.last_socket_id);
if (!sock) {
iface_ctx.last_error = -EIO;
k_sem_give(&iface_ctx.response_sem);
} else {
sock->error = -EIO;
k_sem_give(&sock->sock_send_sem);
}
return true;
}
/* Handler: CME/CMS Error */
static bool on_cmd_sock_error_code(struct net_buf **buf, uint16_t len)
{
struct hl7800_socket *sock = NULL;
char value[MDM_MAX_RESP_SIZE];
size_t out_len;
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
value[out_len] = 0;
LOG_ERR("Error code: %s", value);
sock = socket_from_id(iface_ctx.last_socket_id);
if (!sock) {
iface_ctx.last_error = -EIO;
k_sem_give(&iface_ctx.response_sem);
} else {
sock->error = -EIO;
k_sem_give(&sock->sock_send_sem);
}
return true;
}
static void sock_notif_cb_work(struct k_work *work)
{
struct hl7800_socket *sock = NULL;
struct k_work_delayable *dwork;
dwork = k_work_delayable_from_work(work);
sock = CONTAINER_OF(dwork, struct hl7800_socket, notif_work);
hl7800_lock();
/* send null packet */
if (sock->recv_pkt != NULL) {
/* we are in the middle of RX,
* requeue this and try again
*/
k_work_reschedule_for_queue(&hl7800_workq, &sock->notif_work,
MDM_SOCK_NOTIF_DELAY);
} else {
if (sock->type == SOCK_STREAM) {
LOG_DBG("Sock %d trigger NULL packet", sock->socket_id);
k_work_submit_to_queue(&hl7800_workq, &sock->recv_cb_work);
}
}
hl7800_unlock();
}
/* Handler: +KTCP_NOTIF/+KUDP_NOTIF */
static bool on_cmd_sock_notif(struct net_buf **buf, uint16_t len)
{
struct hl7800_socket *sock = NULL;
char *delim;
char value[MDM_MAX_RESP_SIZE];
size_t out_len;
uint8_t notif_val;
bool err = false;
bool trigger_sem = true;
int id;
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
value[out_len] = 0;
/* find ',' because this is the format we expect */
delim = strchr(value, ',');
if (!delim) {
LOG_ERR("+K**P_NOTIF could not find ','");
goto done;
}
id = strtol(value, NULL, 10);
notif_val = strtol(delim + 1, NULL, 10);
if (notif_val == HL7800_TCP_DISCON) {
LOG_DBG("+K**P_NOTIF: %d,%d", id, notif_val);
} else {
LOG_WRN("+K**P_NOTIF: %d,%d", id, notif_val);
}
sock = socket_from_id(id);
if (!sock) {
goto done;
}
switch (notif_val) {
case HL7800_TCP_DATA_SND:
err = false;
sock->error = 0;
break;
case HL7800_TCP_DISCON:
trigger_sem = false;
err = true;
sock->error = -ENOTCONN;
break;
default:
iface_ctx.network_dropped = true;
err = true;
sock->error = -EIO;
break;
}
if (err) {
/* Send NULL packet to callback to notify upper stack layers
* that the peer closed the connection or there was an error.
* This is so an app will not get stuck in recv() forever.
* Let's do the callback processing in a different work queue
* so RX is not delayed.
*/
k_work_reschedule_for_queue(&hl7800_workq, &sock->notif_work, MDM_SOCK_NOTIF_DELAY);
if (trigger_sem) {
k_sem_give(&sock->sock_send_sem);
}
if (iface_ctx.network_dropped) {
k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.iface_status_work,
IFACE_WORK_DELAY);
}
}
done:
return true;
}
static int delete_socket(struct hl7800_socket *sock, enum net_sock_type type, uint8_t id)
{
char cmd[sizeof("AT+KUDPCLOSE=###")];
if (type == SOCK_STREAM) {
snprintk(cmd, sizeof(cmd), "AT+KTCPDEL=%d", id);
} else if (type == SOCK_DGRAM) {
snprintk(cmd, sizeof(cmd), "AT+KUDPCLOSE=%d", id);
}
return send_at_cmd(sock, cmd, MDM_CMD_SEND_TIMEOUT, 0, false);
}
static void delete_untracked_socket_work_cb(struct k_work *item)
{
struct stale_socket *sock = NULL;
hl7800_lock();
wakeup_hl7800();
do {
sock = dequeue_stale_socket();
if (sock != NULL) {
LOG_DBG("Delete untracked socket [%d]", sock->id);
delete_socket(NULL, sock->type, sock->id);
free_stale_socket(sock);
}
} while (sock != NULL);
set_busy(false);
allow_sleep(true);
hl7800_unlock();
}
static bool on_cmd_sockcreate(enum net_sock_type type, struct net_buf **buf, uint16_t len)
{
size_t out_len;
char value[MDM_MAX_RESP_SIZE];
struct hl7800_socket *sock = NULL;
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
value[out_len] = 0;
iface_ctx.last_socket_id = strtol(value, NULL, 10);
if (type == SOCK_STREAM) {
LOG_DBG("+KTCPCFG: %d", iface_ctx.last_socket_id);
} else if (type == SOCK_DGRAM) {
LOG_DBG("+KUDPCFG: %d", iface_ctx.last_socket_id);
}
/* check if the socket has been created already */
sock = socket_from_id(iface_ctx.last_socket_id);
if (!sock) {
LOG_DBG("look up new socket by creation id");
sock = socket_from_id(MDM_CREATE_SOCKET_ID);
if (!sock || sock->type != type) {
if (queue_stale_socket(type, iface_ctx.last_socket_id) == 0) {
/* delay some time before socket cleanup in case there
* are multiple sockets to cleanup
*/
k_work_reschedule_for_queue(&hl7800_workq,
&iface_ctx.delete_untracked_socket_work,
SOCKET_CLEANUP_WORK_DELAY);
}
goto done;
}
}
sock->socket_id = iface_ctx.last_socket_id;
sock->created = true;
sock->reconfig = false;
/* don't give back semaphore -- OK to follow */
done:
return true;
}
/* Handler: +KTCPCFG: <session_id> */
static bool on_cmd_sock_tcp_create(struct net_buf **buf, uint16_t len)
{
return on_cmd_sockcreate(SOCK_STREAM, buf, len);
}
/* Handler: +KUDPCFG: <session_id> */
static bool on_cmd_sock_udp_create(struct net_buf **buf, uint16_t len)
{
return on_cmd_sockcreate(SOCK_DGRAM, buf, len);
}
static void sockreadrecv_cb_work(struct k_work *work)
{
struct hl7800_socket *sock = NULL;
struct net_pkt *pkt;
sock = CONTAINER_OF(work, struct hl7800_socket, recv_cb_work);
LOG_DBG("Sock %d RX CB (size: %zd)", sock->socket_id,
(sock->recv_pkt != NULL) ? net_pkt_get_len(sock->recv_pkt) : 0);
/* return data */
pkt = sock->recv_pkt;
sock->recv_pkt = NULL;
if (sock->recv_cb) {
sock->recv_cb(sock->context, pkt, NULL, NULL, 0,
sock->recv_user_data);
} else {
net_pkt_unref(pkt);
}
}
static void sock_read(struct net_buf **buf, uint16_t len)
{
struct hl7800_socket *sock = NULL;
struct net_buf *frag;
uint8_t c = 0U;
int i, hdr_len;
char ok_resp[sizeof(OK_STRING)];
char eof[sizeof(EOF_PATTERN)];
size_t out_len;
sock = socket_from_id(iface_ctx.last_socket_id);
if (!sock) {
LOG_ERR("Socket not found! (%d)", iface_ctx.last_socket_id);
goto exit;
}
if (sock->error != 0) {
/* cancel notif work and restart */
k_work_reschedule_for_queue(&hl7800_workq, &sock->notif_work,
MDM_SOCK_NOTIF_DELAY);
}
LOG_DBG("Socket %d RX %u bytes", sock->socket_id, sock->rx_size);
/* remove ending \r\n from last CONNECT */
if (net_buf_frags_len(*buf) < 2) {
/* wait for \n to be RXd. \r was already RXd. */
wait_for_modem_data(buf, 0, 1);
}
/* remove \r\n */
net_buf_remove(buf, 2);
if (!*buf) {
wait_for_modem_data(buf, 0, sock->rx_size);
}
LOG_DBG("Processing RX, buf len: %zd", net_buf_frags_len(*buf));
/* allocate an RX pkt */
sock->recv_pkt = net_pkt_rx_alloc_with_buffer(
net_context_get_iface(sock->context), sock->rx_size,
sock->family, sock->ip_proto, BUF_ALLOC_TIMEOUT);
if (!sock->recv_pkt) {
LOG_ERR("Failed net_pkt_get_reserve_rx!");
goto done;
}
/* set pkt data */
net_pkt_set_context(sock->recv_pkt, sock->context);
/* add IP / protocol headers */
hdr_len = pkt_setup_ip_data(sock->recv_pkt, sock);
/* receive data */
for (i = 0; i < sock->rx_size; i++) {
/* pull data from buf and advance to the next frag if needed */
c = net_buf_get_u8(buf);
/* write data to packet */
if (net_pkt_write_u8(sock->recv_pkt, c)) {
LOG_ERR("Unable to add data! Aborting! Bytes RXd:%d",
i);
goto rx_err;
}
if (!*buf && i < sock->rx_size) {
LOG_DBG("RX more data, bytes RXd:%d", i + 1);
/* wait for at least one more byte */
wait_for_modem_data(buf, 0, 1);
if (!*buf) {
LOG_ERR("No data in buf!");
break;
}
}
}
LOG_DBG("Got all data, get EOF and OK (buf len:%zd)",
net_buf_frags_len(*buf));
if (!*buf || (net_buf_frags_len(*buf) < strlen(EOF_PATTERN))) {
wait_for_modem_data(buf, net_buf_frags_len(*buf),
strlen(EOF_PATTERN));
if (!*buf) {
LOG_WRN("No EOF present");
goto all_rx_data;
}
}
out_len = net_buf_linearize(eof, sizeof(eof), *buf, 0,
strlen(EOF_PATTERN));
eof[out_len] = 0;
/* remove EOF pattern from buffer */
net_buf_remove(buf, strlen(EOF_PATTERN));
if (strcmp(eof, EOF_PATTERN)) {
LOG_WRN("Could not find EOF [%s]", eof);
}
/* Make sure we have \r\nOK\r\n length in the buffer */
if (!*buf || (net_buf_frags_len(*buf) < strlen(OK_STRING) + 4)) {
wait_for_modem_data(buf, net_buf_frags_len(*buf),
strlen(OK_STRING) + 4);
if (!*buf) {
LOG_WRN("No OK present");
goto all_rx_data;
}
}
frag = NULL;
len = net_buf_findcrlf(*buf, &frag);
if (!frag) {
LOG_WRN("Unable to find OK start");
goto all_rx_data;
}
/* remove \r\n before OK */
net_buf_skipcrlf(buf);
out_len = net_buf_linearize(ok_resp, sizeof(ok_resp), *buf, 0,
strlen(OK_STRING));
ok_resp[out_len] = 0;
/* remove the message from the buffer */
net_buf_remove(buf, strlen(OK_STRING));
if (strcmp(ok_resp, OK_STRING)) {
LOG_WRN("Could not find OK [%s]", ok_resp);
}
/* remove \r\n after OK */
net_buf_skipcrlf(buf);
all_rx_data:
net_pkt_cursor_init(sock->recv_pkt);
net_pkt_set_overwrite(sock->recv_pkt, true);
if (hdr_len > 0) {
net_pkt_skip(sock->recv_pkt, hdr_len);
}
/* Let's do the callback processing in a different work queue in
* case the app takes a long time.
*/
k_work_submit_to_queue(&hl7800_workq, &sock->recv_cb_work);
LOG_DBG("Sock %d RX done", sock->socket_id);
goto done;
rx_err:
net_pkt_unref(sock->recv_pkt);
sock->recv_pkt = NULL;
done:
if (sock->type == SOCK_STREAM) {
if (sock->error == 0) {
sock->state = SOCK_CONNECTED;
}
} else {
sock->state = SOCK_IDLE;
}
exit:
set_busy(false);
allow_sleep(true);
hl7800_TX_unlock();
}
static bool on_cmd_connect(struct net_buf **buf, uint16_t len)
{
bool remove_data_from_buffer = true;
struct hl7800_socket *sock = NULL;
sock = socket_from_id(iface_ctx.last_socket_id);
if (!sock) {
LOG_ERR("Sock (%d) not found", iface_ctx.last_socket_id);
goto done;
}
if (sock->state == SOCK_RX) {
remove_data_from_buffer = false;
sock_read(buf, len);
} else {
k_sem_give(&sock->sock_send_sem);
}
done:
return remove_data_from_buffer;
}
static int start_socket_rx(struct hl7800_socket *sock, uint16_t rx_size)
{
char sendbuf[sizeof("AT+KTCPRCV=+#########,#####")];
if ((sock->socket_id <= 0) || (sock->rx_size <= 0)) {
LOG_WRN("Cannot start socket RX, ID: %d rx size: %d",
sock->socket_id, sock->rx_size);
return -1;
}
LOG_DBG("Start socket RX ID:%d size:%d", sock->socket_id, rx_size);
sock->state = SOCK_RX;
if (sock->type == SOCK_DGRAM) {
#if defined(CONFIG_NET_IPV4)
if (rx_size > (net_if_get_mtu(iface_ctx.iface) - NET_IPV4UDPH_LEN)) {
sock->rx_size =
net_if_get_mtu(iface_ctx.iface) - NET_IPV4UDPH_LEN;
}
#endif
#if defined(CONFIG_NET_IPV6)
if (rx_size > (net_if_get_mtu(iface_ctx.iface) - NET_IPV6UDPH_LEN)) {
sock->rx_size =
net_if_get_mtu(iface_ctx.iface) - NET_IPV6UDPH_LEN;
}
#endif
snprintk(sendbuf, sizeof(sendbuf), "AT+KUDPRCV=%d,%u",
sock->socket_id, rx_size);
} else {
#if defined(CONFIG_NET_IPV4)
if (rx_size > (net_if_get_mtu(iface_ctx.iface) - NET_IPV4TCPH_LEN)) {
sock->rx_size =
net_if_get_mtu(iface_ctx.iface) - NET_IPV4TCPH_LEN;
}
#endif
#if defined(CONFIG_NET_IPV6)
if (rx_size > (net_if_get_mtu(iface_ctx.iface) - NET_IPV6TCPH_LEN)) {
sock->rx_size =
net_if_get_mtu(iface_ctx.iface) - NET_IPV6TCPH_LEN;
}
#endif
snprintk(sendbuf, sizeof(sendbuf), "AT+KTCPRCV=%d,%u",
sock->socket_id, sock->rx_size);
}
/* Send AT+K**PRCV, The modem
* will respond with "CONNECT" and the data requested
* and then "OK" or "ERROR".
* The rest of the data processing will be handled
* once CONNECT is RXd.
*/
send_at_cmd(sock, sendbuf, K_NO_WAIT, 0, false);
return 0;
}
static void sock_rx_data_cb_work(struct k_work *work)
{
struct hl7800_socket *sock = NULL;
int rc;
sock = CONTAINER_OF(work, struct hl7800_socket, rx_data_work);
hl7800_lock();
wakeup_hl7800();
/* start RX */
rc = start_socket_rx(sock, sock->rx_size);
/* Only unlock the RX because we just locked it above.
* At the end of socket RX, the TX will be unlocked.
*/
hl7800_RX_unlock();
if (rc < 0) {
/* we didn't start socket RX so unlock TX now. */
hl7800_TX_unlock();
}
}
/* Handler: +KTCP_DATA/+KUDP_DATA: <socket_id>,<left_bytes> */
static bool on_cmd_sockdataind(struct net_buf **buf, uint16_t len)
{
int socket_id, left_bytes, rc;
size_t out_len;
char *delim;
char value[sizeof("##,####")];
struct hl7800_socket *sock = NULL;
bool unlock = false;
bool defer_rx = false;
if (!hl7800_TX_locked()) {
hl7800_TX_lock();
unlock = true;
} else {
defer_rx = true;
}
out_len = net_buf_linearize(value, sizeof(value) - 1, *buf, 0, len);
value[out_len] = 0;
/* First comma separator marks the end of socket_id */
delim = strchr(value, ',');
if (!delim) {
LOG_ERR("Missing comma");
goto error;
}
/* replace comma with null */
*delim++ = '\0';
socket_id = strtol(value, NULL, 0);
/* second param is for left_bytes */
left_bytes = strtol(delim, NULL, 0);
sock = socket_from_id(socket_id);
if (!sock) {
LOG_ERR("Unable to find socket_id:%d", socket_id);
goto error;
}
sock->rx_size = left_bytes;
if (defer_rx) {
LOG_DBG("Defer socket RX -> ID: %d bytes: %u", socket_id,
left_bytes);
k_work_submit_to_queue(&hl7800_workq, &sock->rx_data_work);
} else {
if (left_bytes > 0) {
wakeup_hl7800();
rc = start_socket_rx(sock, left_bytes);
if (rc < 0) {
goto error;
}
goto done;
}
}
error:
if (unlock) {
hl7800_TX_unlock();
}
done:
return true;
}
/* Handler: +WDSI: ## */
static bool on_cmd_device_service_ind(struct net_buf **buf, uint16_t len)
{
char value[MDM_MAX_RESP_SIZE];
size_t out_len;
memset(value, 0, sizeof(value));
out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len);
if (out_len > 0) {
iface_ctx.device_services_ind = strtol(value, NULL, 10);
}
LOG_INF("+WDSI: %d", iface_ctx.device_services_ind);
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
if (iface_ctx.device_services_ind == WDSI_PKG_DOWNLOADED) {
k_work_submit_to_queue(&hl7800_workq,
&iface_ctx.finish_fw_update_work);
}
#endif
return true;
}
static inline struct net_buf *read_rx_allocator(k_timeout_t timeout,
void *user_data)
{
return net_buf_alloc((struct net_buf_pool *)user_data, timeout);
}
static size_t hl7800_read_rx(struct net_buf **buf)
{
uint8_t uart_buffer[CONFIG_MODEM_HL7800_RECV_BUF_SIZE];
size_t bytes_read, total_read;
int ret;
uint16_t rx_len;
bytes_read = 0, total_read = 0;
/* read all of the data from mdm_receiver */
while (true) {
ret = mdm_receiver_recv(&iface_ctx.mdm_ctx, uart_buffer,
sizeof(uart_buffer), &bytes_read);
if (ret < 0 || bytes_read == 0) {
/* mdm_receiver buffer is empty */
break;
}
if (IS_ENABLED(HL7800_ENABLE_VERBOSE_MODEM_RECV_HEXDUMP)) {
LOG_HEXDUMP_DBG((const uint8_t *)&uart_buffer,
bytes_read, "HL7800 RX");
}
/* make sure we have storage */
if (!*buf) {
*buf = net_buf_alloc(&mdm_recv_pool, BUF_ALLOC_TIMEOUT);
if (!*buf) {
LOG_ERR("Can't allocate RX data! "
"Skipping data!");
break;
}
}
rx_len =
net_buf_append_bytes(*buf, bytes_read, uart_buffer,
BUF_ALLOC_TIMEOUT,
read_rx_allocator, &mdm_recv_pool);
if (rx_len < bytes_read) {
LOG_ERR("Data was lost! read %u of %zu!", rx_len,
bytes_read);
}
total_read += bytes_read;
}
return total_read;
}
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
static void finish_fw_update_work_callback(struct k_work *item)
{
ARG_UNUSED(item);
send_at_cmd(NULL, "AT+WDSR=4", MDM_CMD_SEND_TIMEOUT, 0, false);
iface_ctx.fw_updated = true;
set_fota_state(HL7800_FOTA_INSTALL);
hl7800_unlock();
}
static uint8_t calc_fw_update_crc(uint8_t *ptr, int count)
{
uint8_t crc = 0;
unsigned char l;
uint16_t i = 0;
while (i < count) {
l = *ptr;
crc += l;
++ptr;
++i;
}
return crc;
}
static int send_fw_update_packet(struct xmodem_packet *pkt)
{
generate_fota_count_event();
LOG_DBG("Send FW update packet %d,%d", pkt->id, iface_ctx.fw_packet_count);
return mdm_receiver_send(&iface_ctx.mdm_ctx, (const uint8_t *)pkt,
XMODEM_PACKET_SIZE);
}
static int prepare_and_send_fw_packet(void)
{
int ret = 0;
int read_res;
iface_ctx.fw_packet.id_complement = 0xFF - iface_ctx.fw_packet.id;
ret = fs_seek(&iface_ctx.fw_update_file, iface_ctx.file_pos, FS_SEEK_SET);
if (ret < 0) {
set_fota_state(HL7800_FOTA_FILE_ERROR);
LOG_ERR("Could not seek to offset %d of file", iface_ctx.file_pos);
return ret;
}
read_res = fs_read(&iface_ctx.fw_update_file, iface_ctx.fw_packet.data,
XMODEM_DATA_SIZE);
if (read_res < 0) {
set_fota_state(HL7800_FOTA_FILE_ERROR);
LOG_ERR("Failed to read fw update file [%d]", read_res);
return ret;
} else if (read_res < XMODEM_DATA_SIZE) {
set_fota_state(HL7800_FOTA_PAD);
fs_close(&iface_ctx.fw_update_file);
/* pad rest of data */
for (int i = read_res; i < XMODEM_DATA_SIZE; i++) {
iface_ctx.fw_packet.data[i] = XMODEM_PAD_VALUE;
}
}
iface_ctx.fw_packet.crc =
calc_fw_update_crc(iface_ctx.fw_packet.data, XMODEM_DATA_SIZE);
send_fw_update_packet(&iface_ctx.fw_packet);
iface_ctx.file_pos += read_res;
iface_ctx.fw_packet_count++;
iface_ctx.fw_packet.id++;
return ret;
}
static void process_fw_update_rx(struct net_buf **rx_buf)
{
static uint8_t xm_msg;
uint8_t eot = XM_EOT;
xm_msg = net_buf_get_u8(rx_buf);
if (xm_msg == XM_NACK) {
if (iface_ctx.fw_update_state == HL7800_FOTA_START) {
/* send first FW update packet */
set_fota_state(HL7800_FOTA_WIP);
iface_ctx.file_pos = 0;
iface_ctx.fw_packet_count = 1;
iface_ctx.fw_packet.id = 1;
iface_ctx.fw_packet.preamble = XM_SOH_1K;
prepare_and_send_fw_packet();
} else if (iface_ctx.fw_update_state == HL7800_FOTA_WIP) {
LOG_DBG("RX FW update NACK");
/* resend last packet */
send_fw_update_packet(&iface_ctx.fw_packet);
}
} else if (xm_msg == XM_ACK) {
LOG_DBG("RX FW update ACK");
if (iface_ctx.fw_update_state == HL7800_FOTA_WIP) {
/* send next FW update packet */
prepare_and_send_fw_packet();
} else if (iface_ctx.fw_update_state == HL7800_FOTA_PAD) {
set_fota_state(HL7800_FOTA_SEND_EOT);
mdm_receiver_send(&iface_ctx.mdm_ctx, &eot, sizeof(eot));
}
} else {
LOG_WRN("RX unhandled FW update value: %02x", xm_msg);
}
}
#endif /* CONFIG_MODEM_HL7800_FW_UPDATE */
/* RX thread */
static void hl7800_rx(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p1);
ARG_UNUSED(p2);
ARG_UNUSED(p3);
struct net_buf *rx_buf = NULL;
struct net_buf *frag = NULL;
int i, cmp_res;
uint16_t len;
size_t out_len;
bool cmd_handled = false;
static char rx_msg[MDM_HANDLER_MATCH_MAX_LEN];
bool unlock = false;
bool remove_line_from_buf = true;
#ifdef HL7800_LOG_UNHANDLED_RX_MSGS
char msg[MDM_MAX_RESP_SIZE];
#endif
static const struct cmd_handler handlers[] = {
/* MODEM Information */
CMD_HANDLER("AT+CGMI", atcmdinfo_manufacturer),
CMD_HANDLER("AT+CGMM", atcmdinfo_model),
CMD_HANDLER("AT+CGMR", atcmdinfo_revision),
CMD_HANDLER("AT+CGSN", atcmdinfo_imei),
CMD_HANDLER("AT+KGSN=3", atcmdinfo_serial_number),
CMD_HANDLER("+KCELLMEAS: ", atcmdinfo_rssi),
CMD_HANDLER("+CGCONTRDP: ", atcmdinfo_ipaddr),
CMD_HANDLER("+COPS: ", atcmdinfo_operator_status),
CMD_HANDLER("+KSRAT: ", radio_tech_status),
CMD_HANDLER("+KBNDCFG: ", radio_band_configuration),
CMD_HANDLER("+KBND: ", radio_active_bands),
CMD_HANDLER("+CCID: ", atcmdinfo_iccid),
CMD_HANDLER("ACTIVE PROFILE:", atcmdinfo_active_profile),
CMD_HANDLER("STORED PROFILE 0:", atcmdinfo_stored_profile0),
CMD_HANDLER("STORED PROFILE 1:", atcmdinfo_stored_profile1),
CMD_HANDLER("+WPPP: 1,1,", atcmdinfo_pdp_authentication_cfg),
CMD_HANDLER("+CGDCONT: 1", atcmdinfo_pdp_context),
CMD_HANDLER("AT+CEREG?", network_report_query),
CMD_HANDLER("+KCARRIERCFG: ", operator_index_query),
CMD_HANDLER("AT+CIMI", atcmdinfo_imsi),
CMD_HANDLER("+CFUN: ", modem_functionality),
CMD_HANDLER("%MEAS: ", survey_status),
CMD_HANDLER("+CCLK: ", rtc_query),
/* UNSOLICITED modem information */
/* mobile startup report */
CMD_HANDLER("+KSUP: ", startup_report),
/* network status */
CMD_HANDLER("+CEREG: ", network_report),
/* SOLICITED CMD AND SOCKET RESPONSES */
CMD_HANDLER("OK", sockok),
CMD_HANDLER("ERROR", sockerror),
/* SOLICITED SOCKET RESPONSES */
CMD_HANDLER("+CME ERROR: ", sock_error_code),
CMD_HANDLER("+CMS ERROR: ", sock_error_code),
CMD_HANDLER("+CEER: ", sockerror),
CMD_HANDLER("+KTCPCFG: ", sock_tcp_create),
CMD_HANDLER("+KUDPCFG: ", sock_udp_create),
CMD_HANDLER(CONNECT_STRING, connect),
CMD_HANDLER("NO CARRIER", sockerror),
/* UNSOLICITED SOCKET RESPONSES */
CMD_HANDLER("+KTCP_IND: ", ktcp_ind),
CMD_HANDLER("+KUDP_IND: ", kudp_ind),
CMD_HANDLER("+KTCP_NOTIF: ", sock_notif),
CMD_HANDLER("+KUDP_NOTIF: ", sock_notif),
CMD_HANDLER("+KTCP_DATA: ", sockdataind),
CMD_HANDLER("+KUDP_DATA: ", sockdataind),
/* FIRMWARE UPDATE RESPONSES */
CMD_HANDLER("+WDSI: ", device_service_ind),
#ifdef CONFIG_MODEM_HL7800_GPS
CMD_HANDLER("+GNSSEV: ", gps_event),
CMD_HANDLER("Latitude: ", latitude),
CMD_HANDLER("Longitude: ", longitude),
CMD_HANDLER("GpsTime: ", gps_time),
CMD_HANDLER("FixType: ", fix_type),
CMD_HANDLER("HEPE: ", hepe),
CMD_HANDLER("Altitude: ", altitude),
CMD_HANDLER("AltUnc: ", alt_unc),
CMD_HANDLER("Direction: ", direction),
CMD_HANDLER("HorSpeed: ", hor_speed),
CMD_HANDLER("VerSpeed: ", ver_speed),
#endif
#ifdef CONFIG_MODEM_HL7800_POLTE
CMD_HANDLER("%POLTEEVU: \"REGISTER\",0,", polte_registration),
CMD_HANDLER("%POLTECMD: \"LOCATE\",", polte_locate_cmd_rsp),
CMD_HANDLER("%POLTEEVU: \"LOCATION\",", polte_location),
#endif
};
while (true) {
/* wait for incoming data */
(void)k_sem_take(&iface_ctx.mdm_ctx.rx_sem, K_FOREVER);
hl7800_read_rx(&rx_buf);
/* If an external module hasn't locked the command processor,
* then do so now.
*/
if (!hl7800_RX_locked()) {
hl7800_RX_lock();
unlock = true;
} else {
unlock = false;
}
while (rx_buf) {
remove_line_from_buf = true;
cmd_handled = false;
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
if ((iface_ctx.fw_update_state == HL7800_FOTA_START) ||
(iface_ctx.fw_update_state == HL7800_FOTA_WIP) ||
(iface_ctx.fw_update_state == HL7800_FOTA_PAD)) {
process_fw_update_rx(&rx_buf);
if (!rx_buf) {
break;
}
}
#endif
net_buf_skipcrlf(&rx_buf);
if (!rx_buf) {
break;
}
frag = NULL;
len = net_buf_findcrlf(rx_buf, &frag);
if (!frag) {
break;
}
out_len = net_buf_linearize(rx_msg, sizeof(rx_msg),
rx_buf, 0, len);
/* look for matching data handlers */
i = -1;
for (i = 0; i < ARRAY_SIZE(handlers); i++) {
if (iface_ctx.search_no_id_resp) {
cmp_res = strncmp(iface_ctx.no_id_resp_cmd,
handlers[i].cmd,
handlers[i].cmd_len);
} else {
cmp_res =
strncmp(rx_msg, handlers[i].cmd,
handlers[i].cmd_len);
}
if (cmp_res == 0) {
/* found a matching handler */
/* skip cmd_len */
if (!iface_ctx.search_no_id_resp) {
rx_buf = net_buf_skip(
rx_buf,
handlers[i].cmd_len);
}
/* locate next cr/lf */
frag = NULL;
len = net_buf_findcrlf(rx_buf, &frag);
if (!frag) {
break;
}
LOG_DBG("HANDLE %s (len:%u)",
handlers[i].cmd, len);
/* call handler */
if (handlers[i].func) {
remove_line_from_buf =
handlers[i].func(
&rx_buf, len);
}
cmd_handled = true;
iface_ctx.search_no_id_resp = false;
frag = NULL;
/* make sure buf still has data */
if (!rx_buf) {
break;
}
/* We've handled the current line
* and need to exit the "search for
* handler loop". Let's skip any
* "extra" data and look for the next
* CR/LF, leaving us ready for the
* next handler search.
*/
len = net_buf_findcrlf(rx_buf, &frag);
break;
}
}
/* Handle unhandled commands */
if (IS_ENABLED(HL7800_LOG_UNHANDLED_RX_MSGS) &&
!cmd_handled && frag && len > 1) {
out_len = net_buf_linearize(msg, sizeof(msg),
rx_buf, 0, len);
msg[out_len] = 0;
LOG_HEXDUMP_DBG((const uint8_t *)&msg, len,
"UNHANDLED RX");
}
if (remove_line_from_buf && frag && rx_buf) {
/* clear out processed line (buffers) */
net_buf_remove(&rx_buf, len);
}
}
if (unlock) {
hl7800_RX_unlock();
}
/* give up time if we have a solid stream of data */
k_yield();
}
}
static void shutdown_uart(void)
{
#ifdef CONFIG_PM_DEVICE
int rc;
enum pm_device_state state;
rc = pm_device_state_get(iface_ctx.mdm_ctx.uart_dev, &state);
if (rc) {
LOG_ERR("Error getting UART power state (%d)", rc);
}
if (state != PM_DEVICE_STATE_SUSPENDED) {
HL7800_IO_DBG_LOG("Power OFF the UART");
uart_irq_rx_disable(iface_ctx.mdm_ctx.uart_dev);
rc = pm_device_action_run(iface_ctx.mdm_ctx.uart_dev, PM_DEVICE_ACTION_SUSPEND);
if (rc) {
LOG_ERR("Error disabling UART peripheral (%d)", rc);
uart_irq_rx_enable(iface_ctx.mdm_ctx.uart_dev);
}
}
#endif
}
static void power_on_uart(void)
{
#ifdef CONFIG_PM_DEVICE
int rc;
enum pm_device_state state;
rc = pm_device_state_get(iface_ctx.mdm_ctx.uart_dev, &state);
if (rc) {
LOG_ERR("Error getting UART power state (%d)", rc);
}
if (state != PM_DEVICE_STATE_ACTIVE) {
HL7800_IO_DBG_LOG("Power ON the UART");
rc = pm_device_action_run(iface_ctx.mdm_ctx.uart_dev, PM_DEVICE_ACTION_RESUME);
if (rc) {
LOG_ERR("Error enabling UART peripheral (%d)", rc);
uart_irq_rx_disable(iface_ctx.mdm_ctx.uart_dev);
} else {
uart_irq_rx_enable(iface_ctx.mdm_ctx.uart_dev);
}
}
#endif
}
/* Make sure all IO voltages are removed for proper reset. */
static void prepare_io_for_reset(void)
{
HL7800_IO_DBG_LOG("Preparing IO for reset/sleep");
shutdown_uart();
modem_assert_wake(false);
modem_assert_pwr_on(false);
modem_assert_fast_shutd(false);
iface_ctx.wait_for_KSUP = true;
iface_ctx.wait_for_KSUP_tries = 0;
}
static void mdm_vgpio_work_cb(struct k_work *item)
{
ARG_UNUSED(item);
hl7800_lock();
if (!iface_ctx.vgpio_state) {
if (iface_ctx.desired_sleep_level == HL7800_SLEEP_HIBERNATE ||
iface_ctx.desired_sleep_level == HL7800_SLEEP_LITE_HIBERNATE) {
if (iface_ctx.sleep_state != iface_ctx.desired_sleep_level) {
set_sleep_state(iface_ctx.desired_sleep_level);
}
}
if (iface_ctx.iface && iface_ctx.initialized &&
iface_ctx.low_power_mode != HL7800_LPM_PSM) {
net_if_carrier_off(iface_ctx.iface);
}
}
hl7800_unlock();
}
void mdm_vgpio_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins)
{
ARG_UNUSED(port);
ARG_UNUSED(cb);
ARG_UNUSED(pins);
iface_ctx.vgpio_state = read_pin(1, &hl7800_cfg.gpio[MDM_VGPIO]);
HL7800_IO_DBG_LOG("VGPIO:%d", iface_ctx.vgpio_state);
if (!iface_ctx.vgpio_state) {
prepare_io_for_reset();
if (!iface_ctx.restarting && iface_ctx.initialized) {
iface_ctx.reconfig_IP_connection = true;
}
} else {
if (iface_ctx.off) {
return;
}
/* The peripheral must be enabled in ISR context
* because the driver may be
* waiting for +KSUP or waiting to send commands.
* This can occur, for example, during a modem reset.
*/
power_on_uart();
/* Keep the modem awake to see if it has anything to send to us. */
allow_sleep(false);
/* Allow the modem to go back to sleep if it was the one who
* sourced the transition.
*/
allow_sleep(true);
}
check_hl7800_awake();
/* When the network state changes a semaphore must be taken.
* This can't be done in interrupt context because the wait time != 0.
*/
k_work_submit_to_queue(&hl7800_workq, &iface_ctx.mdm_vgpio_work);
}
void mdm_uart_dsr_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins)
{
ARG_UNUSED(port);
ARG_UNUSED(cb);
ARG_UNUSED(pins);
iface_ctx.dsr_state = read_pin(1, &hl7800_cfg.gpio[MDM_UART_DSR]);
HL7800_IO_DBG_LOG("MDM_UART_DSR:%d", iface_ctx.dsr_state);
}
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
static void mark_sockets_for_reconfig(void)
{
int i;
struct hl7800_socket *sock = NULL;
for (i = 0; i < MDM_MAX_SOCKETS; i++) {
sock = &iface_ctx.sockets[i];
if ((sock->context != NULL) && (sock->created)) {
/* mark socket as possibly needing re-configuration */
sock->reconfig = true;
}
}
}
#endif
void mdm_gpio6_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins)
{
ARG_UNUSED(port);
ARG_UNUSED(cb);
ARG_UNUSED(pins);
iface_ctx.gpio6_state = read_pin(1, &hl7800_cfg.gpio[MDM_GPIO6]);
HL7800_IO_DBG_LOG("MDM_GPIO6:%d", iface_ctx.gpio6_state);
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
if (!iface_ctx.gpio6_state) {
/* HL7800 is not awake, shut down UART to save power */
shutdown_uart();
iface_ctx.wait_for_KSUP = true;
iface_ctx.wait_for_KSUP_tries = 0;
iface_ctx.reconfig_IP_connection = true;
mark_sockets_for_reconfig();
} else {
if (iface_ctx.off) {
return;
} else if (iface_ctx.vgpio_state) {
power_on_uart();
/* Keep the modem awake to see if it has anything to send to us. */
allow_sleep(false);
/* Allow the modem to go back to sleep if it was the one who
* sourced the transition.
*/
allow_sleep(true);
}
}
check_hl7800_awake();
if ((iface_ctx.gpio6_callback != NULL) &&
((iface_ctx.desired_sleep_level == HL7800_SLEEP_HIBERNATE) ||
(iface_ctx.desired_sleep_level == HL7800_SLEEP_LITE_HIBERNATE))) {
iface_ctx.gpio6_callback(iface_ctx.gpio6_state);
}
#endif
}
void mdm_uart_cts_callback_isr(const struct device *port, struct gpio_callback *cb, uint32_t pins)
{
ARG_UNUSED(port);
ARG_UNUSED(cb);
ARG_UNUSED(pins);
uint64_t now;
uint64_t elapsed;
int resample_state;
iface_ctx.cts_state = read_pin(0, &hl7800_cfg.gpio[MDM_UART_CTS]);
/* Debounce the CTS signal */
now = k_ticks_to_us_floor64(k_uptime_ticks());
elapsed = now - iface_ctx.last_cts_time;
if (iface_ctx.last_cts_time <= 0) {
/* This is the first transition we have seen, continue */
} else if (elapsed <= CONFIG_MODEM_HL7800_CTS_FILTER_US) {
/* CTS changed too quickly, ignore this transition */
iface_ctx.last_cts_time = now;
return;
}
iface_ctx.last_cts_time = now;
k_busy_wait(CONFIG_MODEM_HL7800_CTS_FILTER_US);
resample_state = read_pin(0, &hl7800_cfg.gpio[MDM_UART_CTS]);
if (iface_ctx.cts_state != resample_state) {
/* CTS changed while we were debouncing, ignore it */
iface_ctx.cts_state = resample_state;
return;
}
iface_ctx.cts_state = resample_state;
if (iface_ctx.cts_state != iface_ctx.last_cts_state) {
iface_ctx.last_cts_state = iface_ctx.cts_state;
} else {
return;
}
HL7800_IO_DBG_LOG("MDM_UART_CTS:%d(%llu)", iface_ctx.cts_state, elapsed);
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
if (iface_ctx.cts_state && iface_ctx.allow_sleep) {
/* HL7800 cannot receive UART data, shut down UART to save power.
* This is critical for proper low power operation. If the UART is disabled
* after VGPIO is low, the UART will not suspend properly.
*/
shutdown_uart();
} else {
if (iface_ctx.off) {
return;
}
if (iface_ctx.vgpio_state && iface_ctx.gpio6_state) {
power_on_uart();
/* Wake up the modem to see if it has anything to send to us. */
allow_sleep(false);
/* Allow the modem to go back to sleep if it was the one who
* sourced the CTS transition.
*/
allow_sleep(true);
}
}
#endif
if ((iface_ctx.cts_callback != NULL) &&
(iface_ctx.desired_sleep_level == HL7800_SLEEP_SLEEP)) {
iface_ctx.cts_callback(iface_ctx.cts_state);
}
check_hl7800_awake();
}
static void modem_reset(void)
{
prepare_io_for_reset();
LOG_INF("Modem Reset");
/* Hard reset the modem */
modem_assert_reset(true);
/* >20 milliseconds required for reset low */
k_sleep(MDM_RESET_LOW_TIME);
iface_ctx.mdm_startup_reporting_on = false;
set_sleep_state(HL7800_SLEEP_UNINITIALIZED);
check_hl7800_awake();
set_network_state(HL7800_NOT_REGISTERED);
set_startup_state(HL7800_STARTUP_STATE_UNKNOWN);
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
if (iface_ctx.fw_update_state == HL7800_FOTA_REBOOT_AND_RECONFIGURE) {
set_fota_state(HL7800_FOTA_COMPLETE);
} else {
set_fota_state(HL7800_FOTA_IDLE);
}
#endif
k_sem_reset(&iface_ctx.mdm_awake);
iface_ctx.off = true;
}
static void modem_run(void)
{
LOG_INF("Modem Run");
iface_ctx.off = false;
modem_assert_reset(false);
allow_sleep(false);
k_sleep(MDM_RESET_HIGH_TIME);
}
static int modem_boot_handler(char *reason)
{
int ret;
LOG_DBG("%s", reason);
ret = k_sem_take(&iface_ctx.mdm_awake, MDM_BOOT_TIME);
if (ret) {
LOG_WRN("Err waiting for boot: %d, DSR: %u", ret, iface_ctx.dsr_state);
} else {
LOG_INF("Modem booted!");
}
/* Turn OFF EPS network registration status reporting because
* it isn't needed until after initialization is complete.
*/
SEND_AT_CMD_EXPECT_OK("AT+CEREG=0");
/* Determine if echo is on/off by reading the profile
* note: It wasn't clear how to read the
* active profile so all 3 are read.
*/
iface_ctx.mdm_echo_is_on = true;
SEND_AT_CMD_EXPECT_OK("AT&V");
if (iface_ctx.mdm_echo_is_on) {
/* Turn OFF echo (after boot/reset) because a profile
* hasn't been saved yet
*/
SEND_AT_CMD_EXPECT_OK("ATE0");
/* Save profile 0 */
SEND_AT_CMD_EXPECT_OK("AT&W");
/* Reread profiles so echo state can be checked again. */
SEND_AT_CMD_EXPECT_OK("AT&V");
}
__ASSERT(!iface_ctx.mdm_echo_is_on, "Echo should be off");
return 0;
error:
return ret;
}
/**
* @brief compares two version strings with any delimiter
*
* @param v1: version string 1
* @param v2: version string 2
*
* @retval 0 if equal, < 0 if v1 < v2, > 0 if v1 > v2.
*/
static int compare_versions(char *v1, const char *v2)
{
int result = 0;
char *tail1;
char *tail2;
unsigned long ver1, ver2;
/* loop through each level of the version string */
while (result == 0) {
/* extract leading version numbers */
ver1 = strtoul(v1, &tail1, 10);
ver2 = strtoul(v2, &tail2, 10);
/* if numbers differ, then set the result */
if (ver1 < ver2) {
result = -1;
} else if (ver1 > ver2) {
result = 1;
} else {
/* if numbers are the same, go to next level */
v1 = tail1;
v2 = tail2;
/* if we reach the end of both, then they are identical */
if (*v1 == '\0' && *v2 == '\0') {
break;
/* if we reach the end of one only, it is the smaller */
} else if (*v1 == '\0') {
result = -1;
} else if (*v2 == '\0') {
result = 1;
/* not at end ... so far they match so keep going */
} else {
v1++;
v2++;
}
}
}
return result;
}
static int setup_gprs_connection(char *access_point_name)
{
char cmd_string[sizeof("AT+KCNXCFG=1,\"GPRS\",\"\",,,"
"\"IPV4V6\"") +
MDM_HL7800_APN_MAX_SIZE];
int cmd_max_len = sizeof(cmd_string) - 1;
memset(cmd_string, 0, cmd_max_len);
strncat(cmd_string, "AT+KCNXCFG=1,\"GPRS\",\"", cmd_max_len);
strncat(cmd_string, access_point_name, cmd_max_len);
strncat(cmd_string, "\",,,\"", cmd_max_len);
strncat(cmd_string, MODEM_HL7800_ADDRESS_FAMILY "\"", cmd_max_len);
return send_at_cmd(NULL, cmd_string, MDM_CMD_SEND_TIMEOUT, 0, false);
}
static int set_bands(const char *bands, bool full_reboot)
{
int ret;
char cmd[sizeof("AT+KBNDCFG=#,####################")];
snprintk(cmd, sizeof(cmd), "AT+KBNDCFG=%d,%s", iface_ctx.mdm_rat, bands);
ret = send_at_cmd(NULL, cmd, MDM_CMD_SEND_TIMEOUT, MDM_DEFAULT_AT_CMD_RETRIES, false);
if (ret < 0) {
return ret;
}
if (!full_reboot) {
ret = send_at_cmd(NULL, "AT+CFUN=1,1", MDM_CMD_SEND_TIMEOUT,
MDM_DEFAULT_AT_CMD_RETRIES, false);
if (ret < 0) {
return ret;
}
ret = modem_boot_handler("LTE bands were just set");
} else {
k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.mdm_reset_work, K_NO_WAIT);
}
return ret;
}
int32_t mdm_hl7800_set_bands(const char *bands)
{
int ret, i;
char temp_bands[MDM_BAND_BITMAP_STR_LENGTH_MAX + 1];
int num_leading_zeros;
if ((bands == NULL) || (strlen(bands) > MDM_BAND_BITMAP_STR_LENGTH_MAX) ||
(strlen(bands) < MDM_BAND_BITMAP_STR_LENGTH_MIN)) {
return -EINVAL;
}
if (strlen(bands) < MDM_BAND_BITMAP_STR_LENGTH_MAX) {
num_leading_zeros = MDM_BAND_BITMAP_STR_LENGTH_MAX - strlen(bands);
for (i = 0; i < num_leading_zeros; i++) {
temp_bands[i] = '0';
if (i == (num_leading_zeros - 1)) {
strncpy(temp_bands + (i + 1), bands, sizeof(temp_bands) - (i + 1));
}
}
} else {
memcpy(temp_bands, bands, sizeof(temp_bands));
}
/* no need to set bands if settings match */
if (strncmp(temp_bands, iface_ctx.mdm_bands_string, sizeof(temp_bands)) == 0) {
return 0;
}
hl7800_lock();
ret = set_bands(temp_bands, true);
hl7800_unlock();
return ret;
}
static int modem_reset_and_configure(void)
{
int ret = 0;
bool sleep = false;
bool config_apn = false;
char *apn;
#ifdef CONFIG_MODEM_HL7800_EDRX
int edrx_act_type;
char set_edrx_msg[sizeof("AT+CEDRXS=2,4,\"0000\"")];
#endif
#if CONFIG_MODEM_HL7800_CONFIGURE_BANDS
uint16_t bands_top = 0;
uint32_t bands_middle = 0, bands_bottom = 0;
char new_bands[MDM_BAND_BITMAP_STR_LENGTH_MAX + 1];
#endif
#if CONFIG_MODEM_HL7800_PSM
const char TURN_ON_PSM[] =
"AT+CPSMS=1,,,\"" CONFIG_MODEM_HL7800_PSM_PERIODIC_TAU
"\",\"" CONFIG_MODEM_HL7800_PSM_ACTIVE_TIME "\"";
#endif
set_busy(true);
iface_ctx.restarting = true;
iface_ctx.dns_ready = false;
if (iface_ctx.iface) {
net_if_carrier_off(iface_ctx.iface);
}
hl7800_stop_rssi_work();
initialize_sleep_level();
reboot:
modem_reset();
modem_run();
ret = modem_boot_handler("Initialization");
if (!iface_ctx.mdm_startup_reporting_on) {
/* Turn on mobile start-up reporting for next reset.
* It will indicate if SIM is present.
* Its value is saved in non-volatile memory on the HL7800.
*/
SEND_AT_CMD_EXPECT_OK("AT+KSREP=1");
goto reboot;
} else if (ret < 0) {
goto error;
}
/* turn on numeric error codes */
SEND_AT_CMD_EXPECT_OK("AT+CMEE=1");
/* modem revision */
SEND_COMPLEX_AT_CMD("AT+CGMR");
/* determine RAT command support */
ret = compare_versions(iface_ctx.mdm_revision, NEW_RAT_CMD_MIN_VERSION);
if (ret < 0) {
iface_ctx.new_rat_cmd_support = false;
} else {
iface_ctx.new_rat_cmd_support = true;
}
/* Query current Radio Access Technology (RAT) */
SEND_AT_CMD_EXPECT_OK("AT+KSRAT?");
/* If CONFIG_MODEM_HL7800_RAT_M1 or CONFIG_MODEM_HL7800_RAT_NB1, then
* set the radio mode. This is only done here if the driver has not been
* initialized (!iface_ctx.configured) yet because the public API also
* allows the RAT to be changed (and will reset the modem).
*/
#ifndef CONFIG_MODEM_HL7800_RAT_NO_CHANGE
if (!iface_ctx.configured) {
#if CONFIG_MODEM_HL7800_RAT_M1
if (iface_ctx.mdm_rat != MDM_RAT_CAT_M1) {
if (iface_ctx.new_rat_cmd_support) {
SEND_AT_CMD_ONCE_EXPECT_OK(SET_RAT_M1_CMD);
} else {
SEND_AT_CMD_ONCE_EXPECT_OK(
SET_RAT_M1_CMD_LEGACY);
}
if (ret >= 0) {
goto reboot;
}
}
#elif CONFIG_MODEM_HL7800_RAT_NB1
if (iface_ctx.mdm_rat != MDM_RAT_CAT_NB1) {
if (iface_ctx.new_rat_cmd_support) {
SEND_AT_CMD_ONCE_EXPECT_OK(SET_RAT_NB1_CMD);
} else {
SEND_AT_CMD_ONCE_EXPECT_OK(
SET_RAT_NB1_CMD_LEGACY);
}
if (ret >= 0) {
goto reboot;
}
}
#endif
}
#endif
SEND_AT_CMD_EXPECT_OK("AT+KBNDCFG?");
/* Configure LTE bands */
#if CONFIG_MODEM_HL7800_CONFIGURE_BANDS
#if CONFIG_MODEM_HL7800_BAND_1
bands_bottom |= 1 << 0;
#endif
#if CONFIG_MODEM_HL7800_BAND_2
bands_bottom |= 1 << 1;
#endif
#if CONFIG_MODEM_HL7800_BAND_3
bands_bottom |= 1 << 2;
#endif
#if CONFIG_MODEM_HL7800_BAND_4
bands_bottom |= 1 << 3;
#endif
#if CONFIG_MODEM_HL7800_BAND_5
bands_bottom |= 1 << 4;
#endif
#if CONFIG_MODEM_HL7800_BAND_8
bands_bottom |= 1 << 7;
#endif
#if CONFIG_MODEM_HL7800_BAND_9
bands_bottom |= 1 << 8;
#endif
#if CONFIG_MODEM_HL7800_BAND_10
bands_bottom |= 1 << 9;
#endif
#if CONFIG_MODEM_HL7800_BAND_12
bands_bottom |= 1 << 11;
#endif
#if CONFIG_MODEM_HL7800_BAND_13
bands_bottom |= 1 << 12;
#endif
#if CONFIG_MODEM_HL7800_BAND_14
bands_bottom |= 1 << 13;
#endif
#if CONFIG_MODEM_HL7800_BAND_17
bands_bottom |= 1 << 16;
#endif
#if CONFIG_MODEM_HL7800_BAND_18
bands_bottom |= 1 << 17;
#endif
#if CONFIG_MODEM_HL7800_BAND_19
bands_bottom |= 1 << 18;
#endif
#if CONFIG_MODEM_HL7800_BAND_20
bands_bottom |= 1 << 19;
#endif
#if CONFIG_MODEM_HL7800_BAND_25
bands_bottom |= 1 << 24;
#endif
#if CONFIG_MODEM_HL7800_BAND_26
bands_bottom |= 1 << 25;
#endif
#if CONFIG_MODEM_HL7800_BAND_27
bands_bottom |= 1 << 26;
#endif
#if CONFIG_MODEM_HL7800_BAND_28
bands_bottom |= 1 << 27;
#endif
#if CONFIG_MODEM_HL7800_BAND_66
bands_top |= 1 << 1;
#endif
/* Check if bands are configured correctly */
if (iface_ctx.mdm_bands_top != bands_top ||
iface_ctx.mdm_bands_middle != bands_middle ||
iface_ctx.mdm_bands_bottom != bands_bottom) {
if (iface_ctx.mdm_bands_top != bands_top) {
LOG_INF("Top band mismatch, want %04x got %04x",
bands_top, iface_ctx.mdm_bands_top);
}
if (iface_ctx.mdm_bands_middle != bands_middle) {
LOG_INF("Middle band mismatch, want %08x got %08x",
bands_middle, iface_ctx.mdm_bands_middle);
}
if (iface_ctx.mdm_bands_bottom != bands_bottom) {
LOG_INF("Bottom band mismatch, want %08x got %08x",
bands_bottom, iface_ctx.mdm_bands_bottom);
}
snprintk(new_bands, sizeof(new_bands),
"%0" STRINGIFY(MDM_TOP_BAND_SIZE) "x%0" STRINGIFY(
MDM_MIDDLE_BAND_SIZE) "x%0" STRINGIFY(MDM_BOTTOM_BAND_SIZE) "x",
bands_top, bands_middle, bands_bottom);
ret = set_bands(new_bands, false);
if (ret < 0) {
goto error;
}
}
#endif
/**
* Disable the radio until all config is done.
* This ensures all settings are applied during this session instead of on the next reboot.
*/
SEND_AT_CMD_EXPECT_OK("AT+CFUN=4,0");
iface_ctx.low_power_mode = HL7800_LPM_NONE;
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
/* enable GPIO6 low power monitoring */
SEND_AT_CMD_EXPECT_OK("AT+KHWIOCFG=3,1,6");
ret = set_sleep_level();
if (ret < 0) {
goto error;
}
#if CONFIG_MODEM_HL7800_PSM
iface_ctx.low_power_mode = HL7800_LPM_PSM;
/* Turn off eDRX */
SEND_AT_CMD_EXPECT_OK("AT+CEDRXS=0");
SEND_AT_CMD_EXPECT_OK(TURN_ON_PSM);
#elif CONFIG_MODEM_HL7800_EDRX
iface_ctx.low_power_mode = HL7800_LPM_EDRX;
/* Turn off PSM */
SEND_AT_CMD_EXPECT_OK("AT+CPSMS=0");
/* turn on eDRX */
if (iface_ctx.mdm_rat == MDM_RAT_CAT_NB1) {
edrx_act_type = 5;
} else {
edrx_act_type = 4;
}
snprintk(set_edrx_msg, sizeof(set_edrx_msg), "AT+CEDRXS=1,%d,\"%s\"",
edrx_act_type, CONFIG_MODEM_HL7800_EDRX_VALUE);
SEND_AT_CMD_EXPECT_OK(set_edrx_msg);
#endif
sleep = true;
#else
/* Turn off sleep mode */
SEND_AT_CMD_EXPECT_OK("AT+KSLEEP=2");
/* Turn off PSM */
SEND_AT_CMD_EXPECT_OK("AT+CPSMS=0");
/* Turn off eDRX */
SEND_AT_CMD_EXPECT_OK("AT+CEDRXS=0");
#endif
/* modem manufacturer */
SEND_COMPLEX_AT_CMD("AT+CGMI");
/* modem model */
SEND_COMPLEX_AT_CMD("AT+CGMM");
/* query modem IMEI */
SEND_COMPLEX_AT_CMD("AT+CGSN");
/* query modem serial number */
SEND_COMPLEX_AT_CMD("AT+KGSN=3");
if (iface_ctx.mdm_startup_state != HL7800_STARTUP_STATE_SIM_NOT_PRESENT) {
/* query SIM ICCID */
SEND_AT_CMD_IGNORE_ERROR("AT+CCID?");
/* query SIM IMSI */
(void)send_at_cmd(NULL, "AT+CIMI", MDM_CMD_SEND_TIMEOUT, MDM_DEFAULT_AT_CMD_RETRIES,
true);
}
/* Query PDP context to get APN */
SEND_AT_CMD_EXPECT_OK("AT+CGDCONT?");
apn = iface_ctx.mdm_apn.value;
if (strcmp(iface_ctx.mdm_pdp_addr_fam, MODEM_HL7800_ADDRESS_FAMILY)) {
config_apn = true;
}
/* Query PDP authentication context to get APN username/password.
* Temporary Workaround - Ignore error
* On some modules this is returning an error and the response data.
*/
SEND_AT_CMD_IGNORE_ERROR("AT+WPPP?");
#if CONFIG_MODEM_HL7800_SET_APN_NAME_ON_STARTUP
if (!iface_ctx.configured) {
if (strncmp(iface_ctx.mdm_apn.value, CONFIG_MODEM_HL7800_APN_NAME,
MDM_HL7800_APN_MAX_STRLEN) != 0) {
apn = CONFIG_MODEM_HL7800_APN_NAME;
config_apn = true;
}
}
#endif
if (config_apn) {
/* set PDP context address family along with current APN */
ret = write_apn(apn);
if (ret < 0) {
goto error;
}
SEND_AT_CMD_EXPECT_OK("AT+CGDCONT?");
}
ret = setup_gprs_connection(iface_ctx.mdm_apn.value);
if (ret < 0) {
goto error;
}
/* query the network status in case we already registered */
SEND_COMPLEX_AT_CMD("AT+CEREG?");
/* Turn on EPS network registration status reporting */
SEND_AT_CMD_EXPECT_OK("AT+CEREG=5");
/* query all socket configs to cleanup any sockets that are not
* tracked by the driver
*/
SEND_AT_CMD_EXPECT_OK("AT+KTCPCFG?");
SEND_AT_CMD_EXPECT_OK("AT+KUDPCFG?");
/* Enabled the LTE radio */
#if !defined(CONFIG_MODEM_HL7800_BOOT_IN_AIRPLANE_MODE)
SEND_AT_CMD_EXPECT_OK("AT+CFUN=1,0");
#endif
/* The modem has been initialized and now the network interface can be
* started in the CEREG message handler.
*/
LOG_INF("Modem ready!");
iface_ctx.restarting = false;
iface_ctx.configured = true;
set_busy(false);
allow_sleep(sleep);
/* trigger APN update event */
event_handler(HL7800_EVENT_APN_UPDATE, &iface_ctx.mdm_apn);
#ifdef CONFIG_MODEM_HL7800_BOOT_DELAY
if (!iface_ctx.initialized) {
if (iface_ctx.iface != NULL) {
hl7800_build_mac(&iface_ctx);
net_if_set_link_addr(iface_ctx.iface, iface_ctx.mac_addr,
sizeof(iface_ctx.mac_addr),
NET_LINK_ETHERNET);
iface_ctx.initialized = true;
}
}
#endif
return 0;
error:
LOG_ERR("Unable to configure modem");
iface_ctx.configured = false;
set_network_state(HL7800_UNABLE_TO_CONFIGURE);
/* Kernel will fault with non-zero return value.
* Allow other parts of application to run when modem cannot be configured.
*/
return 0;
}
static int write_apn(char *access_point_name)
{
char cmd_string[MDM_HL7800_APN_CMD_MAX_SIZE];
/* PDP Context */
memset(cmd_string, 0, MDM_HL7800_APN_CMD_MAX_SIZE);
if (strcmp(MODEM_HL7800_ADDRESS_FAMILY, ADDRESS_FAMILY_IPV4)) {
strncat(cmd_string, "AT+CGDCONT=1,\"" MODEM_HL7800_ADDRESS_FAMILY "\",\"",
MDM_HL7800_APN_CMD_MAX_STRLEN);
} else {
strncat(cmd_string, "AT+CGDCONT=1,\"" ADDRESS_FAMILY_IP "\",\"",
MDM_HL7800_APN_CMD_MAX_STRLEN);
}
strncat(cmd_string, access_point_name, MDM_HL7800_APN_CMD_MAX_STRLEN);
strncat(cmd_string, "\"", MDM_HL7800_APN_CMD_MAX_STRLEN);
return send_at_cmd(NULL, cmd_string, MDM_CMD_SEND_TIMEOUT, 0, false);
}
static void mdm_reset_work_callback(struct k_work *item)
{
ARG_UNUSED(item);
mdm_hl7800_reset();
}
int32_t mdm_hl7800_reset(void)
{
int ret;
hl7800_lock();
ret = modem_reset_and_configure();
hl7800_unlock();
return ret;
}
static void mdm_power_off_work_callback(struct k_work *item)
{
ARG_UNUSED(item);
int ret;
#if defined(CONFIG_DNS_RESOLVER)
struct dns_resolve_context *dns_ctx;
LOG_DBG("Shutdown DNS resolver");
dns_ctx = dns_resolve_get_default();
(void)dns_resolve_close(dns_ctx);
#endif
hl7800_lock();
notify_all_tcp_sockets_closed();
ret = send_at_cmd(NULL, "AT+CPOF", MDM_CMD_SEND_TIMEOUT, 1, false);
if (ret) {
LOG_ERR("AT+CPOF ret:%d", ret);
return;
}
prepare_io_for_reset();
iface_ctx.dns_ready = false;
iface_ctx.configured = false;
iface_ctx.off = true;
set_busy(false);
/* bring the iface down */
if (iface_ctx.iface) {
net_if_carrier_off(iface_ctx.iface);
}
LOG_INF("Modem powered off");
hl7800_unlock();
}
static int hl7800_power_off(void)
{
LOG_INF("Powering off modem");
wakeup_hl7800();
hl7800_stop_rssi_work();
k_work_cancel_delayable(&iface_ctx.iface_status_work);
k_work_cancel_delayable(&iface_ctx.dns_work);
k_work_cancel_delayable(&iface_ctx.mdm_reset_work);
k_work_cancel_delayable(&iface_ctx.allow_sleep_work);
k_work_cancel_delayable(&iface_ctx.delete_untracked_socket_work);
(void)k_work_submit_to_queue(&hl7800_workq, &iface_ctx.mdm_pwr_off_work);
return 0;
}
int32_t mdm_hl7800_power_off(void)
{
int rc;
hl7800_lock();
rc = hl7800_power_off();
hl7800_unlock();
return rc;
}
int mdm_hl7800_register_event_callback(struct mdm_hl7800_callback_agent *agent)
{
int ret;
ret = k_sem_take(&cb_lock, K_NO_WAIT);
if (ret < 0) {
return ret;
}
if (!agent->event_callback) {
LOG_WRN("event_callback is NULL");
}
sys_slist_append(&hl7800_event_callback_list, &agent->node);
k_sem_give(&cb_lock);
return ret;
}
int mdm_hl7800_unregister_event_callback(struct mdm_hl7800_callback_agent *agent)
{
int ret;
ret = k_sem_take(&cb_lock, K_NO_WAIT);
if (ret < 0) {
return ret;
}
ret = (int)sys_slist_find_and_remove(&hl7800_event_callback_list, &agent->node);
if (ret) {
ret = 0;
} else {
ret = -ENOENT;
}
k_sem_give(&cb_lock);
return ret;
}
/*** OFFLOAD FUNCTIONS ***/
static int connect_TCP_socket(struct hl7800_socket *sock)
{
int ret;
char cmd_con[sizeof("AT+KTCPCNX=##")];
snprintk(cmd_con, sizeof(cmd_con), "AT+KTCPCNX=%d", sock->socket_id);
ret = send_at_cmd(sock, cmd_con, MDM_CMD_SEND_TIMEOUT, 0, false);
if (ret < 0) {
LOG_ERR("AT+KTCPCNX ret:%d", ret);
ret = -EIO;
goto done;
}
/* Now wait for +KTCP_IND or +KTCP_NOTIF to ensure
* the connection succeeded or failed.
*/
ret = k_sem_take(&sock->sock_send_sem, MDM_CMD_CONN_TIMEOUT);
if (ret == 0) {
ret = sock->error;
} else if (ret == -EAGAIN) {
ret = -ETIMEDOUT;
}
if (ret < 0) {
LOG_ERR("+KTCP_IND/NOTIF ret:%d", ret);
goto done;
} else {
sock->state = SOCK_CONNECTED;
net_context_set_state(sock->context, NET_CONTEXT_CONNECTED);
}
done:
return ret;
}
static int configure_TCP_socket(struct hl7800_socket *sock)
{
int ret;
char cmd_cfg[sizeof("AT+KTCPCFG=#,#,\"" IPV6_ADDR_FORMAT "\",#####,,,,#,,#")];
int dst_port = -1;
int af;
bool restore_on_boot = false;
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
restore_on_boot = true;
#endif
if (sock->dst.sa_family == AF_INET6) {
af = MDM_HL7800_SOCKET_AF_IPV6;
dst_port = net_sin6(&sock->dst)->sin6_port;
} else if (sock->dst.sa_family == AF_INET) {
af = MDM_HL7800_SOCKET_AF_IPV4;
dst_port = net_sin(&sock->dst)->sin_port;
} else {
return -EINVAL;
}
sock->socket_id = MDM_CREATE_SOCKET_ID;
snprintk(cmd_cfg, sizeof(cmd_cfg), "AT+KTCPCFG=%d,%d,\"%s\",%u,,,,%d,,%d", 1, 0,
hl7800_sprint_ip_addr(&sock->dst), dst_port, af, restore_on_boot);
ret = send_at_cmd(sock, cmd_cfg, MDM_CMD_SEND_TIMEOUT, 0, false);
if (ret < 0) {
LOG_ERR("AT+KTCPCFG ret:%d", ret);
ret = -EIO;
goto done;
}
done:
return ret;
}
static int configure_UDP_socket(struct hl7800_socket *sock)
{
int ret = 0;
char cmd[sizeof("AT+KUDPCFG=1,0,,,,,0,#")];
int af;
bool restore_on_boot = false;
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
restore_on_boot = true;
#endif
sock->socket_id = MDM_CREATE_SOCKET_ID;
if (sock->family == AF_INET) {
af = MDM_HL7800_SOCKET_AF_IPV4;
} else if (sock->family == AF_INET6) {
af = MDM_HL7800_SOCKET_AF_IPV6;
} else {
return -EINVAL;
}
snprintk(cmd, sizeof(cmd), "AT+KUDPCFG=1,0,,,,,%d,%d", af, restore_on_boot);
ret = send_at_cmd(sock, cmd, MDM_CMD_SEND_TIMEOUT, 0, false);
if (ret < 0) {
LOG_ERR("AT+KUDPCFG ret:%d", ret);
goto done;
}
/* Now wait for +KUDP_IND or +KUDP_NOTIF to ensure
* the socket was created.
*/
ret = k_sem_take(&sock->sock_send_sem, MDM_CMD_CONN_TIMEOUT);
if (ret == 0) {
ret = sock->error;
} else if (ret == -EAGAIN) {
ret = -ETIMEDOUT;
}
if (ret < 0) {
LOG_ERR("+KUDP_IND/NOTIF ret:%d", ret);
goto done;
}
done:
return ret;
}
static int reconfigure_IP_connection(void)
{
int ret = 0;
if (iface_ctx.reconfig_IP_connection) {
iface_ctx.reconfig_IP_connection = false;
/* reconfigure GPRS connection so sockets can be used */
ret = setup_gprs_connection(iface_ctx.mdm_apn.value);
if (ret < 0) {
LOG_ERR("AT+KCNXCFG= ret:%d", ret);
goto done;
}
/* query all TCP socket configs */
ret = send_at_cmd(NULL, "AT+KTCPCFG?", MDM_CMD_SEND_TIMEOUT, 0,
false);
/* query all UDP socket configs */
ret = send_at_cmd(NULL, "AT+KUDPCFG?", MDM_CMD_SEND_TIMEOUT, 0,
false);
/* TODO: to make this better, wait for +KUDP_IND or timeout */
k_sleep(K_SECONDS(1));
}
done:
return ret;
}
static int offload_get(sa_family_t family, enum net_sock_type type,
enum net_ip_protocol ip_proto,
struct net_context **context)
{
int ret = 0;
struct hl7800_socket *sock = NULL;
hl7800_lock();
/* new socket */
sock = socket_get();
if (!sock) {
ret = -ENOMEM;
goto done;
}
(*context)->offload_context = sock;
/* set the context iface index to our iface */
(*context)->iface = net_if_get_by_iface(iface_ctx.iface);
sock->family = family;
sock->type = type;
sock->ip_proto = ip_proto;
sock->context = *context;
sock->reconfig = false;
sock->created = false;
sock->socket_id = MDM_CREATE_SOCKET_ID;
/* If UDP, create UDP socket now.
* TCP socket needs to be created later once the
* connection IP address is known.
*/
if (type == SOCK_DGRAM) {
wakeup_hl7800();
/* reconfig IP connection if necessary */
(void)reconfigure_IP_connection();
if (!sock->created) {
ret = configure_UDP_socket(sock);
if (ret < 0) {
socket_put(sock);
goto done;
}
}
}
done:
set_busy(false);
allow_sleep(true);
hl7800_unlock();
return ret;
}
static int offload_bind(struct net_context *context,
const struct sockaddr *addr, socklen_t addr_len)
{
struct hl7800_socket *sock = NULL;
if (!context) {
return -EINVAL;
}
sock = (struct hl7800_socket *)context->offload_context;
if (!sock) {
LOG_ERR("Can't locate socket for net_ctx:%p!", context);
return -EINVAL;
}
/* save bind address information */
sock->src.sa_family = addr->sa_family;
#if defined(CONFIG_NET_IPV6)
if (addr->sa_family == AF_INET6) {
net_ipaddr_copy(&net_sin6(&sock->src)->sin6_addr,
&net_sin6(addr)->sin6_addr);
net_sin6(&sock->src)->sin6_port = net_sin6(addr)->sin6_port;
} else
#endif
#if defined(CONFIG_NET_IPV4)
if (addr->sa_family == AF_INET) {
net_ipaddr_copy(&net_sin(&sock->src)->sin_addr,
&net_sin(addr)->sin_addr);
net_sin(&sock->src)->sin_port = net_sin(addr)->sin_port;
} else
#endif
{
return -EPFNOSUPPORT;
}
return 0;
}
static int offload_listen(struct net_context *context, int backlog)
{
/* NOT IMPLEMENTED */
return -ENOTSUP;
}
static int offload_connect(struct net_context *context,
const struct sockaddr *addr, socklen_t addr_len,
net_context_connect_cb_t cb, int32_t timeout,
void *user_data)
{
int ret = 0;
int dst_port = -1;
struct hl7800_socket *sock;
if (!context || !addr) {
return -EINVAL;
}
sock = (struct hl7800_socket *)context->offload_context;
if (!sock) {
LOG_ERR("Can't locate socket for net_ctx:%p!", context);
return -EINVAL;
}
if (sock->socket_id < 1) {
LOG_ERR("Invalid socket_id(%d) for net_ctx:%p!",
sock->socket_id, context);
return -EINVAL;
}
sock->dst.sa_family = addr->sa_family;
#if defined(CONFIG_NET_IPV6)
if (addr->sa_family == AF_INET6) {
net_ipaddr_copy(&net_sin6(&sock->dst)->sin6_addr,
&net_sin6(addr)->sin6_addr);
dst_port = ntohs(net_sin6(addr)->sin6_port);
net_sin6(&sock->dst)->sin6_port = dst_port;
} else
#endif
#if defined(CONFIG_NET_IPV4)
if (addr->sa_family == AF_INET) {
net_ipaddr_copy(&net_sin(&sock->dst)->sin_addr,
&net_sin(addr)->sin_addr);
dst_port = ntohs(net_sin(addr)->sin_port);
net_sin(&sock->dst)->sin_port = dst_port;
} else
#endif
{
return -EINVAL;
}
if (dst_port < 0) {
LOG_ERR("Invalid port: %d", dst_port);
return -EINVAL;
}
hl7800_lock();
if (sock->type == SOCK_STREAM) {
wakeup_hl7800();
reconfigure_IP_connection();
/* Configure/create TCP connection */
if (!sock->created) {
ret = configure_TCP_socket(sock);
if (ret < 0) {
goto done;
}
}
/* Connect to TCP */
ret = connect_TCP_socket(sock);
if (ret < 0) {
goto done;
}
}
done:
set_busy(false);
allow_sleep(true);
hl7800_unlock();
if (cb) {
cb(context, ret, user_data);
}
return ret;
}
static int offload_accept(struct net_context *context, net_tcp_accept_cb_t cb,
int32_t timeout, void *user_data)
{
/* NOT IMPLEMENTED */
return -ENOTSUP;
}
static int offload_sendto(struct net_pkt *pkt, const struct sockaddr *dst_addr,
socklen_t addr_len, net_context_send_cb_t cb,
int32_t timeout, void *user_data)
{
struct net_context *context = net_pkt_context(pkt);
struct hl7800_socket *sock;
int ret, dst_port = 0;
if (!context) {
return -EINVAL;
}
sock = (struct hl7800_socket *)context->offload_context;
if (!sock) {
LOG_ERR("Can't locate socket for net_ctx:%p!", context);
return -EINVAL;
}
#if defined(CONFIG_NET_IPV6)
if (dst_addr->sa_family == AF_INET6) {
net_ipaddr_copy(&net_sin6(&sock->dst)->sin6_addr,
&net_sin6(dst_addr)->sin6_addr);
dst_port = ntohs(net_sin6(dst_addr)->sin6_port);
net_sin6(&sock->dst)->sin6_port = dst_port;
} else
#endif
#if defined(CONFIG_NET_IPV4)
if (dst_addr->sa_family == AF_INET) {
net_ipaddr_copy(&net_sin(&sock->dst)->sin_addr,
&net_sin(dst_addr)->sin_addr);
dst_port = ntohs(net_sin(dst_addr)->sin_port);
net_sin(&sock->dst)->sin_port = dst_port;
} else
#endif
{
return -EINVAL;
}
hl7800_lock();
wakeup_hl7800();
reconfigure_IP_connection();
ret = send_data(sock, pkt);
set_busy(false);
allow_sleep(true);
hl7800_unlock();
if (ret >= 0) {
net_pkt_unref(pkt);
}
if (cb) {
cb(context, ret, user_data);
}
return ret;
}
static int offload_send(struct net_pkt *pkt, net_context_send_cb_t cb,
int32_t timeout, void *user_data)
{
struct net_context *context = net_pkt_context(pkt);
socklen_t addr_len;
addr_len = 0;
#if defined(CONFIG_NET_IPV6)
if (net_pkt_family(pkt) == AF_INET6) {
addr_len = sizeof(struct sockaddr_in6);
} else
#endif /* CONFIG_NET_IPV6 */
#if defined(CONFIG_NET_IPV4)
if (net_pkt_family(pkt) == AF_INET) {
addr_len = sizeof(struct sockaddr_in);
} else
#endif /* CONFIG_NET_IPV4 */
{
return -EPFNOSUPPORT;
}
return offload_sendto(pkt, &context->remote, addr_len, cb, timeout,
user_data);
}
static int offload_recv(struct net_context *context, net_context_recv_cb_t cb,
int32_t timeout, void *user_data)
{
struct hl7800_socket *sock;
if (!context) {
return -EINVAL;
}
sock = (struct hl7800_socket *)context->offload_context;
if (!sock) {
LOG_ERR("Can't locate socket for net_ctx:%p!", context);
return -EINVAL;
}
sock->recv_cb = cb;
sock->recv_user_data = user_data;
return 0;
}
static int offload_put(struct net_context *context)
{
struct hl7800_socket *sock;
char cmd[sizeof("AT+KTCPCLOSE=##")];
if (!context) {
return -EINVAL;
}
sock = (struct hl7800_socket *)context->offload_context;
if (!sock) {
/* socket was already closed? Exit quietly here. */
return 0;
}
/* cancel notif work if queued */
k_work_cancel_delayable(&sock->notif_work);
hl7800_lock();
/* close connection */
if (sock->type == SOCK_STREAM) {
snprintk(cmd, sizeof(cmd), "AT+KTCPCLOSE=%d",
sock->socket_id);
} else {
snprintk(cmd, sizeof(cmd), "AT+KUDPCLOSE=%d",
sock->socket_id);
}
wakeup_hl7800();
if ((sock->type == SOCK_DGRAM) || (sock->error != -ENOTCONN)) {
send_at_cmd(sock, cmd, MDM_CMD_SEND_TIMEOUT, 0, false);
}
if (sock->type == SOCK_STREAM) {
/* delete session */
delete_socket(sock, sock->type, sock->socket_id);
}
set_busy(false);
allow_sleep(true);
socket_put(sock);
net_context_unref(context);
if (sock->type == SOCK_STREAM) {
/* TCP contexts are referenced twice,
* once for the app and once for the stack.
* Since TCP stack is not used for offload,
* unref a second time.
*/
net_context_unref(context);
}
hl7800_unlock();
return 0;
}
static struct net_offload offload_funcs = {
.get = offload_get,
.bind = offload_bind,
.listen = offload_listen,
.connect = offload_connect,
.accept = offload_accept,
.send = offload_send,
.sendto = offload_sendto,
.recv = offload_recv,
.put = offload_put,
};
/* Use the last 6 digits of the IMEI as the mac address */
static void hl7800_build_mac(struct hl7800_iface_ctx *ictx)
{
ictx->mac_addr[0] = ictx->mdm_imei[MDM_HL7800_IMEI_STRLEN - 6];
ictx->mac_addr[1] = ictx->mdm_imei[MDM_HL7800_IMEI_STRLEN - 5];
ictx->mac_addr[2] = ictx->mdm_imei[MDM_HL7800_IMEI_STRLEN - 4];
ictx->mac_addr[3] = ictx->mdm_imei[MDM_HL7800_IMEI_STRLEN - 3];
ictx->mac_addr[4] = ictx->mdm_imei[MDM_HL7800_IMEI_STRLEN - 2];
ictx->mac_addr[5] = ictx->mdm_imei[MDM_HL7800_IMEI_STRLEN - 1];
}
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
int32_t mdm_hl7800_update_fw(char *file_path)
{
int ret = 0;
struct fs_dirent file_info;
char cmd1[sizeof("AT+WDSD=24643584")];
/* get file info */
ret = fs_stat(file_path, &file_info);
if (ret >= 0) {
LOG_DBG("file '%s' size %zu", file_info.name, file_info.size);
} else {
LOG_ERR("Failed to get file [%s] info: %d", file_path, ret);
goto err;
}
ret = fs_open(&iface_ctx.fw_update_file, file_path, FS_O_READ);
if (ret < 0) {
LOG_ERR("%s open err: %d", file_path, ret);
goto err;
}
/* turn on device service indications */
ret = send_at_cmd(NULL, "AT+WDSI=2", MDM_CMD_SEND_TIMEOUT, 0, false);
if (ret < 0) {
goto err;
}
notify_all_tcp_sockets_closed();
hl7800_stop_rssi_work();
k_work_cancel_delayable(&iface_ctx.iface_status_work);
k_work_cancel_delayable(&iface_ctx.dns_work);
k_work_cancel_delayable(&iface_ctx.mdm_reset_work);
k_work_cancel_delayable(&iface_ctx.allow_sleep_work);
k_work_cancel_delayable(&iface_ctx.delete_untracked_socket_work);
iface_ctx.dns_ready = false;
if (iface_ctx.iface) {
LOG_DBG("HL7800 iface DOWN");
net_if_carrier_off(iface_ctx.iface);
}
/* HL7800 will stay locked for the duration of the FW update */
hl7800_lock();
/* start firmware update process */
LOG_INF("Initiate FW update, total packets: %zd",
((file_info.size / XMODEM_DATA_SIZE) + 1));
set_fota_state(HL7800_FOTA_START);
(void)snprintk(cmd1, sizeof(cmd1), "AT+WDSD=%zd", file_info.size);
(void)send_at_cmd(NULL, cmd1, K_NO_WAIT, 0, false);
err:
return ret;
}
#endif
static int hl7800_init(const struct device *dev)
{
int i, ret = 0;
struct k_work_queue_config cfg = {
.name = "hl7800_workq",
};
ARG_UNUSED(dev);
LOG_DBG("HL7800 Init");
/* The UART starts in the on state and CTS is set low by the HL7800 */
iface_ctx.cts_state = iface_ctx.last_cts_state = 0;
/* Prevent the network interface from starting until
* the modem has been initialized
* because the modem may not have a valid SIM card.
*/
iface_ctx.iface = net_if_get_default();
if (iface_ctx.iface == NULL) {
return -EIO;
}
net_if_carrier_off(iface_ctx.iface);
/* init sockets */
for (i = 0; i < MDM_MAX_SOCKETS; i++) {
iface_ctx.sockets[i].socket_id = MDM_INVALID_SOCKET_ID;
k_work_init(&iface_ctx.sockets[i].recv_cb_work,
sockreadrecv_cb_work);
k_work_init(&iface_ctx.sockets[i].rx_data_work,
sock_rx_data_cb_work);
k_work_init_delayable(&iface_ctx.sockets[i].notif_work,
sock_notif_cb_work);
k_sem_init(&iface_ctx.sockets[i].sock_send_sem, 0, 1);
}
iface_ctx.last_socket_id = 0;
k_sem_init(&iface_ctx.response_sem, 0, 1);
k_sem_init(&iface_ctx.mdm_awake, 0, 1);
/* initialize the work queue */
k_work_queue_start(&hl7800_workq, hl7800_workq_stack,
K_THREAD_STACK_SIZEOF(hl7800_workq_stack),
WORKQ_PRIORITY, &cfg);
/* init work tasks */
k_work_init_delayable(&iface_ctx.rssi_query_work, hl7800_rssi_query_work);
k_work_init_delayable(&iface_ctx.iface_status_work, iface_status_work_cb);
k_work_init_delayable(&iface_ctx.dns_work, dns_work_cb);
k_work_init(&iface_ctx.mdm_vgpio_work, mdm_vgpio_work_cb);
k_work_init_delayable(&iface_ctx.mdm_reset_work, mdm_reset_work_callback);
k_work_init_delayable(&iface_ctx.allow_sleep_work,
allow_sleep_work_callback);
k_work_init_delayable(&iface_ctx.delete_untracked_socket_work,
delete_untracked_socket_work_cb);
k_work_init(&iface_ctx.mdm_pwr_off_work, mdm_power_off_work_callback);
#ifdef CONFIG_MODEM_HL7800_GPS
k_work_init_delayable(&iface_ctx.gps_work, gps_work_callback);
#endif
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
k_work_init(&iface_ctx.finish_fw_update_work,
finish_fw_update_work_callback);
iface_ctx.fw_updated = false;
#endif
/* setup port devices and pin directions */
for (i = 0; i < MAX_MDM_CONTROL_PINS; i++) {
if (!gpio_is_ready_dt(&hl7800_cfg.gpio[i])) {
LOG_ERR("gpio port (%s) not ready!",
hl7800_cfg.gpio[i].port->name);
return -ENODEV;
}
}
ret = gpio_pin_configure_dt(&hl7800_cfg.gpio[MDM_RESET], GPIO_OUTPUT);
if (ret) {
LOG_ERR("Error configuring IO MDM_RESET %d err: %d!",
hl7800_cfg.gpio[MDM_RESET].pin, ret);
return ret;
}
ret = gpio_pin_configure_dt(&hl7800_cfg.gpio[MDM_WAKE], GPIO_OUTPUT);
if (ret) {
LOG_ERR("Error configuring IO MDM_WAKE %d err: %d!",
hl7800_cfg.gpio[MDM_WAKE].pin, ret);
return ret;
}
ret = gpio_pin_configure_dt(&hl7800_cfg.gpio[MDM_PWR_ON], GPIO_OUTPUT);
if (ret) {
LOG_ERR("Error configuring IO MDM_PWR_ON %d err: %d!",
hl7800_cfg.gpio[MDM_PWR_ON].pin, ret);
return ret;
}
ret = gpio_pin_configure_dt(&hl7800_cfg.gpio[MDM_FAST_SHUTD], GPIO_OUTPUT);
if (ret) {
LOG_ERR("Error configuring IO MDM_FAST_SHUTD %d err: %d!",
hl7800_cfg.gpio[MDM_FAST_SHUTD].pin, ret);
return ret;
}
ret = gpio_pin_configure_dt(&hl7800_cfg.gpio[MDM_VGPIO], GPIO_INPUT);
if (ret) {
LOG_ERR("Error configuring IO MDM_VGPIO %d err: %d!",
hl7800_cfg.gpio[MDM_VGPIO].pin, ret);
return ret;
}
ret = gpio_pin_configure_dt(&hl7800_cfg.gpio[MDM_UART_DSR], GPIO_INPUT);
if (ret) {
LOG_ERR("Error configuring IO MDM_UART_DSR %d err: %d!",
hl7800_cfg.gpio[MDM_UART_DSR].pin, ret);
return ret;
}
ret = gpio_pin_configure_dt(&hl7800_cfg.gpio[MDM_UART_CTS], GPIO_INPUT);
if (ret) {
LOG_ERR("Error configuring IO MDM_UART_CTS %d err: %d!",
hl7800_cfg.gpio[MDM_UART_CTS].pin, ret);
return ret;
}
ret = gpio_pin_configure_dt(&hl7800_cfg.gpio[MDM_GPIO6], GPIO_INPUT);
if (ret) {
LOG_ERR("Error configuring IO MDM_GPIO6 %d err: %d!",
hl7800_cfg.gpio[MDM_GPIO6].pin, ret);
return ret;
}
modem_assert_wake(false);
modem_assert_pwr_on(false);
modem_assert_fast_shutd(false);
/* Allow modem to run so we are in a known state.
* This allows HL7800 VGPIO to be high, which is good because the UART
* IO are already configured.
*/
modem_run();
/* setup input pin callbacks */
/* VGPIO */
gpio_init_callback(&iface_ctx.mdm_vgpio_cb, mdm_vgpio_callback_isr,
BIT(hl7800_cfg.gpio[MDM_VGPIO].pin));
ret = gpio_add_callback(hl7800_cfg.gpio[MDM_VGPIO].port,
&iface_ctx.mdm_vgpio_cb);
if (ret) {
LOG_ERR("Cannot setup vgpio callback! (%d)", ret);
return ret;
}
ret = gpio_pin_interrupt_configure_dt(&hl7800_cfg.gpio[MDM_VGPIO], GPIO_INT_EDGE_BOTH);
if (ret) {
LOG_ERR("Error config vgpio interrupt! (%d)", ret);
return ret;
}
/* UART DSR */
gpio_init_callback(&iface_ctx.mdm_uart_dsr_cb, mdm_uart_dsr_callback_isr,
BIT(hl7800_cfg.gpio[MDM_UART_DSR].pin));
ret = gpio_add_callback(hl7800_cfg.gpio[MDM_UART_DSR].port,
&iface_ctx.mdm_uart_dsr_cb);
if (ret) {
LOG_ERR("Cannot setup uart dsr callback! (%d)", ret);
return ret;
}
ret = gpio_pin_interrupt_configure_dt(&hl7800_cfg.gpio[MDM_UART_DSR], GPIO_INT_EDGE_BOTH);
if (ret) {
LOG_ERR("Error config uart dsr interrupt! (%d)", ret);
return ret;
}
/* GPIO6 */
gpio_init_callback(&iface_ctx.mdm_gpio6_cb, mdm_gpio6_callback_isr,
BIT(hl7800_cfg.gpio[MDM_GPIO6].pin));
ret = gpio_add_callback(hl7800_cfg.gpio[MDM_GPIO6].port,
&iface_ctx.mdm_gpio6_cb);
if (ret) {
LOG_ERR("Cannot setup gpio6 callback! (%d)", ret);
return ret;
}
ret = gpio_pin_interrupt_configure_dt(&hl7800_cfg.gpio[MDM_GPIO6], GPIO_INT_EDGE_BOTH);
if (ret) {
LOG_ERR("Error config gpio6 interrupt! (%d)", ret);
return ret;
}
/* UART CTS */
gpio_init_callback(&iface_ctx.mdm_uart_cts_cb, mdm_uart_cts_callback_isr,
BIT(hl7800_cfg.gpio[MDM_UART_CTS].pin));
ret = gpio_add_callback(hl7800_cfg.gpio[MDM_UART_CTS].port,
&iface_ctx.mdm_uart_cts_cb);
if (ret) {
LOG_ERR("Cannot setup uart cts callback! (%d)", ret);
return ret;
}
ret = gpio_pin_interrupt_configure_dt(&hl7800_cfg.gpio[MDM_UART_CTS], GPIO_INT_EDGE_BOTH);
if (ret) {
LOG_ERR("Error config uart cts interrupt! (%d)", ret);
return ret;
}
/* Set modem data storage */
iface_ctx.mdm_ctx.data_manufacturer = iface_ctx.mdm_manufacturer;
iface_ctx.mdm_ctx.data_model = iface_ctx.mdm_model;
iface_ctx.mdm_ctx.data_revision = iface_ctx.mdm_revision;
#ifdef CONFIG_MODEM_SIM_NUMBERS
iface_ctx.mdm_ctx.data_imei = iface_ctx.mdm_imei;
#endif
iface_ctx.mdm_ctx.data_rssi = &iface_ctx.mdm_rssi;
ret = mdm_receiver_register(&iface_ctx.mdm_ctx, MDM_UART_DEV,
mdm_recv_buf, sizeof(mdm_recv_buf));
if (ret < 0) {
LOG_ERR("Error registering modem receiver (%d)!", ret);
return ret;
}
k_queue_init(&iface_ctx.stale_socket_queue);
/* start RX thread */
k_thread_name_set(
k_thread_create(&hl7800_rx_thread, hl7800_rx_stack,
K_THREAD_STACK_SIZEOF(hl7800_rx_stack),
hl7800_rx, NULL, NULL, NULL,
RX_THREAD_PRIORITY, 0, K_NO_WAIT),
"hl7800 rx");
#ifdef CONFIG_MODEM_HL7800_BOOT_DELAY
modem_reset();
#else
ret = modem_reset_and_configure();
#endif
return ret;
}
static void offload_iface_init(struct net_if *iface)
{
const struct device *dev = net_if_get_device(iface);
struct hl7800_iface_ctx *ctx = dev->data;
iface->if_dev->offload = &offload_funcs;
ctx->iface = iface;
if (!IS_ENABLED(CONFIG_MODEM_HL7800_BOOT_DELAY)) {
hl7800_build_mac(&iface_ctx);
net_if_set_link_addr(iface, iface_ctx.mac_addr, sizeof(iface_ctx.mac_addr),
NET_LINK_ETHERNET);
iface_ctx.initialized = true;
}
}
static struct offloaded_if_api api_funcs = {
.iface_api.init = offload_iface_init,
};
NET_DEVICE_DT_INST_OFFLOAD_DEFINE(0, hl7800_init, NULL, &iface_ctx,
&hl7800_cfg, CONFIG_MODEM_HL7800_INIT_PRIORITY,
&api_funcs, MDM_MTU);