/* * Copyright (c) 2020 Laird Connectivity * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT swir_hl7800 #include #include #define LOG_MODULE_NAME modem_hl7800 LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_MODEM_LOG_LEVEL); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 #endif #include "modem_receiver.h" #include #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_IP "IP" #define ADDRESS_FAMILY_IPV4 "IPV4" #if defined(CONFIG_MODEM_HL7800_ADDRESS_FAMILY_IPV4V6) #define MODEM_HL7800_ADDRESS_FAMILY "IPV4V6" #elif defined(CONFIG_MODEM_HL7800_ADDRESS_FAMILY_IPV4) #define MODEM_HL7800_ADDRESS_FAMILY ADDRESS_FAMILY_IPV4 #else #define MODEM_HL7800_ADDRESS_FAMILY "IPV6" #endif #define MDM_HL7800_SOCKET_AF_IPV4 0 #define MDM_HL7800_SOCKET_AF_IPV6 1 #define SET_RAT_M1_CMD_LEGACY "AT+KSRAT=0" #define SET_RAT_NB1_CMD_LEGACY "AT+KSRAT=1" #define SET_RAT_M1_CMD "AT+KSRAT=0,1" #define SET_RAT_NB1_CMD "AT+KSRAT=1,1" #define NEW_RAT_CMD_MIN_VERSION "HL7800.4.5.4.0" #define HL7800_VERSION_FORMAT "HL7800.%zu.%zu.%zu.%zu" #define MAX_PROFILE_LINE_LENGTH \ MAX(sizeof(PROFILE_LINE_1), sizeof(PROFILE_LINE_2)) #define IPV6_ADDR_FORMAT "####:####:####:####:####:####:####:####" #define HL7800_IPV6_ADDR_LEN \ sizeof("a01.a02.a03.a04.a05.a06.a07.a08.a09.a10.a11.a12.a13.a14.a15.a16") #define MDM_ADDR_FAM_MAX_LEN sizeof("IPV4V6") /* The ? can be a + or - */ static const char TIME_STRING_FORMAT[] = "\"yy/MM/dd,hh:mm:ss?zz\""; #define TIME_STRING_DIGIT_STRLEN 2 #define TIME_STRING_SEPARATOR_STRLEN 1 #define TIME_STRING_PLUS_MINUS_INDEX (6 * 3) #define TIME_STRING_FIRST_SEPARATOR_INDEX 0 #define TIME_STRING_FIRST_DIGIT_INDEX 1 #define TIME_STRING_TO_TM_STRUCT_YEAR_OFFSET (2000 - 1900) /* Time structure min, max */ #define TM_YEAR_RANGE 0, 99 #define TM_MONTH_RANGE_PLUS_1 1, 12 #define TM_DAY_RANGE 1, 31 #define TM_HOUR_RANGE 0, 23 #define TM_MIN_RANGE 0, 59 #define TM_SEC_RANGE 0, 60 /* leap second */ #define QUARTER_HOUR_RANGE 0, 96 #define SECONDS_PER_QUARTER_HOUR (15 * 60) #define SEND_AT_CMD_ONCE_EXPECT_OK(c) \ do { \ ret = send_at_cmd(NULL, (c), MDM_CMD_SEND_TIMEOUT, 0, false); \ if (ret < 0) { \ LOG_ERR("%s result:%d", (c), ret); \ goto error; \ } \ } while (false) #define SEND_AT_CMD_IGNORE_ERROR(c) \ do { \ ret = send_at_cmd(NULL, (c), MDM_CMD_SEND_TIMEOUT, 0, false); \ if (ret < 0) { \ LOG_ERR("%s result:%d", (c), ret); \ } \ } while (false) #define SEND_AT_CMD_EXPECT_OK(c) \ do { \ ret = send_at_cmd(NULL, (c), MDM_CMD_SEND_TIMEOUT, \ MDM_DEFAULT_AT_CMD_RETRIES, false); \ if (ret < 0) { \ LOG_ERR("%s result:%d", (c), ret); \ goto error; \ } \ } while (false) /* Complex has "no_id_resp" set to true because the sending command * is the command used to process the response */ #define SEND_COMPLEX_AT_CMD(c) \ do { \ ret = send_at_cmd(NULL, (c), MDM_CMD_SEND_TIMEOUT, \ MDM_DEFAULT_AT_CMD_RETRIES, true); \ if (ret < 0) { \ LOG_ERR("%s result:%d", (c), ret); \ goto error; \ } \ } while (false) NET_BUF_POOL_DEFINE(mdm_recv_pool, CONFIG_MODEM_HL7800_RECV_BUF_CNT, CONFIG_MODEM_HL7800_RECV_BUF_SIZE, 0, NULL); static uint8_t mdm_recv_buf[MDM_MAX_DATA_LENGTH]; static K_SEM_DEFINE(hl7800_RX_lock_sem, 1, 1); static K_SEM_DEFINE(hl7800_TX_lock_sem, 1, 1); static K_SEM_DEFINE(cb_lock, 1, 1); /* RX thread structures */ K_THREAD_STACK_DEFINE(hl7800_rx_stack, CONFIG_MODEM_HL7800_RX_STACK_SIZE); struct k_thread hl7800_rx_thread; #define RX_THREAD_PRIORITY K_PRIO_COOP(7) /* RX thread work queue */ K_THREAD_STACK_DEFINE(hl7800_workq_stack, CONFIG_MODEM_HL7800_RX_WORKQ_STACK_SIZE); static struct k_work_q hl7800_workq; #define WORKQ_PRIORITY K_PRIO_COOP(7) static const char EOF_PATTERN[] = "--EOF--Pattern--"; static const char CONNECT_STRING[] = "CONNECT"; static const char OK_STRING[] = "OK"; struct hl7800_socket { struct net_context *context; sa_family_t family; enum net_sock_type type; enum net_ip_protocol ip_proto; struct sockaddr src; struct sockaddr dst; bool created; bool reconfig; int socket_id; int rx_size; int error; enum socket_state state; /** semaphore */ struct k_sem sock_send_sem; /** socket callbacks */ struct k_work recv_cb_work; struct k_work rx_data_work; struct k_work_delayable notif_work; net_context_recv_cb_t recv_cb; struct net_pkt *recv_pkt; void *recv_user_data; }; struct stale_socket { int reserved; /* first word of queue data item reserved for the kernel */ enum net_sock_type type; uint8_t id; bool allocated; }; #define NO_ID_RESP_CMD_MAX_LENGTH 32 struct hl7800_config { struct gpio_dt_spec gpio[MAX_MDM_CONTROL_PINS]; }; struct hl7800_iface_ctx { struct net_if *iface; uint8_t mac_addr[6]; struct in_addr ipv4Addr, subnet, gateway, dns_v4; #ifdef CONFIG_NET_IPV6 struct in6_addr ipv6Addr, dns_v6; char dns_v6_string[HL7800_IPV6_ADDR_LEN]; #endif bool restarting; bool initialized; bool wait_for_KSUP; uint32_t wait_for_KSUP_tries; bool reconfig_IP_connection; char dns_v4_string[NET_IPV4_ADDR_LEN]; char no_id_resp_cmd[NO_ID_RESP_CMD_MAX_LENGTH]; bool search_no_id_resp; /* GPIO PORT devices */ struct gpio_callback mdm_vgpio_cb; struct gpio_callback mdm_uart_dsr_cb; struct gpio_callback mdm_gpio6_cb; struct gpio_callback mdm_uart_cts_cb; int vgpio_state; int dsr_state; int gpio6_state; int cts_state; /* 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 iface_ctx; static size_t hl7800_read_rx(struct net_buf **buf); static char *get_network_state_string(enum mdm_hl7800_network_state state); static char *get_startup_state_string(enum mdm_hl7800_startup_state state); static char *get_sleep_state_string(enum mdm_hl7800_sleep state); static void set_network_state(enum mdm_hl7800_network_state state); static void set_startup_state(enum mdm_hl7800_startup_state state); static void set_sleep_state(enum mdm_hl7800_sleep state); static void generate_network_state_event(void); static void generate_startup_state_event(void); static void generate_sleep_state_event(void); static int modem_boot_handler(char *reason); static void mdm_vgpio_work_cb(struct k_work *item); static void mdm_reset_work_callback(struct k_work *item); static void mdm_power_off_work_callback(struct k_work *item); static int write_apn(char *access_point_name); #ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE static void mark_sockets_for_reconfig(void); #endif static void hl7800_build_mac(struct hl7800_iface_ctx *ictx); static void rssi_query(void); #ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE static void initialize_sleep_level(void); static int set_sleep_level(void); #endif #ifdef CONFIG_MODEM_HL7800_FW_UPDATE static char *get_fota_state_string(enum mdm_hl7800_fota_state state); static void set_fota_state(enum mdm_hl7800_fota_state state); static void generate_fota_state_event(void); static void generate_fota_count_event(void); #endif static struct stale_socket *alloc_stale_socket(void) { struct stale_socket *sock = NULL; for (int i = 0; i < MDM_MAX_SOCKETS; i++) { if (!iface_ctx.stale_sockets[i].allocated) { sock = &iface_ctx.stale_sockets[i]; sock->allocated = true; break; } } return sock; } static void free_stale_socket(struct stale_socket *sock) { if (sock != NULL) { sock->allocated = false; } } static int queue_stale_socket(enum net_sock_type type, uint8_t id) { int ret = 0; struct stale_socket *sock = NULL; sock = alloc_stale_socket(); if (sock != NULL) { sock->type = type; sock->id = id; k_queue_append(&iface_ctx.stale_socket_queue, (void *)sock); } else { LOG_ERR("Could not alloc stale socket"); ret = -ENOMEM; } return ret; } static struct stale_socket *dequeue_stale_socket(void) { struct stale_socket *sock = NULL; sock = (struct stale_socket *)k_queue_get(&iface_ctx.stale_socket_queue, K_NO_WAIT); return sock; } static bool convert_time_string_to_struct(struct tm *tm, int32_t *offset, char *time_string); static int modem_reset_and_configure(void); static int read_pin(int default_state, const struct gpio_dt_spec *spec) { int state = gpio_pin_get_raw(spec->port, spec->pin); if (state < 0) { LOG_ERR("Unable to read port: %s pin: %d status: %d", spec->port->name, spec->pin, state); state = default_state; } return state; } #ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE static bool is_cmd_ready(void) { iface_ctx.vgpio_state = read_pin(0, &hl7800_cfg.gpio[MDM_VGPIO]); iface_ctx.gpio6_state = read_pin(0, &hl7800_cfg.gpio[MDM_GPIO6]); iface_ctx.cts_state = read_pin(1, &hl7800_cfg.gpio[MDM_UART_CTS]); return iface_ctx.vgpio_state && iface_ctx.gpio6_state && !iface_ctx.cts_state; } #endif /** * The definition of awake is that the HL7800 * is ready to receive AT commands successfully */ static void check_hl7800_awake(void) { #ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE bool is_cmd_rdy = is_cmd_ready(); if (is_cmd_rdy && (iface_ctx.sleep_state != HL7800_SLEEP_AWAKE) && !iface_ctx.allow_sleep && !iface_ctx.wait_for_KSUP) { PRINT_AWAKE_MSG; set_sleep_state(HL7800_SLEEP_AWAKE); k_sem_give(&iface_ctx.mdm_awake); } else if (!is_cmd_rdy && iface_ctx.sleep_state == HL7800_SLEEP_AWAKE && iface_ctx.allow_sleep) { PRINT_NOT_AWAKE_MSG; if (iface_ctx.desired_sleep_level == HL7800_SLEEP_HIBERNATE || iface_ctx.desired_sleep_level == HL7800_SLEEP_LITE_HIBERNATE) { /* If the device is sleeping (not ready to receive commands) * then the device may send +KSUP when waking up. * We should wait for it. */ iface_ctx.wait_for_KSUP = true; iface_ctx.wait_for_KSUP_tries = 0; set_sleep_state(iface_ctx.desired_sleep_level); } else if (iface_ctx.desired_sleep_level == HL7800_SLEEP_SLEEP) { set_sleep_state(HL7800_SLEEP_SLEEP); } } #endif } static int hl7800_RX_lock(void) { HL7800_RX_LOCK_DBG_LOG("Locking RX [%p]...", k_current_get()); int rc = k_sem_take(&hl7800_RX_lock_sem, K_FOREVER); if (rc != 0) { LOG_ERR("Unable to lock hl7800 (%d)", rc); } else { HL7800_RX_LOCK_DBG_LOG("Locked RX [%p]", k_current_get()); } return rc; } static void hl7800_RX_unlock(void) { HL7800_RX_LOCK_DBG_LOG("UNLocking RX [%p]...", k_current_get()); k_sem_give(&hl7800_RX_lock_sem); HL7800_RX_LOCK_DBG_LOG("UNLocked RX [%p]", k_current_get()); } static bool hl7800_RX_locked(void) { if (k_sem_count_get(&hl7800_RX_lock_sem) == 0) { return true; } else { return false; } } static int hl7800_TX_lock(void) { HL7800_TX_LOCK_DBG_LOG("Locking TX [%p]...", k_current_get()); int rc = k_sem_take(&hl7800_TX_lock_sem, K_FOREVER); if (rc != 0) { LOG_ERR("Unable to lock hl7800 (%d)", rc); } else { HL7800_TX_LOCK_DBG_LOG("Locked TX [%p]", k_current_get()); } return rc; } static void hl7800_TX_unlock(void) { HL7800_TX_LOCK_DBG_LOG("UNLocking TX [%p]...", k_current_get()); k_sem_give(&hl7800_TX_lock_sem); HL7800_TX_LOCK_DBG_LOG("UNLocked TX [%p]", k_current_get()); } static bool hl7800_TX_locked(void) { if (k_sem_count_get(&hl7800_TX_lock_sem) == 0) { return true; } else { return false; } } static void hl7800_lock(void) { hl7800_TX_lock(); hl7800_RX_lock(); } static void hl7800_unlock(void) { hl7800_RX_unlock(); hl7800_TX_unlock(); } static struct hl7800_socket *socket_get(void) { int i; struct hl7800_socket *sock = NULL; for (i = 0; i < MDM_MAX_SOCKETS; i++) { if (!iface_ctx.sockets[i].context) { sock = &iface_ctx.sockets[i]; break; } } return sock; } static struct hl7800_socket *socket_from_id(int socket_id) { int i; struct hl7800_socket *sock = NULL; if (socket_id < 1) { return NULL; } for (i = 0; i < MDM_MAX_SOCKETS; i++) { if (iface_ctx.sockets[i].socket_id == socket_id) { sock = &iface_ctx.sockets[i]; break; } } return sock; } static 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)) { iface_ctx.wake_up_callback = func; } void mdm_hl7800_register_gpio6_callback(void (*func)(int state)) { iface_ctx.gpio6_callback = func; } void mdm_hl7800_register_cts_callback(void (*func)(int state)) { iface_ctx.cts_callback = func; } static void modem_assert_wake(bool assert) { int state; if (assert) { HL7800_IO_DBG_LOG("MDM_WAKE_PIN -> ASSERTED"); state = 1; } else { HL7800_IO_DBG_LOG("MDM_WAKE_PIN -> NOT_ASSERTED"); state = 0; } gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_WAKE], state); if (iface_ctx.wake_up_callback != NULL) { iface_ctx.wake_up_callback(state); } } static void modem_assert_pwr_on(bool assert) { if (assert) { HL7800_IO_DBG_LOG("MDM_PWR_ON -> ASSERTED"); gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_PWR_ON], 1); } else { HL7800_IO_DBG_LOG("MDM_PWR_ON -> NOT_ASSERTED"); gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_PWR_ON], 0); } } static void modem_assert_fast_shutd(bool assert) { if (assert) { HL7800_IO_DBG_LOG("MDM_FAST_SHUTD -> ASSERTED"); gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_FAST_SHUTD], 1); } else { HL7800_IO_DBG_LOG("MDM_FAST_SHUTD -> NOT_ASSERTED"); gpio_pin_set_dt(&hl7800_cfg.gpio[MDM_FAST_SHUTD], 0); } } static void allow_sleep_work_callback(struct k_work *item) { ARG_UNUSED(item); LOG_DBG("Allow sleep"); iface_ctx.allow_sleep = true; set_sleep_state(iface_ctx.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, &iface_ctx.allow_sleep_work, K_MSEC(CONFIG_MODEM_HL7800_ALLOW_SLEEP_DELAY_MS)); } else { LOG_DBG("Keep awake"); k_work_cancel_delayable(&iface_ctx.allow_sleep_work); iface_ctx.allow_sleep = false; modem_assert_wake(true); } #endif } static void event_handler(enum mdm_hl7800_event event, void *event_data) { sys_snode_t *node; struct mdm_hl7800_callback_agent *agent; k_sem_take(&cb_lock, K_FOREVER); SYS_SLIST_FOR_EACH_NODE(&hl7800_event_callback_list, node) { agent = CONTAINER_OF(node, struct mdm_hl7800_callback_agent, node); if (agent->event_callback != NULL) { agent->event_callback(event, event_data); } } k_sem_give(&cb_lock); } void mdm_hl7800_get_signal_quality(int *rsrp, int *sinr) { if (CONFIG_MODEM_HL7800_RSSI_RATE_SECONDS == 0) { rssi_query(); } *rsrp = iface_ctx.mdm_rssi; *sinr = iface_ctx.mdm_sinr; } void mdm_hl7800_wakeup(bool wakeup) { allow_sleep(!wakeup); } /* Send an AT command with a series of response handlers */ static int send_at_cmd(struct hl7800_socket *sock, const uint8_t *data, k_timeout_t timeout, int retries, bool no_id_resp) { int ret = 0; iface_ctx.last_error = 0; do { if (!sock) { k_sem_reset(&iface_ctx.response_sem); iface_ctx.last_socket_id = 0; } else { sock->error = 0; k_sem_reset(&sock->sock_send_sem); iface_ctx.last_socket_id = sock->socket_id; } if (no_id_resp) { strncpy(iface_ctx.no_id_resp_cmd, data, sizeof(iface_ctx.no_id_resp_cmd) - 1); iface_ctx.search_no_id_resp = true; } LOG_DBG("OUT: [%s]", (char *)data); mdm_receiver_send(&iface_ctx.mdm_ctx, data, strlen(data)); mdm_receiver_send(&iface_ctx.mdm_ctx, "\r", 1); if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) { goto done; } if (!sock) { ret = k_sem_take(&iface_ctx.response_sem, timeout); } else { ret = k_sem_take(&sock->sock_send_sem, timeout); } if (ret == 0) { if (sock) { ret = sock->error; } else { ret = iface_ctx.last_error; } } else if (ret == -EAGAIN) { ret = -ETIMEDOUT; } retries--; if (retries < 0) { retries = 0; } } while (ret != 0 && retries > 0); done: iface_ctx.search_no_id_resp = false; return ret; } static int wakeup_hl7800(void) { #ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE int ret; allow_sleep(false); /* If modem is in sleep mode (not hibernate), * then it can respond in ~10 ms. */ if (iface_ctx.desired_sleep_level == HL7800_SLEEP_SLEEP) { k_sleep(MDM_WAKE_TO_CHECK_CTS_DELAY_MS); } if (!is_cmd_ready()) { LOG_DBG("Waiting to wakeup"); ret = k_sem_take(&iface_ctx.mdm_awake, MDM_WAKEUP_TIME); if (ret) { LOG_DBG("Err waiting for wakeup: %d", ret); } } #endif return 0; } int32_t mdm_hl7800_send_at_cmd(const uint8_t *data) { int ret; if (!data) { return -EINVAL; } hl7800_lock(); wakeup_hl7800(); iface_ctx.last_socket_id = 0; ret = send_at_cmd(NULL, data, MDM_CMD_SEND_TIMEOUT, 0, false); allow_sleep(true); hl7800_unlock(); return ret; } /* The access point name (and username and password) are stored in the modem's * non-volatile memory. */ int32_t mdm_hl7800_update_apn(char *access_point_name) { int ret = -EINVAL; hl7800_lock(); wakeup_hl7800(); iface_ctx.last_socket_id = 0; ret = write_apn(access_point_name); allow_sleep(true); hl7800_unlock(); if (ret >= 0) { /* After a reset the APN will be re-read from the modem * and an event will be generated. */ k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.mdm_reset_work, K_NO_WAIT); } return ret; } bool mdm_hl7800_valid_rat(uint8_t value) { if ((value == MDM_RAT_CAT_M1) || (value == MDM_RAT_CAT_NB1)) { return true; } return false; } int32_t mdm_hl7800_update_rat(enum mdm_hl7800_radio_mode value) { int ret = -EINVAL; if (value == iface_ctx.mdm_rat) { /* The set command will fail (in the modem) * if the RAT isn't different. */ return 0; } else if (!mdm_hl7800_valid_rat(value)) { return ret; } hl7800_lock(); wakeup_hl7800(); iface_ctx.last_socket_id = 0; if (value == MDM_RAT_CAT_M1) { if (iface_ctx.new_rat_cmd_support) { SEND_AT_CMD_ONCE_EXPECT_OK(SET_RAT_M1_CMD); } else { SEND_AT_CMD_ONCE_EXPECT_OK(SET_RAT_M1_CMD_LEGACY); } } else { /* MDM_RAT_CAT_NB1 */ if (iface_ctx.new_rat_cmd_support) { SEND_AT_CMD_ONCE_EXPECT_OK(SET_RAT_NB1_CMD); } else { SEND_AT_CMD_ONCE_EXPECT_OK(SET_RAT_NB1_CMD_LEGACY); } } error: allow_sleep(true); hl7800_unlock(); /* Changing the RAT causes the modem to reset. * A reset and reconfigure ensures the modem configuration and * state are valid. */ if (ret >= 0) { k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.mdm_reset_work, K_NO_WAIT); } return ret; } int32_t mdm_hl7800_get_local_time(struct tm *tm, int32_t *offset) { int ret; iface_ctx.local_time_valid = false; hl7800_lock(); wakeup_hl7800(); iface_ctx.last_socket_id = 0; ret = send_at_cmd(NULL, "AT+CCLK?", MDM_CMD_SEND_TIMEOUT, 0, false); allow_sleep(true); if (iface_ctx.local_time_valid) { memcpy(tm, &iface_ctx.local_time, sizeof(struct tm)); memcpy(offset, &iface_ctx.local_time_offset, sizeof(*offset)); } else { ret = -EIO; } hl7800_unlock(); return ret; } int32_t mdm_hl7800_get_operator_index(void) { int ret; hl7800_lock(); wakeup_hl7800(); iface_ctx.last_socket_id = 0; ret = send_at_cmd(NULL, "AT+KCARRIERCFG?", MDM_CMD_SEND_TIMEOUT, 0, false); allow_sleep(true); hl7800_unlock(); if (ret < 0) { return ret; } else { return iface_ctx.operator_index; } } int32_t mdm_hl7800_get_functionality(void) { int ret; hl7800_lock(); wakeup_hl7800(); iface_ctx.last_socket_id = 0; ret = send_at_cmd(NULL, "AT+CFUN?", MDM_CMD_SEND_TIMEOUT, 0, false); allow_sleep(true); hl7800_unlock(); if (ret < 0) { return ret; } else { return iface_ctx.functionality; } } int32_t mdm_hl7800_set_functionality(enum mdm_hl7800_functionality mode) { int ret; char buf[sizeof("AT+CFUN=###,0")] = { 0 }; hl7800_lock(); wakeup_hl7800(); snprintk(buf, sizeof(buf), "AT+CFUN=%u,0", mode); iface_ctx.last_socket_id = 0; ret = send_at_cmd(NULL, buf, MDM_CMD_SEND_TIMEOUT, MDM_DEFAULT_AT_CMD_RETRIES, false); allow_sleep(true); hl7800_unlock(); return ret; } #ifdef CONFIG_MODEM_HL7800_GPS int32_t mdm_hl7800_set_gps_rate(uint32_t rate) { int ret = -1; hl7800_lock(); wakeup_hl7800(); iface_ctx.gps_query_location_rate_seconds = rate; /* Stopping first allows changing the rate between two non-zero values. * Ignore error if GNSS isn't running. */ SEND_AT_CMD_IGNORE_ERROR("AT+GNSSSTOP"); if (rate == 0) { SEND_AT_CMD_EXPECT_OK("AT+CFUN=1,0"); } else { /* Navigation doesn't work when LTE is on. */ SEND_AT_CMD_EXPECT_OK("AT+CFUN=4,0"); SEND_AT_CMD_EXPECT_OK("AT+GNSSCONF=1,1"); if (IS_ENABLED(CONFIG_MODEM_HL7800_USE_GLONASS)) { SEND_AT_CMD_EXPECT_OK("AT+GNSSCONF=10,1"); } /* Enable all NMEA sentences */ SEND_AT_CMD_EXPECT_OK("AT+GNSSNMEA=0,1000,0,1FF"); /* Enable GPS */ SEND_AT_CMD_EXPECT_OK("AT+GNSSSTART=0"); } error: if (rate && ret == 0) { k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.gps_work, K_SECONDS(iface_ctx.gps_query_location_rate_seconds)); } else { k_work_cancel_delayable(&iface_ctx.gps_work); } LOG_DBG("GPS status: %d rate: %u", ret, rate); 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, &iface_ctx.mdm_rssi); event_handler(HL7800_EVENT_SINR, &iface_ctx.mdm_sinr); event_handler(HL7800_EVENT_APN_UPDATE, &iface_ctx.mdm_apn); event_handler(HL7800_EVENT_RAT, &iface_ctx.mdm_rat); event_handler(HL7800_EVENT_BANDS, iface_ctx.mdm_bands_string); event_handler(HL7800_EVENT_ACTIVE_BANDS, iface_ctx.mdm_active_bands_string); event_handler(HL7800_EVENT_REVISION, iface_ctx.mdm_revision); hl7800_unlock(); } uint32_t mdm_hl7800_log_filter_set(uint32_t level) { uint32_t new_log_level = 0; #ifdef CONFIG_LOG_RUNTIME_FILTERING new_log_level = log_filter_set(NULL, Z_LOG_LOCAL_DOMAIN_ID, log_source_id_get(STRINGIFY(LOG_MODULE_NAME)), level); #endif return new_log_level; } static int send_data(struct hl7800_socket *sock, struct net_pkt *pkt) { int ret; struct net_buf *frag; char dst_addr[NET_IPV6_ADDR_LEN]; char buf[sizeof("AT+KUDPSND=##,\"" IPV6_ADDR_FORMAT "\",#####,####")]; size_t send_len, actual_send_len; send_len = 0, actual_send_len = 0; if (!sock) { return -EINVAL; } sock->error = 0; sock->state = SOCK_TX; frag = pkt->frags; send_len = net_buf_frags_len(frag); /* start sending data */ k_sem_reset(&sock->sock_send_sem); if (sock->type == SOCK_STREAM) { snprintk(buf, sizeof(buf), "AT+KTCPSND=%d,%zu", sock->socket_id, send_len); } else { if (!net_addr_ntop(sock->family, &net_sin(&sock->dst)->sin_addr, dst_addr, sizeof(dst_addr))) { LOG_ERR("Invalid dst addr"); return -EINVAL; } snprintk(buf, sizeof(buf), "AT+KUDPSND=%d,\"%s\",%u,%zu", sock->socket_id, dst_addr, net_sin(&sock->dst)->sin_port, send_len); } send_at_cmd(sock, buf, K_NO_WAIT, 0, false); /* wait for CONNECT or error */ ret = k_sem_take(&sock->sock_send_sem, MDM_IP_SEND_RX_TIMEOUT); if (ret) { LOG_ERR("Err waiting for CONNECT (%d)", ret); goto done; } /* check for error */ if (sock->error != 0) { ret = sock->error; LOG_ERR("AT+K**PSND (%d)", ret); goto done; } /* Loop through packet data and send */ while (frag) { actual_send_len += frag->len; mdm_receiver_send(&iface_ctx.mdm_ctx, frag->data, frag->len); frag = frag->frags; } if (actual_send_len != send_len) { LOG_WRN("AT+K**PSND act: %zd exp: %zd", actual_send_len, send_len); } LOG_DBG("Sent %zu bytes", actual_send_len); /* Send EOF pattern to terminate data */ k_sem_reset(&sock->sock_send_sem); mdm_receiver_send(&iface_ctx.mdm_ctx, EOF_PATTERN, strlen(EOF_PATTERN)); ret = k_sem_take(&sock->sock_send_sem, MDM_IP_SEND_RX_TIMEOUT); if (ret == 0) { ret = sock->error; } else if (ret == -EAGAIN) { ret = -ETIMEDOUT; } done: if (sock->type == SOCK_STREAM) { if (sock->error == 0) { sock->state = SOCK_CONNECTED; } } else { sock->state = SOCK_IDLE; } return ret; } /*** NET_BUF HELPERS ***/ static bool is_crlf(uint8_t c) { if (c == '\n' || c == '\r') { return true; } else { return false; } } static void net_buf_skipcrlf(struct net_buf **buf) { /* chop off any /n or /r */ while (*buf && is_crlf(*(*buf)->data)) { net_buf_pull_u8(*buf); if (!(*buf)->len) { *buf = net_buf_frag_del(NULL, *buf); } } } static uint16_t net_buf_findcrlf(struct net_buf *buf, struct net_buf **frag) { uint16_t len = 0U, pos = 0U; while (buf && !is_crlf(*(buf->data + pos))) { if (pos + 1 >= buf->len) { len += buf->len; buf = buf->frags; pos = 0U; } else { pos++; } } if (buf && is_crlf(*(buf->data + pos))) { len += pos; *frag = buf; return len; } return 0; } static uint8_t net_buf_get_u8(struct net_buf **buf) { uint8_t val = net_buf_pull_u8(*buf); if (!(*buf)->len) { *buf = net_buf_frag_del(NULL, *buf); } return val; } static uint32_t net_buf_remove(struct net_buf **buf, uint32_t len) { uint32_t to_remove; uint32_t removed = 0; while (*buf && len > 0) { to_remove = (*buf)->len; if (to_remove > len) { to_remove = len; } net_buf_pull(*buf, to_remove); removed += to_remove; len -= to_remove; if (!(*buf)->len) { *buf = net_buf_frag_del(NULL, *buf); } } return removed; } /*** UDP / TCP Helper Function ***/ /* Setup IP header data to be used by some network applications. * While much is dummy data, some fields such as dst, port and family are * important. * Return the IP + protocol header length. */ static int pkt_setup_ip_data(struct net_pkt *pkt, struct hl7800_socket *sock) { int hdr_len = 0; uint16_t src_port = 0U, dst_port = 0U; #if defined(CONFIG_NET_TCP) struct net_tcp_hdr *tcp; #endif #if defined(CONFIG_NET_IPV6) if (net_pkt_family(pkt) == AF_INET6) { if (net_ipv6_create( pkt, &((struct sockaddr_in6 *)&sock->dst)->sin6_addr, &((struct sockaddr_in6 *)&sock->src)->sin6_addr)) { return -1; } src_port = ntohs(net_sin6(&sock->src)->sin6_port); dst_port = ntohs(net_sin6(&sock->dst)->sin6_port); hdr_len = sizeof(struct net_ipv6_hdr); } #endif #if defined(CONFIG_NET_IPV4) if (net_pkt_family(pkt) == AF_INET) { if (net_ipv4_create( pkt, &((struct sockaddr_in *)&sock->dst)->sin_addr, &((struct sockaddr_in *)&sock->src)->sin_addr)) { return -1; } src_port = ntohs(net_sin(&sock->src)->sin_port); dst_port = ntohs(net_sin(&sock->dst)->sin_port); hdr_len = sizeof(struct net_ipv4_hdr); } #endif #if defined(CONFIG_NET_UDP) if (sock->ip_proto == IPPROTO_UDP) { if (net_udp_create(pkt, dst_port, src_port)) { return -1; } hdr_len += NET_UDPH_LEN; } #endif #if defined(CONFIG_NET_TCP) if (sock->ip_proto == IPPROTO_TCP) { NET_PKT_DATA_ACCESS_DEFINE(tcp_access, struct net_tcp_hdr); tcp = (struct net_tcp_hdr *)net_pkt_get_data(pkt, &tcp_access); if (!tcp) { return -1; } (void)memset(tcp, 0, NET_TCPH_LEN); /* Setup TCP header */ tcp->src_port = dst_port; tcp->dst_port = src_port; if (net_pkt_set_data(pkt, &tcp_access)) { return -1; } hdr_len += NET_TCPH_LEN; } #endif /* CONFIG_NET_TCP */ return hdr_len; } /*** MODEM RESPONSE HANDLERS ***/ static uint32_t wait_for_modem_data(struct net_buf **buf, uint32_t current_len, uint32_t expected_len) { uint32_t waitForDataTries = 0; while ((current_len < expected_len) && (waitForDataTries < MDM_WAIT_FOR_DATA_RETRIES)) { LOG_DBG("cur:%d, exp:%d", current_len, expected_len); k_sleep(MDM_WAIT_FOR_DATA_TIME); current_len += hl7800_read_rx(buf); waitForDataTries++; } return current_len; } static uint32_t wait_for_modem_data_and_newline(struct net_buf **buf, uint32_t current_len, uint32_t expected_len) { return wait_for_modem_data(buf, current_len, (expected_len + strlen("\r\n"))); } /* Handler: AT+CGMI */ static bool on_cmd_atcmdinfo_manufacturer(struct net_buf **buf, uint16_t len) { struct net_buf *frag = NULL; size_t out_len; int len_no_null = MDM_MANUFACTURER_LENGTH - 1; /* make sure revision data is received * waiting for: Sierra Wireless\r\n */ wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), MDM_MANUFACTURER_LENGTH); frag = NULL; len = net_buf_findcrlf(*buf, &frag); if (!frag) { LOG_ERR("Unable to find mfg end"); goto done; } if (len < len_no_null) { LOG_WRN("mfg too short (len:%d)", len); } else if (len > len_no_null) { LOG_WRN("mfg too long (len:%d)", len); len = MDM_MANUFACTURER_LENGTH; } out_len = net_buf_linearize(iface_ctx.mdm_manufacturer, sizeof(iface_ctx.mdm_manufacturer) - 1, *buf, 0, len); iface_ctx.mdm_manufacturer[out_len] = 0; LOG_INF("Manufacturer: %s", iface_ctx.mdm_manufacturer); done: return true; } /* Handler: AT+CGMM */ static bool on_cmd_atcmdinfo_model(struct net_buf **buf, uint16_t len) { struct net_buf *frag = NULL; size_t out_len; int len_no_null = MDM_MODEL_LENGTH - 1; /* make sure model data is received * waiting for: HL7800\r\n */ wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), MDM_MODEL_LENGTH); frag = NULL; len = net_buf_findcrlf(*buf, &frag); if (!frag) { LOG_ERR("Unable to find model end"); goto done; } if (len < len_no_null) { LOG_WRN("model too short (len:%d)", len); } else if (len > len_no_null) { LOG_WRN("model too long (len:%d)", len); len = MDM_MODEL_LENGTH; } out_len = net_buf_linearize(iface_ctx.mdm_model, sizeof(iface_ctx.mdm_model) - 1, *buf, 0, len); iface_ctx.mdm_model[out_len] = 0; LOG_INF("Model: %s", iface_ctx.mdm_model); done: return true; } /* Handler: AT+CGMR */ static bool on_cmd_atcmdinfo_revision(struct net_buf **buf, uint16_t len) { struct net_buf *frag = NULL; size_t out_len; /* make sure revision data is received * waiting for something like: AHL7800.1.2.3.1.20171211\r\n */ wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), MDM_HL7800_REVISION_MAX_SIZE); frag = NULL; len = net_buf_findcrlf(*buf, &frag); if (!frag) { LOG_ERR("Unable to find rev end"); goto done; } if (len == 0) { LOG_WRN("revision not found"); } else if (len > MDM_HL7800_REVISION_MAX_STRLEN) { LOG_WRN("revision too long (len:%d)", len); len = MDM_HL7800_REVISION_MAX_STRLEN; } out_len = net_buf_linearize( iface_ctx.mdm_revision, sizeof(iface_ctx.mdm_revision) - 1, *buf, 0, len); iface_ctx.mdm_revision[out_len] = 0; LOG_INF("Revision: %s", iface_ctx.mdm_revision); event_handler(HL7800_EVENT_REVISION, iface_ctx.mdm_revision); done: return true; } /* Handler: AT+CGSN */ static bool on_cmd_atcmdinfo_imei(struct net_buf **buf, uint16_t len) { struct net_buf *frag = NULL; size_t out_len; /* make sure IMEI data is received * waiting for: ###############\r\n */ wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), MDM_HL7800_IMEI_SIZE); frag = NULL; len = net_buf_findcrlf(*buf, &frag); if (!frag) { LOG_ERR("Unable to find IMEI end"); goto done; } if (len < MDM_HL7800_IMEI_STRLEN) { LOG_WRN("IMEI too short (len:%d)", len); } else if (len > MDM_HL7800_IMEI_STRLEN) { LOG_WRN("IMEI too long (len:%d)", len); len = MDM_HL7800_IMEI_STRLEN; } out_len = net_buf_linearize(iface_ctx.mdm_imei, sizeof(iface_ctx.mdm_imei) - 1, *buf, 0, len); iface_ctx.mdm_imei[out_len] = 0; LOG_INF("IMEI: %s", iface_ctx.mdm_imei); done: return true; } /* Handler: +CCID: , * NOTE: EID will only be present for eSIM */ static bool on_cmd_atcmdinfo_iccid(struct net_buf **buf, uint16_t len) { char value[MDM_CCID_RESP_MAX_SIZE]; char *delim; int iccid_len; size_t out_len; out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len); value[out_len] = 0; LOG_DBG("+CCID: %s", value); if (len > MDM_HL7800_ICCID_MAX_STRLEN) { delim = strchr(value, ','); if (!delim) { LOG_ERR("Could not process +CCID"); return true; } /* Replace ',' with null so value contains two null terminated strings */ *delim = 0; LOG_INF("EID: %s", delim + 1); } iccid_len = strlen(value); strncpy(iface_ctx.mdm_iccid, value, sizeof(iface_ctx.mdm_iccid)); len = MIN(iccid_len, sizeof(iface_ctx.mdm_iccid) - 1); iface_ctx.mdm_iccid[len] = '\0'; if (iccid_len > len) { LOG_WRN("ICCID too long: %d (max %d)", iccid_len, len); } LOG_INF("ICCID: %s", iface_ctx.mdm_iccid); return true; } static bool on_cmd_atcmdinfo_imsi(struct net_buf **buf, uint16_t len) { struct net_buf *frag = NULL; size_t out_len; /* The handler for the IMSI is based on the command. * waiting for: \r\n */ wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), MDM_HL7800_IMSI_MIN_STR_SIZE); frag = NULL; len = net_buf_findcrlf(*buf, &frag); if (!frag) { LOG_ERR("Unable to find IMSI end"); goto done; } if (len > MDM_HL7800_IMSI_MAX_STRLEN) { LOG_WRN("IMSI too long (len:%d)", len); len = MDM_HL7800_IMSI_MAX_STRLEN; } out_len = net_buf_linearize(iface_ctx.mdm_imsi, MDM_HL7800_IMSI_MAX_STR_SIZE, *buf, 0, len); iface_ctx.mdm_imsi[out_len] = 0; if (strstr(iface_ctx.mdm_imsi, "ERROR") != NULL) { LOG_ERR("Unable to read IMSI"); memset(iface_ctx.mdm_imsi, 0, sizeof(iface_ctx.mdm_imsi)); } LOG_INF("IMSI: %s", iface_ctx.mdm_imsi); done: return true; } static void dns_work_cb(struct k_work *work) { #if defined(CONFIG_DNS_RESOLVER) && !defined(CONFIG_DNS_SERVER_IP_ADDRESSES) int ret; struct dns_resolve_context *dnsCtx; struct sockaddr temp_addr; bool valid_address = false; static const char *const dns_servers_str[] = { #ifdef CONFIG_NET_IPV6 iface_ctx.dns_v6_string, #endif #ifdef CONFIG_NET_IPV4 iface_ctx.dns_v4_string, #endif NULL}; #ifdef CONFIG_NET_IPV6 valid_address = net_ipaddr_parse(iface_ctx.dns_v6_string, strlen(iface_ctx.dns_v6_string), &temp_addr); if (!valid_address && IS_ENABLED(CONFIG_NET_IPV4)) { /* IPv6 DNS string is not valid, replace it with IPv4 address and recheck */ strncpy(iface_ctx.dns_v6_string, iface_ctx.dns_v4_string, strlen(iface_ctx.dns_v4_string)); valid_address = net_ipaddr_parse(iface_ctx.dns_v6_string, strlen(iface_ctx.dns_v6_string), &temp_addr); } #else valid_address = net_ipaddr_parse(iface_ctx.dns_v4_string, strlen(iface_ctx.dns_v4_string), &temp_addr); #endif if (!valid_address) { LOG_WRN("No valid DNS address!"); } else if (iface_ctx.iface && net_if_is_up(iface_ctx.iface) && !iface_ctx.dns_ready) { /* set new DNS addr in DNS resolver */ LOG_DBG("Refresh DNS resolver"); dnsCtx = dns_resolve_get_default(); ret = dns_resolve_reconfigure(dnsCtx, (const char **)dns_servers_str, NULL); if (ret < 0) { LOG_ERR("dns_resolve_init fail (%d)", ret); return; } iface_ctx.dns_ready = true; } #endif } char *mdm_hl7800_get_iccid(void) { return iface_ctx.mdm_iccid; } char *mdm_hl7800_get_sn(void) { return iface_ctx.mdm_sn; } char *mdm_hl7800_get_imei(void) { return iface_ctx.mdm_imei; } char *mdm_hl7800_get_fw_version(void) { return iface_ctx.mdm_revision; } char *mdm_hl7800_get_imsi(void) { return iface_ctx.mdm_imsi; } /* Convert HL7800 IPv6 address string in format * a01.a02.a03.a04.a05.a06.a07.a08.a09.a10.a11.a12.a13.a14.a15.a16 to * an IPv6 address. */ static int hl7800_net_addr6_pton(const char *src, struct in6_addr *dst) { int num_sections = 8; int i, len; uint16_t ipv6_section; len = strlen(src); for (i = 0; i < len; i++) { if (!(src[i] >= '0' && src[i] <= '9') && src[i] != '.') { return -EINVAL; } } for (i = 0; i < num_sections; i++) { if (!src || *src == '\0') { return -EINVAL; } ipv6_section = (uint16_t)strtol(src, NULL, 10); src = strchr(src, '.'); src++; if (!src || *src == '\0') { return -EINVAL; } ipv6_section = (ipv6_section << 8) | (uint16_t)strtol(src, NULL, 10); UNALIGNED_PUT(htons(ipv6_section), &dst->s6_addr16[i]); src = strchr(src, '.'); if (src) { src++; } else { if (i < num_sections - 1) { return -EINVAL; } } } return 0; } /* Handler: +CGCONTRDP: ,,,, * ,, */ static bool on_cmd_atcmdinfo_ipaddr(struct net_buf **buf, uint16_t len) { int ret; int num_delims = CGCONTRDP_RESPONSE_NUM_DELIMS; char *delims[CGCONTRDP_RESPONSE_NUM_DELIMS]; size_t out_len; char value[MDM_IP_INFO_RESP_SIZE]; char *search_start, *addr_start, *sm_start; struct in_addr new_ipv4_addr; struct in6_addr new_ipv6_addr; bool is_ipv4; int addr_len; char temp_addr_str[HL7800_IPV6_ADDR_LEN]; k_timeout_t delay; out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len); value[out_len] = 0; search_start = value; LOG_DBG("IP info: %s", value); /* find all delimiters (,) */ for (int i = 0; i < num_delims; i++) { delims[i] = strchr(search_start, ','); if (!delims[i]) { LOG_ERR("Could not find delim %d, val: %s", i, value); return true; } /* Start next search after current delim location */ search_start = delims[i] + 1; } /* determine if IPv4 or IPv6 by checking length of ip address plus * gateway string. */ is_ipv4 = false; addr_len = delims[3] - delims[2]; LOG_DBG("IP string len: %d", addr_len); if (addr_len <= (NET_IPV4_ADDR_LEN * 2)) { is_ipv4 = true; } /* Find start of subnet mask */ addr_start = delims[2] + 1; if (is_ipv4) { num_delims = 4; } else { num_delims = 16; } search_start = addr_start; sm_start = addr_start; for (int i = 0; i < num_delims; i++) { sm_start = strchr(search_start, '.'); if (!sm_start) { LOG_ERR("Could not find submask start"); return true; } /* Start next search after current delim location */ search_start = sm_start + 1; } /* get new IP addr */ addr_len = sm_start - addr_start; strncpy(temp_addr_str, addr_start, addr_len); temp_addr_str[addr_len] = 0; LOG_DBG("IP addr: %s", temp_addr_str); if (is_ipv4) { ret = net_addr_pton(AF_INET, temp_addr_str, &new_ipv4_addr); } else { ret = hl7800_net_addr6_pton(temp_addr_str, &new_ipv6_addr); } if (ret < 0) { LOG_ERR("Invalid IP addr"); return true; } if (is_ipv4) { /* move past the '.' */ sm_start += 1; /* store new subnet mask */ addr_len = delims[3] - sm_start; strncpy(temp_addr_str, sm_start, addr_len); temp_addr_str[addr_len] = 0; ret = net_addr_pton(AF_INET, temp_addr_str, &iface_ctx.subnet); if (ret < 0) { LOG_ERR("Invalid subnet"); return true; } /* store new gateway */ addr_start = delims[3] + 1; addr_len = delims[4] - addr_start; strncpy(temp_addr_str, addr_start, addr_len); temp_addr_str[addr_len] = 0; ret = net_addr_pton(AF_INET, temp_addr_str, &iface_ctx.gateway); if (ret < 0) { LOG_ERR("Invalid gateway"); return true; } } /* store new dns */ addr_start = delims[4] + 1; addr_len = delims[5] - addr_start; strncpy(temp_addr_str, addr_start, addr_len); temp_addr_str[addr_len] = 0; if (is_ipv4) { ret = strncmp(temp_addr_str, iface_ctx.dns_v4_string, addr_len); if (ret != 0) { iface_ctx.dns_ready = false; } strncpy(iface_ctx.dns_v4_string, addr_start, addr_len); iface_ctx.dns_v4_string[addr_len] = 0; ret = net_addr_pton(AF_INET, iface_ctx.dns_v4_string, &iface_ctx.dns_v4); LOG_DBG("IPv4 DNS addr: %s", iface_ctx.dns_v4_string); } #ifdef CONFIG_NET_IPV6 else { ret = strncmp(temp_addr_str, iface_ctx.dns_v6_string, addr_len); if (ret != 0) { iface_ctx.dns_ready = false; } /* store HL7800 formatted IPv6 DNS string temporarily */ strncpy(iface_ctx.dns_v6_string, addr_start, addr_len); ret = hl7800_net_addr6_pton(iface_ctx.dns_v6_string, &iface_ctx.dns_v6); net_addr_ntop(AF_INET6, &iface_ctx.dns_v6, iface_ctx.dns_v6_string, sizeof(iface_ctx.dns_v6_string)); LOG_DBG("IPv6 DNS addr: %s", iface_ctx.dns_v6_string); } #endif if (ret < 0) { LOG_ERR("Invalid dns"); return true; } if (iface_ctx.iface) { if (is_ipv4) { #ifdef CONFIG_NET_IPV4 /* remove the current IPv4 addr before adding a new one. * We dont care if it is successful or not. */ net_if_ipv4_addr_rm(iface_ctx.iface, &iface_ctx.ipv4Addr); if (!net_if_ipv4_addr_add(iface_ctx.iface, &new_ipv4_addr, NET_ADDR_DHCP, 0)) { LOG_ERR("Cannot set iface IPv4 addr"); } net_if_ipv4_set_netmask(iface_ctx.iface, &iface_ctx.subnet); net_if_ipv4_set_gw(iface_ctx.iface, &iface_ctx.gateway); #endif /* store the new IP addr */ net_ipaddr_copy(&iface_ctx.ipv4Addr, &new_ipv4_addr); } else { #if CONFIG_NET_IPV6 net_if_ipv6_addr_rm(iface_ctx.iface, &iface_ctx.ipv6Addr); if (!net_if_ipv6_addr_add(iface_ctx.iface, &new_ipv6_addr, NET_ADDR_AUTOCONF, 0)) { LOG_ERR("Cannot set iface IPv6 addr"); } #endif } /* start DNS update work */ delay = K_NO_WAIT; if (!iface_ctx.initialized) { /* Delay this in case the network * stack is still starting up */ delay = K_SECONDS(DNS_WORK_DELAY_SECS); } k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.dns_work, delay); } else { LOG_ERR("iface NULL"); } return true; } /* Handler1: +COPS: [,,[,]] * * Handler2: * +COPS: [list of supported (, long alphanumeric , short * alphanumeric , numeric [,< AcT>])s][,, * (list of supported s),(list of supported s)] */ static bool on_cmd_atcmdinfo_operator_status(struct net_buf **buf, uint16_t len) { size_t out_len; char value[MDM_MAX_RESP_SIZE]; int num_delims = COPS_RESPONSE_NUM_DELIMS; char *delims[COPS_RESPONSE_NUM_DELIMS]; char *search_start; int i; out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len); value[out_len] = 0; /* For AT+COPS=?, result is most likely longer than size of log string */ if (strchr(value, '(') != NULL) { LOG_HEXDUMP_DBG(value, out_len, "Operator: "); goto done; } else { LOG_INF("Operator: %s", value); } /* Process AT+COPS? */ if (len == 1) { /* only mode was returned, there is no operator info */ iface_ctx.operator_status = NO_OPERATOR; goto done; } search_start = value; /* find all delimiters (,) */ for (i = 0; i < num_delims; i++) { delims[i] = strchr(search_start, ','); if (!delims[i]) { LOG_ERR("Could not find delim %d, val: %s", i, value); goto done; } /* Start next search after current delim location */ search_start = delims[i] + 1; } /* we found both delimiters, that means we have an operator */ iface_ctx.operator_status = REGISTERED; done: return true; } /* Handler: +KGSN: T5640400011101 */ static bool on_cmd_atcmdinfo_serial_number(struct net_buf **buf, uint16_t len) { struct net_buf *frag = NULL; char value[MDM_SN_RESPONSE_LENGTH]; size_t out_len; int sn_len; char *val_start; /* make sure SN# data is received. * we are waiting for: +KGSN: ##############\r\n */ wait_for_modem_data(buf, net_buf_frags_len(*buf), MDM_SN_RESPONSE_LENGTH); frag = NULL; len = net_buf_findcrlf(*buf, &frag); if (!frag) { LOG_ERR("Unable to find sn end"); goto done; } /* get msg data */ out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len); value[out_len] = 0; /* find ':' */ val_start = strchr(value, ':'); if (!val_start) { LOG_ERR("Unable to find sn ':'"); goto done; } /* Remove ": " chars */ val_start += 2; sn_len = len - (val_start - value); if (sn_len < MDM_HL7800_SERIAL_NUMBER_STRLEN) { LOG_WRN("sn too short (len:%d)", sn_len); } else if (sn_len > MDM_HL7800_SERIAL_NUMBER_STRLEN) { LOG_WRN("sn too long (len:%d)", sn_len); sn_len = MDM_HL7800_SERIAL_NUMBER_STRLEN; } strncpy(iface_ctx.mdm_sn, val_start, sn_len); iface_ctx.mdm_sn[sn_len] = 0; LOG_INF("Serial #: %s", iface_ctx.mdm_sn); done: return true; } /* Handler: +KSRAT: # */ static bool on_cmd_radio_tech_status(struct net_buf **buf, uint16_t len) { size_t out_len; char value[MDM_MAX_RESP_SIZE]; out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len); value[out_len] = 0; iface_ctx.mdm_rat = strtol(value, NULL, 10); LOG_INF("+KSRAT: %d", iface_ctx.mdm_rat); event_handler(HL7800_EVENT_RAT, &iface_ctx.mdm_rat); return true; } /* Handler: +KBNDCFG: #,####################### */ static bool on_cmd_radio_band_configuration(struct net_buf **buf, uint16_t len) { size_t out_len; char value[MDM_MAX_RESP_SIZE]; char n_tmp[sizeof("#########")]; out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len); value[out_len] = 0; if (value[0] != (iface_ctx.mdm_rat == MDM_RAT_CAT_M1 ? '0' : '1')) { /* Invalid RAT */ return true; } else if (strlen(value) < sizeof("#,###################")) { /* String size too short */ return true; } memcpy(iface_ctx.mdm_bands_string, &value[MDM_TOP_BAND_START_POSITION], MDM_HL7800_LTE_BAND_STRLEN); memcpy(n_tmp, &value[MDM_TOP_BAND_START_POSITION], MDM_TOP_BAND_SIZE); n_tmp[MDM_TOP_BAND_SIZE] = 0; iface_ctx.mdm_bands_top = strtoul(n_tmp, NULL, 16); memcpy(n_tmp, &value[MDM_MIDDLE_BAND_START_POSITION], MDM_MIDDLE_BAND_SIZE); n_tmp[MDM_MIDDLE_BAND_SIZE] = 0; iface_ctx.mdm_bands_middle = strtoul(n_tmp, NULL, 16); memcpy(n_tmp, &value[MDM_BOTTOM_BAND_START_POSITION], MDM_BOTTOM_BAND_SIZE); n_tmp[MDM_BOTTOM_BAND_SIZE] = 0; iface_ctx.mdm_bands_bottom = strtoul(n_tmp, NULL, 16); LOG_INF("Current band configuration: %04x %08x %08x", iface_ctx.mdm_bands_top, iface_ctx.mdm_bands_middle, iface_ctx.mdm_bands_bottom); event_handler(HL7800_EVENT_BANDS, iface_ctx.mdm_bands_string); return true; } /* Handler: +KBND: #,####################### */ static bool on_cmd_radio_active_bands(struct net_buf **buf, uint16_t len) { size_t out_len; char value[MDM_MAX_RESP_SIZE]; out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len); value[out_len] = 0; if (strlen(value) < sizeof("#,###################")) { /* String size too short */ return true; } memcpy(iface_ctx.mdm_active_bands_string, &value[MDM_TOP_BAND_START_POSITION], MDM_HL7800_LTE_BAND_STRLEN); event_handler(HL7800_EVENT_ACTIVE_BANDS, iface_ctx.mdm_active_bands_string); return true; } static char *get_startup_state_string(enum mdm_hl7800_startup_state state) { /* clang-format off */ switch (state) { PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, READY); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, WAITING_FOR_ACCESS_CODE); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, SIM_NOT_PRESENT); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, SIMLOCK); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, UNRECOVERABLE_ERROR); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, UNKNOWN); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_STARTUP_STATE, INACTIVE_SIM); default: return "UNKNOWN"; } /* clang-format on */ } static void set_startup_state(enum mdm_hl7800_startup_state state) { iface_ctx.mdm_startup_state = state; generate_startup_state_event(); } static void generate_startup_state_event(void) { struct mdm_hl7800_compound_event event; event.code = iface_ctx.mdm_startup_state; event.string = get_startup_state_string(iface_ctx.mdm_startup_state); LOG_INF("Startup State: %s", event.string); event_handler(HL7800_EVENT_STARTUP_STATE_CHANGE, &event); } int mdm_hl7800_set_desired_sleep_level(enum mdm_hl7800_sleep level) { int r = -EPERM; #if CONFIG_MODEM_HL7800_LOW_POWER_MODE switch (level) { case HL7800_SLEEP_AWAKE: case HL7800_SLEEP_HIBERNATE: case HL7800_SLEEP_LITE_HIBERNATE: case HL7800_SLEEP_SLEEP: iface_ctx.desired_sleep_level = level; r = 0; break; default: r = -EINVAL; } if (r == 0) { hl7800_lock(); wakeup_hl7800(); r = set_sleep_level(); allow_sleep(true); hl7800_unlock(); } #endif return r; } #ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE static void initialize_sleep_level(void) { if (iface_ctx.desired_sleep_level == HL7800_SLEEP_UNINITIALIZED) { if (IS_ENABLED(CONFIG_MODEM_HL7800_SLEEP_LEVEL_HIBERNATE)) { iface_ctx.desired_sleep_level = HL7800_SLEEP_HIBERNATE; } else if (IS_ENABLED(CONFIG_MODEM_HL7800_SLEEP_LEVEL_LITE_HIBERNATE)) { iface_ctx.desired_sleep_level = HL7800_SLEEP_LITE_HIBERNATE; } else if (IS_ENABLED(CONFIG_MODEM_HL7800_SLEEP_LEVEL_SLEEP)) { iface_ctx.desired_sleep_level = HL7800_SLEEP_SLEEP; } else { iface_ctx.desired_sleep_level = HL7800_SLEEP_AWAKE; } } } 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 1 means the HL7800 decides when it enters sleep mode */ switch (iface_ctx.desired_sleep_level) { case HL7800_SLEEP_HIBERNATE: snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 1, 2, delay); break; case HL7800_SLEEP_LITE_HIBERNATE: snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 1, 1, delay); break; case HL7800_SLEEP_SLEEP: snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 1, 0, delay); break; default: /* don't sleep */ snprintk(cmd, sizeof(cmd), SLEEP_CMD_FMT, 2, 0, delay); break; } SEND_AT_CMD_EXPECT_OK(cmd); error: return ret; } #endif /* CONFIG_MODEM_HL7800_LOW_POWER_MODE */ static char *get_sleep_state_string(enum mdm_hl7800_sleep state) { /* clang-format off */ switch (state) { PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, UNINITIALIZED); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, HIBERNATE); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, LITE_HIBERNATE); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, SLEEP); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_SLEEP, AWAKE); default: return "UNKNOWN"; } /* clang-format on */ } static void set_sleep_state(enum mdm_hl7800_sleep state) { iface_ctx.sleep_state = state; if (iface_ctx.sleep_state != HL7800_SLEEP_AWAKE) { k_sem_reset(&iface_ctx.mdm_awake); } generate_sleep_state_event(); } static void generate_sleep_state_event(void) { struct mdm_hl7800_compound_event event; event.code = iface_ctx.sleep_state; event.string = get_sleep_state_string(iface_ctx.sleep_state); LOG_INF("Sleep State: %s", event.string); event_handler(HL7800_EVENT_SLEEP_STATE_CHANGE, &event); } #ifdef CONFIG_MODEM_HL7800_FW_UPDATE static char *get_fota_state_string(enum mdm_hl7800_fota_state state) { /* clang-format off */ switch (state) { PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, IDLE); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, START); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, WIP); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, PAD); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, SEND_EOT); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, FILE_ERROR); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, INSTALL); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, REBOOT_AND_RECONFIGURE); PREFIXED_SWITCH_CASE_RETURN_STRING(HL7800_FOTA, COMPLETE); default: return "UNKNOWN"; } /* clang-format on */ } static void set_fota_state(enum mdm_hl7800_fota_state state) { LOG_INF("FOTA state: %s->%s", get_fota_state_string(iface_ctx.fw_update_state), get_fota_state_string(state)); iface_ctx.fw_update_state = state; generate_fota_state_event(); } static void generate_fota_state_event(void) { struct mdm_hl7800_compound_event event; event.code = iface_ctx.fw_update_state; event.string = get_fota_state_string(iface_ctx.fw_update_state); event_handler(HL7800_EVENT_FOTA_STATE, &event); } static void generate_fota_count_event(void) { uint32_t count = iface_ctx.fw_packet_count * XMODEM_DATA_SIZE; event_handler(HL7800_EVENT_FOTA_COUNT, &count); } #endif /* Handler: +KSUP: # */ static bool on_cmd_startup_report(struct net_buf **buf, uint16_t len) { size_t out_len; char value[MDM_MAX_RESP_SIZE]; memset(value, 0, sizeof(value)); out_len = net_buf_linearize(value, sizeof(value), *buf, 0, len); if (out_len > 0) { set_startup_state(strtol(value, NULL, 10)); } else { set_startup_state(HL7800_STARTUP_STATE_UNKNOWN); } #ifdef CONFIG_MODEM_HL7800_FW_UPDATE if (iface_ctx.fw_updated) { iface_ctx.fw_updated = false; set_fota_state(HL7800_FOTA_REBOOT_AND_RECONFIGURE); /* issue reset after a firmware update to reconfigure modem state */ k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.mdm_reset_work, K_NO_WAIT); } else #endif { PRINT_AWAKE_MSG; iface_ctx.wait_for_KSUP = false; iface_ctx.mdm_startup_reporting_on = true; iface_ctx.reconfig_IP_connection = true; #ifdef CONFIG_MODEM_HL7800_LOW_POWER_MODE mark_sockets_for_reconfig(); #endif set_sleep_state(HL7800_SLEEP_AWAKE); k_sem_give(&iface_ctx.mdm_awake); } return true; } static bool profile_handler(struct net_buf **buf, uint16_t len, bool active_profile) { uint32_t size; int echo_state = -1; struct net_buf *frag = NULL; uint16_t line_length; char line[MAX_PROFILE_LINE_LENGTH]; size_t output_length; /* Prepare net buffer for this parser. */ net_buf_remove(buf, len); net_buf_skipcrlf(buf); size = wait_for_modem_data(buf, net_buf_frags_len(*buf), sizeof(PROFILE_LINE_1)); net_buf_skipcrlf(buf); /* remove any \r\n that are in the front */ /* Parse configuration data to determine if echo is on/off. */ line_length = net_buf_findcrlf(*buf, &frag); if (line_length) { memset(line, 0, sizeof(line)); output_length = net_buf_linearize(line, SIZE_WITHOUT_NUL(line), *buf, 0, line_length); LOG_DBG("length: %u: %s", line_length, line); /* Echo on off is the first thing on the line: E0, E1 */ if (output_length >= SIZE_WITHOUT_NUL("E?")) { echo_state = (line[1] == '1') ? 1 : 0; } } LOG_DBG("echo: %d", echo_state); net_buf_remove(buf, line_length); net_buf_skipcrlf(buf); if (active_profile) { iface_ctx.mdm_echo_is_on = (echo_state != 0); } /* Discard next line. This waits for the longest possible response even * though most registers won't have the value 0xFF. */ size = wait_for_modem_data(buf, net_buf_frags_len(*buf), sizeof(PROFILE_LINE_2)); net_buf_skipcrlf(buf); len = net_buf_findcrlf(*buf, &frag); net_buf_remove(buf, len); net_buf_skipcrlf(buf); return false; } static bool on_cmd_atcmdinfo_active_profile(struct net_buf **buf, uint16_t len) { return profile_handler(buf, len, true); } static bool on_cmd_atcmdinfo_stored_profile0(struct net_buf **buf, uint16_t len) { return profile_handler(buf, len, false); } static bool on_cmd_atcmdinfo_stored_profile1(struct net_buf **buf, uint16_t len) { return profile_handler(buf, len, false); } /* +WPPP: 1,1,"username","password" */ static bool on_cmd_atcmdinfo_pdp_authentication_cfg(struct net_buf **buf, uint16_t len) { struct net_buf *frag = NULL; uint16_t line_length; char line[MDM_HL7800_APN_CMD_MAX_SIZE]; size_t output_length; size_t i; char *p; wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), MDM_HL7800_APN_CMD_MAX_SIZE); line_length = net_buf_findcrlf(*buf, &frag); if (line_length) { memset(line, 0, sizeof(line)); output_length = net_buf_linearize(line, SIZE_WITHOUT_NUL(line), *buf, 0, line_length); LOG_DBG("length: %u: %s", line_length, line); if (output_length > 0) { memset(iface_ctx.mdm_apn.username, 0, sizeof(iface_ctx.mdm_apn.username)); memset(iface_ctx.mdm_apn.password, 0, sizeof(iface_ctx.mdm_apn.password)); i = 0; p = strchr(line, '"'); if (p != NULL) { p += 1; i = 0; while ((p != NULL) && (*p != '"') && (i < MDM_HL7800_APN_USERNAME_MAX_STRLEN)) { iface_ctx.mdm_apn.username[i++] = *p++; } } LOG_INF("APN Username: %s", iface_ctx.mdm_apn.username); p = strchr(p + 1, '"'); if (p != NULL) { p += 1; i = 0; while ((p != NULL) && (*p != '"') && (i < MDM_HL7800_APN_PASSWORD_MAX_STRLEN)) { iface_ctx.mdm_apn.password[i++] = *p++; } } LOG_INF("APN Password: %s", iface_ctx.mdm_apn.password); } } net_buf_remove(buf, line_length); net_buf_skipcrlf(buf); return false; } /* Only context 1 is used. Other contexts are unhandled. * * +CGDCONT: 1,"IP","access point name",,0,0,0,0,0,,0,,,,, */ static bool on_cmd_atcmdinfo_pdp_context(struct net_buf **buf, uint16_t len) { struct net_buf *frag = NULL; uint16_t line_length; char line[MDM_HL7800_APN_CMD_MAX_SIZE]; size_t output_length; char *p; size_t i; wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), MDM_HL7800_APN_CMD_MAX_SIZE); line_length = net_buf_findcrlf(*buf, &frag); if (line_length) { memset(line, 0, sizeof(line)); output_length = net_buf_linearize(line, SIZE_WITHOUT_NUL(line), *buf, 0, line_length); LOG_DBG("length: %u: %s", line_length, line); if (output_length > 0) { memset(iface_ctx.mdm_apn.value, 0, sizeof(iface_ctx.mdm_apn.value)); memset(iface_ctx.mdm_pdp_addr_fam, 0, MDM_ADDR_FAM_MAX_LEN); /* Address family after first , */ p = strchr(line, ','); if (p == NULL) { LOG_WRN("Issue parsing APN response"); goto done; } p += 2; i = 0; while ((p != NULL) && (*p != '"') && (i < MDM_ADDR_FAM_MAX_LEN)) { iface_ctx.mdm_pdp_addr_fam[i++] = *p++; } if (strcmp(iface_ctx.mdm_pdp_addr_fam, ADDRESS_FAMILY_IP) == 0) { snprintk(iface_ctx.mdm_pdp_addr_fam, sizeof(iface_ctx.mdm_pdp_addr_fam), "%s", ADDRESS_FAMILY_IPV4); } LOG_DBG("PDP address family: %s", iface_ctx.mdm_pdp_addr_fam); /* APN after second , " */ p = strchr(p, ','); if (p == NULL) { LOG_WRN("Issue parsing APN response"); goto done; } p++; if (*p == ',') { /* APN is blank */ goto done; } if (*p == '"') { p++; i = 0; while ((p != NULL) && (*p != '"') && (i < MDM_HL7800_APN_MAX_STRLEN)) { iface_ctx.mdm_apn.value[i++] = *p++; } } LOG_INF("APN: %s", iface_ctx.mdm_apn.value); } } done: net_buf_remove(buf, line_length); net_buf_skipcrlf(buf); return false; } static int hl7800_query_rssi(void) { int ret; ret = send_at_cmd(NULL, "AT+KCELLMEAS=0", MDM_CMD_SEND_TIMEOUT, 1, false); if (ret < 0) { LOG_ERR("AT+KCELLMEAS ret:%d", ret); } return ret; } static void hl7800_start_rssi_work(void) { /* Rate is not checked here to allow one reading * when going from network down->up */ k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.rssi_query_work, K_NO_WAIT); } static void hl7800_stop_rssi_work(void) { int rc; rc = k_work_cancel_delayable(&iface_ctx.rssi_query_work); if (rc != 0) { LOG_ERR("Could not cancel RSSI work [%d]", rc); } } static void rssi_query(void) { hl7800_lock(); wakeup_hl7800(); hl7800_query_rssi(); allow_sleep(true); hl7800_unlock(); } static void hl7800_rssi_query_work(struct k_work *work) { rssi_query(); /* re-start RSSI query work */ if (CONFIG_MODEM_HL7800_RSSI_RATE_SECONDS > 0) { k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.rssi_query_work, K_SECONDS(CONFIG_MODEM_HL7800_RSSI_RATE_SECONDS)); } } #ifdef CONFIG_MODEM_HL7800_GPS /* Unsolicited notification * Handler: +GNSSEV: , */ 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 (iface_ctx.gps_query_location_rate_seconds) { k_work_reschedule_for_queue(&hl7800_workq, &iface_ctx.gps_work, K_SECONDS(iface_ctx.gps_query_location_rate_seconds)); } } /* The AT+GNSSLOC? command returns 1 of 2 things: * * +GNSSLOC: * Latitude: "49 Deg 10 Min 21.49 Sec N" * Longitude: "123 Deg 4 Min 14.76 Sec W" * GpsTime: "yyyy mm dd hh:mm:ss" * FixType: "2D" or "3D" * HEPE: "8.485 m" (Horizontal Estimated Position Error) * Altitude: "-1 m" * AltUnc: "3.0 m" * Direction: "0.0 deg" * HorSpeed: "0.0 m/s" * VerSpeed: "0.0 m/s" * OK * * OR * * +GNSSLOC: * FIX NOT AVAILABLE * OK * * Since each response is on its own line, the command handler is used * to handle each one as an individual response. */ static bool gps_handler(struct net_buf **buf, uint16_t len, enum mdm_hl7800_gps_string_types str_type) { struct mdm_hl7800_compound_event event; char gps_str[MDM_HL7800_MAX_GPS_STR_SIZE]; size_t gps_len = sizeof(gps_str) - 1; struct net_buf *frag = NULL; size_t out_len; wait_for_modem_data_and_newline(buf, net_buf_frags_len(*buf), sizeof(gps_str)); frag = NULL; len = net_buf_findcrlf(*buf, &frag); if (!frag) { LOG_ERR("Unable to find end"); goto done; } if (len > gps_len) { LOG_WRN("GPS string too long (len:%d)", len); len = gps_len; } out_len = net_buf_linearize(gps_str, gps_len, *buf, 0, len); gps_str[out_len] = 0; event.code = str_type; event.string = gps_str; event_handler(HL7800_EVENT_GPS, &event); done: return true; } static bool on_cmd_latitude(struct net_buf **buf, uint16_t len) { return gps_handler(buf, len, HL7800_GPS_STR_LATITUDE); } static bool on_cmd_longitude(struct net_buf **buf, uint16_t len) { return gps_handler(buf, len, HL7800_GPS_STR_LONGITUDE); } static bool on_cmd_gps_time(struct net_buf **buf, uint16_t len) { return gps_handler(buf, len, HL7800_GPS_STR_GPS_TIME); } static bool on_cmd_fix_type(struct net_buf **buf, uint16_t len) { return gps_handler(buf, len, HL7800_GPS_STR_FIX_TYPE); } static bool on_cmd_hepe(struct net_buf **buf, uint16_t len) { return gps_handler(buf, len, HL7800_GPS_STR_HEPE); } static bool on_cmd_altitude(struct net_buf **buf, uint16_t len) { return gps_handler(buf, len, HL7800_GPS_STR_ALTITUDE); } static bool on_cmd_alt_unc(struct net_buf **buf, uint16_t len) { return gps_handler(buf, len, HL7800_GPS_STR_ALT_UNC); } static bool on_cmd_direction(struct net_buf **buf, uint16_t len) { return gps_handler(buf, len, HL7800_GPS_STR_DIRECTION); } static bool on_cmd_hor_speed(struct net_buf **buf, uint16_t len) { return gps_handler(buf, len, HL7800_GPS_STR_HOR_SPEED); } static bool on_cmd_ver_speed(struct net_buf **buf, uint16_t len) { return gps_handler(buf, len, HL7800_GPS_STR_VER_SPEED); } #endif /* CONFIG_MODEM_HL7800_GPS */ #ifdef CONFIG_MODEM_HL7800_POLTE /* Handler: %POLTEEVU: "REGISTER",0, , */ 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", */ 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",[,,,