a1498f3e40
The reboot option of the +KSRAT command is only supported by newer firmware. Add a check to determine what version of the command to use when setting the RAT. Signed-off-by: Ryan Erickson <ryan.erickson@lairdconnect.com>
4982 lines
124 KiB
C
4982 lines
124 KiB
C
/*
|
|
* Copyright (c) 2020 Laird Connectivity
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT swir_hl7800
|
|
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(modem_hl7800, CONFIG_MODEM_LOG_LEVEL);
|
|
|
|
#include <zephyr/types.h>
|
|
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <zephyr.h>
|
|
#include <drivers/gpio.h>
|
|
#include <device.h>
|
|
#include <init.h>
|
|
|
|
#include <pm/device.h>
|
|
#include <drivers/uart.h>
|
|
|
|
#include <net/net_context.h>
|
|
#include <net/net_if.h>
|
|
#include <net/net_offload.h>
|
|
#include <net/net_pkt.h>
|
|
#include <net/dns_resolve.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 <fs/fs.h>
|
|
#endif
|
|
|
|
#include "modem_receiver.h"
|
|
#include <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_DBG(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_SERVER_CLOSED,
|
|
SOCK_CONNECTED,
|
|
};
|
|
|
|
struct mdm_control_pinconfig {
|
|
char *dev_name;
|
|
gpio_pin_t pin;
|
|
gpio_flags_t config;
|
|
};
|
|
|
|
#define PINCONFIG(name_, pin_, config_) \
|
|
{ \
|
|
.dev_name = name_, .pin = pin_, .config = config_ \
|
|
}
|
|
|
|
/* pin settings */
|
|
enum mdm_control_pins {
|
|
MDM_RESET = 0,
|
|
MDM_WAKE,
|
|
MDM_PWR_ON,
|
|
MDM_FAST_SHUTD,
|
|
MDM_UART_DTR,
|
|
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
|
|
|
|
static const struct mdm_control_pinconfig pinconfig[] = {
|
|
/* MDM_RESET */
|
|
PINCONFIG(DT_INST_GPIO_LABEL(0, mdm_reset_gpios),
|
|
DT_INST_GPIO_PIN(0, mdm_reset_gpios),
|
|
(GPIO_OUTPUT | GPIO_OPEN_DRAIN)),
|
|
|
|
/* MDM_WAKE */
|
|
PINCONFIG(DT_INST_GPIO_LABEL(0, mdm_wake_gpios),
|
|
DT_INST_GPIO_PIN(0, mdm_wake_gpios),
|
|
(GPIO_OUTPUT | GPIO_OPEN_SOURCE)),
|
|
|
|
/* MDM_PWR_ON */
|
|
PINCONFIG(DT_INST_GPIO_LABEL(0, mdm_pwr_on_gpios),
|
|
DT_INST_GPIO_PIN(0, mdm_pwr_on_gpios),
|
|
(GPIO_OUTPUT | GPIO_OPEN_DRAIN)),
|
|
|
|
/* MDM_FAST_SHUTD */
|
|
PINCONFIG(DT_INST_GPIO_LABEL(0, mdm_fast_shutd_gpios),
|
|
DT_INST_GPIO_PIN(0, mdm_fast_shutd_gpios),
|
|
(GPIO_OUTPUT | GPIO_OPEN_DRAIN)),
|
|
|
|
/* MDM_UART_DTR */
|
|
PINCONFIG(DT_INST_GPIO_LABEL(0, mdm_uart_dtr_gpios),
|
|
DT_INST_GPIO_PIN(0, mdm_uart_dtr_gpios), GPIO_OUTPUT),
|
|
|
|
/* MDM_VGPIO */
|
|
PINCONFIG(DT_INST_GPIO_LABEL(0, mdm_vgpio_gpios),
|
|
DT_INST_GPIO_PIN(0, mdm_vgpio_gpios),
|
|
(GPIO_INPUT | GPIO_INT_EDGE_BOTH)),
|
|
|
|
/* MDM_UART_DSR */
|
|
PINCONFIG(DT_INST_GPIO_LABEL(0, mdm_uart_dsr_gpios),
|
|
DT_INST_GPIO_PIN(0, mdm_uart_dsr_gpios),
|
|
(GPIO_INPUT | GPIO_INT_EDGE_BOTH)),
|
|
|
|
/* MDM_UART_CTS */
|
|
PINCONFIG(DT_INST_GPIO_LABEL(0, mdm_uart_cts_gpios),
|
|
DT_INST_GPIO_PIN(0, mdm_uart_cts_gpios),
|
|
(GPIO_INPUT | GPIO_INT_EDGE_BOTH)),
|
|
|
|
/* MDM_GPIO6 */
|
|
PINCONFIG(DT_INST_GPIO_LABEL(0, mdm_gpio6_gpios),
|
|
DT_INST_GPIO_PIN(0, mdm_gpio6_gpios),
|
|
(GPIO_INPUT | GPIO_INT_EDGE_BOTH)),
|
|
};
|
|
|
|
#define MDM_UART_DEV_NAME DT_INST_BUS_LABEL(0)
|
|
|
|
#define MDM_WAKE_ASSERTED 1 /* Asserted keeps the module awake */
|
|
#define MDM_WAKE_NOT_ASSERTED 0
|
|
#define MDM_RESET_ASSERTED 0
|
|
#define MDM_RESET_NOT_ASSERTED 1
|
|
#define MDM_PWR_ON_ASSERTED 0
|
|
#define MDM_PWR_ON_NOT_ASSERTED 1
|
|
#define MDM_FAST_SHUTD_ASSERTED 0
|
|
#define MDM_FAST_SHUTD_NOT_ASSERTED 1
|
|
#define MDM_UART_DTR_ASSERTED 0 /* Asserted keeps the module awake */
|
|
#define MDM_UART_DTR_NOT_ASSERTED 1
|
|
|
|
#define MDM_SEND_OK_ENABLED 0
|
|
#define MDM_SEND_OK_DISABLED 1
|
|
|
|
#define MDM_CMD_SEND_TIMEOUT K_SECONDS(5)
|
|
#define MDM_IP_SEND_RX_TIMEOUT K_SECONDS(60)
|
|
#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_HANDLER_MATCH_MAX_LEN 100
|
|
|
|
#define MDM_MAX_SOCKETS 6
|
|
|
|
#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_DEFAULT_AT_CMD_RETRIES 3
|
|
#define MDM_WAKEUP_TIME K_SECONDS(12)
|
|
#define MDM_BOOT_TIME K_SECONDS(12)
|
|
|
|
#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_TIMEOUT_SECS 30
|
|
#define RSSI_UNKNOWN -999
|
|
|
|
#define DNS_WORK_DELAY_SECS 1
|
|
#define IFACE_WORK_DELAY K_MSEC(500)
|
|
#define WAIT_FOR_KSUP_RETRIES 5
|
|
#define ALLOW_SLEEP_DELAY_SECS K_SECONDS(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 SETUP_GPRS_CONNECTION_CMD "AT+KCNXCFG=1,\"GPRS\",\"\",,,\"IPV4V6\""
|
|
#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))
|
|
|
|
#ifdef CONFIG_NEWLIB_LIBC
|
|
/* 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)
|
|
#endif
|
|
|
|
#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 (0)
|
|
|
|
#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 (0)
|
|
|
|
#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 (0)
|
|
|
|
/* Complex has "no_id_resp" set to true because the sending command
|
|
* is the command used to process the respone
|
|
*/
|
|
#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 (0)
|
|
|
|
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);
|
|
|
|
/* 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;
|
|
bool error;
|
|
int error_val;
|
|
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;
|
|
};
|
|
|
|
#define NO_ID_RESP_CMD_MAX_LENGTH 32
|
|
|
|
struct hl7800_iface_ctx {
|
|
struct net_if *iface;
|
|
uint8_t mac_addr[6];
|
|
struct in_addr ipv4Addr, subnet, gateway, dns;
|
|
bool restarting;
|
|
bool initialized;
|
|
bool wait_for_KSUP;
|
|
uint32_t wait_for_KSUP_tries;
|
|
bool reconfig_IP_connection;
|
|
char dns_string[NET_IPV4_ADDR_LEN];
|
|
char no_id_resp_cmd[NO_ID_RESP_CMD_MAX_LENGTH];
|
|
bool search_no_id_resp;
|
|
|
|
/* GPIO PORT devices */
|
|
const struct device *gpio_port_dev[MAX_MDM_CONTROL_PINS];
|
|
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;
|
|
uint32_t vgpio_state;
|
|
uint32_t dsr_state;
|
|
uint32_t gpio6_state;
|
|
uint32_t cts_state;
|
|
|
|
/* 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;
|
|
|
|
/* 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;
|
|
|
|
#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_SIZE];
|
|
uint8_t 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];
|
|
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;
|
|
|
|
/* modem state */
|
|
bool allow_sleep;
|
|
bool uart_on;
|
|
enum mdm_hl7800_sleep_state sleep_state;
|
|
enum mdm_hl7800_network_state network_state;
|
|
enum net_operator_status operator_status;
|
|
void (*event_callback)(enum mdm_hl7800_event event, void *event_data);
|
|
#ifdef CONFIG_NEWLIB_LIBC
|
|
struct tm local_time;
|
|
int32_t local_time_offset;
|
|
#endif
|
|
bool local_time_valid;
|
|
bool configured;
|
|
};
|
|
|
|
struct cmd_handler {
|
|
const char *cmd;
|
|
uint16_t cmd_len;
|
|
bool (*func)(struct net_buf **buf, uint16_t len);
|
|
};
|
|
|
|
static struct hl7800_iface_ctx ictx;
|
|
|
|
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 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 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 int write_apn(char *access_point_name);
|
|
|
|
#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
|
|
|
|
#ifdef CONFIG_NEWLIB_LIBC
|
|
static bool convert_time_string_to_struct(struct tm *tm, int32_t *offset,
|
|
char *time_string);
|
|
#endif
|
|
|
|
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
|
|
static bool is_cmd_ready(void)
|
|
{
|
|
ictx.vgpio_state = (uint32_t)gpio_pin_get(ictx.gpio_port_dev[MDM_VGPIO],
|
|
pinconfig[MDM_VGPIO].pin);
|
|
|
|
ictx.gpio6_state = (uint32_t)gpio_pin_get(ictx.gpio_port_dev[MDM_GPIO6],
|
|
pinconfig[MDM_GPIO6].pin);
|
|
|
|
ictx.cts_state = (uint32_t)gpio_pin_get(
|
|
ictx.gpio_port_dev[MDM_UART_CTS], pinconfig[MDM_UART_CTS].pin);
|
|
|
|
return ictx.vgpio_state && ictx.gpio6_state && !ictx.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 && (ictx.sleep_state != HL7800_SLEEP_STATE_AWAKE) &&
|
|
!ictx.allow_sleep && !ictx.wait_for_KSUP) {
|
|
PRINT_AWAKE_MSG;
|
|
set_sleep_state(HL7800_SLEEP_STATE_AWAKE);
|
|
k_sem_give(&ictx.mdm_awake);
|
|
} else if (!is_cmd_rdy &&
|
|
ictx.sleep_state == HL7800_SLEEP_STATE_AWAKE &&
|
|
ictx.allow_sleep) {
|
|
PRINT_NOT_AWAKE_MSG;
|
|
/* If the device is sleeping (not ready to receive commands)
|
|
* then the device may send +KSUP when waking up.
|
|
* We should wait for it.
|
|
*/
|
|
ictx.wait_for_KSUP = true;
|
|
ictx.wait_for_KSUP_tries = 0;
|
|
|
|
set_sleep_state(HL7800_SLEEP_STATE_ASLEEP);
|
|
k_sem_reset(&ictx.mdm_awake);
|
|
}
|
|
#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 (!ictx.sockets[i].context) {
|
|
sock = &ictx.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 (ictx.sockets[i].socket_id == socket_id) {
|
|
sock = &ictx.sockets[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return sock;
|
|
}
|
|
|
|
static void socket_put(struct hl7800_socket *sock)
|
|
{
|
|
if (!sock) {
|
|
return;
|
|
}
|
|
|
|
sock->context = NULL;
|
|
sock->socket_id = -1;
|
|
sock->created = false;
|
|
sock->reconfig = false;
|
|
sock->error = false;
|
|
sock->error_val = -1;
|
|
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;
|
|
}
|
|
}
|
|
|
|
static void modem_assert_wake(bool assert)
|
|
{
|
|
if (assert) {
|
|
HL7800_IO_DBG_LOG("MDM_WAKE_PIN -> ASSERTED");
|
|
gpio_pin_set(ictx.gpio_port_dev[MDM_WAKE],
|
|
pinconfig[MDM_WAKE].pin, MDM_WAKE_ASSERTED);
|
|
} else {
|
|
HL7800_IO_DBG_LOG("MDM_WAKE_PIN -> NOT_ASSERTED");
|
|
gpio_pin_set(ictx.gpio_port_dev[MDM_WAKE],
|
|
pinconfig[MDM_WAKE].pin, MDM_WAKE_NOT_ASSERTED);
|
|
}
|
|
}
|
|
|
|
static void modem_assert_pwr_on(bool assert)
|
|
{
|
|
if (assert) {
|
|
HL7800_IO_DBG_LOG("MDM_PWR_ON -> ASSERTED");
|
|
gpio_pin_set(ictx.gpio_port_dev[MDM_PWR_ON],
|
|
pinconfig[MDM_PWR_ON].pin, MDM_PWR_ON_ASSERTED);
|
|
} else {
|
|
HL7800_IO_DBG_LOG("MDM_PWR_ON -> NOT_ASSERTED");
|
|
gpio_pin_set(ictx.gpio_port_dev[MDM_PWR_ON],
|
|
pinconfig[MDM_PWR_ON].pin,
|
|
MDM_PWR_ON_NOT_ASSERTED);
|
|
}
|
|
}
|
|
|
|
static void modem_assert_fast_shutd(bool assert)
|
|
{
|
|
if (assert) {
|
|
HL7800_IO_DBG_LOG("MDM_FAST_SHUTD -> ASSERTED");
|
|
gpio_pin_set(ictx.gpio_port_dev[MDM_FAST_SHUTD],
|
|
pinconfig[MDM_FAST_SHUTD].pin,
|
|
MDM_FAST_SHUTD_ASSERTED);
|
|
} else {
|
|
HL7800_IO_DBG_LOG("MDM_FAST_SHUTD -> NOT_ASSERTED");
|
|
gpio_pin_set(ictx.gpio_port_dev[MDM_FAST_SHUTD],
|
|
pinconfig[MDM_FAST_SHUTD].pin,
|
|
MDM_FAST_SHUTD_NOT_ASSERTED);
|
|
}
|
|
}
|
|
|
|
static void modem_assert_uart_dtr(bool assert)
|
|
{
|
|
if (assert) {
|
|
HL7800_IO_DBG_LOG("MDM_UART_DTR -> ASSERTED");
|
|
gpio_pin_set(ictx.gpio_port_dev[MDM_UART_DTR],
|
|
pinconfig[MDM_UART_DTR].pin,
|
|
MDM_UART_DTR_ASSERTED);
|
|
} else {
|
|
HL7800_IO_DBG_LOG("MDM_UART_DTR -> NOT_ASSERTED");
|
|
gpio_pin_set(ictx.gpio_port_dev[MDM_UART_DTR],
|
|
pinconfig[MDM_UART_DTR].pin,
|
|
MDM_UART_DTR_NOT_ASSERTED);
|
|
}
|
|
}
|
|
|
|
static void allow_sleep_work_callback(struct k_work *item)
|
|
{
|
|
ARG_UNUSED(item);
|
|
LOG_DBG("Allow sleep");
|
|
ictx.allow_sleep = true;
|
|
modem_assert_wake(false);
|
|
modem_assert_uart_dtr(false);
|
|
}
|
|
|
|
static void allow_sleep(bool allow)
|
|
{
|
|
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
|
|
if (allow) {
|
|
k_work_reschedule_for_queue(&hl7800_workq,
|
|
&ictx.allow_sleep_work,
|
|
ALLOW_SLEEP_DELAY_SECS);
|
|
} else {
|
|
LOG_DBG("Keep awake");
|
|
k_work_cancel_delayable(&ictx.allow_sleep_work);
|
|
ictx.allow_sleep = false;
|
|
modem_assert_wake(true);
|
|
modem_assert_uart_dtr(true);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void event_handler(enum mdm_hl7800_event event, void *event_data)
|
|
{
|
|
if (ictx.event_callback != NULL) {
|
|
ictx.event_callback(event, event_data);
|
|
}
|
|
}
|
|
|
|
void mdm_hl7800_get_signal_quality(int *rsrp, int *sinr)
|
|
{
|
|
*rsrp = ictx.mdm_ctx.data_rssi;
|
|
*sinr = ictx.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;
|
|
|
|
ictx.last_error = 0;
|
|
|
|
do {
|
|
if (!sock) {
|
|
k_sem_reset(&ictx.response_sem);
|
|
ictx.last_socket_id = 0;
|
|
} else {
|
|
k_sem_reset(&sock->sock_send_sem);
|
|
ictx.last_socket_id = sock->socket_id;
|
|
}
|
|
if (no_id_resp) {
|
|
strncpy(ictx.no_id_resp_cmd, data,
|
|
sizeof(ictx.no_id_resp_cmd) - 1);
|
|
ictx.search_no_id_resp = true;
|
|
}
|
|
|
|
LOG_DBG("OUT: [%s]", log_strdup(data));
|
|
mdm_receiver_send(&ictx.mdm_ctx, data, strlen(data));
|
|
mdm_receiver_send(&ictx.mdm_ctx, "\r", 1);
|
|
|
|
if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) {
|
|
goto done;
|
|
}
|
|
|
|
if (!sock) {
|
|
ret = k_sem_take(&ictx.response_sem, timeout);
|
|
} else {
|
|
ret = k_sem_take(&sock->sock_send_sem, timeout);
|
|
}
|
|
|
|
if (ret == 0) {
|
|
ret = ictx.last_error;
|
|
} else if (ret == -EAGAIN) {
|
|
ret = -ETIMEDOUT;
|
|
}
|
|
|
|
retries--;
|
|
if (retries < 0) {
|
|
retries = 0;
|
|
}
|
|
} while (ret != 0 && retries > 0);
|
|
done:
|
|
ictx.search_no_id_resp = false;
|
|
return ret;
|
|
}
|
|
|
|
static int wakeup_hl7800(void)
|
|
{
|
|
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
|
|
int ret;
|
|
|
|
allow_sleep(false);
|
|
if (!is_cmd_ready()) {
|
|
LOG_DBG("Waiting to wakeup");
|
|
ret = k_sem_take(&ictx.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();
|
|
ictx.last_socket_id = 0;
|
|
ret = send_at_cmd(NULL, data, MDM_CMD_SEND_TIMEOUT, 0, 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();
|
|
ictx.last_socket_id = 0;
|
|
ret = write_apn(access_point_name);
|
|
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, &ictx.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 == ictx.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();
|
|
ictx.last_socket_id = 0;
|
|
|
|
if (value == MDM_RAT_CAT_M1) {
|
|
if (ictx.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 (ictx.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:
|
|
if (ret >= 0) {
|
|
/* Changing the RAT causes the modem to reset. */
|
|
ret = modem_boot_handler("RAT changed");
|
|
}
|
|
|
|
allow_sleep(true);
|
|
hl7800_unlock();
|
|
|
|
/* A reset and reconfigure ensures the modem configuration and
|
|
* state are valid
|
|
*/
|
|
if (ret >= 0) {
|
|
k_work_reschedule_for_queue(&hl7800_workq, &ictx.mdm_reset_work,
|
|
K_NO_WAIT);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_NEWLIB_LIBC
|
|
int32_t mdm_hl7800_get_local_time(struct tm *tm, int32_t *offset)
|
|
{
|
|
int ret;
|
|
|
|
ictx.local_time_valid = false;
|
|
|
|
hl7800_lock();
|
|
wakeup_hl7800();
|
|
ictx.last_socket_id = 0;
|
|
ret = send_at_cmd(NULL, "AT+CCLK?", MDM_CMD_SEND_TIMEOUT, 0, false);
|
|
allow_sleep(true);
|
|
if (ictx.local_time_valid) {
|
|
memcpy(tm, &ictx.local_time, sizeof(struct tm));
|
|
memcpy(offset, &ictx.local_time_offset, sizeof(*offset));
|
|
} else {
|
|
ret = -EIO;
|
|
}
|
|
hl7800_unlock();
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
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, &ictx.mdm_ctx.data_rssi);
|
|
event_handler(HL7800_EVENT_SINR, &ictx.mdm_sinr);
|
|
event_handler(HL7800_EVENT_APN_UPDATE, &ictx.mdm_apn);
|
|
event_handler(HL7800_EVENT_RAT, &ictx.mdm_rat);
|
|
event_handler(HL7800_EVENT_BANDS, ictx.mdm_bands_string);
|
|
event_handler(HL7800_EVENT_ACTIVE_BANDS, ictx.mdm_active_bands_string);
|
|
event_handler(HL7800_EVENT_REVISION, ictx.mdm_revision);
|
|
hl7800_unlock();
|
|
}
|
|
|
|
static int send_data(struct hl7800_socket *sock, struct net_pkt *pkt)
|
|
{
|
|
int ret;
|
|
struct net_buf *frag;
|
|
char dst_addr[sizeof("###.###.###.###")];
|
|
size_t send_len, actual_send_len;
|
|
char buf[sizeof("AT+KUDPSND=##,\"###.###.###.###\",#####,####")];
|
|
|
|
send_len = 0, actual_send_len = 0;
|
|
|
|
if (!sock) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
ictx.last_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 (ictx.last_error != 0) {
|
|
ret = ictx.last_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(&ictx.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(&ictx.mdm_ctx, EOF_PATTERN, strlen(EOF_PATTERN));
|
|
ret = k_sem_take(&sock->sock_send_sem, MDM_CMD_SEND_TIMEOUT);
|
|
if (ret == 0) {
|
|
ret = ictx.last_error;
|
|
} else if (ret == -EAGAIN) {
|
|
ret = -ETIMEDOUT;
|
|
}
|
|
done:
|
|
if (sock->type == SOCK_STREAM) {
|
|
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(ictx.mdm_manufacturer,
|
|
sizeof(ictx.mdm_manufacturer) - 1, *buf, 0,
|
|
len);
|
|
ictx.mdm_manufacturer[out_len] = 0;
|
|
LOG_INF("Manufacturer: %s", log_strdup(ictx.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(ictx.mdm_model, sizeof(ictx.mdm_model) - 1,
|
|
*buf, 0, len);
|
|
ictx.mdm_model[out_len] = 0;
|
|
LOG_INF("Model: %s", log_strdup(ictx.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(
|
|
ictx.mdm_revision, sizeof(ictx.mdm_revision) - 1, *buf, 0, len);
|
|
ictx.mdm_revision[out_len] = 0;
|
|
LOG_INF("Revision: %s", log_strdup(ictx.mdm_revision));
|
|
event_handler(HL7800_EVENT_REVISION, ictx.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(ictx.mdm_imei, sizeof(ictx.mdm_imei) - 1,
|
|
*buf, 0, len);
|
|
ictx.mdm_imei[out_len] = 0;
|
|
|
|
LOG_INF("IMEI: %s", log_strdup(ictx.mdm_imei));
|
|
done:
|
|
return true;
|
|
}
|
|
|
|
/* Handler: +CCID: <ICCID> */
|
|
static bool on_cmd_atcmdinfo_iccid(struct net_buf **buf, uint16_t len)
|
|
{
|
|
struct net_buf *frag = NULL;
|
|
size_t out_len;
|
|
|
|
/* make sure ICCID data is received
|
|
* waiting for: <ICCID>\r\n
|
|
*/
|
|
wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf),
|
|
MDM_HL7800_ICCID_SIZE);
|
|
|
|
frag = NULL;
|
|
len = net_buf_findcrlf(*buf, &frag);
|
|
if (!frag) {
|
|
LOG_ERR("Unable to find ICCID end");
|
|
goto done;
|
|
}
|
|
if (len > MDM_HL7800_ICCID_STRLEN) {
|
|
LOG_WRN("ICCID too long (len:%d)", len);
|
|
len = MDM_HL7800_ICCID_STRLEN;
|
|
}
|
|
|
|
out_len = net_buf_linearize(ictx.mdm_iccid, MDM_HL7800_ICCID_STRLEN,
|
|
*buf, 0, len);
|
|
ictx.mdm_iccid[out_len] = 0;
|
|
|
|
LOG_INF("ICCID: %s", log_strdup(ictx.mdm_iccid));
|
|
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;
|
|
const char *dns_servers_str[] = { ictx.dns_string };
|
|
|
|
/* set new DNS addr in DNS resolver */
|
|
LOG_DBG("Refresh DNS resolver");
|
|
dnsCtx = dns_resolve_get_default();
|
|
|
|
ret = dns_resolve_reconfigure(dnsCtx, dns_servers_str, NULL);
|
|
if (ret < 0) {
|
|
LOG_ERR("dns_resolve_init fail (%d)", ret);
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
char *mdm_hl7800_get_iccid(void)
|
|
{
|
|
return ictx.mdm_iccid;
|
|
}
|
|
|
|
char *mdm_hl7800_get_sn(void)
|
|
{
|
|
return ictx.mdm_sn;
|
|
}
|
|
|
|
char *mdm_hl7800_get_imei(void)
|
|
{
|
|
return ictx.mdm_imei;
|
|
}
|
|
|
|
char *mdm_hl7800_get_fw_version(void)
|
|
{
|
|
return ictx.mdm_revision;
|
|
}
|
|
|
|
/* 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, *gw_start, *dns_start;
|
|
struct in_addr new_ipv4_addr;
|
|
bool is_ipv4;
|
|
int ipv4_len;
|
|
char ipv4_addr_str[NET_IPV4_ADDR_LEN];
|
|
int sn_len;
|
|
char sm_str[NET_IPV4_ADDR_LEN];
|
|
int gw_len;
|
|
char gw_str[NET_IPV4_ADDR_LEN];
|
|
int dns_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", log_strdup(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,
|
|
log_strdup(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;
|
|
ipv4_len = delims[3] - delims[2];
|
|
LOG_DBG("IP string len: %d", ipv4_len);
|
|
if (ipv4_len <= (NET_IPV4_ADDR_LEN * 2)) {
|
|
is_ipv4 = true;
|
|
}
|
|
|
|
if (!is_ipv4) {
|
|
goto done;
|
|
}
|
|
|
|
/* Find start of subnet mask */
|
|
addr_start = delims[2] + 1;
|
|
num_delims = 4;
|
|
search_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 IPv4 addr */
|
|
ipv4_len = sm_start - addr_start;
|
|
strncpy(ipv4_addr_str, addr_start, ipv4_len);
|
|
ipv4_addr_str[ipv4_len] = 0;
|
|
ret = net_addr_pton(AF_INET, ipv4_addr_str, &new_ipv4_addr);
|
|
if (ret < 0) {
|
|
LOG_ERR("Invalid IPv4 addr");
|
|
return true;
|
|
}
|
|
|
|
/* move past the '.' */
|
|
sm_start += 1;
|
|
/* store new subnet mask */
|
|
sn_len = delims[3] - sm_start;
|
|
strncpy(sm_str, sm_start, sn_len);
|
|
sm_str[sn_len] = 0;
|
|
ret = net_addr_pton(AF_INET, sm_str, &ictx.subnet);
|
|
if (ret < 0) {
|
|
LOG_ERR("Invalid subnet");
|
|
return true;
|
|
}
|
|
|
|
/* store new gateway */
|
|
gw_start = delims[3] + 1;
|
|
gw_len = delims[4] - gw_start;
|
|
strncpy(gw_str, gw_start, gw_len);
|
|
gw_str[gw_len] = 0;
|
|
ret = net_addr_pton(AF_INET, gw_str, &ictx.gateway);
|
|
if (ret < 0) {
|
|
LOG_ERR("Invalid gateway");
|
|
return true;
|
|
}
|
|
|
|
/* store new dns */
|
|
dns_start = delims[4] + 1;
|
|
dns_len = delims[5] - dns_start;
|
|
strncpy(ictx.dns_string, dns_start, dns_len);
|
|
ictx.dns_string[dns_len] = 0;
|
|
ret = net_addr_pton(AF_INET, ictx.dns_string, &ictx.dns);
|
|
if (ret < 0) {
|
|
LOG_ERR("Invalid dns");
|
|
return true;
|
|
}
|
|
|
|
if (ictx.iface) {
|
|
/* remove the current IPv4 addr before adding a new one.
|
|
* We dont care if it is successful or not.
|
|
*/
|
|
net_if_ipv4_addr_rm(ictx.iface, &ictx.ipv4Addr);
|
|
|
|
if (!net_if_ipv4_addr_add(ictx.iface, &new_ipv4_addr,
|
|
NET_ADDR_DHCP, 0)) {
|
|
LOG_ERR("Cannot set iface IPv4 addr");
|
|
return true;
|
|
}
|
|
|
|
net_if_ipv4_set_netmask(ictx.iface, &ictx.subnet);
|
|
net_if_ipv4_set_gw(ictx.iface, &ictx.gateway);
|
|
|
|
/* store the new IP addr */
|
|
net_ipaddr_copy(&ictx.ipv4Addr, &new_ipv4_addr);
|
|
|
|
/* start DNS update work */
|
|
delay = K_NO_WAIT;
|
|
if (!ictx.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, &ictx.dns_work,
|
|
delay);
|
|
} else {
|
|
LOG_ERR("iface NULL");
|
|
}
|
|
|
|
/* TODO: IPv6 addr present, configure iface with it */
|
|
done:
|
|
return true;
|
|
}
|
|
|
|
/* Handler: +COPS: <mode>[,<format>,<oper>[,<AcT>]] */
|
|
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;
|
|
LOG_INF("Operator: %s", log_strdup(value));
|
|
|
|
if (len == 1) {
|
|
/* only mode was returned, there is no operator info */
|
|
ictx.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,
|
|
log_strdup(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 */
|
|
ictx.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(ictx.mdm_sn, val_start, sn_len);
|
|
ictx.mdm_sn[sn_len] = 0;
|
|
LOG_INF("Serial #: %s", log_strdup(ictx.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;
|
|
ictx.mdm_rat = strtol(value, NULL, 10);
|
|
LOG_INF("+KSRAT: %d", ictx.mdm_rat);
|
|
event_handler(HL7800_EVENT_RAT, &ictx.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] != (ictx.mdm_rat == MDM_RAT_CAT_M1 ? '0' : '1')) {
|
|
/* Invalid RAT */
|
|
return true;
|
|
} else if (strlen(value) < sizeof("#,###################")) {
|
|
/* String size too short */
|
|
return true;
|
|
}
|
|
|
|
memcpy(ictx.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;
|
|
ictx.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;
|
|
ictx.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;
|
|
ictx.mdm_bands_bottom = strtoul(n_tmp, NULL, 16);
|
|
|
|
LOG_INF("Current band configuration: %04x %08x %08x",
|
|
ictx.mdm_bands_top, ictx.mdm_bands_middle,
|
|
ictx.mdm_bands_bottom);
|
|
|
|
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(ictx.mdm_active_bands_string,
|
|
&value[MDM_TOP_BAND_START_POSITION], MDM_HL7800_LTE_BAND_STRLEN);
|
|
event_handler(HL7800_EVENT_ACTIVE_BANDS, ictx.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)
|
|
{
|
|
ictx.mdm_startup_state = state;
|
|
generate_startup_state_event();
|
|
}
|
|
|
|
static void generate_startup_state_event(void)
|
|
{
|
|
struct mdm_hl7800_compound_event event;
|
|
|
|
event.code = ictx.mdm_startup_state;
|
|
event.string = get_startup_state_string(ictx.mdm_startup_state);
|
|
LOG_INF("Startup State: %s", event.string);
|
|
event_handler(HL7800_EVENT_STARTUP_STATE_CHANGE, &event);
|
|
}
|
|
|
|
static char *get_sleep_state_string(enum mdm_hl7800_sleep_state state)
|
|
{
|
|
/* clang-format off */
|
|
switch (state) {
|
|
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP_STATE, UNINITIALIZED);
|
|
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP_STATE, ASLEEP);
|
|
PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP_STATE, AWAKE);
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
/* clang-format on */
|
|
}
|
|
|
|
static void set_sleep_state(enum mdm_hl7800_sleep_state state)
|
|
{
|
|
ictx.sleep_state = state;
|
|
generate_sleep_state_event();
|
|
}
|
|
|
|
static void generate_sleep_state_event(void)
|
|
{
|
|
struct mdm_hl7800_compound_event event;
|
|
|
|
event.code = ictx.sleep_state;
|
|
event.string = get_sleep_state_string(ictx.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",
|
|
log_strdup(get_fota_state_string(ictx.fw_update_state)),
|
|
log_strdup(get_fota_state_string(state)));
|
|
ictx.fw_update_state = state;
|
|
generate_fota_state_event();
|
|
}
|
|
|
|
static void generate_fota_state_event(void)
|
|
{
|
|
struct mdm_hl7800_compound_event event;
|
|
|
|
event.code = ictx.fw_update_state;
|
|
event.string = get_fota_state_string(ictx.fw_update_state);
|
|
event_handler(HL7800_EVENT_FOTA_STATE, &event);
|
|
}
|
|
|
|
static void generate_fota_count_event(void)
|
|
{
|
|
uint32_t count = ictx.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 (ictx.fw_updated) {
|
|
ictx.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, &ictx.mdm_reset_work,
|
|
K_NO_WAIT);
|
|
} else
|
|
#endif
|
|
{
|
|
PRINT_AWAKE_MSG;
|
|
ictx.wait_for_KSUP = false;
|
|
ictx.mdm_startup_reporting_on = true;
|
|
set_sleep_state(HL7800_SLEEP_STATE_AWAKE);
|
|
k_sem_give(&ictx.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, log_strdup(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) {
|
|
ictx.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, log_strdup(line));
|
|
if (output_length > 0) {
|
|
memset(ictx.mdm_apn.username, 0,
|
|
sizeof(ictx.mdm_apn.username));
|
|
memset(ictx.mdm_apn.password, 0,
|
|
sizeof(ictx.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)) {
|
|
ictx.mdm_apn.username[i++] = *p++;
|
|
}
|
|
}
|
|
LOG_INF("APN Username: %s",
|
|
log_strdup(ictx.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)) {
|
|
ictx.mdm_apn.password[i++] = *p++;
|
|
}
|
|
}
|
|
LOG_INF("APN Password: %s",
|
|
log_strdup(ictx.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, log_strdup(line));
|
|
if (output_length > 0) {
|
|
memset(ictx.mdm_apn.value, 0,
|
|
sizeof(ictx.mdm_apn.value));
|
|
|
|
/* The name is after the 3rd " */
|
|
p = strchr(line, '"');
|
|
if (p == NULL) {
|
|
LOG_WRN("Issue parsing APN response");
|
|
goto done;
|
|
}
|
|
p = strchr(p + 1, '"');
|
|
if (p == NULL) {
|
|
LOG_WRN("Issue parsing APN response");
|
|
goto done;
|
|
}
|
|
p = strchr(p + 1, '"');
|
|
if (p != NULL) {
|
|
p += 1;
|
|
i = 0;
|
|
while ((p != NULL) && (*p != '"') &&
|
|
(i < MDM_HL7800_APN_MAX_STRLEN)) {
|
|
ictx.mdm_apn.value[i++] = *p++;
|
|
}
|
|
}
|
|
LOG_INF("APN: %s", log_strdup(ictx.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)
|
|
{
|
|
k_work_reschedule_for_queue(&hl7800_workq, &ictx.rssi_query_work,
|
|
K_NO_WAIT);
|
|
}
|
|
|
|
static void hl7800_stop_rssi_work(void)
|
|
{
|
|
int rc;
|
|
|
|
rc = k_work_cancel_delayable(&ictx.rssi_query_work);
|
|
if (rc != 0) {
|
|
LOG_ERR("Could not cancel RSSI work [%d]", rc);
|
|
}
|
|
}
|
|
|
|
static void hl7800_rssi_query_work(struct k_work *work)
|
|
{
|
|
hl7800_lock();
|
|
wakeup_hl7800();
|
|
hl7800_query_rssi();
|
|
allow_sleep(true);
|
|
hl7800_unlock();
|
|
|
|
/* re-start RSSI query work */
|
|
k_work_reschedule_for_queue(&hl7800_workq, &ictx.rssi_query_work,
|
|
K_SECONDS(RSSI_TIMEOUT_SECS));
|
|
}
|
|
|
|
static void notify_all_tcp_sockets_closed(void)
|
|
{
|
|
int i;
|
|
struct hl7800_socket *sock = NULL;
|
|
|
|
for (i = 0; i < MDM_MAX_SOCKETS; i++) {
|
|
sock = &ictx.sockets[i];
|
|
if ((sock->context != NULL) && (sock->type == SOCK_STREAM)) {
|
|
sock->state = SOCK_SERVER_CLOSED;
|
|
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();
|
|
|
|
if (!ictx.initialized && ictx.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,
|
|
&ictx.iface_status_work,
|
|
IFACE_WORK_DELAY);
|
|
goto done;
|
|
} else if (ictx.wait_for_KSUP &&
|
|
ictx.wait_for_KSUP_tries < WAIT_FOR_KSUP_RETRIES) {
|
|
LOG_DBG("Wait for +KSUP before updating network state");
|
|
ictx.wait_for_KSUP_tries++;
|
|
/* we have not received +KSUP yet, lets wait more time to receive +KSUP */
|
|
k_work_reschedule_for_queue(&hl7800_workq,
|
|
&ictx.iface_status_work,
|
|
IFACE_WORK_DELAY);
|
|
goto done;
|
|
} else if (ictx.wait_for_KSUP &&
|
|
ictx.wait_for_KSUP_tries >= WAIT_FOR_KSUP_RETRIES) {
|
|
/* give up waiting for KSUP */
|
|
LOG_DBG("Give up waiting for");
|
|
ictx.wait_for_KSUP = false;
|
|
check_hl7800_awake();
|
|
}
|
|
|
|
wakeup_hl7800();
|
|
|
|
LOG_DBG("Updating network state...");
|
|
|
|
/* 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 (ictx.network_state) {
|
|
case HL7800_HOME_NETWORK:
|
|
case HL7800_ROAMING:
|
|
if (ictx.iface && !net_if_is_up(ictx.iface)) {
|
|
LOG_DBG("HL7800 iface UP");
|
|
net_if_up(ictx.iface);
|
|
}
|
|
break;
|
|
case HL7800_OUT_OF_COVERAGE:
|
|
default:
|
|
if (ictx.iface && net_if_is_up(ictx.iface)) {
|
|
LOG_DBG("HL7800 iface DOWN");
|
|
net_if_down(ictx.iface);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (ictx.iface && !net_if_is_up(ictx.iface)) {
|
|
hl7800_stop_rssi_work();
|
|
notify_all_tcp_sockets_closed();
|
|
} else if (ictx.iface && net_if_is_up(ictx.iface)) {
|
|
hl7800_start_rssi_work();
|
|
/* get IP address info */
|
|
SEND_AT_CMD_IGNORE_ERROR("AT+CGCONTRDP=1");
|
|
/* get active bands */
|
|
SEND_AT_CMD_IGNORE_ERROR("AT+KBND?");
|
|
}
|
|
LOG_DBG("Network state updated");
|
|
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)
|
|
{
|
|
ictx.network_state = state;
|
|
generate_network_state_event();
|
|
}
|
|
|
|
static void generate_network_state_event(void)
|
|
{
|
|
struct mdm_hl7800_compound_event event;
|
|
|
|
event.code = ictx.network_state;
|
|
event.string = get_network_state_string(ictx.network_state);
|
|
LOG_INF("Network State: %d %s", ictx.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,
|
|
&ictx.iface_status_work,
|
|
IFACE_WORK_DELAY);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#ifdef CONFIG_NEWLIB_LIBC
|
|
/* 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));
|
|
ictx.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:%d)",
|
|
len, str_len);
|
|
} else {
|
|
net_buf_linearize(rtc_string, str_len, *buf, 0, str_len);
|
|
LOG_INF("RTC string: '%s'", log_strdup(rtc_string));
|
|
ictx.local_time_valid = convert_time_string_to_struct(
|
|
&ictx.local_time, &ictx.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);
|
|
}
|
|
#endif
|
|
|
|
/* 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(ictx.mdm_network_status,
|
|
sizeof(ictx.mdm_network_status) - 1, *buf,
|
|
0, len);
|
|
ictx.mdm_network_status[out_len] = 0;
|
|
LOG_DBG("Network status: %s", log_strdup(ictx.mdm_network_status));
|
|
pos = strchr(ictx.mdm_network_status, ',');
|
|
if (pos) {
|
|
l = pos - ictx.mdm_network_status;
|
|
strncpy(val, ictx.mdm_network_status, l);
|
|
val[l] = 0;
|
|
set_network_state(strtol(val, NULL, 0));
|
|
} else {
|
|
set_network_state(strtol(ictx.mdm_network_status, NULL, 0));
|
|
}
|
|
|
|
/* keep HL7800 awake because we want to process the network state soon */
|
|
allow_sleep(false);
|
|
/* start work to adjust iface */
|
|
k_work_reschedule_for_queue(&hl7800_workq, &ictx.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,
|
|
log_strdup(value));
|
|
goto done;
|
|
}
|
|
/* Start next search after current delim location */
|
|
search_start = delims[i] + 1;
|
|
}
|
|
/* the first value in the message is the RSRP */
|
|
ictx.mdm_ctx.data_rssi = strtol(value, NULL, 10);
|
|
/* the 4th ',' (last in the msg) is the start of the SINR */
|
|
ictx.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", ictx.mdm_ctx.data_rssi,
|
|
ictx.mdm_sinr);
|
|
event_handler(HL7800_EVENT_RSSI, &ictx.mdm_ctx.data_rssi);
|
|
event_handler(HL7800_EVENT_SINR, &ictx.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;
|
|
|
|
ictx.last_error = 0;
|
|
sock = socket_from_id(ictx.last_socket_id);
|
|
if (!sock) {
|
|
k_sem_give(&ictx.response_sem);
|
|
} else {
|
|
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)
|
|
{
|
|
struct hl7800_socket *sock = NULL;
|
|
char *delim;
|
|
char value[MDM_MAX_RESP_SIZE];
|
|
size_t out_len;
|
|
int id;
|
|
|
|
ictx.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("+K**P_IND could not find ','");
|
|
goto done;
|
|
}
|
|
|
|
id = strtol(value, NULL, 10);
|
|
LOG_DBG("+K**P_IND ID: %d", id);
|
|
sock = socket_from_id(id);
|
|
if (sock) {
|
|
k_sem_give(&sock->sock_send_sem);
|
|
} else {
|
|
LOG_ERR("Could not find socket id (%d)", id);
|
|
}
|
|
|
|
done:
|
|
return true;
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
ictx.last_error = -EIO;
|
|
sock = socket_from_id(ictx.last_socket_id);
|
|
if (!sock) {
|
|
k_sem_give(&ictx.response_sem);
|
|
} else {
|
|
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", log_strdup(value));
|
|
|
|
ictx.last_error = -EIO;
|
|
sock = socket_from_id(ictx.last_socket_id);
|
|
if (!sock) {
|
|
k_sem_give(&ictx.response_sem);
|
|
} else {
|
|
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 {
|
|
LOG_DBG("Sock %d trigger NULL packet", sock->socket_id);
|
|
sock->state = SOCK_SERVER_CLOSED;
|
|
k_work_submit_to_queue(&hl7800_workq, &sock->recv_cb_work);
|
|
sock->error = false;
|
|
}
|
|
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;
|
|
}
|
|
|
|
notif_val = strtol(delim + 1, NULL, 10);
|
|
switch (notif_val) {
|
|
case HL7800_TCP_DATA_SND:
|
|
err = false;
|
|
ictx.last_error = 0;
|
|
break;
|
|
case HL7800_TCP_DISCON:
|
|
trigger_sem = false;
|
|
err = true;
|
|
ictx.last_error = -EIO;
|
|
break;
|
|
default:
|
|
err = true;
|
|
ictx.last_error = -EIO;
|
|
break;
|
|
}
|
|
|
|
id = strtol(value, NULL, 10);
|
|
LOG_WRN("+K**P_NOTIF: %d,%d", id, notif_val);
|
|
|
|
sock = socket_from_id(id);
|
|
if (err) {
|
|
if (sock) {
|
|
/* 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.
|
|
*/
|
|
sock->error = true;
|
|
sock->error_val = notif_val;
|
|
k_work_reschedule_for_queue(&hl7800_workq,
|
|
&sock->notif_work,
|
|
MDM_SOCK_NOTIF_DELAY);
|
|
if (trigger_sem) {
|
|
k_sem_give(&sock->sock_send_sem);
|
|
}
|
|
} else {
|
|
LOG_ERR("Could not find socket id (%d)", id);
|
|
}
|
|
}
|
|
done:
|
|
return true;
|
|
}
|
|
|
|
/* Handler: +KTCPCFG/+KUDPCFG: <session_id> */
|
|
static bool on_cmd_sockcreate(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;
|
|
ictx.last_socket_id = strtol(value, NULL, 10);
|
|
LOG_DBG("+K**PCFG: %d", ictx.last_socket_id);
|
|
|
|
/* check if the socket has been created already */
|
|
sock = socket_from_id(ictx.last_socket_id);
|
|
if (!sock) {
|
|
/* look up new socket by special id */
|
|
sock = socket_from_id(MDM_MAX_SOCKETS + 1);
|
|
if (!sock) {
|
|
LOG_ERR("No matching socket");
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
sock->socket_id = ictx.last_socket_id;
|
|
sock->created = true;
|
|
sock->reconfig = false;
|
|
/* don't give back semaphore -- OK to follow */
|
|
done:
|
|
return true;
|
|
}
|
|
|
|
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", sock->socket_id);
|
|
/* 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(ictx.last_socket_id);
|
|
if (!sock) {
|
|
LOG_ERR("Socket not found! (%d)", ictx.last_socket_id);
|
|
goto exit;
|
|
}
|
|
|
|
if (sock->error) {
|
|
/* 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);
|
|
}
|
|
net_buf_skipcrlf(buf);
|
|
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_ERR("No EOF present");
|
|
goto rx_err;
|
|
}
|
|
}
|
|
|
|
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_ERR("Could not find EOF");
|
|
goto rx_err;
|
|
}
|
|
|
|
/* 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_ERR("No OK present");
|
|
goto rx_err;
|
|
}
|
|
}
|
|
|
|
frag = NULL;
|
|
len = net_buf_findcrlf(*buf, &frag);
|
|
if (!frag) {
|
|
LOG_ERR("Unable to find OK start");
|
|
goto rx_err;
|
|
}
|
|
/* 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_ERR("Could not find OK");
|
|
goto rx_err;
|
|
}
|
|
|
|
/* remove \r\n after OK */
|
|
net_buf_skipcrlf(buf);
|
|
|
|
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) {
|
|
sock->state = SOCK_CONNECTED;
|
|
} else {
|
|
sock->state = SOCK_IDLE;
|
|
}
|
|
exit:
|
|
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(ictx.last_socket_id);
|
|
if (!sock) {
|
|
LOG_ERR("Sock (%d) not found", ictx.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(ictx.iface) - NET_IPV4UDPH_LEN)) {
|
|
sock->rx_size =
|
|
net_if_get_mtu(ictx.iface) - NET_IPV4UDPH_LEN;
|
|
}
|
|
#endif
|
|
#if defined(CONFIG_NET_IPV6)
|
|
if (rx_size > (net_if_get_mtu(ictx.iface) - NET_IPV6UDPH_LEN)) {
|
|
sock->rx_size =
|
|
net_if_get_mtu(ictx.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(ictx.iface) - NET_IPV4TCPH_LEN)) {
|
|
sock->rx_size =
|
|
net_if_get_mtu(ictx.iface) - NET_IPV4TCPH_LEN;
|
|
}
|
|
#endif
|
|
#if defined(CONFIG_NET_IPV6)
|
|
if (rx_size > (net_if_get_mtu(ictx.iface) - NET_IPV6TCPH_LEN)) {
|
|
sock->rx_size =
|
|
net_if_get_mtu(ictx.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) {
|
|
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) {
|
|
ictx.device_services_ind = strtol(value, NULL, 10);
|
|
}
|
|
LOG_INF("+WDSI: %d", ictx.device_services_ind);
|
|
|
|
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
|
|
if (ictx.device_services_ind == WDSI_PKG_DOWNLOADED) {
|
|
k_work_submit_to_queue(&hl7800_workq,
|
|
&ictx.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(&ictx.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);
|
|
ictx.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, ictx.fw_packet_count);
|
|
return mdm_receiver_send(&ictx.mdm_ctx, (const uint8_t *)pkt,
|
|
XMODEM_PACKET_SIZE);
|
|
}
|
|
|
|
static int prepare_and_send_fw_packet(void)
|
|
{
|
|
int ret = 0;
|
|
int read_res;
|
|
|
|
ictx.fw_packet.id_complement = 0xFF - ictx.fw_packet.id;
|
|
|
|
ret = fs_seek(&ictx.fw_update_file, ictx.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", ictx.file_pos);
|
|
return ret;
|
|
}
|
|
|
|
read_res = fs_read(&ictx.fw_update_file, ictx.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(&ictx.fw_update_file);
|
|
/* pad rest of data */
|
|
for (int i = read_res; i < XMODEM_DATA_SIZE; i++) {
|
|
ictx.fw_packet.data[i] = XMODEM_PAD_VALUE;
|
|
}
|
|
}
|
|
|
|
ictx.fw_packet.crc =
|
|
calc_fw_update_crc(ictx.fw_packet.data, XMODEM_DATA_SIZE);
|
|
|
|
send_fw_update_packet(&ictx.fw_packet);
|
|
|
|
ictx.file_pos += read_res;
|
|
ictx.fw_packet_count++;
|
|
ictx.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 (ictx.fw_update_state == HL7800_FOTA_START) {
|
|
/* send first FW update packet */
|
|
set_fota_state(HL7800_FOTA_WIP);
|
|
ictx.file_pos = 0;
|
|
ictx.fw_packet_count = 1;
|
|
ictx.fw_packet.id = 1;
|
|
ictx.fw_packet.preamble = XM_SOH_1K;
|
|
|
|
prepare_and_send_fw_packet();
|
|
} else if (ictx.fw_update_state == HL7800_FOTA_WIP) {
|
|
LOG_DBG("RX FW update NACK");
|
|
/* resend last packet */
|
|
send_fw_update_packet(&ictx.fw_packet);
|
|
}
|
|
} else if (xm_msg == XM_ACK) {
|
|
LOG_DBG("RX FW update ACK");
|
|
if (ictx.fw_update_state == HL7800_FOTA_WIP) {
|
|
/* send next FW update packet */
|
|
prepare_and_send_fw_packet();
|
|
} else if (ictx.fw_update_state == HL7800_FOTA_PAD) {
|
|
set_fota_state(HL7800_FOTA_SEND_EOT);
|
|
mdm_receiver_send(&ictx.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)
|
|
{
|
|
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),
|
|
#ifdef CONFIG_NEWLIB_LIBC
|
|
CMD_HANDLER("+CCLK: ", rtc_query),
|
|
#endif
|
|
|
|
/* 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: ", sockcreate),
|
|
CMD_HANDLER("+KUDPCFG: ", sockcreate),
|
|
CMD_HANDLER(CONNECT_STRING, connect),
|
|
CMD_HANDLER("NO CARRIER", sockerror),
|
|
|
|
/* UNSOLICITED SOCKET RESPONSES */
|
|
CMD_HANDLER("+KTCP_IND: ", sock_ind),
|
|
CMD_HANDLER("+KUDP_IND: ", sock_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),
|
|
};
|
|
|
|
while (true) {
|
|
/* wait for incoming data */
|
|
(void)k_sem_take(&ictx.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 ((ictx.fw_update_state == HL7800_FOTA_START) ||
|
|
(ictx.fw_update_state == HL7800_FOTA_WIP) ||
|
|
(ictx.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 (ictx.search_no_id_resp) {
|
|
cmp_res = strncmp(ictx.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 (!ictx.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;
|
|
ictx.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;
|
|
|
|
if (ictx.uart_on) {
|
|
HL7800_IO_DBG_LOG("Power OFF the UART");
|
|
uart_irq_rx_disable(ictx.mdm_ctx.uart_dev);
|
|
rc = pm_device_state_set(ictx.mdm_ctx.uart_dev,
|
|
PM_DEVICE_STATE_OFF, NULL, NULL);
|
|
if (rc) {
|
|
LOG_ERR("Error disabling UART peripheral (%d)", rc);
|
|
}
|
|
ictx.uart_on = false;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void power_on_uart(void)
|
|
{
|
|
#ifdef CONFIG_PM_DEVICE
|
|
int rc;
|
|
|
|
if (!ictx.uart_on) {
|
|
HL7800_IO_DBG_LOG("Power ON the UART");
|
|
rc = pm_device_state_set(ictx.mdm_ctx.uart_dev,
|
|
PM_DEVICE_STATE_ACTIVE, NULL, NULL);
|
|
if (rc) {
|
|
LOG_ERR("Error enabling UART peripheral (%d)", rc);
|
|
}
|
|
uart_irq_rx_enable(ictx.mdm_ctx.uart_dev);
|
|
ictx.uart_on = true;
|
|
}
|
|
#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_uart_dtr(true);
|
|
modem_assert_wake(false);
|
|
modem_assert_pwr_on(false);
|
|
modem_assert_fast_shutd(false);
|
|
ictx.wait_for_KSUP = true;
|
|
ictx.wait_for_KSUP_tries = 0;
|
|
}
|
|
|
|
static void mdm_vgpio_work_cb(struct k_work *item)
|
|
{
|
|
ARG_UNUSED(item);
|
|
|
|
hl7800_lock();
|
|
if (!ictx.vgpio_state) {
|
|
if (ictx.sleep_state != HL7800_SLEEP_STATE_ASLEEP) {
|
|
set_sleep_state(HL7800_SLEEP_STATE_ASLEEP);
|
|
}
|
|
if (ictx.iface && ictx.initialized &&
|
|
net_if_is_up(ictx.iface)) {
|
|
net_if_down(ictx.iface);
|
|
}
|
|
}
|
|
hl7800_unlock();
|
|
}
|
|
|
|
void mdm_vgpio_callback_isr(const struct device *port, struct gpio_callback *cb,
|
|
uint32_t pins)
|
|
{
|
|
ictx.vgpio_state = (uint32_t)gpio_pin_get(ictx.gpio_port_dev[MDM_VGPIO],
|
|
pinconfig[MDM_VGPIO].pin);
|
|
HL7800_IO_DBG_LOG("VGPIO:%d", ictx.vgpio_state);
|
|
if (!ictx.vgpio_state) {
|
|
prepare_io_for_reset();
|
|
if (!ictx.restarting && ictx.initialized) {
|
|
ictx.reconfig_IP_connection = true;
|
|
}
|
|
check_hl7800_awake();
|
|
} else {
|
|
/* 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();
|
|
allow_sleep(false);
|
|
}
|
|
|
|
/* 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, &ictx.mdm_vgpio_work);
|
|
}
|
|
|
|
void mdm_uart_dsr_callback_isr(const struct device *port,
|
|
struct gpio_callback *cb, uint32_t pins)
|
|
{
|
|
ictx.dsr_state = (uint32_t)gpio_pin_get(
|
|
ictx.gpio_port_dev[MDM_UART_DSR], pinconfig[MDM_UART_DSR].pin);
|
|
|
|
HL7800_IO_DBG_LOG("MDM_UART_DSR:%d", ictx.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 = &ictx.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)
|
|
{
|
|
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
|
|
ictx.gpio6_state = (uint32_t)gpio_pin_get(ictx.gpio_port_dev[MDM_GPIO6],
|
|
pinconfig[MDM_GPIO6].pin);
|
|
HL7800_IO_DBG_LOG("MDM_GPIO6:%d", ictx.gpio6_state);
|
|
if (!ictx.gpio6_state) {
|
|
/* HL7800 is not awake, shut down UART to save power */
|
|
shutdown_uart();
|
|
ictx.wait_for_KSUP = true;
|
|
ictx.wait_for_KSUP_tries = 0;
|
|
ictx.reconfig_IP_connection = true;
|
|
mark_sockets_for_reconfig();
|
|
/* TODO: may need to indicate all TCP connections lost here */
|
|
} else {
|
|
power_on_uart();
|
|
}
|
|
|
|
check_hl7800_awake();
|
|
#else
|
|
HL7800_IO_DBG_LOG("Spurious gpio6 interrupt from the modem");
|
|
#endif
|
|
}
|
|
|
|
void mdm_uart_cts_callback(const struct device *port, struct gpio_callback *cb,
|
|
uint32_t pins)
|
|
{
|
|
ictx.cts_state = (uint32_t)gpio_pin_get(
|
|
ictx.gpio_port_dev[MDM_UART_CTS], pinconfig[MDM_UART_CTS].pin);
|
|
/* CTS toggles A LOT,
|
|
* comment out the debug print unless we really need it.
|
|
*/
|
|
/* LOG_DBG("MDM_UART_CTS:%d", val); */
|
|
check_hl7800_awake();
|
|
}
|
|
|
|
static void modem_reset(void)
|
|
{
|
|
prepare_io_for_reset();
|
|
|
|
LOG_INF("Modem Reset");
|
|
/* Hard reset the modem */
|
|
gpio_pin_set(ictx.gpio_port_dev[MDM_RESET], pinconfig[MDM_RESET].pin,
|
|
MDM_RESET_ASSERTED);
|
|
/* >20 milliseconds required for reset low */
|
|
k_sleep(MDM_RESET_LOW_TIME);
|
|
|
|
ictx.mdm_startup_reporting_on = false;
|
|
set_sleep_state(HL7800_SLEEP_STATE_UNINITIALIZED);
|
|
check_hl7800_awake();
|
|
set_network_state(HL7800_NOT_REGISTERED);
|
|
set_startup_state(HL7800_STARTUP_STATE_UNKNOWN);
|
|
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
|
|
set_fota_state(HL7800_FOTA_IDLE);
|
|
#endif
|
|
k_sem_reset(&ictx.mdm_awake);
|
|
}
|
|
|
|
static void modem_run(void)
|
|
{
|
|
LOG_INF("Modem Run");
|
|
gpio_pin_set(ictx.gpio_port_dev[MDM_RESET], pinconfig[MDM_RESET].pin,
|
|
MDM_RESET_NOT_ASSERTED);
|
|
k_sleep(MDM_RESET_HIGH_TIME);
|
|
allow_sleep(false);
|
|
}
|
|
|
|
static int modem_boot_handler(char *reason)
|
|
{
|
|
int ret;
|
|
|
|
LOG_DBG("%s", reason);
|
|
ret = k_sem_take(&ictx.mdm_awake, MDM_BOOT_TIME);
|
|
if (ret) {
|
|
LOG_ERR("Err waiting for boot: %d, DSR: %u", ret,
|
|
ictx.dsr_state);
|
|
return -1;
|
|
} else if (ictx.mdm_startup_state != HL7800_STARTUP_STATE_READY) {
|
|
return -1;
|
|
} 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.
|
|
*/
|
|
ictx.mdm_echo_is_on = true;
|
|
SEND_AT_CMD_EXPECT_OK("AT&V");
|
|
|
|
if (ictx.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(!ictx.mdm_echo_is_on, "Echo should be off");
|
|
|
|
/* The Laird bootloader puts the modem into airplane mode ("AT+CFUN=4,0").
|
|
* The radio is enabled here because airplane mode
|
|
* survives reset and power removal.
|
|
*/
|
|
SEND_AT_CMD_EXPECT_OK("AT+CFUN=1,0");
|
|
|
|
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 modem_reset_and_configure(void)
|
|
{
|
|
int ret = 0;
|
|
bool sleep = false;
|
|
#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[sizeof("AT+KBNDCFG=#,####################")];
|
|
#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
|
|
|
|
ictx.restarting = true;
|
|
if (ictx.iface && net_if_is_up(ictx.iface)) {
|
|
net_if_down(ictx.iface);
|
|
}
|
|
|
|
hl7800_stop_rssi_work();
|
|
|
|
reboot:
|
|
modem_reset();
|
|
modem_run();
|
|
ret = modem_boot_handler("Initialization");
|
|
if (!ictx.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(ictx.mdm_revision, NEW_RAT_CMD_MIN_VERSION);
|
|
if (ret < 0) {
|
|
ictx.new_rat_cmd_support = false;
|
|
} else {
|
|
ictx.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 (!ictx.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 (!ictx.configured) {
|
|
#if CONFIG_MODEM_HL7800_RAT_M1
|
|
if (ictx.mdm_rat != MDM_RAT_CAT_M1) {
|
|
if (ictx.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 (ictx.mdm_rat != MDM_RAT_CAT_NB1) {
|
|
if (ictx.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 (ictx.mdm_bands_top != bands_top ||
|
|
ictx.mdm_bands_middle != bands_middle ||
|
|
ictx.mdm_bands_bottom != bands_bottom) {
|
|
if (ictx.mdm_bands_top != bands_top) {
|
|
LOG_INF("Top band mismatch, want %04x got %04x",
|
|
bands_top, ictx.mdm_bands_top);
|
|
}
|
|
if (ictx.mdm_bands_middle != bands_middle) {
|
|
LOG_INF("Middle band mismatch, want %08x got %08x",
|
|
bands_middle, ictx.mdm_bands_middle);
|
|
}
|
|
if (ictx.mdm_bands_bottom != bands_bottom) {
|
|
LOG_INF("Bottom band mismatch, want %08x got %08x",
|
|
bands_bottom, ictx.mdm_bands_bottom);
|
|
}
|
|
|
|
snprintk(new_bands, sizeof(new_bands),
|
|
"AT+KBNDCFG=%d,%04x%08x%08x", ictx.mdm_rat, bands_top,
|
|
bands_middle, bands_bottom);
|
|
SEND_AT_CMD_EXPECT_OK(new_bands);
|
|
|
|
SEND_AT_CMD_EXPECT_OK("AT+CFUN=1,1");
|
|
|
|
modem_boot_handler("LTE bands were just set");
|
|
if (ret < 0) {
|
|
goto error;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE
|
|
|
|
/* enable GPIO6 low power monitoring */
|
|
SEND_AT_CMD_EXPECT_OK("AT+KHWIOCFG=3,1,6");
|
|
|
|
/* Turn on sleep mode */
|
|
SEND_AT_CMD_EXPECT_OK("AT+KSLEEP=0,2,10");
|
|
|
|
#if CONFIG_MODEM_HL7800_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
|
|
/* Turn off PSM */
|
|
SEND_AT_CMD_EXPECT_OK("AT+CPSMS=0");
|
|
|
|
/* turn on eDRX */
|
|
if (ictx.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");
|
|
|
|
/* query SIM ICCID */
|
|
SEND_AT_CMD_EXPECT_OK("AT+CCID?");
|
|
|
|
/* An empty string is used here so that it doesn't conflict
|
|
* with the APN used in the +CGDCONT command.
|
|
*/
|
|
SEND_AT_CMD_EXPECT_OK(SETUP_GPRS_CONNECTION_CMD);
|
|
|
|
/* Query PDP context to get APN */
|
|
SEND_AT_CMD_EXPECT_OK("AT+CGDCONT?");
|
|
|
|
/* Query PDP authentication context to get APN username/password.
|
|
* Temporary Workaroud - 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 (!ictx.configured) {
|
|
if (strncmp(ictx.mdm_apn.value, CONFIG_MODEM_HL7800_APN_NAME,
|
|
MDM_HL7800_APN_MAX_STRLEN) != 0) {
|
|
ret = write_apn(CONFIG_MODEM_HL7800_APN_NAME);
|
|
if (ret < 0) {
|
|
goto error;
|
|
} else {
|
|
goto reboot;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* 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=4");
|
|
|
|
/* The modem has been initialized and now the network interface can be
|
|
* started in the CEREG message handler.
|
|
*/
|
|
LOG_INF("Modem ready!");
|
|
ictx.restarting = false;
|
|
ictx.configured = true;
|
|
allow_sleep(sleep);
|
|
/* trigger APN update event */
|
|
event_handler(HL7800_EVENT_APN_UPDATE, &ictx.mdm_apn);
|
|
return 0;
|
|
|
|
error:
|
|
LOG_ERR("Unable to configure modem");
|
|
ictx.configured = false;
|
|
set_network_state(HL7800_UNABLE_TO_CONFIGURE);
|
|
modem_reset();
|
|
/* 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);
|
|
strncat(cmd_string, "AT+CGDCONT=1,\"IPV4V6\",\"",
|
|
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();
|
|
|
|
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
|
|
if (ictx.fw_update_state == HL7800_FOTA_REBOOT_AND_RECONFIGURE) {
|
|
set_fota_state(HL7800_FOTA_COMPLETE);
|
|
}
|
|
#endif
|
|
|
|
hl7800_unlock();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int hl7800_power_off(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
LOG_INF("Powering off modem");
|
|
wakeup_hl7800();
|
|
hl7800_stop_rssi_work();
|
|
|
|
/* use the restarting flag to prevent +CEREG updates */
|
|
ictx.restarting = true;
|
|
|
|
ret = send_at_cmd(NULL, "AT+CPOF", MDM_CMD_SEND_TIMEOUT, 1, false);
|
|
if (ret) {
|
|
LOG_ERR("AT+CPOF ret:%d", ret);
|
|
return ret;
|
|
}
|
|
/* bring the iface down */
|
|
if (ictx.iface && net_if_is_up(ictx.iface)) {
|
|
net_if_down(ictx.iface);
|
|
}
|
|
LOG_INF("Modem powered off");
|
|
return ret;
|
|
}
|
|
|
|
int32_t mdm_hl7800_power_off(void)
|
|
{
|
|
int rc;
|
|
|
|
hl7800_lock();
|
|
rc = hl7800_power_off();
|
|
hl7800_unlock();
|
|
|
|
return rc;
|
|
}
|
|
|
|
void mdm_hl7800_register_event_callback(mdm_hl7800_event_callback_t cb)
|
|
{
|
|
int key = irq_lock();
|
|
|
|
ictx.event_callback = cb;
|
|
irq_unlock(key);
|
|
}
|
|
|
|
/*** 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 succeded or failed.
|
|
*/
|
|
ret = k_sem_take(&sock->sock_send_sem, MDM_CMD_CONN_TIMEOUT);
|
|
if (ret == 0) {
|
|
ret = ictx.last_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=#,#,\"###.###.###.###\",#####")];
|
|
int dst_port = -1;
|
|
|
|
#if defined(CONFIG_NET_IPV6)
|
|
if (sock->dst.sa_family == AF_INET6) {
|
|
dst_port = net_sin6(&sock->dst)->sin6_port;
|
|
} else
|
|
#endif
|
|
#if defined(CONFIG_NET_IPV4)
|
|
if (sock->dst.sa_family == AF_INET) {
|
|
dst_port = net_sin(&sock->dst)->sin_port;
|
|
} else
|
|
#endif
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* socket # needs assigning */
|
|
sock->socket_id = MDM_MAX_SOCKETS + 1;
|
|
|
|
snprintk(cmd_cfg, sizeof(cmd_cfg), "AT+KTCPCFG=%d,%d,\"%s\",%u", 1, 0,
|
|
hl7800_sprint_ip_addr(&sock->dst), dst_port);
|
|
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;
|
|
}
|
|
|
|
if (sock->state == SOCK_CONNECTED) {
|
|
/* if the socket was previously connected, reconnect */
|
|
ret = connect_TCP_socket(sock);
|
|
if (ret < 0) {
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
done:
|
|
return ret;
|
|
}
|
|
|
|
static int configure_UDP_socket(struct hl7800_socket *sock)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* socket # needs assigning */
|
|
sock->socket_id = MDM_MAX_SOCKETS + 1;
|
|
|
|
ret = send_at_cmd(sock, "AT+KUDPCFG=1,0", 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 = ictx.last_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_sockets(void)
|
|
{
|
|
int i, ret = 0;
|
|
struct hl7800_socket *sock = NULL;
|
|
|
|
for (i = 0; i < MDM_MAX_SOCKETS; i++) {
|
|
sock = &ictx.sockets[i];
|
|
if ((sock->context != NULL) && sock->created &&
|
|
sock->reconfig) {
|
|
/* reconfigure socket so it is ready for use */
|
|
if (sock->type == SOCK_DGRAM) {
|
|
LOG_DBG("Reconfig UDP socket %d",
|
|
sock->socket_id);
|
|
ret = configure_UDP_socket(sock);
|
|
if (ret < 0) {
|
|
goto done;
|
|
}
|
|
} else if (sock->type == SOCK_STREAM) {
|
|
LOG_DBG("Reconfig TCP socket %d",
|
|
sock->socket_id);
|
|
ret = configure_TCP_socket(sock);
|
|
if (ret < 0) {
|
|
goto done;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
done:
|
|
return ret;
|
|
}
|
|
|
|
static int reconfigure_IP_connection(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (ictx.reconfig_IP_connection) {
|
|
ictx.reconfig_IP_connection = false;
|
|
|
|
/* reconfigure GPRS connection so sockets can be used */
|
|
ret = send_at_cmd(NULL, SETUP_GPRS_CONNECTION_CMD,
|
|
MDM_CMD_SEND_TIMEOUT, 0, false);
|
|
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);
|
|
|
|
/* reconfigure any sockets that were already setup */
|
|
ret = reconfigure_sockets();
|
|
}
|
|
|
|
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(ictx.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_MAX_SOCKETS + 1; /* socket # needs assigning */
|
|
|
|
/* 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 */
|
|
if (reconfigure_IP_connection() < 0) {
|
|
socket_put(sock);
|
|
goto done;
|
|
}
|
|
|
|
ret = configure_UDP_socket(sock);
|
|
if (ret < 0) {
|
|
socket_put(sock);
|
|
goto done;
|
|
}
|
|
}
|
|
done:
|
|
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:
|
|
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);
|
|
|
|
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 cmd1[sizeof("AT+KTCPCLOSE=##")];
|
|
char cmd2[sizeof("AT+KTCPDEL=##")];
|
|
|
|
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(cmd1, sizeof(cmd1), "AT+KTCPCLOSE=%d",
|
|
sock->socket_id);
|
|
snprintk(cmd2, sizeof(cmd2), "AT+KTCPDEL=%d", sock->socket_id);
|
|
} else {
|
|
snprintk(cmd1, sizeof(cmd1), "AT+KUDPCLOSE=%d",
|
|
sock->socket_id);
|
|
}
|
|
|
|
wakeup_hl7800();
|
|
|
|
send_at_cmd(sock, cmd1, MDM_CMD_SEND_TIMEOUT, 0, false);
|
|
|
|
if (sock->type == SOCK_STREAM) {
|
|
/* delete session */
|
|
send_at_cmd(sock, cmd2, MDM_CMD_SEND_TIMEOUT, 0, 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,
|
|
};
|
|
|
|
static inline uint8_t *hl7800_get_mac(const struct device *dev)
|
|
{
|
|
struct hl7800_iface_ctx *ctx = dev->data;
|
|
|
|
/* use the last 6 digits of the IMEI as the mac address */
|
|
ctx->mac_addr[0] = ictx.mdm_imei[MDM_HL7800_IMEI_STRLEN - 6];
|
|
ctx->mac_addr[1] = ictx.mdm_imei[MDM_HL7800_IMEI_STRLEN - 5];
|
|
ctx->mac_addr[2] = ictx.mdm_imei[MDM_HL7800_IMEI_STRLEN - 4];
|
|
ctx->mac_addr[3] = ictx.mdm_imei[MDM_HL7800_IMEI_STRLEN - 3];
|
|
ctx->mac_addr[4] = ictx.mdm_imei[MDM_HL7800_IMEI_STRLEN - 2];
|
|
ctx->mac_addr[5] = ictx.mdm_imei[MDM_HL7800_IMEI_STRLEN - 1];
|
|
|
|
return ctx->mac_addr;
|
|
}
|
|
|
|
#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")];
|
|
|
|
/* HL7800 will stay locked for the duration of the FW update */
|
|
hl7800_lock();
|
|
|
|
/* get file info */
|
|
ret = fs_stat(file_path, &file_info);
|
|
if (ret >= 0) {
|
|
LOG_DBG("file '%s' size %zu", log_strdup(file_info.name),
|
|
file_info.size);
|
|
} else {
|
|
LOG_ERR("Failed to get file [%s] info: %d",
|
|
log_strdup(file_path), ret);
|
|
goto err;
|
|
}
|
|
|
|
ret = fs_open(&ictx.fw_update_file, file_path, FS_O_READ);
|
|
if (ret < 0) {
|
|
LOG_ERR("%s open err: %d", log_strdup(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;
|
|
}
|
|
|
|
if (ictx.iface && net_if_is_up(ictx.iface)) {
|
|
LOG_DBG("HL7800 iface DOWN");
|
|
hl7800_stop_rssi_work();
|
|
net_if_down(ictx.iface);
|
|
notify_all_tcp_sockets_closed();
|
|
}
|
|
|
|
/* 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);
|
|
snprintk(cmd1, sizeof(cmd1), "AT+WDSD=%zd", file_info.size);
|
|
send_at_cmd(NULL, cmd1, K_NO_WAIT, 0, false);
|
|
|
|
goto done;
|
|
|
|
err:
|
|
hl7800_unlock();
|
|
done:
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static int hl7800_init(const struct device *dev)
|
|
{
|
|
int i, ret = 0;
|
|
|
|
ARG_UNUSED(dev);
|
|
|
|
LOG_DBG("HL7800 Init");
|
|
|
|
/* check for valid pinconfig */
|
|
__ASSERT(ARRAY_SIZE(pinconfig) == MAX_MDM_CONTROL_PINS,
|
|
"Incorrect modem pinconfig!");
|
|
|
|
/* Prevent the network interface from starting until
|
|
* the modem has been initialized
|
|
* because the modem may not have a valid SIM card.
|
|
*/
|
|
ictx.iface = net_if_get_default();
|
|
if (ictx.iface == NULL) {
|
|
return -EIO;
|
|
}
|
|
net_if_flag_set(ictx.iface, NET_IF_NO_AUTO_START);
|
|
|
|
(void)memset(&ictx, 0, sizeof(ictx));
|
|
/* init sockets */
|
|
for (i = 0; i < MDM_MAX_SOCKETS; i++) {
|
|
ictx.sockets[i].socket_id = -1;
|
|
k_work_init(&ictx.sockets[i].recv_cb_work,
|
|
sockreadrecv_cb_work);
|
|
k_work_init(&ictx.sockets[i].rx_data_work,
|
|
sock_rx_data_cb_work);
|
|
k_work_init_delayable(&ictx.sockets[i].notif_work,
|
|
sock_notif_cb_work);
|
|
k_sem_init(&ictx.sockets[i].sock_send_sem, 0, 1);
|
|
}
|
|
ictx.last_socket_id = 0;
|
|
k_sem_init(&ictx.response_sem, 0, 1);
|
|
k_sem_init(&ictx.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, NULL);
|
|
|
|
/* init work tasks */
|
|
k_work_init_delayable(&ictx.rssi_query_work, hl7800_rssi_query_work);
|
|
k_work_init_delayable(&ictx.iface_status_work, iface_status_work_cb);
|
|
k_work_init_delayable(&ictx.dns_work, dns_work_cb);
|
|
k_work_init(&ictx.mdm_vgpio_work, mdm_vgpio_work_cb);
|
|
k_work_init_delayable(&ictx.mdm_reset_work, mdm_reset_work_callback);
|
|
k_work_init_delayable(&ictx.allow_sleep_work,
|
|
allow_sleep_work_callback);
|
|
|
|
#ifdef CONFIG_MODEM_HL7800_FW_UPDATE
|
|
k_work_init(&ictx.finish_fw_update_work,
|
|
finish_fw_update_work_callback);
|
|
ictx.fw_updated = false;
|
|
#endif
|
|
|
|
/* setup port devices and pin directions */
|
|
for (i = 0; i < MAX_MDM_CONTROL_PINS; i++) {
|
|
ictx.gpio_port_dev[i] =
|
|
device_get_binding(pinconfig[i].dev_name);
|
|
if (!ictx.gpio_port_dev[i]) {
|
|
LOG_ERR("gpio port (%s) not found!",
|
|
pinconfig[i].dev_name);
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = gpio_pin_configure(ictx.gpio_port_dev[i],
|
|
pinconfig[i].pin, pinconfig[i].config);
|
|
if (ret) {
|
|
LOG_ERR("Error configuring io %s %d err: %d!",
|
|
pinconfig[i].dev_name, pinconfig[i].pin, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* when this driver starts, the UART peripheral is already enabled */
|
|
ictx.uart_on = true;
|
|
|
|
modem_assert_wake(false);
|
|
modem_assert_uart_dtr(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(&ictx.mdm_vgpio_cb, mdm_vgpio_callback_isr,
|
|
BIT(pinconfig[MDM_VGPIO].pin));
|
|
ret = gpio_add_callback(ictx.gpio_port_dev[MDM_VGPIO],
|
|
&ictx.mdm_vgpio_cb);
|
|
if (ret) {
|
|
LOG_ERR("Cannot setup vgpio callback! (%d)", ret);
|
|
return ret;
|
|
}
|
|
ret = gpio_pin_interrupt_configure(ictx.gpio_port_dev[MDM_VGPIO],
|
|
pinconfig[MDM_VGPIO].pin,
|
|
pinconfig[MDM_VGPIO].config);
|
|
if (ret) {
|
|
LOG_ERR("Error config vgpio interrupt! (%d)", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* UART DSR */
|
|
gpio_init_callback(&ictx.mdm_uart_dsr_cb, mdm_uart_dsr_callback_isr,
|
|
BIT(pinconfig[MDM_UART_DSR].pin));
|
|
ret = gpio_add_callback(ictx.gpio_port_dev[MDM_UART_DSR],
|
|
&ictx.mdm_uart_dsr_cb);
|
|
if (ret) {
|
|
LOG_ERR("Cannot setup uart dsr callback! (%d)", ret);
|
|
return ret;
|
|
}
|
|
ret = gpio_pin_interrupt_configure(ictx.gpio_port_dev[MDM_UART_DSR],
|
|
pinconfig[MDM_UART_DSR].pin,
|
|
pinconfig[MDM_UART_DSR].config);
|
|
if (ret) {
|
|
LOG_ERR("Error config uart dsr interrupt! (%d)", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* GPIO6 */
|
|
gpio_init_callback(&ictx.mdm_gpio6_cb, mdm_gpio6_callback_isr,
|
|
BIT(pinconfig[MDM_GPIO6].pin));
|
|
ret = gpio_add_callback(ictx.gpio_port_dev[MDM_GPIO6],
|
|
&ictx.mdm_gpio6_cb);
|
|
if (ret) {
|
|
LOG_ERR("Cannot setup gpio6 callback! (%d)", ret);
|
|
return ret;
|
|
}
|
|
ret = gpio_pin_interrupt_configure(ictx.gpio_port_dev[MDM_GPIO6],
|
|
pinconfig[MDM_GPIO6].pin,
|
|
pinconfig[MDM_GPIO6].config);
|
|
if (ret) {
|
|
LOG_ERR("Error config gpio6 interrupt! (%d)", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* UART CTS */
|
|
gpio_init_callback(&ictx.mdm_uart_cts_cb, mdm_uart_cts_callback,
|
|
BIT(pinconfig[MDM_UART_CTS].pin));
|
|
ret = gpio_add_callback(ictx.gpio_port_dev[MDM_UART_CTS],
|
|
&ictx.mdm_uart_cts_cb);
|
|
if (ret) {
|
|
LOG_ERR("Cannot setup uart cts callback! (%d)", ret);
|
|
return ret;
|
|
}
|
|
ret = gpio_pin_interrupt_configure(ictx.gpio_port_dev[MDM_UART_CTS],
|
|
pinconfig[MDM_UART_CTS].pin,
|
|
pinconfig[MDM_UART_CTS].config);
|
|
if (ret) {
|
|
LOG_ERR("Error config uart cts interrupt! (%d)", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Set modem data storage */
|
|
ictx.mdm_ctx.data_manufacturer = ictx.mdm_manufacturer;
|
|
ictx.mdm_ctx.data_model = ictx.mdm_model;
|
|
ictx.mdm_ctx.data_revision = ictx.mdm_revision;
|
|
#ifdef CONFIG_MODEM_SIM_NUMBERS
|
|
ictx.mdm_ctx.data_imei = ictx.mdm_imei;
|
|
#endif
|
|
|
|
ret = mdm_receiver_register(&ictx.mdm_ctx, MDM_UART_DEV_NAME,
|
|
mdm_recv_buf, sizeof(mdm_recv_buf));
|
|
if (ret < 0) {
|
|
LOG_ERR("Error registering modem receiver (%d)!", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* start RX thread */
|
|
k_thread_name_set(
|
|
k_thread_create(&hl7800_rx_thread, hl7800_rx_stack,
|
|
K_THREAD_STACK_SIZEOF(hl7800_rx_stack),
|
|
(k_thread_entry_t)hl7800_rx, NULL, NULL, NULL,
|
|
RX_THREAD_PRIORITY, 0, K_NO_WAIT),
|
|
"hl7800 rx");
|
|
|
|
ret = modem_reset_and_configure();
|
|
|
|
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;
|
|
net_if_set_link_addr(iface, hl7800_get_mac(dev), sizeof(ctx->mac_addr),
|
|
NET_LINK_ETHERNET);
|
|
ctx->iface = iface;
|
|
ictx.initialized = true;
|
|
}
|
|
|
|
static struct net_if_api api_funcs = {
|
|
.init = offload_iface_init,
|
|
};
|
|
|
|
NET_DEVICE_DT_INST_OFFLOAD_DEFINE(0, hl7800_init, NULL, &ictx,
|
|
NULL, CONFIG_MODEM_HL7800_INIT_PRIORITY,
|
|
&api_funcs, MDM_MTU);
|