| /* |
| * Copyright (c) 2020 Laird Connectivity |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT swir_hl7800 |
| |
| #include <zephyr/logging/log.h> |
| #include <zephyr/logging/log_ctrl.h> |
| #define LOG_MODULE_NAME modem_hl7800 |
| LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_MODEM_LOG_LEVEL); |
| |
| #include <zephyr/types.h> |
| #include <stddef.h> |
| #include <stdlib.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/device.h> |
| #include <zephyr/init.h> |
| |
| #include <zephyr/pm/device.h> |
| #include <zephyr/drivers/uart.h> |
| |
| #include <zephyr/net/net_context.h> |
| #include <zephyr/net/net_if.h> |
| #include <zephyr/net/net_offload.h> |
| #include <zephyr/net/net_pkt.h> |
| #include <zephyr/net/dns_resolve.h> |
| #include <zephyr/net/offloaded_netdev.h> |
| #if defined(CONFIG_NET_IPV6) |
| #include "ipv6.h" |
| #endif |
| #if defined(CONFIG_NET_IPV4) |
| #include "ipv4.h" |
| #endif |
| #if defined(CONFIG_NET_UDP) |
| #include "udp_internal.h" |
| #endif |
| |
| #ifdef CONFIG_MODEM_HL7800_FW_UPDATE |
| #include <zephyr/fs/fs.h> |
| #endif |
| |
| #include "modem_receiver.h" |
| #include <zephyr/drivers/modem/hl7800.h> |
| |
| #define PREFIXED_SWITCH_CASE_RETURN_STRING(prefix, val) \ |
| case prefix##_##val: { \ |
| return #val; \ |
| } |
| |
| /* Uncomment the #define below to enable a hexdump of all incoming |
| * data from the modem receiver |
| */ |
| /* #define HL7800_ENABLE_VERBOSE_MODEM_RECV_HEXDUMP 1 */ |
| |
| #define HL7800_LOG_UNHANDLED_RX_MSGS 1 |
| |
| /* Uncomment the #define(s) below to enable extra debugging */ |
| /* #define HL7800_RX_LOCK_LOG 1 */ |
| /* #define HL7800_TX_LOCK_LOG 1 */ |
| /* #define HL7800_IO_LOG 1 */ |
| |
| #define HL7800_RX_LOCK_DBG_LOG(fmt, ...) \ |
| do { \ |
| if (IS_ENABLED(HL7800_RX_LOCK_LOG)) { \ |
| LOG_DBG(fmt, ##__VA_ARGS__); \ |
| } \ |
| } while (false) |
| |
| #define HL7800_TX_LOCK_DBG_LOG(fmt, ...) \ |
| do { \ |
| if (IS_ENABLED(HL7800_TX_LOCK_LOG)) { \ |
| LOG_DBG(fmt, ##__VA_ARGS__); \ |
| } \ |
| } while (false) |
| |
| #define HL7800_IO_DBG_LOG(fmt, ...) \ |
| do { \ |
| if (IS_ENABLED(HL7800_IO_LOG)) { \ |
| LOG_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_CONNECTED, |
| }; |
| |
| enum hl7800_lpm { |
| HL7800_LPM_NONE, |
| HL7800_LPM_EDRX, |
| HL7800_LPM_PSM, |
| }; |
| |
| /* pin settings */ |
| enum mdm_control_pins { |
| MDM_RESET = 0, |
| MDM_WAKE, |
| MDM_PWR_ON, |
| MDM_FAST_SHUTD, |
| MDM_VGPIO, |
| MDM_UART_DSR, |
| MDM_UART_CTS, |
| MDM_GPIO6, |
| MAX_MDM_CONTROL_PINS, |
| }; |
| |
| enum net_operator_status { NO_OPERATOR, REGISTERED }; |
| |
| enum device_service_indications { |
| WDSI_PKG_DOWNLOADED = 3, |
| }; |
| |
| #ifdef CONFIG_MODEM_HL7800_FW_UPDATE |
| enum XMODEM_CONTROL_CHARACTERS { |
| XM_SOH = 0x01, |
| XM_SOH_1K = 0x02, |
| XM_EOT = 0x04, |
| XM_ACK = 0x06, /* 'R' */ |
| XM_NACK = 0x15, /* 'N' */ |
| XM_ETB = 0x17, |
| XM_CAN = 0x18, |
| XM_C = 0x43 |
| }; |
| |
| #define XMODEM_DATA_SIZE 1024 |
| #define XMODEM_PACKET_SIZE (XMODEM_DATA_SIZE + 4) |
| #define XMODEM_PAD_VALUE 26 |
| |
| struct xmodem_packet { |
| uint8_t preamble; |
| uint8_t id; |
| uint8_t id_complement; |
| uint8_t data[XMODEM_DATA_SIZE]; |
| uint8_t crc; |
| }; |
| #endif |
| |
| #define MDM_UART_DEV DEVICE_DT_GET(DT_INST_BUS(0)) |
| |
| #define MDM_SEND_OK_ENABLED 0 |
| #define MDM_SEND_OK_DISABLED 1 |
| |
| #define MDM_CMD_SEND_TIMEOUT K_SECONDS(6) |
| #define MDM_IP_SEND_RX_TIMEOUT K_SECONDS(62) |
| #define MDM_SOCK_NOTIF_DELAY K_MSEC(150) |
| #define MDM_CMD_CONN_TIMEOUT K_SECONDS(31) |
| |
| #define MDM_MAX_DATA_LENGTH 1500 |
| #define MDM_MTU 1500 |
| #define MDM_MAX_RESP_SIZE 128 |
| #define MDM_IP_INFO_RESP_SIZE 256 |
| #define MDM_EID_LENGTH 33 |
| #define MDM_CCID_RESP_MAX_SIZE (MDM_HL7800_ICCID_MAX_SIZE + MDM_EID_LENGTH) |
| |
| #define MDM_HANDLER_MATCH_MAX_LEN 100 |
| |
| #define MDM_MAX_SOCKETS 6 |
| |
| /* Special value used to indicate that a socket is being created |
| * and that its actual ID hasn't been assigned yet. |
| */ |
| #define MDM_CREATE_SOCKET_ID (MDM_MAX_SOCKETS + 1) |
| |
| #define BUF_ALLOC_TIMEOUT K_SECONDS(1) |
| |
| #define SIZE_OF_NUL 1 |
| |
| #define SIZE_WITHOUT_NUL(v) (sizeof(v) - SIZE_OF_NUL) |
| |
| #define CMD_HANDLER(cmd_, cb_) \ |
| { \ |
| .cmd = cmd_, .cmd_len = (uint16_t)sizeof(cmd_) - 1, \ |
| .func = on_cmd_##cb_ \ |
| } |
| |
| #define MDM_MANUFACTURER_LENGTH 16 |
| #define MDM_MODEL_LENGTH 7 |
| #define MDM_SN_RESPONSE_LENGTH (MDM_HL7800_SERIAL_NUMBER_SIZE + 7) |
| #define MDM_NETWORK_STATUS_LENGTH 45 |
| |
| #define MDM_TOP_BAND_SIZE 4 |
| #define MDM_MIDDLE_BAND_SIZE 8 |
| #define MDM_BOTTOM_BAND_SIZE 8 |
| #define MDM_TOP_BAND_START_POSITION 2 |
| #define MDM_MIDDLE_BAND_START_POSITION 6 |
| #define MDM_BOTTOM_BAND_START_POSITION 14 |
| #define MDM_BAND_BITMAP_STR_LENGTH_MAX \ |
| (MDM_TOP_BAND_SIZE + MDM_MIDDLE_BAND_SIZE + MDM_BOTTOM_BAND_SIZE) |
| #define MDM_BAND_BITMAP_STR_LENGTH_MIN 1 |
| |
| #define MDM_DEFAULT_AT_CMD_RETRIES 3 |
| #define MDM_WAKEUP_TIME K_SECONDS(12) |
| #define MDM_BOOT_TIME K_SECONDS(12) |
| #define MDM_WAKE_TO_CHECK_CTS_DELAY_MS K_MSEC(20) |
| |
| #define MDM_WAIT_FOR_DATA_TIME K_MSEC(50) |
| #define MDM_RESET_LOW_TIME K_MSEC(50) |
| #define MDM_RESET_HIGH_TIME K_MSEC(10) |
| #define MDM_WAIT_FOR_DATA_RETRIES 3 |
| |
| #define RSSI_UNKNOWN -999 |
| |
| #define DNS_WORK_DELAY_SECS 1 |
| #define IFACE_WORK_DELAY K_MSEC(500) |
| #define SOCKET_CLEANUP_WORK_DELAY K_MSEC(100) |
| #define WAIT_FOR_KSUP_RETRIES 5 |
| |
| #define CGCONTRDP_RESPONSE_NUM_DELIMS 7 |
| #define COPS_RESPONSE_NUM_DELIMS 2 |
| #define KCELLMEAS_RESPONSE_NUM_DELIMS 4 |
| |
| #define PROFILE_LINE_1 \ |
| "E1 Q0 V1 X4 &C1 &D1 &R1 &S0 +IFC=2,2 &K3 +IPR=115200 +FCLASS0\r\n" |
| #define PROFILE_LINE_2 \ |
| "S00:255 S01:255 S03:255 S04:255 S05:255 S07:255 S08:255 S10:255\r\n" |
| |
| #define ADDRESS_FAMILY_IPV4 "IPV4" |
| #if defined(CONFIG_MODEM_HL7800_ADDRESS_FAMILY_IPV4V6) |
| #define MODEM_HL7800_ADDRESS_FAMILY "IPV4V6" |
| #elif defined(CONFIG_MODEM_HL7800_ADDRESS_FAMILY_IPV4) |
| #define MODEM_HL7800_ADDRESS_FAMILY ADDRESS_FAMILY_IPV4 |
| #else |
| #define MODEM_HL7800_ADDRESS_FAMILY "IPV6" |
| #endif |
| #define MDM_HL7800_SOCKET_AF_IPV4 0 |
| #define MDM_HL7800_SOCKET_AF_IPV6 1 |
| |
| #define SET_RAT_M1_CMD_LEGACY "AT+KSRAT=0" |
| #define SET_RAT_NB1_CMD_LEGACY "AT+KSRAT=1" |
| #define SET_RAT_M1_CMD "AT+KSRAT=0,1" |
| #define SET_RAT_NB1_CMD "AT+KSRAT=1,1" |
| #define NEW_RAT_CMD_MIN_VERSION "HL7800.4.5.4.0" |
| #define HL7800_VERSION_FORMAT "HL7800.%zu.%zu.%zu.%zu" |
| |
| #define MAX_PROFILE_LINE_LENGTH \ |
| MAX(sizeof(PROFILE_LINE_1), sizeof(PROFILE_LINE_2)) |
| |
| #define IPV6_ADDR_FORMAT "####:####:####:####:####:####:####:####" |
| #define HL7800_IPV6_ADDR_LEN \ |
| sizeof("a01.a02.a03.a04.a05.a06.a07.a08.a09.a10.a11.a12.a13.a14.a15.a16") |
| |
| #define MDM_ADDR_FAM_MAX_LEN sizeof("IPV4V6") |
| |
| /* The ? can be a + or - */ |
| static const char TIME_STRING_FORMAT[] = "\"yy/MM/dd,hh:mm:ss?zz\""; |
| #define TIME_STRING_DIGIT_STRLEN 2 |
| #define TIME_STRING_SEPARATOR_STRLEN 1 |
| #define TIME_STRING_PLUS_MINUS_INDEX (6 * 3) |
| #define TIME_STRING_FIRST_SEPARATOR_INDEX 0 |
| #define TIME_STRING_FIRST_DIGIT_INDEX 1 |
| #define TIME_STRING_TO_TM_STRUCT_YEAR_OFFSET (2000 - 1900) |
| |
| /* Time structure min, max */ |
| #define TM_YEAR_RANGE 0, 99 |
| #define TM_MONTH_RANGE_PLUS_1 1, 12 |
| #define TM_DAY_RANGE 1, 31 |
| #define TM_HOUR_RANGE 0, 23 |
| #define TM_MIN_RANGE 0, 59 |
| #define TM_SEC_RANGE 0, 60 /* leap second */ |
| #define QUARTER_HOUR_RANGE 0, 96 |
| #define SECONDS_PER_QUARTER_HOUR (15 * 60) |
| |
| #define SEND_AT_CMD_ONCE_EXPECT_OK(c) \ |
| do { \ |
| ret = send_at_cmd(NULL, (c), MDM_CMD_SEND_TIMEOUT, 0, false); \ |
| if (ret < 0) { \ |
| LOG_ERR("%s result:%d", (c), ret); \ |
| goto error; \ |
| } \ |
| } while (false) |
| |
| #define SEND_AT_CMD_IGNORE_ERROR(c) \ |
| do { \ |
| ret = send_at_cmd(NULL, (c), MDM_CMD_SEND_TIMEOUT, 0, false); \ |
| if (ret < 0) { \ |
| LOG_ERR("%s result:%d", (c), ret); \ |
| } \ |
| } while (false) |
| |
| #define SEND_AT_CMD_EXPECT_OK(c) \ |
| do { \ |
| ret = send_at_cmd(NULL, (c), MDM_CMD_SEND_TIMEOUT, \ |
| MDM_DEFAULT_AT_CMD_RETRIES, false); \ |
| if (ret < 0) { \ |
| LOG_ERR("%s result:%d", (c), ret); \ |
| goto error; \ |
| } \ |
| } while (false) |
| |
| /* Complex has "no_id_resp" set to true because the sending command |
| * is the command used to process the response |
| */ |
| #define SEND_COMPLEX_AT_CMD(c) \ |
| do { \ |
| ret = send_at_cmd(NULL, (c), MDM_CMD_SEND_TIMEOUT, \ |
| MDM_DEFAULT_AT_CMD_RETRIES, true); \ |
| if (ret < 0) { \ |
| LOG_ERR("%s result:%d", (c), ret); \ |
| goto error; \ |
| } \ |
| } while (false) |
| |
| NET_BUF_POOL_DEFINE(mdm_recv_pool, CONFIG_MODEM_HL7800_RECV_BUF_CNT, |
| CONFIG_MODEM_HL7800_RECV_BUF_SIZE, 0, NULL); |
| |
| static uint8_t mdm_recv_buf[MDM_MAX_DATA_LENGTH]; |
| |
| static K_SEM_DEFINE(hl7800_RX_lock_sem, 1, 1); |
| static K_SEM_DEFINE(hl7800_TX_lock_sem, 1, 1); |
| static K_SEM_DEFINE(cb_lock, 1, 1); |
| |
| /* RX thread structures */ |
| K_THREAD_STACK_DEFINE(hl7800_rx_stack, CONFIG_MODEM_HL7800_RX_STACK_SIZE); |
| struct k_thread hl7800_rx_thread; |
| #define RX_THREAD_PRIORITY K_PRIO_COOP(7) |
| |
| /* RX thread work queue */ |
| K_THREAD_STACK_DEFINE(hl7800_workq_stack, |
| CONFIG_MODEM_HL7800_RX_WORKQ_STACK_SIZE); |
| static struct k_work_q hl7800_workq; |
| #define WORKQ_PRIORITY K_PRIO_COOP(7) |
| |
| static const char EOF_PATTERN[] = "--EOF--Pattern--"; |
| static const char CONNECT_STRING[] = "CONNECT"; |
| static const char OK_STRING[] = "OK"; |
| |
| struct hl7800_socket { |
| struct net_context *context; |
| sa_family_t family; |
| enum net_sock_type type; |
| enum net_ip_protocol ip_proto; |
| struct sockaddr src; |
| struct sockaddr dst; |
| |
| bool created; |
| bool reconfig; |
| int socket_id; |
| int rx_size; |
| int error; |
| enum socket_state state; |
| |
| /** semaphore */ |
| struct k_sem sock_send_sem; |
| |
| /** socket callbacks */ |
| struct k_work recv_cb_work; |
| struct k_work rx_data_work; |
| struct k_work_delayable notif_work; |
| net_context_recv_cb_t recv_cb; |
| struct net_pkt *recv_pkt; |
| void *recv_user_data; |
| }; |
| |
| struct stale_socket { |
| int reserved; /* first word of queue data item reserved for the kernel */ |
| enum net_sock_type type; |
| uint8_t id; |
| bool allocated; |
| }; |
| |
| #define NO_ID_RESP_CMD_MAX_LENGTH 32 |
| |
| struct hl7800_config { |
| struct gpio_dt_spec gpio[MAX_MDM_CONTROL_PINS]; |
| }; |
| |
| struct hl7800_iface_ctx { |
| struct net_if *iface; |
| uint8_t mac_addr[6]; |
| struct in_addr ipv4Addr, subnet, gateway, dns_v4; |
| #ifdef CONFIG_NET_IPV6 |
| struct in6_addr ipv6Addr, dns_v6; |
| char dns_v6_string[HL7800_IPV6_ADDR_LEN]; |
| #endif |
| bool restarting; |
| bool initialized; |
| bool wait_for_KSUP; |
| uint32_t wait_for_KSUP_tries; |
| bool reconfig_IP_connection; |
| char dns_v4_string[NET_IPV4_ADDR_LEN]; |
| char no_id_resp_cmd[NO_ID_RESP_CMD_MAX_LENGTH]; |
| bool search_no_id_resp; |
| |
| /* GPIO PORT devices */ |
| struct gpio_callback mdm_vgpio_cb; |
| struct gpio_callback mdm_uart_dsr_cb; |
| struct gpio_callback mdm_gpio6_cb; |
| struct gpio_callback mdm_uart_cts_cb; |
| int vgpio_state; |
| int dsr_state; |
| int gpio6_state; |
| int cts_state; |
| |
| /* RX specific attributes */ |
| struct mdm_receiver_context mdm_ctx; |
| |
| /* socket data */ |
| struct hl7800_socket sockets[MDM_MAX_SOCKETS]; |
| int last_socket_id; |
| int last_error; |
| struct stale_socket stale_sockets[MDM_MAX_SOCKETS]; |
| struct k_queue stale_socket_queue; |
| |
| /* semaphores */ |
| struct k_sem response_sem; |
| struct k_sem mdm_awake; |
| |
| /* work */ |
| struct k_work_delayable rssi_query_work; |
| struct k_work_delayable iface_status_work; |
| struct k_work_delayable dns_work; |
| struct k_work mdm_vgpio_work; |
| struct k_work_delayable mdm_reset_work; |
| struct k_work_delayable allow_sleep_work; |
| struct k_work_delayable delete_untracked_socket_work; |
| struct k_work mdm_pwr_off_work; |
| |
| #ifdef CONFIG_MODEM_HL7800_FW_UPDATE |
| /* firmware update */ |
| enum mdm_hl7800_fota_state fw_update_state; |
| struct fs_file_t fw_update_file; |
| struct xmodem_packet fw_packet; |
| uint32_t fw_packet_count; |
| int file_pos; |
| struct k_work finish_fw_update_work; |
| bool fw_updated; |
| #endif |
| |
| /* modem info */ |
| /* NOTE: make sure length is +1 for null char */ |
| char mdm_manufacturer[MDM_MANUFACTURER_LENGTH]; |
| char mdm_model[MDM_MODEL_LENGTH]; |
| char mdm_revision[MDM_HL7800_REVISION_MAX_SIZE]; |
| char mdm_imei[MDM_HL7800_IMEI_SIZE]; |
| char mdm_sn[MDM_HL7800_SERIAL_NUMBER_SIZE]; |
| char mdm_network_status[MDM_NETWORK_STATUS_LENGTH]; |
| char mdm_iccid[MDM_HL7800_ICCID_MAX_SIZE]; |
| enum mdm_hl7800_startup_state mdm_startup_state; |
| enum mdm_hl7800_radio_mode mdm_rat; |
| char mdm_active_bands_string[MDM_HL7800_LTE_BAND_STR_SIZE]; |
| char mdm_bands_string[MDM_HL7800_LTE_BAND_STR_SIZE]; |
| char mdm_imsi[MDM_HL7800_IMSI_MAX_STR_SIZE]; |
| int mdm_rssi; |
| uint16_t mdm_bands_top; |
| uint32_t mdm_bands_middle; |
| uint32_t mdm_bands_bottom; |
| int32_t mdm_sinr; |
| bool mdm_echo_is_on; |
| struct mdm_hl7800_apn mdm_apn; |
| bool mdm_startup_reporting_on; |
| int device_services_ind; |
| bool new_rat_cmd_support; |
| uint8_t operator_index; |
| enum mdm_hl7800_functionality functionality; |
| char mdm_pdp_addr_fam[MDM_ADDR_FAM_MAX_LEN]; |
| |
| /* modem state */ |
| bool allow_sleep; |
| bool uart_on; |
| enum mdm_hl7800_sleep desired_sleep_level; |
| enum mdm_hl7800_sleep sleep_state; |
| enum hl7800_lpm low_power_mode; |
| enum mdm_hl7800_network_state network_state; |
| bool network_dropped; |
| bool dns_ready; |
| enum net_operator_status operator_status; |
| struct tm local_time; |
| int32_t local_time_offset; |
| bool local_time_valid; |
| bool configured; |
| bool off; |
| void (*wake_up_callback)(int state); |
| void (*gpio6_callback)(int state); |
| void (*cts_callback)(int state); |
| |
| #ifdef CONFIG_MODEM_HL7800_GPS |
| struct k_work_delayable gps_work; |
| uint32_t gps_query_location_rate_seconds; |
| #endif |
| }; |
| |
| struct cmd_handler { |
| const char *cmd; |
| uint16_t cmd_len; |
| bool (*func)(struct net_buf **buf, uint16_t len); |
| }; |
| |
| static sys_slist_t hl7800_event_callback_list = |
| SYS_SLIST_STATIC_INIT(&hl7800_event_callback_list); |
| |
| const static struct hl7800_config hl7800_cfg = { |
| .gpio = { |
| GPIO_DT_SPEC_INST_GET(0, mdm_reset_gpios), |
| GPIO_DT_SPEC_INST_GET(0, mdm_wake_gpios), |
| GPIO_DT_SPEC_INST_GET(0, mdm_pwr_on_gpios), |
| GPIO_DT_SPEC_INST_GET(0, mdm_fast_shutd_gpios), |
| GPIO_DT_SPEC_INST_GET(0, mdm_vgpio_gpios), |
| GPIO_DT_SPEC_INST_GET(0, mdm_uart_dsr_gpios), |
| GPIO_DT_SPEC_INST_GET(0, mdm_uart_cts_gpios), |
| GPIO_DT_SPEC_INST_GET(0, mdm_gpio6_gpios), |
| }, |
| }; |
| static struct hl7800_iface_ctx 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); |
| static void set_network_state(enum mdm_hl7800_network_state state); |
| static void set_startup_state(enum mdm_hl7800_startup_state state); |
| static void set_sleep_state(enum mdm_hl7800_sleep state); |
| static void generate_network_state_event(void); |
| static void generate_startup_state_event(void); |
| static void generate_sleep_state_event(void); |
| static int modem_boot_handler(char *reason); |
| static void mdm_vgpio_work_cb(struct k_work *item); |
| static void mdm_reset_work_callback(struct k_work *item); |
| static void mdm_power_off_work_callback(struct k_work *item); |
| static int write_apn(char *access_point_name); |
| #ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE |
| static void mark_sockets_for_reconfig(void); |
| #endif |
| static void hl7800_build_mac(struct hl7800_iface_ctx *ictx); |
| static void rssi_query(void); |
| |
| #ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE |
| static void initialize_sleep_level(void); |
| static int set_sleep_level(void); |
| #endif |
| |
| #ifdef CONFIG_MODEM_HL7800_FW_UPDATE |
| static char *get_fota_state_string(enum mdm_hl7800_fota_state state); |
| static void set_fota_state(enum mdm_hl7800_fota_state state); |
| static void generate_fota_state_event(void); |
| static void generate_fota_count_event(void); |
| #endif |
| |
| static struct stale_socket *alloc_stale_socket(void) |
| { |
| struct stale_socket *sock = NULL; |
| |
| for (int i = 0; i < MDM_MAX_SOCKETS; i++) { |
| if (!ictx.stale_sockets[i].allocated) { |
| sock = &ictx.stale_sockets[i]; |
| sock->allocated = true; |
| break; |
| } |
| } |
| |
| return sock; |
| } |
| |
| static void free_stale_socket(struct stale_socket *sock) |
| { |
| if (sock != NULL) { |
| sock->allocated = false; |
| } |
| } |
| |
| static int queue_stale_socket(enum net_sock_type type, uint8_t id) |
| { |
| int ret = 0; |
| struct stale_socket *sock = NULL; |
| |
| sock = alloc_stale_socket(); |
| if (sock != NULL) { |
| sock->type = type; |
| sock->id = id; |
| k_queue_append(&ictx.stale_socket_queue, (void *)sock); |
| } else { |
| LOG_ERR("Could not alloc stale socket"); |
| ret = -ENOMEM; |
| } |
| |
| return ret; |
| } |
| |
| static struct stale_socket *dequeue_stale_socket(void) |
| { |
| struct stale_socket *sock = NULL; |
| |
| sock = (struct stale_socket *)k_queue_get(&ictx.stale_socket_queue, K_NO_WAIT); |
| |
| return sock; |
| } |
| |
| static bool convert_time_string_to_struct(struct tm *tm, int32_t *offset, |
| char *time_string); |
| static int modem_reset_and_configure(void); |
| |
| static int read_pin(int default_state, const struct gpio_dt_spec *spec) |
| { |
| int state = gpio_pin_get_raw(spec->port, spec->pin); |
| |
| if (state < 0) { |
| LOG_ERR("Unable to read port: %s pin: %d status: %d", |
| spec->port->name, spec->pin, state); |
| state = default_state; |
| } |
| |
| return state; |
| } |
| |
| #ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE |
| static bool is_cmd_ready(void) |
| { |
| ictx.vgpio_state = read_pin(0, &hl7800_cfg.gpio[MDM_VGPIO]); |
| |
| ictx.gpio6_state = read_pin(0, &hl7800_cfg.gpio[MDM_GPIO6]); |
| |
| ictx.cts_state = read_pin(1, &hl7800_cfg.gpio[MDM_UART_CTS]); |
| |
| 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_AWAKE) && |
| !ictx.allow_sleep && !ictx.wait_for_KSUP) { |
| PRINT_AWAKE_MSG; |
| set_sleep_state(HL7800_SLEEP_AWAKE); |
| k_sem_give(&ictx.mdm_awake); |
| } else if (!is_cmd_rdy && ictx.sleep_state == HL7800_SLEEP_AWAKE && |
| ictx.allow_sleep) { |
| PRINT_NOT_AWAKE_MSG; |
| |
| if (ictx.desired_sleep_level == HL7800_SLEEP_HIBERNATE || |
| ictx.desired_sleep_level == HL7800_SLEEP_LITE_HIBERNATE) { |
| /* If the device is sleeping (not ready to receive commands) |
| * then the device may send +KSUP when waking up. |
| * We should wait for it. |
| */ |
| ictx.wait_for_KSUP = true; |
| ictx.wait_for_KSUP_tries = 0; |
| |
| set_sleep_state(ictx.desired_sleep_level); |
| |
| } else if (ictx.desired_sleep_level == HL7800_SLEEP_SLEEP) { |
| set_sleep_state(HL7800_SLEEP_SLEEP); |
| } |
| } |
| #endif |
| } |
| |
| static int hl7800_RX_lock(void) |
| { |
| HL7800_RX_LOCK_DBG_LOG("Locking RX [%p]...", k_current_get()); |
| int rc = k_sem_take(&hl7800_RX_lock_sem, K_FOREVER); |
| |
| if (rc != 0) { |
| LOG_ERR("Unable to lock hl7800 (%d)", rc); |
| } else { |
| HL7800_RX_LOCK_DBG_LOG("Locked RX [%p]", k_current_get()); |
| } |
| |
| return rc; |
| } |
| |
| static void hl7800_RX_unlock(void) |
| { |
| HL7800_RX_LOCK_DBG_LOG("UNLocking RX [%p]...", k_current_get()); |
| k_sem_give(&hl7800_RX_lock_sem); |
| HL7800_RX_LOCK_DBG_LOG("UNLocked RX [%p]", k_current_get()); |
| } |
| |
| static bool hl7800_RX_locked(void) |
| { |
| if (k_sem_count_get(&hl7800_RX_lock_sem) == 0) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| static int hl7800_TX_lock(void) |
| { |
| HL7800_TX_LOCK_DBG_LOG("Locking TX [%p]...", k_current_get()); |
| int rc = k_sem_take(&hl7800_TX_lock_sem, K_FOREVER); |
| |
| if (rc != 0) { |
| LOG_ERR("Unable to lock hl7800 (%d)", rc); |
| } else { |
| HL7800_TX_LOCK_DBG_LOG("Locked TX [%p]", k_current_get()); |
| } |
| |
| return rc; |
| } |
| |
| static void hl7800_TX_unlock(void) |
| { |
| HL7800_TX_LOCK_DBG_LOG("UNLocking TX [%p]...", k_current_get()); |
| k_sem_give(&hl7800_TX_lock_sem); |
| HL7800_TX_LOCK_DBG_LOG("UNLocked TX [%p]", k_current_get()); |
| } |
| |
| static bool hl7800_TX_locked(void) |
| { |
| if (k_sem_count_get(&hl7800_TX_lock_sem) == 0) { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| static void hl7800_lock(void) |
| { |
| hl7800_TX_lock(); |
| hl7800_RX_lock(); |
| } |
| |
| static void hl7800_unlock(void) |
| { |
| hl7800_RX_unlock(); |
| hl7800_TX_unlock(); |
| } |
| |
| static struct hl7800_socket *socket_get(void) |
| { |
| int i; |
| struct hl7800_socket *sock = NULL; |
| |
| for (i = 0; i < MDM_MAX_SOCKETS; i++) { |
| if (!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 = 0; |
| sock->rx_size = 0; |
| sock->state = SOCK_IDLE; |
| (void)memset(&sock->src, 0, sizeof(struct sockaddr)); |
| (void)memset(&sock->dst, 0, sizeof(struct sockaddr)); |
| } |
| |
| char *hl7800_sprint_ip_addr(const struct sockaddr *addr) |
| { |
| static char buf[NET_IPV6_ADDR_LEN]; |
| |
| #if defined(CONFIG_NET_IPV6) |
| if (addr->sa_family == AF_INET6) { |
| return net_addr_ntop(AF_INET6, &net_sin6(addr)->sin6_addr, buf, |
| sizeof(buf)); |
| } else |
| #endif |
| #if defined(CONFIG_NET_IPV4) |
| if (addr->sa_family == AF_INET) { |
| return net_addr_ntop(AF_INET, &net_sin(addr)->sin_addr, buf, |
| sizeof(buf)); |
| } else |
| #endif |
| { |
| LOG_ERR("Unknown IP address family:%d", addr->sa_family); |
| return NULL; |
| } |
| } |
| |
| void mdm_hl7800_register_wake_test_point_callback(void (*func)(int state)) |
| { |
| ictx.wake_up_callback = func; |
| } |
| |
| void mdm_hl7800_register_gpio6_callback(void (*func)(int state)) |
| { |
| ictx.gpio6_callback = func; |
| } |
| |
| void mdm_hl7800_register_cts_callback(void (*func)(int state)) |
| { |
| ictx.cts_callback = func; |
| } |
| |
| static void modem_assert_wake(bool assert) |
| { |
| int state; |
| |
| if (assert) { |
| HL7800_IO_DBG_LOG("MDM_WAKE_PIN -> ASSERTED"); |
| state = 1; |
| } else { |
| HL7800_IO_DBG_LOG("MDM_WAKE_PIN -> NOT_ASSERTED"); |
| state = 0; |
| } |
| |
| gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_WAKE], state); |
| |
| if (ictx.wake_up_callback != NULL) { |
| ictx.wake_up_callback(state); |
| } |
| } |
| |
| static void modem_assert_pwr_on(bool assert) |
| { |
| if (assert) { |
| HL7800_IO_DBG_LOG("MDM_PWR_ON -> ASSERTED"); |
| gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_PWR_ON], 1); |
| } else { |
| HL7800_IO_DBG_LOG("MDM_PWR_ON -> NOT_ASSERTED"); |
| gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_PWR_ON], 0); |
| } |
| } |
| |
| static void modem_assert_fast_shutd(bool assert) |
| { |
| if (assert) { |
| HL7800_IO_DBG_LOG("MDM_FAST_SHUTD -> ASSERTED"); |
| gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_FAST_SHUTD], 1); |
| } else { |
| HL7800_IO_DBG_LOG("MDM_FAST_SHUTD -> NOT_ASSERTED"); |
| gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_FAST_SHUTD], 0); |
| } |
| } |
| |
| static void allow_sleep_work_callback(struct k_work *item) |
| { |
| ARG_UNUSED(item); |
| LOG_DBG("Allow sleep"); |
| ictx.allow_sleep = true; |
| set_sleep_state(ictx.desired_sleep_level); |
| modem_assert_wake(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, |
| K_MSEC(CONFIG_MODEM_HL7800_ALLOW_SLEEP_DELAY_MS)); |
| } else { |
| LOG_DBG("Keep awake"); |
| k_work_cancel_delayable(&ictx.allow_sleep_work); |
| ictx.allow_sleep = false; |
| modem_assert_wake(true); |
| } |
| #endif |
| } |
| |
| static void event_handler(enum mdm_hl7800_event event, void *event_data) |
| { |
| sys_snode_t *node; |
| struct mdm_hl7800_callback_agent *agent; |
| |
| k_sem_take(&cb_lock, K_FOREVER); |
| SYS_SLIST_FOR_EACH_NODE(&hl7800_event_callback_list, node) { |
| agent = CONTAINER_OF(node, struct mdm_hl7800_callback_agent, node); |
| if (agent->event_callback != NULL) { |
| agent->event_callback(event, event_data); |
| } |
| } |
| k_sem_give(&cb_lock); |
| } |
| |
| void mdm_hl7800_get_signal_quality(int *rsrp, int *sinr) |
| { |
| if (CONFIG_MODEM_HL7800_RSSI_RATE_SECONDS == 0) { |
| rssi_query(); |
| } |
| |
| *rsrp = ictx.mdm_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 { |
| sock->error = 0; |
| 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]", (char *)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) { |
| if (sock) { |
| ret = sock->error; |
| } else { |
| 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 modem is in sleep mode (not hibernate), |
| * then it can respond in ~10 ms. |
| */ |
| if (ictx.desired_sleep_level == HL7800_SLEEP_SLEEP) { |
| k_sleep(MDM_WAKE_TO_CHECK_CTS_DELAY_MS); |
| } |
| |
| if (!is_cmd_ready()) { |
| LOG_DBG("Waiting to wakeup"); |
| ret = k_sem_take(&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: |
| |
| allow_sleep(true); |
| hl7800_unlock(); |
| |
| /* Changing the RAT causes the modem to reset. |
| * A reset and reconfigure ensures the modem configuration and |
| * state are valid. |
| */ |
| if (ret >= 0) { |
| k_work_reschedule_for_queue(&hl7800_workq, &ictx.mdm_reset_work, K_NO_WAIT); |
| } |
| |
| return ret; |
| } |
| |
| 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; |
| } |
| |
| int32_t mdm_hl7800_get_operator_index(void) |
| { |
| int ret; |
| |
| hl7800_lock(); |
| wakeup_hl7800(); |
| ictx.last_socket_id = 0; |
| ret = send_at_cmd(NULL, "AT+KCARRIERCFG?", MDM_CMD_SEND_TIMEOUT, 0, |
| false); |
| allow_sleep(true); |
| hl7800_unlock(); |
| if (ret < 0) { |
| return ret; |
| } else { |
| return ictx.operator_index; |
| } |
| } |
| |
| int32_t mdm_hl7800_get_functionality(void) |
| { |
| int ret; |
| |
| hl7800_lock(); |
| wakeup_hl7800(); |
| ictx.last_socket_id = 0; |
| ret = send_at_cmd(NULL, "AT+CFUN?", MDM_CMD_SEND_TIMEOUT, 0, false); |
| allow_sleep(true); |
| hl7800_unlock(); |
| |
| if (ret < 0) { |
| return ret; |
| } else { |
| return ictx.functionality; |
| } |
| } |
| |
| int32_t mdm_hl7800_set_functionality(enum mdm_hl7800_functionality mode) |
| { |
| int ret; |
| char buf[sizeof("AT+CFUN=###,0")] = { 0 }; |
| |
| hl7800_lock(); |
| wakeup_hl7800(); |
| snprintk(buf, sizeof(buf), "AT+CFUN=%u,0", mode); |
| ictx.last_socket_id = 0; |
| ret = send_at_cmd(NULL, buf, MDM_CMD_SEND_TIMEOUT, |
| MDM_DEFAULT_AT_CMD_RETRIES, false); |
| allow_sleep(true); |
| hl7800_unlock(); |
| |
| return ret; |
| } |
| |
| #ifdef CONFIG_MODEM_HL7800_GPS |
| int32_t mdm_hl7800_set_gps_rate(uint32_t rate) |
| { |
| int ret = -1; |
| |
| hl7800_lock(); |
| wakeup_hl7800(); |
| ictx.gps_query_location_rate_seconds = rate; |
| |
| /* Stopping first allows changing the rate between two non-zero values. |
| * Ignore error if GNSS isn't running. |
| */ |
| SEND_AT_CMD_IGNORE_ERROR("AT+GNSSSTOP"); |
| |
| if (rate == 0) { |
| SEND_AT_CMD_EXPECT_OK("AT+CFUN=1,0"); |
| } else { |
| /* Navigation doesn't work when LTE is on. */ |
| SEND_AT_CMD_EXPECT_OK("AT+CFUN=4,0"); |
| |
| SEND_AT_CMD_EXPECT_OK("AT+GNSSCONF=1,1"); |
| |
| if (IS_ENABLED(CONFIG_MODEM_HL7800_USE_GLONASS)) { |
| SEND_AT_CMD_EXPECT_OK("AT+GNSSCONF=10,1"); |
| } |
| /* Enable all NMEA sentences */ |
| SEND_AT_CMD_EXPECT_OK("AT+GNSSNMEA=0,1000,0,1FF"); |
| /* Enable GPS */ |
| SEND_AT_CMD_EXPECT_OK("AT+GNSSSTART=0"); |
| } |
| |
| error: |
| if (rate && ret == 0) { |
| k_work_reschedule_for_queue(&hl7800_workq, &ictx.gps_work, |
| K_SECONDS(ictx.gps_query_location_rate_seconds)); |
| } else { |
| k_work_cancel_delayable(&ictx.gps_work); |
| } |
| LOG_DBG("GPS status: %d rate: %u", ret, rate); |
| |
| allow_sleep(true); |
| hl7800_unlock(); |
| return ret; |
| } |
| #endif /* CONFIG_MODEM_HL7800_GPS */ |
| |
| #ifdef CONFIG_MODEM_HL7800_POLTE |
| int32_t mdm_hl7800_polte_register(void) |
| { |
| int ret = -1; |
| |
| hl7800_lock(); |
| wakeup_hl7800(); |
| /* register for events */ |
| SEND_AT_CMD_EXPECT_OK("AT%POLTEEV=\"REGISTER\",1"); |
| SEND_AT_CMD_EXPECT_OK("AT%POLTEEV=\"LOCATION\",1"); |
| /* register with polte.io */ |
| SEND_AT_CMD_EXPECT_OK("AT%POLTECMD=\"REGISTER\""); |
| error: |
| LOG_DBG("PoLTE register status: %d", ret); |
| allow_sleep(true); |
| hl7800_unlock(); |
| return ret; |
| } |
| |
| int32_t mdm_hl7800_polte_enable(char *user, char *password) |
| { |
| int ret = -1; |
| char buf[sizeof(MDM_HL7800_SET_POLTE_USER_AND_PASSWORD_FMT_STR) + |
| MDM_HL7800_MAX_POLTE_USER_ID_SIZE + MDM_HL7800_MAX_POLTE_PASSWORD_SIZE] = { 0 }; |
| |
| hl7800_lock(); |
| wakeup_hl7800(); |
| |
| /* register for events */ |
| SEND_AT_CMD_EXPECT_OK("AT%POLTEEV=\"REGISTER\",1"); |
| SEND_AT_CMD_EXPECT_OK("AT%POLTEEV=\"LOCATION\",1"); |
| /* restore user and password (not saved in NV by modem) */ |
| snprintk(buf, sizeof(buf), MDM_HL7800_SET_POLTE_USER_AND_PASSWORD_FMT_STR, user, password); |
| ret = send_at_cmd(NULL, buf, MDM_CMD_SEND_TIMEOUT, MDM_DEFAULT_AT_CMD_RETRIES, false); |
| |
| error: |
| LOG_DBG("PoLTE register status: %d", ret); |
| allow_sleep(true); |
| hl7800_unlock(); |
| return ret; |
| } |
| |
| int32_t mdm_hl7800_polte_locate(void) |
| { |
| int ret = -1; |
| |
| hl7800_lock(); |
| wakeup_hl7800(); |
| SEND_AT_CMD_EXPECT_OK("AT%POLTECMD=\"LOCATE\",2,1"); |
| error: |
| LOG_DBG("PoLTE locate status: %d", ret); |
| allow_sleep(true); |
| hl7800_unlock(); |
| return ret; |
| } |
| |
| #endif /* CONFIG_MODEM_HL7800_POLTE */ |
| |
| /** |
| * @brief Perform a site survey. |
| * |
| */ |
| int32_t mdm_hl7800_perform_site_survey(void) |
| { |
| int ret; |
| |
| hl7800_lock(); |
| wakeup_hl7800(); |
| ret = send_at_cmd(NULL, "at%meas=\"97\"", MDM_CMD_SEND_TIMEOUT, 0, false); |
| allow_sleep(true); |
| hl7800_unlock(); |
| return ret; |
| } |
| |
| void mdm_hl7800_generate_status_events(void) |
| { |
| hl7800_lock(); |
| generate_startup_state_event(); |
| generate_network_state_event(); |
| generate_sleep_state_event(); |
| #ifdef CONFIG_MODEM_HL7800_FW_UPDATE |
| generate_fota_state_event(); |
| #endif |
| event_handler(HL7800_EVENT_RSSI, &ictx.mdm_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(); |
| } |
| |
| uint32_t mdm_hl7800_log_filter_set(uint32_t level) |
| { |
| uint32_t new_log_level = 0; |
| |
| #ifdef CONFIG_LOG_RUNTIME_FILTERING |
| new_log_level = |
| log_filter_set(NULL, Z_LOG_LOCAL_DOMAIN_ID, |
| log_source_id_get(STRINGIFY(LOG_MODULE_NAME)), |
| level); |
| #endif |
| |
| return new_log_level; |
| } |
| |
| static int send_data(struct hl7800_socket *sock, struct net_pkt *pkt) |
| { |
| int ret; |
| struct net_buf *frag; |
| char dst_addr[NET_IPV6_ADDR_LEN]; |
| char buf[sizeof("AT+KUDPSND=##,\"" IPV6_ADDR_FORMAT "\",#####,####")]; |
| size_t send_len, actual_send_len; |
| |
| send_len = 0, actual_send_len = 0; |
| |
| if (!sock) { |
| return -EINVAL; |
| } |
| |
| sock->error = 0; |
| sock->state = SOCK_TX; |
| |
| frag = pkt->frags; |
| send_len = net_buf_frags_len(frag); |
| /* start sending data */ |
| k_sem_reset(&sock->sock_send_sem); |
| if (sock->type == SOCK_STREAM) { |
| snprintk(buf, sizeof(buf), "AT+KTCPSND=%d,%zu", sock->socket_id, |
| send_len); |
| } else { |
| if (!net_addr_ntop(sock->family, &net_sin(&sock->dst)->sin_addr, |
| dst_addr, sizeof(dst_addr))) { |
| LOG_ERR("Invalid dst addr"); |
| return -EINVAL; |
| } |
| snprintk(buf, sizeof(buf), "AT+KUDPSND=%d,\"%s\",%u,%zu", |
| sock->socket_id, dst_addr, |
| net_sin(&sock->dst)->sin_port, send_len); |
| } |
| send_at_cmd(sock, buf, K_NO_WAIT, 0, false); |
| |
| /* wait for CONNECT or error */ |
| ret = k_sem_take(&sock->sock_send_sem, MDM_IP_SEND_RX_TIMEOUT); |
| if (ret) { |
| LOG_ERR("Err waiting for CONNECT (%d)", ret); |
| goto done; |
| } |
| /* check for error */ |
| if (sock->error != 0) { |
| ret = sock->error; |
| LOG_ERR("AT+K**PSND (%d)", ret); |
| goto done; |
| } |
| |
| /* Loop through packet data and send */ |
| while (frag) { |
| actual_send_len += frag->len; |
| mdm_receiver_send(&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_IP_SEND_RX_TIMEOUT); |
| if (ret == 0) { |
| ret = sock->error; |
| } else if (ret == -EAGAIN) { |
| ret = -ETIMEDOUT; |
| } |
| done: |
| if (sock->type == SOCK_STREAM) { |
| if (sock->error == 0) { |
| sock->state = SOCK_CONNECTED; |
| } |
| } else { |
| sock->state = SOCK_IDLE; |
| } |
| |
| return ret; |
| } |
| |
| /*** NET_BUF HELPERS ***/ |
| |
| static bool is_crlf(uint8_t c) |
| { |
| if (c == '\n' || c == '\r') { |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| static void net_buf_skipcrlf(struct net_buf **buf) |
| { |
| /* chop off any /n or /r */ |
| while (*buf && is_crlf(*(*buf)->data)) { |
| net_buf_pull_u8(*buf); |
| if (!(*buf)->len) { |
| *buf = net_buf_frag_del(NULL, *buf); |
| } |
| } |
| } |
| |
| static uint16_t net_buf_findcrlf(struct net_buf *buf, struct net_buf **frag) |
| { |
| uint16_t len = 0U, pos = 0U; |
| |
| while (buf && !is_crlf(*(buf->data + pos))) { |
| if (pos + 1 >= buf->len) { |
| len += buf->len; |
| buf = buf->frags; |
| pos = 0U; |
| } else { |
| pos++; |
| } |
| } |
| |
| if (buf && is_crlf(*(buf->data + pos))) { |
| len += pos; |
| *frag = buf; |
| return len; |
| } |
| |
| return 0; |
| } |
| |
| static uint8_t net_buf_get_u8(struct net_buf **buf) |
| { |
| uint8_t val = net_buf_pull_u8(*buf); |
| |
| if (!(*buf)->len) { |
| *buf = net_buf_frag_del(NULL, *buf); |
| } |
| return val; |
| } |
| |
| static uint32_t net_buf_remove(struct net_buf **buf, uint32_t len) |
| { |
| uint32_t to_remove; |
| uint32_t removed = 0; |
| |
| while (*buf && len > 0) { |
| to_remove = (*buf)->len; |
| if (to_remove > len) { |
| to_remove = len; |
| } |
| net_buf_pull(*buf, to_remove); |
| removed += to_remove; |
| len -= to_remove; |
| if (!(*buf)->len) { |
| *buf = net_buf_frag_del(NULL, *buf); |
| } |
| } |
| return removed; |
| } |
| |
| /*** UDP / TCP Helper Function ***/ |
| |
| /* Setup IP header data to be used by some network applications. |
| * While much is dummy data, some fields such as dst, port and family are |
| * important. |
| * Return the IP + protocol header length. |
| */ |
| static int pkt_setup_ip_data(struct net_pkt *pkt, struct hl7800_socket *sock) |
| { |
| int hdr_len = 0; |
| uint16_t src_port = 0U, dst_port = 0U; |
| #if defined(CONFIG_NET_TCP) |
| struct net_tcp_hdr *tcp; |
| #endif |
| |
| #if defined(CONFIG_NET_IPV6) |
| if (net_pkt_family(pkt) == AF_INET6) { |
| if (net_ipv6_create( |
| pkt, |
| &((struct sockaddr_in6 *)&sock->dst)->sin6_addr, |
| &((struct sockaddr_in6 *)&sock->src)->sin6_addr)) { |
| return -1; |
| } |
| src_port = ntohs(net_sin6(&sock->src)->sin6_port); |
| dst_port = ntohs(net_sin6(&sock->dst)->sin6_port); |
| |
| hdr_len = sizeof(struct net_ipv6_hdr); |
| } |
| #endif |
| #if defined(CONFIG_NET_IPV4) |
| if (net_pkt_family(pkt) == AF_INET) { |
| if (net_ipv4_create( |
| pkt, &((struct sockaddr_in *)&sock->dst)->sin_addr, |
| &((struct sockaddr_in *)&sock->src)->sin_addr)) { |
| return -1; |
| } |
| src_port = ntohs(net_sin(&sock->src)->sin_port); |
| dst_port = ntohs(net_sin(&sock->dst)->sin_port); |
| |
| hdr_len = sizeof(struct net_ipv4_hdr); |
| } |
| #endif |
| |
| #if defined(CONFIG_NET_UDP) |
| if (sock->ip_proto == IPPROTO_UDP) { |
| if (net_udp_create(pkt, dst_port, src_port)) { |
| return -1; |
| } |
| |
| hdr_len += NET_UDPH_LEN; |
| } |
| #endif |
| #if defined(CONFIG_NET_TCP) |
| if (sock->ip_proto == IPPROTO_TCP) { |
| NET_PKT_DATA_ACCESS_DEFINE(tcp_access, struct net_tcp_hdr); |
| |
| tcp = (struct net_tcp_hdr *)net_pkt_get_data(pkt, &tcp_access); |
| if (!tcp) { |
| return -1; |
| } |
| |
| (void)memset(tcp, 0, NET_TCPH_LEN); |
| |
| /* Setup TCP header */ |
| tcp->src_port = dst_port; |
| tcp->dst_port = src_port; |
| |
| if (net_pkt_set_data(pkt, &tcp_access)) { |
| return -1; |
| } |
| |
| hdr_len += NET_TCPH_LEN; |
| } |
| #endif /* CONFIG_NET_TCP */ |
| |
| return hdr_len; |
| } |
| |
| /*** MODEM RESPONSE HANDLERS ***/ |
| |
| static uint32_t wait_for_modem_data(struct net_buf **buf, uint32_t current_len, |
| uint32_t expected_len) |
| { |
| uint32_t waitForDataTries = 0; |
| |
| while ((current_len < expected_len) && |
| (waitForDataTries < MDM_WAIT_FOR_DATA_RETRIES)) { |
| LOG_DBG("cur:%d, exp:%d", current_len, expected_len); |
| k_sleep(MDM_WAIT_FOR_DATA_TIME); |
| current_len += hl7800_read_rx(buf); |
| waitForDataTries++; |
| } |
| |
| return current_len; |
| } |
| |
| static uint32_t wait_for_modem_data_and_newline(struct net_buf **buf, |
| uint32_t current_len, |
| uint32_t expected_len) |
| { |
| return wait_for_modem_data(buf, current_len, |
| (expected_len + strlen("\r\n"))); |
| } |
| |
| /* Handler: AT+CGMI */ |
| static bool on_cmd_atcmdinfo_manufacturer(struct net_buf **buf, uint16_t len) |
| { |
| struct net_buf *frag = NULL; |
| size_t out_len; |
| int len_no_null = MDM_MANUFACTURER_LENGTH - 1; |
| |
| /* make sure revision data is received |
| * waiting for: Sierra Wireless\r\n |
| */ |
| wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), |
| MDM_MANUFACTURER_LENGTH); |
| |
| frag = NULL; |
| len = net_buf_findcrlf(*buf, &frag); |
| if (!frag) { |
| LOG_ERR("Unable to find mfg end"); |
| goto done; |
| } |
| if (len < len_no_null) { |
| LOG_WRN("mfg too short (len:%d)", len); |
| } else if (len > len_no_null) { |
| LOG_WRN("mfg too long (len:%d)", len); |
| len = MDM_MANUFACTURER_LENGTH; |
| } |
| |
| out_len = net_buf_linearize(ictx.mdm_manufacturer, |
| sizeof(ictx.mdm_manufacturer) - 1, *buf, 0, |
| len); |
| ictx.mdm_manufacturer[out_len] = 0; |
| LOG_INF("Manufacturer: %s", 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", 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", 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", ictx.mdm_imei); |
| done: |
| return true; |
| } |
| |
| /* Handler: +CCID: <ICCID>,<EID> |
| * NOTE: EID will only be present for eSIM |
| */ |
| static bool on_cmd_atcmdinfo_iccid(struct net_buf **buf, uint16_t len) |
| { |
| int ret; |
| char value[MDM_CCID_RESP_MAX_SIZE]; |
| char *delim; |
| size_t out_len; |
| |
| out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len); |
| value[out_len] = 0; |
| |
| LOG_DBG("+CCID: %s", value); |
| |
| if (len > MDM_HL7800_ICCID_MAX_STRLEN) { |
| delim = strchr(value, ','); |
| if (!delim) { |
| LOG_ERR("Could not process +CCID"); |
| return true; |
| } |
| /* Replace ',' with null so value contains two null terminated strings */ |
| *delim = 0; |
| LOG_INF("EID: %s", delim + 1); |
| } |
| |
| ret = snprintk(ictx.mdm_iccid, sizeof(ictx.mdm_iccid), "%s", value); |
| if (ret > MDM_HL7800_ICCID_MAX_STRLEN) { |
| LOG_WRN("ICCID too long: %d", ret); |
| } |
| |
| LOG_INF("ICCID: %s", ictx.mdm_iccid); |
| |
| return true; |
| } |
| |
| static bool on_cmd_atcmdinfo_imsi(struct net_buf **buf, uint16_t len) |
| { |
| struct net_buf *frag = NULL; |
| size_t out_len; |
| |
| /* The handler for the IMSI is based on the command. |
| * waiting for: <IMSI>\r\n |
| */ |
| wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), |
| MDM_HL7800_IMSI_MIN_STR_SIZE); |
| |
| frag = NULL; |
| len = net_buf_findcrlf(*buf, &frag); |
| if (!frag) { |
| LOG_ERR("Unable to find IMSI end"); |
| goto done; |
| } |
| if (len > MDM_HL7800_IMSI_MAX_STRLEN) { |
| LOG_WRN("IMSI too long (len:%d)", len); |
| len = MDM_HL7800_IMSI_MAX_STRLEN; |
| } |
| |
| out_len = net_buf_linearize(ictx.mdm_imsi, MDM_HL7800_IMSI_MAX_STR_SIZE, |
| *buf, 0, len); |
| ictx.mdm_imsi[out_len] = 0; |
| |
| if (strstr(ictx.mdm_imsi, "ERROR") != NULL) { |
| LOG_ERR("Unable to read IMSI"); |
| memset(ictx.mdm_imsi, 0, sizeof(ictx.mdm_imsi)); |
| } |
| |
| LOG_INF("IMSI: %s", ictx.mdm_imsi); |
| done: |
| return true; |
| } |
| |
| static void dns_work_cb(struct k_work *work) |
| { |
| #if defined(CONFIG_DNS_RESOLVER) && !defined(CONFIG_DNS_SERVER_IP_ADDRESSES) |
| int ret; |
| struct dns_resolve_context *dnsCtx; |
| struct sockaddr temp_addr; |
| bool valid_address = false; |
| static const char *const dns_servers_str[] = { |
| #ifdef CONFIG_NET_IPV6 |
| ictx.dns_v6_string, |
| #endif |
| #ifdef CONFIG_NET_IPV4 |
| ictx.dns_v4_string, |
| #endif |
| NULL}; |
| |
| #ifdef CONFIG_NET_IPV6 |
| valid_address = |
| net_ipaddr_parse(ictx.dns_v6_string, strlen(ictx.dns_v6_string), &temp_addr); |
| if (!valid_address && IS_ENABLED(CONFIG_NET_IPV4)) { |
| /* IPv6 DNS string is not valid, replace it with IPv4 address and recheck */ |
| strncpy(ictx.dns_v6_string, ictx.dns_v4_string, strlen(ictx.dns_v4_string)); |
| valid_address = net_ipaddr_parse(ictx.dns_v6_string, strlen(ictx.dns_v6_string), |
| &temp_addr); |
| } |
| #else |
| valid_address = |
| net_ipaddr_parse(ictx.dns_v4_string, strlen(ictx.dns_v4_string), &temp_addr); |
| #endif |
| |
| if (!valid_address) { |
| LOG_WRN("No valid DNS address!"); |
| } else if (ictx.iface && net_if_is_up(ictx.iface) && !ictx.dns_ready) { |
| /* set new DNS addr in DNS resolver */ |
| LOG_DBG("Refresh DNS resolver"); |
| dnsCtx = dns_resolve_get_default(); |
| ret = dns_resolve_reconfigure(dnsCtx, (const char **)dns_servers_str, NULL); |
| if (ret < 0) { |
| LOG_ERR("dns_resolve_init fail (%d)", ret); |
| return; |
| } |
| ictx.dns_ready = true; |
| } |
| #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; |
| } |
| |
| char *mdm_hl7800_get_imsi(void) |
| { |
| return ictx.mdm_imsi; |
| } |
| |
| /* Convert HL7800 IPv6 address string in format |
| * a01.a02.a03.a04.a05.a06.a07.a08.a09.a10.a11.a12.a13.a14.a15.a16 to |
| * an IPv6 address. |
| */ |
| static int hl7800_net_addr6_pton(const char *src, struct in6_addr *dst) |
| { |
| int num_sections = 8; |
| int i, len; |
| uint16_t ipv6_section; |
| |
| len = strlen(src); |
| for (i = 0; i < len; i++) { |
| if (!(src[i] >= '0' && src[i] <= '9') && src[i] != '.') { |
| return -EINVAL; |
| } |
| } |
| |
| for (i = 0; i < num_sections; i++) { |
| if (!src || *src == '\0') { |
| return -EINVAL; |
| } |
| |
| ipv6_section = (uint16_t)strtol(src, NULL, 10); |
| src = strchr(src, '.'); |
| src++; |
| if (!src || *src == '\0') { |
| return -EINVAL; |
| } |
| ipv6_section = (ipv6_section << 8) | (uint16_t)strtol(src, NULL, 10); |
| UNALIGNED_PUT(htons(ipv6_section), &dst->s6_addr16[i]); |
| |
| src = strchr(src, '.'); |
| if (src) { |
| src++; |
| } else { |
| if (i < num_sections - 1) { |
| return -EINVAL; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* Handler: +CGCONTRDP: <cid>,<bearer_id>,<apn>,<local_addr and subnet_mask>, |
| * <gw_addr>,<DNS_prim_addr>,<DNS_sec_addr> |
| */ |
| static bool on_cmd_atcmdinfo_ipaddr(struct net_buf **buf, uint16_t len) |
| { |
| int ret; |
| int num_delims = CGCONTRDP_RESPONSE_NUM_DELIMS; |
| char *delims[CGCONTRDP_RESPONSE_NUM_DELIMS]; |
| size_t out_len; |
| char value[MDM_IP_INFO_RESP_SIZE]; |
| char *search_start, *addr_start, *sm_start; |
| struct in_addr new_ipv4_addr; |
| struct in6_addr new_ipv6_addr; |
| bool is_ipv4; |
| int addr_len; |
| char temp_addr_str[HL7800_IPV6_ADDR_LEN]; |
| k_timeout_t delay; |
| |
| out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len); |
| value[out_len] = 0; |
| search_start = value; |
| LOG_DBG("IP info: %s", value); |
| |
| /* find all delimiters (,) */ |
| for (int i = 0; i < num_delims; i++) { |
| delims[i] = strchr(search_start, ','); |
| if (!delims[i]) { |
| LOG_ERR("Could not find delim %d, val: %s", i, |
| value); |
| return true; |
| } |
| /* Start next search after current delim location */ |
| search_start = delims[i] + 1; |
| } |
| |
| /* determine if IPv4 or IPv6 by checking length of ip address plus |
| * gateway string. |
| */ |
| is_ipv4 = false; |
| addr_len = delims[3] - delims[2]; |
| LOG_DBG("IP string len: %d", addr_len); |
| if (addr_len <= (NET_IPV4_ADDR_LEN * 2)) { |
| is_ipv4 = true; |
| } |
| |
| /* Find start of subnet mask */ |
| addr_start = delims[2] + 1; |
| if (is_ipv4) { |
| num_delims = 4; |
| } else { |
| num_delims = 16; |
| } |
| search_start = addr_start; |
| sm_start = addr_start; |
| for (int i = 0; i < num_delims; i++) { |
| sm_start = strchr(search_start, '.'); |
| if (!sm_start) { |
| LOG_ERR("Could not find submask start"); |
| return true; |
| } |
| /* Start next search after current delim location */ |
| search_start = sm_start + 1; |
| } |
| |
| /* get new IP addr */ |
| addr_len = sm_start - addr_start; |
| strncpy(temp_addr_str, addr_start, addr_len); |
| temp_addr_str[addr_len] = 0; |
| LOG_DBG("IP addr: %s", temp_addr_str); |
| if (is_ipv4) { |
| ret = net_addr_pton(AF_INET, temp_addr_str, &new_ipv4_addr); |
| } else { |
| ret = hl7800_net_addr6_pton(temp_addr_str, &new_ipv6_addr); |
| } |
| if (ret < 0) { |
| LOG_ERR("Invalid IP addr"); |
| return true; |
| } |
| |
| if (is_ipv4) { |
| /* move past the '.' */ |
| sm_start += 1; |
| /* store new subnet mask */ |
| addr_len = delims[3] - sm_start; |
| strncpy(temp_addr_str, sm_start, addr_len); |
| temp_addr_str[addr_len] = 0; |
| ret = net_addr_pton(AF_INET, temp_addr_str, &ictx.subnet); |
| if (ret < 0) { |
| LOG_ERR("Invalid subnet"); |
| return true; |
| } |
| |
| /* store new gateway */ |
| addr_start = delims[3] + 1; |
| addr_len = delims[4] - addr_start; |
| strncpy(temp_addr_str, addr_start, addr_len); |
| temp_addr_str[addr_len] = 0; |
| ret = net_addr_pton(AF_INET, temp_addr_str, &ictx.gateway); |
| if (ret < 0) { |
| LOG_ERR("Invalid gateway"); |
| return true; |
| } |
| } |
| |
| /* store new dns */ |
| addr_start = delims[4] + 1; |
| addr_len = delims[5] - addr_start; |
| strncpy(temp_addr_str, addr_start, addr_len); |
| temp_addr_str[addr_len] = 0; |
| if (is_ipv4) { |
| ret = strncmp(temp_addr_str, ictx.dns_v4_string, addr_len); |
| if (ret != 0) { |
| ictx.dns_ready = false; |
| } |
| strncpy(ictx.dns_v4_string, addr_start, addr_len); |
| ictx.dns_v4_string[addr_len] = 0; |
| ret = net_addr_pton(AF_INET, ictx.dns_v4_string, &ictx.dns_v4); |
| LOG_DBG("IPv4 DNS addr: %s", ictx.dns_v4_string); |
| } |
| #ifdef CONFIG_NET_IPV6 |
| else { |
| ret = strncmp(temp_addr_str, ictx.dns_v6_string, addr_len); |
| if (ret != 0) { |
| ictx.dns_ready = false; |
| } |
| /* store HL7800 formatted IPv6 DNS string temporarily */ |
| strncpy(ictx.dns_v6_string, addr_start, addr_len); |
| |
| ret = hl7800_net_addr6_pton(ictx.dns_v6_string, &ictx.dns_v6); |
| net_addr_ntop(AF_INET6, &ictx.dns_v6, ictx.dns_v6_string, |
| sizeof(ictx.dns_v6_string)); |
| LOG_DBG("IPv6 DNS addr: %s", ictx.dns_v6_string); |
| } |
| #endif |
| if (ret < 0) { |
| LOG_ERR("Invalid dns"); |
| return true; |
| } |
| |
| if (ictx.iface) { |
| if (is_ipv4) { |
| #ifdef CONFIG_NET_IPV4 |
| /* remove the current IPv4 addr before adding a new one. |
| * We dont care if it is successful or not. |
| */ |
| net_if_ipv4_addr_rm(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"); |
| } |
| |
| net_if_ipv4_set_netmask(ictx.iface, &ictx.subnet); |
| net_if_ipv4_set_gw(ictx.iface, &ictx.gateway); |
| #endif |
| /* store the new IP addr */ |
| net_ipaddr_copy(&ictx.ipv4Addr, &new_ipv4_addr); |
| } else { |
| #if CONFIG_NET_IPV6 |
| net_if_ipv6_addr_rm(ictx.iface, &ictx.ipv6Addr); |
| if (!net_if_ipv6_addr_add(ictx.iface, &new_ipv6_addr, NET_ADDR_AUTOCONF, |
| 0)) { |
| LOG_ERR("Cannot set iface IPv6 addr"); |
| } |
| #endif |
| } |
| |
| /* start DNS update work */ |
| delay = K_NO_WAIT; |
| if (!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"); |
| } |
| |
| return true; |
| } |
| |
| /* Handler1: +COPS: <mode>[,<format>,<oper>[,<AcT>]] |
| * |
| * Handler2: |
| * +COPS: [list of supported (<stat>, long alphanumeric <oper>, short |
| * alphanumeric <oper>, numeric <oper>[,< AcT>])s][,, |
| * (list of supported <mode>s),(list of supported <format>s)] |
| */ |
| static bool on_cmd_atcmdinfo_operator_status(struct net_buf **buf, uint16_t len) |
| { |
| size_t out_len; |
| char value[MDM_MAX_RESP_SIZE]; |
| int num_delims = COPS_RESPONSE_NUM_DELIMS; |
| char *delims[COPS_RESPONSE_NUM_DELIMS]; |
| char *search_start; |
| int i; |
| |
| out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len); |
| value[out_len] = 0; |
| |
| /* For AT+COPS=?, result is most likely longer than size of log string */ |
| if (strchr(value, '(') != NULL) { |
| LOG_HEXDUMP_DBG(value, out_len, "Operator: "); |
| goto done; |
| } else { |
| LOG_INF("Operator: %s", value); |
| } |
| |
| /* Process AT+COPS? */ |
| if (len == 1) { |
| /* only mode was returned, there is no operator info */ |
| 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, 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", 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); |
| |
| event_handler(HL7800_EVENT_BANDS, ictx.mdm_bands_string); |
| |
| return true; |
| } |
| |
| /* Handler: +KBND: #,####################### */ |
| static bool on_cmd_radio_active_bands(struct net_buf **buf, uint16_t len) |
| { |
| size_t out_len; |
| char value[MDM_MAX_RESP_SIZE]; |
| |
| out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len); |
| value[out_len] = 0; |
| |
| if (strlen(value) < sizeof("#,###################")) { |
| /* String size too short */ |
| return true; |
| } |
| |
| memcpy(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); |
| } |
| |
| int mdm_hl7800_set_desired_sleep_level(enum mdm_hl7800_sleep level) |
| { |
| int r = -EPERM; |
| |
| #if CONFIG_MODEM_HL7800_LOW_POWER_MODE |
| switch (level) { |
| case HL7800_SLEEP_AWAKE: |
| case HL7800_SLEEP_HIBERNATE: |
| case HL7800_SLEEP_LITE_HIBERNATE: |
| case HL7800_SLEEP_SLEEP: |
| ictx.desired_sleep_level = level; |
| r = 0; |
| break; |
| default: |
| r = -EINVAL; |
| } |
| |
| if (r == 0) { |
| hl7800_lock(); |
| wakeup_hl7800(); |
| r = set_sleep_level(); |
| allow_sleep(true); |
| hl7800_unlock(); |
| } |
| #endif |
| |
| return r; |
| } |
| |
| #ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE |
| |
| static void initialize_sleep_level(void) |
| { |
| if (ictx.desired_sleep_level == HL7800_SLEEP_UNINITIALIZED) { |
| if (IS_ENABLED(CONFIG_MODEM_HL7800_SLEEP_LEVEL_HIBERNATE)) { |
| ictx.desired_sleep_level = HL7800_SLEEP_HIBERNATE; |
| } else if (IS_ENABLED(CONFIG_MODEM_HL7800_SLEEP_LEVEL_LITE_HIBERNATE)) { |
| ictx.desired_sleep_level = HL7800_SLEEP_LITE_HIBERNATE; |
| } else if (IS_ENABLED(CONFIG_MODEM_HL7800_SLEEP_LEVEL_SLEEP)) { |
| ictx.desired_sleep_level = HL7800_SLEEP_SLEEP; |
| } else { |
| ictx.desired_sleep_level = HL7800_SLEEP_AWAKE; |
| } |
| } |
| } |
| |
| static int set_sleep_level(void) |
| { |
| char cmd[sizeof("AT+KSLEEP=#,#,##")]; |
| static const char SLEEP_CMD_FMT[] = "AT+KSLEEP=%d,%d,%d"; |
| int delay = CONFIG_MODEM_HL7800_SLEEP_DELAY_AFTER_REBOOT; |
| int ret = 0; |
| |
| /* AT+KSLEEP= <management>[,<level>[,<delay to sleep after reboot>]] |
| * management 1 means the HL7800 decides when it enters sleep mode |
| */ |
| switch (ictx.desired_sleep_level) { |
| case HL7800_SLEEP_HIBERNATE: |
| snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 1, 2, delay); |
| break; |
| case HL7800_SLEEP_LITE_HIBERNATE: |
| snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 1, 1, delay); |
| break; |
| case HL7800_SLEEP_SLEEP: |
| snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 1, 0, delay); |
| break; |
| default: |
| /* don't sleep */ |
| snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 2, 0, delay); |
| break; |
| } |
| |
| SEND_AT_CMD_EXPECT_OK(cmd); |
| |
| error: |
| return ret; |
| } |
| |
| #endif /* CONFIG_MODEM_HL7800_LOW_POWER_MODE */ |
| |
| static char *get_sleep_state_string(enum mdm_hl7800_sleep state) |
| { |
| /* clang-format off */ |
| switch (state) { |
| PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, UNINITIALIZED); |
| PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, HIBERNATE); |
| PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, LITE_HIBERNATE); |
| PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, SLEEP); |
| PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, AWAKE); |
| default: |
| return "UNKNOWN"; |
| } |
| /* clang-format on */ |
| } |
| |
| static void set_sleep_state(enum mdm_hl7800_sleep state) |
| { |
| ictx.sleep_state = state; |
| if (ictx.sleep_state != HL7800_SLEEP_AWAKE) { |
| k_sem_reset(&ictx.mdm_awake); |
| } |
| 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", |
| get_fota_state_string(ictx.fw_update_state), |
| 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; |
| ictx.reconfig_IP_connection = true; |
| #ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE |
| mark_sockets_for_reconfig(); |
| #endif |
| set_sleep_state(HL7800_SLEEP_AWAKE); |
| k_sem_give(&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, 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, 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", |
| 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", |
| 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, line); |
| if (output_length > 0) { |
| memset(ictx.mdm_apn.value, 0, sizeof(ictx.mdm_apn.value)); |
| memset(ictx.mdm_pdp_addr_fam, 0, MDM_ADDR_FAM_MAX_LEN); |
| |
| /* Address family after first , */ |
| p = strchr(line, ','); |
| if (p == NULL) { |
| LOG_WRN("Issue parsing APN response"); |
| goto done; |
| } |
| p += 2; |
| i = 0; |
| while ((p != NULL) && (*p != '"') && (i < MDM_ADDR_FAM_MAX_LEN)) { |
| ictx.mdm_pdp_addr_fam[i++] = *p++; |
| } |
| LOG_DBG("PDP address family: %s", ictx.mdm_pdp_addr_fam); |
| |
| /* APN after second , " */ |
| p = strchr(p, ','); |
| if (p == NULL) { |
| LOG_WRN("Issue parsing APN response"); |
| goto done; |
| } |
| p++; |
| if (*p == ',') { |
| /* APN is blank */ |
| goto done; |
| } |
| if (*p == '"') { |
| p++; |
| i = 0; |
| while ((p != NULL) && (*p != '"') && |
| (i < MDM_HL7800_APN_MAX_STRLEN)) { |
| ictx.mdm_apn.value[i++] = *p++; |
| } |
| } |
| |
| LOG_INF("APN: %s", 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) |
| { |
| /* Rate is not checked here to allow one reading |
| * when going from network down->up |
| */ |
| 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 rssi_query(void) |
| { |
| hl7800_lock(); |
| wakeup_hl7800(); |
| hl7800_query_rssi(); |
| allow_sleep(true); |
| hl7800_unlock(); |
| } |
| |
| static void hl7800_rssi_query_work(struct k_work *work) |
| { |
| rssi_query(); |
| |
| /* re-start RSSI query work */ |
| if (CONFIG_MODEM_HL7800_RSSI_RATE_SECONDS > 0) { |
| k_work_reschedule_for_queue(&hl7800_workq, &ictx.rssi_query_work, |
| K_SECONDS(CONFIG_MODEM_HL7800_RSSI_RATE_SECONDS)); |
| } |
| } |
| |
| #ifdef CONFIG_MODEM_HL7800_GPS |
| /* Unsolicited notification |
| * Handler: +GNSSEV: <eventType>,<eventStatus> |
| */ |
| static bool on_cmd_gps_event(struct net_buf **buf, uint16_t len) |
| { |
| size_t out_len; |
| char value[MDM_MAX_RESP_SIZE]; |
| char *start = NULL; |
| char *end = NULL; |
| int8_t event = -1; |
| int8_t status = -1; |
| |
| memset(value, 0, sizeof(value)); |
| out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len); |
| if (out_len > 0) { |
| start = value; |
| event = strtol(start, &end, 10); |
| if (end == strchr(value, ',')) { |
| start = end + 1; |
| status = strtol(start, &end, 10); |
| } |
| } |
| |
| LOG_INF("GPS event: %d status: %d", event, status); |
| |
| if (event == HL7800_GNSS_EVENT_POSITION) { |
| event_handler(HL7800_EVENT_GPS_POSITION_STATUS, &status); |
| } |
| |
| return true; |
| } |
| |
| static void gps_work_callback(struct k_work *work) |
| { |
| ARG_UNUSED(work); |
| int r; |
| |
| hl7800_lock(); |
| wakeup_hl7800(); |
| r = send_at_cmd(NULL, "AT+GNSSLOC?", MDM_CMD_SEND_TIMEOUT, 1, false); |
| allow_sleep(true); |
| hl7800_unlock(); |
| |
| LOG_DBG("GPS location request status: %d", r); |
| |
| if (ictx.gps_query_location_rate_seconds) { |
| k_work_reschedule_for_queue(&hl7800_workq, &ictx.gps_work, |
| K_SECONDS(ictx.gps_query_location_rate_seconds)); |
| } |
| } |
| |
| /* The AT+GNSSLOC? command returns 1 of 2 things: |
| * |
| * +GNSSLOC: |
| * Latitude: "49 Deg 10 Min 21.49 Sec N" |
| * Longitude: "123 Deg 4 Min 14.76 Sec W" |
| * GpsTime: "yyyy mm dd hh:mm:ss" |
| * FixType: "2D" or "3D" |
| * HEPE: "8.485 m" (Horizontal Estimated Position Error) |
| * Altitude: "-1 m" |
| * AltUnc: "3.0 m" |
| * Direction: "0.0 deg" |
| * HorSpeed: "0.0 m/s" |
| * VerSpeed: "0.0 m/s" |
| * OK |
| * |
| * OR |
| * |
| * +GNSSLOC: |
| * FIX NOT AVAILABLE |
| * OK |
| * |
| * Since each response is on its own line, the command handler is used |
| * to handle each one as an individual response. |
| */ |
| static bool gps_handler(struct net_buf **buf, uint16_t len, |
| enum mdm_hl7800_gps_string_types str_type) |
| { |
| struct mdm_hl7800_compound_event event; |
| char gps_str[MDM_HL7800_MAX_GPS_STR_SIZE]; |
| size_t gps_len = sizeof(gps_str) - 1; |
| struct net_buf *frag = NULL; |
| size_t out_len; |
| |
| wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), sizeof(gps_str)); |
| |
| frag = NULL; |
| len = net_buf_findcrlf(*buf, &frag); |
| if (!frag) { |
| LOG_ERR("Unable to find end"); |
| goto done; |
| } |
| |
| if (len > gps_len) { |
| LOG_WRN("GPS string too long (len:%d)", len); |
| len = gps_len; |
| } |
| |
| out_len = net_buf_linearize(gps_str, gps_len, *buf, 0, len); |
| gps_str[out_len] = 0; |
| |
| event.code = str_type; |
| event.string = gps_str; |
| event_handler(HL7800_EVENT_GPS, &event); |
| done: |
| return true; |
| } |
| |
| static bool on_cmd_latitude(struct net_buf **buf, uint16_t len) |
| { |
| return gps_handler(buf, len, HL7800_GPS_STR_LATITUDE); |
| } |
| |
| static bool on_cmd_longitude(struct net_buf **buf, uint16_t len) |
| { |
| return gps_handler(buf, len, HL7800_GPS_STR_LONGITUDE); |
| } |
| |
| static bool on_cmd_gps_time(struct net_buf **buf, uint16_t len) |
| { |
| return gps_handler(buf, len, HL7800_GPS_STR_GPS_TIME); |
| } |
| |
| static bool on_cmd_fix_type(struct net_buf **buf, uint16_t len) |
| { |
| return gps_handler(buf, len, HL7800_GPS_STR_FIX_TYPE); |
| } |
| |
| static bool on_cmd_hepe(struct net_buf **buf, uint16_t len) |
| { |
| return gps_handler(buf, len, HL7800_GPS_STR_HEPE); |
| } |
| |
| static bool on_cmd_altitude(struct net_buf **buf, uint16_t len) |
| { |
| return gps_handler(buf, len, HL7800_GPS_STR_ALTITUDE); |
| } |
| |
| static bool on_cmd_alt_unc(struct net_buf **buf, uint16_t len) |
| { |
| return gps_handler(buf, len, HL7800_GPS_STR_ALT_UNC); |
| } |
| |
| static bool on_cmd_direction(struct net_buf **buf, uint16_t len) |
| { |
| return gps_handler(buf, len, HL7800_GPS_STR_DIRECTION); |
| } |
| |
| static bool on_cmd_hor_speed(struct net_buf **buf, uint16_t len) |
| { |
| return gps_handler(buf, len, HL7800_GPS_STR_HOR_SPEED); |
| } |
| |
| static bool on_cmd_ver_speed(struct net_buf **buf, uint16_t len) |
| { |
| return gps_handler(buf, len, HL7800_GPS_STR_VER_SPEED); |
| } |
| #endif /* CONFIG_MODEM_HL7800_GPS */ |
| |
| #ifdef CONFIG_MODEM_HL7800_POLTE |
| /* Handler: %POLTEEVU: "REGISTER",0, <mqttAuthUser>, <mqttAuthPassword> */ |
| static bool on_cmd_polte_registration(struct net_buf **buf, uint16_t len) |
| { |
| char rsp[MDM_MAX_RESP_SIZE] = { 0 }; |
| size_t rsp_len = sizeof(rsp) - 1; |
| char *rsp_end = rsp + rsp_len; |
| struct mdm_hl7800_polte_registration_event_data data; |
| struct net_buf *frag = NULL; |
| size_t out_len; |
| char *location; |
| bool parsed; |
| |
| memset(&data, 0, sizeof(data)); |
| |
| wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), sizeof(rsp)); |
| |
| location = rsp; |
| parsed = false; |
| frag = NULL; |
| len = net_buf_findcrlf(*buf, &frag); |
| do { |
| if (!frag) { |
| LOG_ERR("Unable to find end"); |
| break; |
| } |
| |
| if (len > rsp_len) { |
| LOG_WRN("string too long (len:%d)", len); |
| len = rsp_len; |
| } |
| |
| out_len = net_buf_linearize(rsp, rsp_len, *buf, 0, len); |
| rsp[out_len] = 0; |
| |
| /* Command handler looks for string up to the user field */ |
| location = strstr(location, "\""); |
| if (location != NULL && location < rsp_end) { |
| location += 1; |
| if (location >= rsp_end) { |
| break; |
| } |
| data.user = location; |
| } else { |
| break; |
| } |
| |
| /* Find end of user field and null terminate string */ |
| location = strstr(location, "\""); |
| if (location != NULL && location < rsp_end) { |
| *location = 0; |
| location += 1; |
| if (location >= rsp_end) { |
| break; |
| } |
| } else { |
| break; |
| } |
| |
| location = strstr(location, ",\""); |
| if (location != NULL && location < rsp_end) { |
| location += 2; |
| if (location >= rsp_end) { |
| break; |
| } |
| data.password = location; |
| |
| } else { |
| break; |
| } |
| |
| location = strstr(location, "\""); |
| if (location != NULL && location < rsp_end) { |
| *location = 0; |
| } else { |
| break; |
| } |
| parsed = true; |
| } while (false); |
| |
| if (parsed && data.user && data.password) { |
| data.status = 0; |
| } else { |
| data.status = -1; |
| LOG_ERR("Unable to parse PoLTE registration"); |
| } |
| |
| event_handler(HL7800_EVENT_POLTE_REGISTRATION, &data); |
| |
| return true; |
| } |
| |
| /* Handler: %POLTECMD: "LOCATE",<res> */ |
| static bool on_cmd_polte_locate_cmd_rsp(struct net_buf **buf, uint16_t len) |
| { |
| char rsp[sizeof("99")] = { 0 }; |
| size_t rsp_len = sizeof(rsp) - 1; |
| size_t out_len; |
| struct net_buf *frag = NULL; |
| struct mdm_hl7800_polte_location_data data; |
| |
| memset(&data, 0, sizeof(data)); |
| |
| wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), sizeof(rsp)); |
| |
| data.status = -1; |
| frag = NULL; |
| len = net_buf_findcrlf(*buf, &frag); |
| do { |
| if (!frag) { |
| LOG_ERR("Unable to find end"); |
| break; |
| } |
| |
| if (len > rsp_len) { |
| LOG_WRN("string too long (len:%d)", len); |
| len = rsp_len; |
| } |
| |
| out_len = net_buf_linearize(rsp, rsp_len, *buf, 0, len); |
| rsp[out_len] = 0; |
| |
| data.status = (uint32_t)strtoul(rsp, NULL, 10); |
| } while (false); |
| |
| event_handler(HL7800_EVENT_POLTE_LOCATE_STATUS, &data); |
| |
| return true; |
| } |
| |
| /* Handler: |
| * %POLTEEVU: "LOCATION",<stat>[,<latitude>,<longitude>,<time>,<confidence>] |
| */ |
| static bool on_cmd_polte_location(struct net_buf **buf, uint16_t len) |
| { |
| char rsp[MDM_MAX_RESP_SIZE] = { 0 }; |
| size_t rsp_len = sizeof(rsp) - 1; |
| char *rsp_end = rsp + rsp_len; |
| struct net_buf *frag = NULL; |
| size_t out_len = 0; |
| char *start; |
| char *end; |
| bool parsed; |
| struct mdm_hl7800_polte_location_data data; |
| static const char POLTE_LOC_DELIMITER[] = "\",\""; |
| |
| memset(&data, 0, sizeof(data)); |
| |
| wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), sizeof(rsp)); |
| |
| parsed = false; |
| frag = NULL; |
| len = net_buf_findcrlf(*buf, &frag); |
| do { |
| if (!frag) { |
| LOG_ERR("Unable to find end"); |
| break; |
| } |
| |
| if (len > rsp_len) { |
| LOG_WRN("string too long (len:%d)", len); |
| len = rsp_len; |
| } |
| |
| out_len = net_buf_linearize(rsp, rsp_len, *buf, 0, len); |
| rsp[out_len] = 0; |
| |
| data.status = -1; |
| start = rsp; |
| end = ""; |
| /* Comma isn't present when there is an error. */ |
| start = strstr(start, ","); |
| if (start != NULL && start < rsp_end) { |
| *start = ' '; |
| start += 1; |
| } |
| data.status = (uint32_t)strtoul(rsp, &end, 10); |
| if (data.status != 0) { |
| LOG_WRN("Response not received from PoLTE server: %d", data.status); |
| data.status = MDM_HL7800_POLTE_SERVER_ERROR; |
| parsed = true; |
| break; |
| } else if (start >= rsp_end) { |
| break; |
| } |
| |
| start = strstr(start, "\"") + 1; |
| end = strstr(start, POLTE_LOC_DELIMITER); |
| if (start > rsp && start < rsp_end && end < rsp_end && end > start) { |
| memcpy(data.latitude, start, MIN(end - start, sizeof(data.latitude) - 1)); |
| } else { |
| break; |
| } |
| |
|