You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2239 lines
54 KiB
2239 lines
54 KiB
/* |
|
* Copyright (c) 2019-2020 Foundries.io |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT u_blox_sara_r4 |
|
|
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(modem_ublox_sara_r4, CONFIG_MODEM_LOG_LEVEL); |
|
|
|
#include <zephyr/kernel.h> |
|
#include <ctype.h> |
|
#include <errno.h> |
|
#include <zephyr/drivers/gpio.h> |
|
#include <zephyr/device.h> |
|
#include <zephyr/init.h> |
|
#include <zephyr/posix/fcntl.h> |
|
|
|
#include <zephyr/net/net_if.h> |
|
#include <zephyr/net/net_offload.h> |
|
#include <zephyr/net/offloaded_netdev.h> |
|
#include <zephyr/net/socket_offload.h> |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_AUTODETECT_APN) |
|
#include <stdio.h> |
|
#endif |
|
|
|
#include "modem_context.h" |
|
#include "modem_socket.h" |
|
#include "modem_cmd_handler.h" |
|
#include "modem_iface_uart.h" |
|
|
|
#if !defined(CONFIG_MODEM_UBLOX_SARA_R4_MANUAL_MCCMNO) |
|
#define CONFIG_MODEM_UBLOX_SARA_R4_MANUAL_MCCMNO "" |
|
#endif |
|
|
|
|
|
#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) |
|
#include "tls_internal.h" |
|
#include <zephyr/net/tls_credentials.h> |
|
#endif |
|
|
|
/* pin settings */ |
|
static const struct gpio_dt_spec power_gpio = GPIO_DT_SPEC_INST_GET(0, mdm_power_gpios); |
|
#if DT_INST_NODE_HAS_PROP(0, mdm_reset_gpios) |
|
static const struct gpio_dt_spec reset_gpio = GPIO_DT_SPEC_INST_GET(0, mdm_reset_gpios); |
|
#endif |
|
#if DT_INST_NODE_HAS_PROP(0, mdm_vint_gpios) |
|
static const struct gpio_dt_spec vint_gpio = GPIO_DT_SPEC_INST_GET(0, mdm_vint_gpios); |
|
#endif |
|
|
|
#define MDM_UART_NODE DT_INST_BUS(0) |
|
#define MDM_UART_DEV DEVICE_DT_GET(MDM_UART_NODE) |
|
|
|
#define MDM_RESET_NOT_ASSERTED 1 |
|
#define MDM_RESET_ASSERTED 0 |
|
|
|
#define MDM_CMD_TIMEOUT K_SECONDS(10) |
|
#define MDM_DNS_TIMEOUT K_SECONDS(70) |
|
#define MDM_CMD_CONN_TIMEOUT K_SECONDS(120) |
|
#define MDM_REGISTRATION_TIMEOUT K_SECONDS(180) |
|
#define MDM_PROMPT_CMD_DELAY K_MSEC(50) |
|
|
|
#define MDM_MAX_DATA_LENGTH 1024 |
|
#define MDM_RECV_MAX_BUF 30 |
|
#define MDM_RECV_BUF_SIZE 128 |
|
|
|
#define MDM_MAX_SOCKETS 6 |
|
#define MDM_BASE_SOCKET_NUM 0 |
|
|
|
#define MDM_NETWORK_RETRY_COUNT 3 |
|
#define MDM_WAIT_FOR_RSSI_COUNT 10 |
|
#define MDM_WAIT_FOR_RSSI_DELAY K_SECONDS(2) |
|
|
|
#define MDM_MANUFACTURER_LENGTH 10 |
|
#define MDM_MODEL_LENGTH 16 |
|
#define MDM_REVISION_LENGTH 64 |
|
#define MDM_IMEI_LENGTH 16 |
|
#define MDM_IMSI_LENGTH 16 |
|
#define MDM_APN_LENGTH 32 |
|
#define MDM_MAX_CERT_LENGTH 8192 |
|
#if defined(CONFIG_MODEM_UBLOX_SARA_AUTODETECT_VARIANT) |
|
#define MDM_VARIANT_UBLOX_R4 4 |
|
#define MDM_VARIANT_UBLOX_U2 2 |
|
#endif |
|
|
|
NET_BUF_POOL_DEFINE(mdm_recv_pool, MDM_RECV_MAX_BUF, MDM_RECV_BUF_SIZE, |
|
0, NULL); |
|
|
|
/* RX thread structures */ |
|
K_KERNEL_STACK_DEFINE(modem_rx_stack, |
|
CONFIG_MODEM_UBLOX_SARA_R4_RX_STACK_SIZE); |
|
struct k_thread modem_rx_thread; |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_RSSI_WORK) |
|
/* RX thread work queue */ |
|
K_KERNEL_STACK_DEFINE(modem_workq_stack, |
|
CONFIG_MODEM_UBLOX_SARA_R4_RX_WORKQ_STACK_SIZE); |
|
static struct k_work_q modem_workq; |
|
#endif |
|
|
|
/* socket read callback data */ |
|
struct socket_read_data { |
|
char *recv_buf; |
|
size_t recv_buf_len; |
|
struct sockaddr *recv_addr; |
|
uint16_t recv_read_len; |
|
}; |
|
|
|
/* driver data */ |
|
struct modem_data { |
|
struct net_if *net_iface; |
|
uint8_t mac_addr[6]; |
|
|
|
/* modem interface */ |
|
struct modem_iface_uart_data iface_data; |
|
uint8_t iface_rb_buf[MDM_MAX_DATA_LENGTH]; |
|
|
|
/* modem cmds */ |
|
struct modem_cmd_handler_data cmd_handler_data; |
|
uint8_t cmd_match_buf[MDM_RECV_BUF_SIZE + 1]; |
|
|
|
/* socket data */ |
|
struct modem_socket_config socket_config; |
|
struct modem_socket sockets[MDM_MAX_SOCKETS]; |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_RSSI_WORK) |
|
/* RSSI work */ |
|
struct k_work_delayable rssi_query_work; |
|
#endif |
|
|
|
/* modem data */ |
|
char mdm_manufacturer[MDM_MANUFACTURER_LENGTH]; |
|
char mdm_model[MDM_MODEL_LENGTH]; |
|
char mdm_revision[MDM_REVISION_LENGTH]; |
|
char mdm_imei[MDM_IMEI_LENGTH]; |
|
char mdm_imsi[MDM_IMSI_LENGTH]; |
|
int mdm_rssi; |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_AUTODETECT_VARIANT) |
|
/* modem variant */ |
|
int mdm_variant; |
|
#endif |
|
#if defined(CONFIG_MODEM_UBLOX_SARA_AUTODETECT_APN) |
|
/* APN */ |
|
char mdm_apn[MDM_APN_LENGTH]; |
|
#endif |
|
|
|
/* modem state */ |
|
int ev_creg; |
|
|
|
/* bytes written to socket in last transaction */ |
|
int sock_written; |
|
|
|
/* response semaphore */ |
|
struct k_sem sem_response; |
|
|
|
/* prompt semaphore */ |
|
struct k_sem sem_prompt; |
|
}; |
|
|
|
static struct modem_data mdata; |
|
static struct modem_context mctx; |
|
|
|
#if defined(CONFIG_DNS_RESOLVER) |
|
static struct zsock_addrinfo result; |
|
static struct sockaddr result_addr; |
|
static char result_canonname[DNS_MAX_NAME_SIZE + 1]; |
|
#endif |
|
|
|
/* helper macro to keep readability */ |
|
#define ATOI(s_, value_, desc_) modem_atoi(s_, value_, desc_, __func__) |
|
|
|
/** |
|
* @brief Convert string to long integer, but handle errors |
|
* |
|
* @param s: string with representation of integer number |
|
* @param err_value: on error return this value instead |
|
* @param desc: name the string being converted |
|
* @param func: function where this is called (typically __func__) |
|
* |
|
* @retval return integer conversion on success, or err_value on error |
|
*/ |
|
static int modem_atoi(const char *s, const int err_value, |
|
const char *desc, const char *func) |
|
{ |
|
int ret; |
|
char *endptr; |
|
|
|
ret = (int)strtol(s, &endptr, 10); |
|
if (!endptr || *endptr != '\0') { |
|
LOG_ERR("bad %s '%s' in %s", s, desc, |
|
func); |
|
return err_value; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_AUTODETECT_APN) |
|
|
|
/* the list of SIM profiles. Global scope, so the app can change it */ |
|
const char *modem_sim_profiles = |
|
CONFIG_MODEM_UBLOX_SARA_AUTODETECT_APN_PROFILES; |
|
|
|
int find_apn(char *apn, int apnlen, const char *profiles, const char *imsi) |
|
{ |
|
int rc = -1; |
|
|
|
/* try to find a match */ |
|
char *s = strstr(profiles, imsi); |
|
|
|
if (s) { |
|
char *eos; |
|
|
|
/* find the assignment operator preceding the match */ |
|
while (s >= profiles && !strchr("=", *s)) { |
|
s--; |
|
} |
|
/* find the apn preceding the assignment operator */ |
|
while (s >= profiles && strchr(" =", *s)) { |
|
s--; |
|
} |
|
|
|
/* mark end of apn string */ |
|
eos = s+1; |
|
|
|
/* find first character of the apn */ |
|
while (s >= profiles && !strchr(" ,", *s)) { |
|
s--; |
|
} |
|
s++; |
|
|
|
/* copy the key */ |
|
if (s >= profiles) { |
|
int len = eos - s; |
|
|
|
if (len < apnlen) { |
|
memcpy(apn, s, len); |
|
apn[len] = '\0'; |
|
rc = 0; |
|
} else { |
|
LOG_ERR("buffer overflow"); |
|
} |
|
} |
|
} |
|
|
|
return rc; |
|
} |
|
|
|
/* try to detect APN automatically, based on IMSI */ |
|
int modem_detect_apn(const char *imsi) |
|
{ |
|
int rc = -1; |
|
|
|
if (imsi != NULL && strlen(imsi) >= 5) { |
|
|
|
/* extract MMC and MNC from IMSI */ |
|
char mmcmnc[6]; |
|
*mmcmnc = 0; |
|
strncat(mmcmnc, imsi, sizeof(mmcmnc)-1); |
|
|
|
/* try to find a matching IMSI, and assign the APN */ |
|
rc = find_apn(mdata.mdm_apn, |
|
sizeof(mdata.mdm_apn), |
|
modem_sim_profiles, |
|
mmcmnc); |
|
if (rc < 0) { |
|
rc = find_apn(mdata.mdm_apn, |
|
sizeof(mdata.mdm_apn), |
|
modem_sim_profiles, |
|
"*"); |
|
} |
|
} |
|
|
|
if (rc == 0) { |
|
LOG_INF("Assign APN: \"%s\"", mdata.mdm_apn); |
|
} |
|
|
|
return rc; |
|
} |
|
#endif |
|
|
|
/* Forward declaration */ |
|
MODEM_CMD_DEFINE(on_cmd_sockwrite); |
|
|
|
/* send binary data via the +USO[ST/WR] commands */ |
|
static ssize_t send_socket_data(void *obj, |
|
const struct msghdr *msg, |
|
k_timeout_t timeout) |
|
{ |
|
int ret; |
|
char send_buf[sizeof("AT+USO**=###," |
|
"!####.####.####.####.####.####.####.####!," |
|
"#####,#########\r\n")]; |
|
uint16_t dst_port = 0U; |
|
struct modem_socket *sock = (struct modem_socket *)obj; |
|
const struct modem_cmd handler_cmds[] = { |
|
MODEM_CMD("+USOST: ", on_cmd_sockwrite, 2U, ","), |
|
MODEM_CMD("+USOWR: ", on_cmd_sockwrite, 2U, ","), |
|
}; |
|
struct sockaddr *dst_addr = msg->msg_name; |
|
size_t buf_len = 0; |
|
|
|
if (!sock) { |
|
return -EINVAL; |
|
} |
|
|
|
for (int i = 0; i < msg->msg_iovlen; i++) { |
|
if (!msg->msg_iov[i].iov_base || msg->msg_iov[i].iov_len == 0) { |
|
errno = EINVAL; |
|
return -1; |
|
} |
|
buf_len += msg->msg_iov[i].iov_len; |
|
} |
|
|
|
if (!sock->is_connected && sock->ip_proto != IPPROTO_UDP) { |
|
errno = ENOTCONN; |
|
return -1; |
|
} |
|
|
|
if (!dst_addr && sock->ip_proto == IPPROTO_UDP) { |
|
dst_addr = &sock->dst; |
|
} |
|
|
|
/* |
|
* Binary and ASCII mode allows sending MDM_MAX_DATA_LENGTH bytes to |
|
* the socket in one command |
|
*/ |
|
if (buf_len > MDM_MAX_DATA_LENGTH) { |
|
if (sock->type == SOCK_DGRAM) { |
|
errno = EMSGSIZE; |
|
return -1; |
|
} |
|
|
|
buf_len = MDM_MAX_DATA_LENGTH; |
|
} |
|
|
|
/* The number of bytes written will be reported by the modem */ |
|
mdata.sock_written = 0; |
|
|
|
if (sock->ip_proto == IPPROTO_UDP) { |
|
char ip_str[NET_IPV6_ADDR_LEN]; |
|
|
|
ret = modem_context_sprint_ip_addr(dst_addr, ip_str, sizeof(ip_str)); |
|
if (ret != 0) { |
|
LOG_ERR("Error formatting IP string %d", ret); |
|
goto exit; |
|
} |
|
|
|
ret = modem_context_get_addr_port(dst_addr, &dst_port); |
|
if (ret != 0) { |
|
LOG_ERR("Error getting port from IP address %d", ret); |
|
goto exit; |
|
} |
|
|
|
snprintk(send_buf, sizeof(send_buf), |
|
"AT+USOST=%d,\"%s\",%u,%zu", sock->id, |
|
ip_str, |
|
dst_port, buf_len); |
|
} else { |
|
snprintk(send_buf, sizeof(send_buf), "AT+USOWR=%d,%zu", |
|
sock->id, buf_len); |
|
} |
|
|
|
k_sem_take(&mdata.cmd_handler_data.sem_tx_lock, K_FOREVER); |
|
|
|
/* Reset prompt '@' semaphore */ |
|
k_sem_reset(&mdata.sem_prompt); |
|
|
|
ret = modem_cmd_send_nolock(&mctx.iface, &mctx.cmd_handler, |
|
NULL, 0U, send_buf, NULL, K_NO_WAIT); |
|
if (ret < 0) { |
|
goto exit; |
|
} |
|
|
|
/* set command handlers */ |
|
ret = modem_cmd_handler_update_cmds(&mdata.cmd_handler_data, |
|
handler_cmds, |
|
ARRAY_SIZE(handler_cmds), |
|
true); |
|
if (ret < 0) { |
|
goto exit; |
|
} |
|
|
|
/* Wait for prompt '@' */ |
|
ret = k_sem_take(&mdata.sem_prompt, K_SECONDS(1)); |
|
if (ret != 0) { |
|
ret = -ETIMEDOUT; |
|
LOG_ERR("No @ prompt received"); |
|
goto exit; |
|
} |
|
|
|
/* |
|
* The AT commands manual requires a 50 ms wait |
|
* after '@' prompt if using AT+USOWR, but not |
|
* if using AT+USOST. This if condition is matched with |
|
* the command selection above. |
|
*/ |
|
if (sock->ip_proto != IPPROTO_UDP) { |
|
k_sleep(MDM_PROMPT_CMD_DELAY); |
|
} |
|
|
|
/* Reset response semaphore before sending data |
|
* So that we are sure that we won't use a previously pending one |
|
* And we won't miss the one that is going to be freed |
|
*/ |
|
k_sem_reset(&mdata.sem_response); |
|
|
|
/* Send data directly on modem iface */ |
|
for (int i = 0; i < msg->msg_iovlen; i++) { |
|
int len = MIN(buf_len, msg->msg_iov[i].iov_len); |
|
|
|
if (len == 0) { |
|
break; |
|
} |
|
modem_cmd_send_data_nolock(&mctx.iface, msg->msg_iov[i].iov_base, len); |
|
buf_len -= len; |
|
} |
|
|
|
if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) { |
|
ret = 0; |
|
goto exit; |
|
} |
|
|
|
ret = modem_cmd_handler_await(&mdata.cmd_handler_data, &mdata.sem_response, timeout); |
|
|
|
exit: |
|
/* unset handler commands and ignore any errors */ |
|
(void)modem_cmd_handler_update_cmds(&mdata.cmd_handler_data, |
|
NULL, 0U, false); |
|
k_sem_give(&mdata.cmd_handler_data.sem_tx_lock); |
|
|
|
if (ret < 0) { |
|
return ret; |
|
} |
|
|
|
return mdata.sock_written; |
|
} |
|
|
|
#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) |
|
/* send binary data via the +USO[ST/WR] commands */ |
|
static ssize_t send_cert(struct modem_socket *sock, |
|
struct modem_cmd *handler_cmds, |
|
size_t handler_cmds_len, |
|
const char *cert_data, size_t cert_len, |
|
int cert_type) |
|
{ |
|
int ret; |
|
char *filename = "ca"; |
|
char send_buf[sizeof("AT+USECMNG=#,#,!####!,####\r\n")]; |
|
|
|
/* TODO support other cert types as well */ |
|
if (cert_type != 0) { |
|
return -EINVAL; |
|
} |
|
|
|
if (!sock) { |
|
return -EINVAL; |
|
} |
|
|
|
__ASSERT_NO_MSG(cert_len <= MDM_MAX_CERT_LENGTH); |
|
|
|
snprintk(send_buf, sizeof(send_buf), |
|
"AT+USECMNG=0,%d,\"%s\",%d", cert_type, filename, cert_len); |
|
|
|
k_sem_take(&mdata.cmd_handler_data.sem_tx_lock, K_FOREVER); |
|
|
|
ret = modem_cmd_send_nolock(&mctx.iface, &mctx.cmd_handler, |
|
NULL, 0U, send_buf, NULL, K_NO_WAIT); |
|
if (ret < 0) { |
|
goto exit; |
|
} |
|
|
|
/* set command handlers */ |
|
ret = modem_cmd_handler_update_cmds(&mdata.cmd_handler_data, |
|
handler_cmds, handler_cmds_len, |
|
true); |
|
if (ret < 0) { |
|
goto exit; |
|
} |
|
|
|
/* Reset response semaphore before sending data |
|
* So that we are sure that we won't use a previously pending one |
|
* And we won't miss the one that is going to be freed |
|
*/ |
|
k_sem_reset(&mdata.sem_response); |
|
|
|
/* slight pause per spec so that @ prompt is received */ |
|
k_sleep(MDM_PROMPT_CMD_DELAY); |
|
modem_cmd_send_data_nolock(&mctx.iface, cert_data, cert_len); |
|
|
|
ret = modem_cmd_handler_await(&mdata.cmd_handler_data, &mdata.sem_response, K_MSEC(1000)); |
|
|
|
exit: |
|
/* unset handler commands and ignore any errors */ |
|
(void)modem_cmd_handler_update_cmds(&mdata.cmd_handler_data, |
|
NULL, 0U, false); |
|
k_sem_give(&mdata.cmd_handler_data.sem_tx_lock); |
|
|
|
return ret; |
|
} |
|
#endif |
|
|
|
/* |
|
* Modem Response Command Handlers |
|
*/ |
|
|
|
/* Handler: OK */ |
|
MODEM_CMD_DEFINE(on_cmd_ok) |
|
{ |
|
modem_cmd_handler_set_error(data, 0); |
|
k_sem_give(&mdata.sem_response); |
|
return 0; |
|
} |
|
|
|
/* Handler: @ */ |
|
MODEM_CMD_DEFINE(on_prompt) |
|
{ |
|
k_sem_give(&mdata.sem_prompt); |
|
|
|
/* A direct cmd should return the number of byte processed. |
|
* Therefore, here we always return 1 |
|
*/ |
|
return 1; |
|
} |
|
|
|
/* Handler: ERROR */ |
|
MODEM_CMD_DEFINE(on_cmd_error) |
|
{ |
|
modem_cmd_handler_set_error(data, -EIO); |
|
k_sem_give(&mdata.sem_response); |
|
return 0; |
|
} |
|
|
|
/* Handler: +CME Error: <err>[0] */ |
|
MODEM_CMD_DEFINE(on_cmd_exterror) |
|
{ |
|
/* TODO: map extended error codes to values */ |
|
modem_cmd_handler_set_error(data, -EIO); |
|
k_sem_give(&mdata.sem_response); |
|
return 0; |
|
} |
|
|
|
/* |
|
* Modem Info Command Handlers |
|
*/ |
|
|
|
/* Handler: <manufacturer> */ |
|
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_manufacturer) |
|
{ |
|
size_t out_len; |
|
|
|
out_len = net_buf_linearize(mdata.mdm_manufacturer, |
|
sizeof(mdata.mdm_manufacturer) - 1, |
|
data->rx_buf, 0, len); |
|
mdata.mdm_manufacturer[out_len] = '\0'; |
|
LOG_INF("Manufacturer: %s", mdata.mdm_manufacturer); |
|
return 0; |
|
} |
|
|
|
/* Handler: <model> */ |
|
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_model) |
|
{ |
|
size_t out_len; |
|
|
|
out_len = net_buf_linearize(mdata.mdm_model, |
|
sizeof(mdata.mdm_model) - 1, |
|
data->rx_buf, 0, len); |
|
mdata.mdm_model[out_len] = '\0'; |
|
LOG_INF("Model: %s", mdata.mdm_model); |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_AUTODETECT_VARIANT) |
|
/* Set modem type */ |
|
if (strstr(mdata.mdm_model, "R4")) { |
|
mdata.mdm_variant = MDM_VARIANT_UBLOX_R4; |
|
} else { |
|
if (strstr(mdata.mdm_model, "U2")) { |
|
mdata.mdm_variant = MDM_VARIANT_UBLOX_U2; |
|
} |
|
} |
|
LOG_INF("Variant: %d", mdata.mdm_variant); |
|
#endif |
|
|
|
return 0; |
|
} |
|
|
|
/* Handler: <rev> */ |
|
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_revision) |
|
{ |
|
size_t out_len; |
|
|
|
out_len = net_buf_linearize(mdata.mdm_revision, |
|
sizeof(mdata.mdm_revision) - 1, |
|
data->rx_buf, 0, len); |
|
mdata.mdm_revision[out_len] = '\0'; |
|
LOG_INF("Revision: %s", mdata.mdm_revision); |
|
return 0; |
|
} |
|
|
|
/* Handler: <IMEI> */ |
|
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_imei) |
|
{ |
|
size_t out_len; |
|
|
|
out_len = net_buf_linearize(mdata.mdm_imei, sizeof(mdata.mdm_imei) - 1, |
|
data->rx_buf, 0, len); |
|
mdata.mdm_imei[out_len] = '\0'; |
|
LOG_INF("IMEI: %s", mdata.mdm_imei); |
|
return 0; |
|
} |
|
|
|
/* Handler: <IMSI> */ |
|
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_imsi) |
|
{ |
|
size_t out_len; |
|
|
|
out_len = net_buf_linearize(mdata.mdm_imsi, sizeof(mdata.mdm_imsi) - 1, |
|
data->rx_buf, 0, len); |
|
mdata.mdm_imsi[out_len] = '\0'; |
|
LOG_INF("IMSI: %s", mdata.mdm_imsi); |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_AUTODETECT_APN) |
|
/* set the APN automatically */ |
|
modem_detect_apn(mdata.mdm_imsi); |
|
#endif |
|
|
|
return 0; |
|
} |
|
|
|
#if !defined(CONFIG_MODEM_UBLOX_SARA_U2) |
|
/* |
|
* Handler: +CESQ: <rxlev>[0],<ber>[1],<rscp>[2],<ecn0>[3],<rsrq>[4],<rsrp>[5] |
|
*/ |
|
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_rssi_cesq) |
|
{ |
|
int rsrp, rxlev; |
|
|
|
rsrp = ATOI(argv[5], 0, "rsrp"); |
|
rxlev = ATOI(argv[0], 0, "rxlev"); |
|
if (rsrp >= 0 && rsrp <= 97) { |
|
mdata.mdm_rssi = -140 + (rsrp - 1); |
|
LOG_INF("RSRP: %d", mdata.mdm_rssi); |
|
} else if (rxlev >= 0 && rxlev <= 63) { |
|
mdata.mdm_rssi = -110 + (rxlev - 1); |
|
LOG_INF("RSSI: %d", mdata.mdm_rssi); |
|
} else { |
|
mdata.mdm_rssi = -1000; |
|
LOG_INF("RSRP/RSSI not known"); |
|
} |
|
|
|
return 0; |
|
} |
|
#endif |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_U2) \ |
|
|| defined(CONFIG_MODEM_UBLOX_SARA_AUTODETECT_VARIANT) |
|
/* Handler: +CSQ: <signal_power>[0],<qual>[1] */ |
|
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_rssi_csq) |
|
{ |
|
int rssi; |
|
|
|
rssi = ATOI(argv[0], 0, "signal_power"); |
|
if (rssi == 31) { |
|
mdata.mdm_rssi = -46; |
|
} else if (rssi >= 0 && rssi <= 31) { |
|
/* FIXME: This value depends on the RAT */ |
|
mdata.mdm_rssi = -110 + ((rssi * 2) + 1); |
|
} else { |
|
mdata.mdm_rssi = -1000; |
|
} |
|
|
|
LOG_INF("RSSI: %d", mdata.mdm_rssi); |
|
return 0; |
|
} |
|
#endif |
|
|
|
#if defined(CONFIG_MODEM_CELL_INFO) |
|
static int unquoted_atoi(const char *s, int base) |
|
{ |
|
if (*s == '"') { |
|
s++; |
|
} |
|
|
|
return strtol(s, NULL, base); |
|
} |
|
|
|
/* |
|
* Handler: +COPS: <mode>[0],<format>[1],<oper>[2] |
|
*/ |
|
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_cops) |
|
{ |
|
if (argc >= 3) { |
|
mctx.data_operator = unquoted_atoi(argv[2], 10); |
|
LOG_INF("operator: %u", |
|
mctx.data_operator); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* Handler: +CEREG: <n>[0],<stat>[1],<tac>[2],<ci>[3],<AcT>[4] |
|
*/ |
|
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_cereg) |
|
{ |
|
if (argc >= 4) { |
|
mctx.data_lac = unquoted_atoi(argv[2], 16); |
|
mctx.data_cellid = unquoted_atoi(argv[3], 16); |
|
LOG_INF("lac: %u, cellid: %u", |
|
mctx.data_lac, |
|
mctx.data_cellid); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static const struct setup_cmd query_cellinfo_cmds[] = { |
|
SETUP_CMD_NOHANDLE("AT+CEREG=2"), |
|
SETUP_CMD("AT+CEREG?", "", on_cmd_atcmdinfo_cereg, 5U, ","), |
|
SETUP_CMD_NOHANDLE("AT+COPS=3,2"), |
|
SETUP_CMD("AT+COPS?", "", on_cmd_atcmdinfo_cops, 3U, ","), |
|
}; |
|
#endif /* CONFIG_MODEM_CELL_INFO */ |
|
|
|
/* |
|
* Modem Socket Command Handlers |
|
*/ |
|
|
|
/* Handler: +USOCR: <socket_id>[0] */ |
|
MODEM_CMD_DEFINE(on_cmd_sockcreate) |
|
{ |
|
struct modem_socket *sock = NULL; |
|
int id; |
|
|
|
/* look up new socket by special id */ |
|
sock = modem_socket_from_newid(&mdata.socket_config); |
|
if (sock) { |
|
id = ATOI(argv[0], -1, "socket_id"); |
|
|
|
/* on error give up modem socket */ |
|
if (modem_socket_id_assign(&mdata.socket_config, sock, id) < 0) { |
|
modem_socket_put(&mdata.socket_config, sock->sock_fd); |
|
} |
|
} |
|
|
|
/* don't give back semaphore -- OK to follow */ |
|
return 0; |
|
} |
|
|
|
/* Handler: +USO[WR|ST]: <socket_id>[0],<length>[1] */ |
|
MODEM_CMD_DEFINE(on_cmd_sockwrite) |
|
{ |
|
mdata.sock_written = ATOI(argv[1], 0, "length"); |
|
LOG_DBG("bytes written: %d", mdata.sock_written); |
|
return 0; |
|
} |
|
|
|
#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) |
|
/* Handler: +USECMNG: 0,<type>[0],<internal_name>[1],<md5_string>[2] */ |
|
MODEM_CMD_DEFINE(on_cmd_cert_write) |
|
{ |
|
LOG_DBG("cert md5: %s", argv[2]); |
|
return 0; |
|
} |
|
#endif |
|
|
|
/* Common code for +USOR[D|F]: "<data>" */ |
|
static int on_cmd_sockread_common(int socket_id, |
|
struct modem_cmd_handler_data *data, |
|
int socket_data_length, uint16_t len) |
|
{ |
|
struct modem_socket *sock = NULL; |
|
struct socket_read_data *sock_data; |
|
int ret; |
|
|
|
if (!len) { |
|
LOG_ERR("Short +USOR[D|F] value. Aborting!"); |
|
return -EAGAIN; |
|
} |
|
|
|
/* |
|
* make sure we still have buf data and next char in the buffer is a |
|
* quote. |
|
*/ |
|
if (!data->rx_buf || *data->rx_buf->data != '\"') { |
|
LOG_ERR("Incorrect format! Ignoring data!"); |
|
return -EINVAL; |
|
} |
|
|
|
/* zero length */ |
|
if (socket_data_length <= 0) { |
|
LOG_ERR("Length problem (%d). Aborting!", socket_data_length); |
|
return -EAGAIN; |
|
} |
|
|
|
/* check to make sure we have all of the data (minus quotes) */ |
|
if ((net_buf_frags_len(data->rx_buf) - 2) < socket_data_length) { |
|
LOG_DBG("Not enough data -- wait!"); |
|
return -EAGAIN; |
|
} |
|
|
|
/* skip quote */ |
|
len--; |
|
net_buf_pull_u8(data->rx_buf); |
|
if (!data->rx_buf->len) { |
|
data->rx_buf = net_buf_frag_del(NULL, data->rx_buf); |
|
} |
|
|
|
sock = modem_socket_from_id(&mdata.socket_config, socket_id); |
|
if (!sock) { |
|
LOG_ERR("Socket not found! (%d)", socket_id); |
|
ret = -EINVAL; |
|
goto exit; |
|
} |
|
|
|
sock_data = (struct socket_read_data *)sock->data; |
|
if (!sock_data) { |
|
LOG_ERR("Socket data not found! Skip handling (%d)", socket_id); |
|
ret = -EINVAL; |
|
goto exit; |
|
} |
|
|
|
ret = net_buf_linearize(sock_data->recv_buf, sock_data->recv_buf_len, |
|
data->rx_buf, 0, (uint16_t)socket_data_length); |
|
data->rx_buf = net_buf_skip(data->rx_buf, ret); |
|
sock_data->recv_read_len = ret; |
|
if (ret != socket_data_length) { |
|
LOG_ERR("Total copied data is different then received data!" |
|
" copied:%d vs. received:%d", ret, socket_data_length); |
|
ret = -EINVAL; |
|
} |
|
|
|
exit: |
|
/* remove packet from list (ignore errors) */ |
|
(void)modem_socket_packet_size_update(&mdata.socket_config, sock, |
|
-socket_data_length); |
|
|
|
/* don't give back semaphore -- OK to follow */ |
|
return ret; |
|
} |
|
|
|
/* |
|
* Handler: +USORF: <socket_id>[0],<remote_ip_addr>[1],<remote_port>[2], |
|
* <length>[3],"<data>" |
|
*/ |
|
MODEM_CMD_DEFINE(on_cmd_sockreadfrom) |
|
{ |
|
/* TODO: handle remote_ip_addr */ |
|
|
|
return on_cmd_sockread_common(ATOI(argv[0], 0, "socket_id"), data, |
|
ATOI(argv[3], 0, "length"), len); |
|
} |
|
|
|
/* Handler: +USORD: <socket_id>[0],<length>[1],"<data>" */ |
|
MODEM_CMD_DEFINE(on_cmd_sockread) |
|
{ |
|
return on_cmd_sockread_common(ATOI(argv[0], 0, "socket_id"), data, |
|
ATOI(argv[1], 0, "length"), len); |
|
} |
|
|
|
#if defined(CONFIG_DNS_RESOLVER) |
|
/* Handler: +UDNSRN: "<resolved_ip_address>"[0], "<resolved_ip_address>"[1] */ |
|
MODEM_CMD_DEFINE(on_cmd_dns) |
|
{ |
|
/* chop off end quote */ |
|
argv[0][strlen(argv[0]) - 1] = '\0'; |
|
|
|
/* FIXME: Hard-code DNS on SARA-R4 to return IPv4 */ |
|
result_addr.sa_family = AF_INET; |
|
/* skip beginning quote when parsing */ |
|
(void)net_addr_pton(result.ai_family, &argv[0][1], |
|
&((struct sockaddr_in *)&result_addr)->sin_addr); |
|
return 0; |
|
} |
|
#endif |
|
|
|
/* |
|
* MODEM UNSOLICITED NOTIFICATION HANDLERS |
|
*/ |
|
|
|
/* Handler: +UUSOCL: <socket_id>[0] */ |
|
MODEM_CMD_DEFINE(on_cmd_socknotifyclose) |
|
{ |
|
struct modem_socket *sock; |
|
|
|
sock = modem_socket_from_id(&mdata.socket_config, |
|
ATOI(argv[0], 0, "socket_id")); |
|
if (sock) { |
|
sock->is_connected = false; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* Handler: +UUSOR[D|F]: <socket_id>[0],<length>[1] */ |
|
MODEM_CMD_DEFINE(on_cmd_socknotifydata) |
|
{ |
|
int ret, socket_id, new_total; |
|
struct modem_socket *sock; |
|
|
|
socket_id = ATOI(argv[0], 0, "socket_id"); |
|
new_total = ATOI(argv[1], 0, "length"); |
|
sock = modem_socket_from_id(&mdata.socket_config, socket_id); |
|
if (!sock) { |
|
return 0; |
|
} |
|
|
|
ret = modem_socket_packet_size_update(&mdata.socket_config, sock, |
|
new_total); |
|
if (ret < 0) { |
|
LOG_ERR("socket_id:%d left_bytes:%d err: %d", socket_id, |
|
new_total, ret); |
|
} |
|
|
|
if (new_total > 0) { |
|
modem_socket_data_ready(&mdata.socket_config, sock); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* Handler: +CREG: <stat>[0] */ |
|
MODEM_CMD_DEFINE(on_cmd_socknotifycreg) |
|
{ |
|
mdata.ev_creg = ATOI(argv[0], 0, "stat"); |
|
LOG_DBG("CREG:%d", mdata.ev_creg); |
|
return 0; |
|
} |
|
|
|
/* RX thread */ |
|
static void modem_rx(void *p1, void *p2, void *p3) |
|
{ |
|
ARG_UNUSED(p1); |
|
ARG_UNUSED(p2); |
|
ARG_UNUSED(p3); |
|
|
|
while (true) { |
|
/* wait for incoming data */ |
|
modem_iface_uart_rx_wait(&mctx.iface, K_FOREVER); |
|
|
|
modem_cmd_handler_process(&mctx.cmd_handler, &mctx.iface); |
|
|
|
/* give up time if we have a solid stream of data */ |
|
k_yield(); |
|
} |
|
} |
|
|
|
static int pin_init(void) |
|
{ |
|
LOG_INF("Setting Modem Pins"); |
|
|
|
#if DT_INST_NODE_HAS_PROP(0, mdm_reset_gpios) |
|
LOG_DBG("MDM_RESET_PIN -> NOT_ASSERTED"); |
|
gpio_pin_set_dt(&reset_gpio, MDM_RESET_NOT_ASSERTED); |
|
#endif |
|
|
|
LOG_DBG("MDM_POWER_PIN -> ENABLE"); |
|
gpio_pin_set_dt(&power_gpio, 1); |
|
k_sleep(K_SECONDS(4)); |
|
|
|
LOG_DBG("MDM_POWER_PIN -> DISABLE"); |
|
gpio_pin_set_dt(&power_gpio, 0); |
|
#if defined(CONFIG_MODEM_UBLOX_SARA_U2) |
|
k_sleep(K_SECONDS(1)); |
|
#else |
|
k_sleep(K_SECONDS(4)); |
|
#endif |
|
LOG_DBG("MDM_POWER_PIN -> ENABLE"); |
|
gpio_pin_set_dt(&power_gpio, 1); |
|
k_sleep(K_SECONDS(1)); |
|
|
|
/* make sure module is powered off */ |
|
#if DT_INST_NODE_HAS_PROP(0, mdm_vint_gpios) |
|
LOG_DBG("Waiting for MDM_VINT_PIN = 0"); |
|
|
|
while (gpio_pin_get_dt(&vint_gpio) > 0) { |
|
#if defined(CONFIG_MODEM_UBLOX_SARA_U2) |
|
/* try to power off again */ |
|
LOG_DBG("MDM_POWER_PIN -> DISABLE"); |
|
gpio_pin_set_dt(&power_gpio, 0); |
|
k_sleep(K_SECONDS(1)); |
|
LOG_DBG("MDM_POWER_PIN -> ENABLE"); |
|
gpio_pin_set_dt(&power_gpio, 1); |
|
#endif |
|
k_sleep(K_MSEC(100)); |
|
} |
|
#else |
|
k_sleep(K_SECONDS(8)); |
|
#endif |
|
|
|
LOG_DBG("MDM_POWER_PIN -> DISABLE"); |
|
|
|
unsigned int irq_lock_key = irq_lock(); |
|
|
|
gpio_pin_set_dt(&power_gpio, 0); |
|
#if defined(CONFIG_MODEM_UBLOX_SARA_U2) |
|
k_usleep(50); /* 50-80 microseconds */ |
|
#else |
|
k_sleep(K_SECONDS(1)); |
|
#endif |
|
gpio_pin_set_dt(&power_gpio, 1); |
|
|
|
irq_unlock(irq_lock_key); |
|
|
|
LOG_DBG("MDM_POWER_PIN -> ENABLE"); |
|
|
|
#if DT_INST_NODE_HAS_PROP(0, mdm_vint_gpios) |
|
LOG_DBG("Waiting for MDM_VINT_PIN = 1"); |
|
do { |
|
k_sleep(K_MSEC(100)); |
|
} while (gpio_pin_get_dt(&vint_gpio) == 0); |
|
#else |
|
k_sleep(K_SECONDS(10)); |
|
#endif |
|
|
|
gpio_pin_configure_dt(&power_gpio, GPIO_INPUT); |
|
|
|
LOG_INF("... Done!"); |
|
|
|
return 0; |
|
} |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_AUTODETECT_VARIANT) |
|
static void modem_rssi_query_work(struct k_work *work) |
|
{ |
|
static const struct modem_cmd cmds[] = { |
|
MODEM_CMD("+CSQ: ", on_cmd_atcmdinfo_rssi_csq, 2U, ","), |
|
MODEM_CMD("+CESQ: ", on_cmd_atcmdinfo_rssi_cesq, 6U, ","), |
|
}; |
|
const char *send_cmd_u2 = "AT+CSQ"; |
|
const char *send_cmd_r4 = "AT+CESQ"; |
|
int ret; |
|
|
|
/* choose cmd according to variant */ |
|
const char *send_cmd = send_cmd_r4; |
|
|
|
if (mdata.mdm_variant == MDM_VARIANT_UBLOX_U2) { |
|
send_cmd = send_cmd_u2; |
|
} |
|
|
|
/* query modem RSSI */ |
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, |
|
cmds, ARRAY_SIZE(cmds), |
|
send_cmd, |
|
&mdata.sem_response, |
|
MDM_CMD_TIMEOUT); |
|
if (ret < 0) { |
|
LOG_ERR("AT+C[E]SQ ret:%d", ret); |
|
} |
|
|
|
#if defined(CONFIG_MODEM_CELL_INFO) |
|
/* query cell info */ |
|
ret = modem_cmd_handler_setup_cmds_nolock(&mctx.iface, |
|
&mctx.cmd_handler, |
|
query_cellinfo_cmds, |
|
ARRAY_SIZE(query_cellinfo_cmds), |
|
&mdata.sem_response, |
|
MDM_CMD_TIMEOUT); |
|
if (ret < 0) { |
|
LOG_WRN("modem query for cell info returned %d", ret); |
|
} |
|
#endif |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_RSSI_WORK) |
|
/* re-start RSSI query work */ |
|
if (work) { |
|
k_work_reschedule_for_queue( |
|
&modem_workq, &mdata.rssi_query_work, |
|
K_SECONDS(CONFIG_MODEM_UBLOX_SARA_RSSI_WORK_PERIOD)); |
|
} |
|
#endif |
|
} |
|
#else |
|
static void modem_rssi_query_work(struct k_work *work) |
|
{ |
|
static const struct modem_cmd cmd = |
|
#if defined(CONFIG_MODEM_UBLOX_SARA_U2) |
|
MODEM_CMD("+CSQ: ", on_cmd_atcmdinfo_rssi_csq, 2U, ","); |
|
static char *send_cmd = "AT+CSQ"; |
|
#else |
|
MODEM_CMD("+CESQ: ", on_cmd_atcmdinfo_rssi_cesq, 6U, ","); |
|
static char *send_cmd = "AT+CESQ"; |
|
#endif |
|
int ret; |
|
|
|
/* query modem RSSI */ |
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, |
|
&cmd, 1U, send_cmd, &mdata.sem_response, |
|
MDM_CMD_TIMEOUT); |
|
if (ret < 0) { |
|
LOG_ERR("AT+C[E]SQ ret:%d", ret); |
|
} |
|
|
|
#if defined(CONFIG_MODEM_CELL_INFO) |
|
/* query cell info */ |
|
ret = modem_cmd_handler_setup_cmds_nolock(&mctx.iface, |
|
&mctx.cmd_handler, |
|
query_cellinfo_cmds, |
|
ARRAY_SIZE(query_cellinfo_cmds), |
|
&mdata.sem_response, |
|
MDM_CMD_TIMEOUT); |
|
if (ret < 0) { |
|
LOG_WRN("modem query for cell info returned %d", ret); |
|
} |
|
#endif |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_RSSI_WORK) |
|
/* re-start RSSI query work */ |
|
if (work) { |
|
k_work_reschedule_for_queue( |
|
&modem_workq, &mdata.rssi_query_work, |
|
K_SECONDS(CONFIG_MODEM_UBLOX_SARA_RSSI_WORK_PERIOD)); |
|
} |
|
#endif |
|
} |
|
#endif |
|
|
|
static void modem_reset(void) |
|
{ |
|
int ret = 0, retry_count = 0, counter = 0; |
|
static const struct setup_cmd setup_cmds[] = { |
|
/* turn off echo */ |
|
SETUP_CMD_NOHANDLE("ATE0"), |
|
/* stop functionality */ |
|
SETUP_CMD_NOHANDLE("AT+CFUN=0"), |
|
/* extended error numbers */ |
|
SETUP_CMD_NOHANDLE("AT+CMEE=1"), |
|
#if defined(CONFIG_BOARD_PARTICLE_BORON) |
|
/* use external SIM */ |
|
SETUP_CMD_NOHANDLE("AT+UGPIOC=23,0,0"), |
|
#endif |
|
#if defined(CONFIG_MODEM_UBLOX_SARA_R4_NET_STATUS_PIN) |
|
/* enable the network status indication */ |
|
SETUP_CMD_NOHANDLE("AT+UGPIOC=" |
|
STRINGIFY(CONFIG_MODEM_UBLOX_SARA_R4_NET_STATUS_PIN) |
|
",2"), |
|
#endif |
|
/* UNC messages for registration */ |
|
SETUP_CMD_NOHANDLE("AT+CREG=1"), |
|
/* query modem info */ |
|
SETUP_CMD("AT+CGMI", "", on_cmd_atcmdinfo_manufacturer, 0U, ""), |
|
SETUP_CMD("AT+CGMM", "", on_cmd_atcmdinfo_model, 0U, ""), |
|
SETUP_CMD("AT+CGMR", "", on_cmd_atcmdinfo_revision, 0U, ""), |
|
SETUP_CMD("AT+CGSN", "", on_cmd_atcmdinfo_imei, 0U, ""), |
|
SETUP_CMD("AT+CIMI", "", on_cmd_atcmdinfo_imsi, 0U, ""), |
|
#if !defined(CONFIG_MODEM_UBLOX_SARA_AUTODETECT_APN) |
|
/* setup PDP context definition */ |
|
SETUP_CMD_NOHANDLE("AT+CGDCONT=1,\"IP\",\"" |
|
CONFIG_MODEM_UBLOX_SARA_R4_APN "\""), |
|
/* start functionality */ |
|
SETUP_CMD_NOHANDLE("AT+CFUN=1"), |
|
#endif |
|
}; |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_AUTODETECT_VARIANT) |
|
static const struct setup_cmd post_setup_cmds_u2[] = { |
|
#if !defined(CONFIG_MODEM_UBLOX_SARA_AUTODETECT_APN) |
|
/* set the APN */ |
|
SETUP_CMD_NOHANDLE("AT+UPSD=0,1,\"" |
|
CONFIG_MODEM_UBLOX_SARA_R4_APN "\""), |
|
#endif |
|
/* set dynamic IP */ |
|
SETUP_CMD_NOHANDLE("AT+UPSD=0,7,\"0.0.0.0\""), |
|
/* activate the GPRS connection */ |
|
SETUP_CMD_NOHANDLE("AT+UPSDA=0,3"), |
|
}; |
|
#endif |
|
|
|
static const struct setup_cmd post_setup_cmds[] = { |
|
#if defined(CONFIG_MODEM_UBLOX_SARA_U2) |
|
/* set the APN */ |
|
SETUP_CMD_NOHANDLE("AT+UPSD=0,1,\"" |
|
CONFIG_MODEM_UBLOX_SARA_R4_APN "\""), |
|
/* set dynamic IP */ |
|
SETUP_CMD_NOHANDLE("AT+UPSD=0,7,\"0.0.0.0\""), |
|
/* activate the GPRS connection */ |
|
SETUP_CMD_NOHANDLE("AT+UPSDA=0,3"), |
|
#else |
|
/* activate the PDP context */ |
|
SETUP_CMD_NOHANDLE("AT+CGACT=1,1"), |
|
#endif |
|
}; |
|
|
|
restart: |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_AUTODETECT_APN) |
|
mdata.mdm_apn[0] = '\0'; |
|
strncat(mdata.mdm_apn, |
|
CONFIG_MODEM_UBLOX_SARA_R4_APN, |
|
sizeof(mdata.mdm_apn)-1); |
|
#endif |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_RSSI_WORK) |
|
/* stop RSSI delay work */ |
|
k_work_cancel_delayable(&mdata.rssi_query_work); |
|
#endif |
|
|
|
pin_init(); |
|
|
|
LOG_INF("Waiting for modem to respond"); |
|
|
|
/* Give the modem a while to start responding to simple 'AT' commands. |
|
* Also wait for CSPS=1 or RRCSTATE=1 notification |
|
*/ |
|
ret = -1; |
|
while (counter++ < 50 && ret < 0) { |
|
k_sleep(K_SECONDS(2)); |
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, |
|
NULL, 0, "AT", &mdata.sem_response, |
|
MDM_CMD_TIMEOUT); |
|
if (ret < 0 && ret != -ETIMEDOUT) { |
|
break; |
|
} |
|
} |
|
|
|
if (ret < 0) { |
|
LOG_ERR("MODEM WAIT LOOP ERROR: %d", ret); |
|
goto error; |
|
} |
|
|
|
ret = modem_cmd_handler_setup_cmds(&mctx.iface, &mctx.cmd_handler, |
|
setup_cmds, ARRAY_SIZE(setup_cmds), |
|
&mdata.sem_response, |
|
MDM_REGISTRATION_TIMEOUT); |
|
if (ret < 0) { |
|
goto error; |
|
} |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_AUTODETECT_APN) |
|
/* autodetect APN from IMSI */ |
|
char cmd[sizeof("AT+CGDCONT=1,\"IP\",\"\"")+MDM_APN_LENGTH]; |
|
|
|
snprintk(cmd, sizeof(cmd), "AT+CGDCONT=1,\"IP\",\"%s\"", mdata.mdm_apn); |
|
|
|
/* setup PDP context definition */ |
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, |
|
NULL, 0, |
|
(const char *)cmd, |
|
&mdata.sem_response, |
|
MDM_REGISTRATION_TIMEOUT); |
|
|
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, |
|
NULL, 0, |
|
"AT+CFUN=1", |
|
&mdata.sem_response, |
|
MDM_REGISTRATION_TIMEOUT); |
|
#endif |
|
|
|
if (strlen(CONFIG_MODEM_UBLOX_SARA_R4_MANUAL_MCCMNO) > 0) { |
|
/* use manual MCC/MNO entry */ |
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, |
|
NULL, 0, |
|
"AT+COPS=1,2,\"" |
|
CONFIG_MODEM_UBLOX_SARA_R4_MANUAL_MCCMNO |
|
"\"", |
|
&mdata.sem_response, |
|
MDM_REGISTRATION_TIMEOUT); |
|
} else { |
|
/* register operator automatically */ |
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, |
|
NULL, 0, "AT+COPS=0,0", |
|
&mdata.sem_response, |
|
MDM_REGISTRATION_TIMEOUT); |
|
} |
|
|
|
if (ret < 0) { |
|
LOG_ERR("AT+COPS ret:%d", ret); |
|
goto error; |
|
} |
|
|
|
LOG_INF("Waiting for network"); |
|
|
|
/* |
|
* TODO: A lot of this should be setup as a 3GPP module to handle |
|
* basic connection to the network commands / polling |
|
*/ |
|
|
|
/* wait for +CREG: 1(normal) or 5(roaming) */ |
|
counter = 0; |
|
while (counter++ < 40 && mdata.ev_creg != 1 && mdata.ev_creg != 5) { |
|
if (counter == 20) { |
|
LOG_WRN("Force restart of RF functionality"); |
|
|
|
/* Disable RF temporarily */ |
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, |
|
NULL, 0, "AT+CFUN=0", &mdata.sem_response, |
|
MDM_CMD_TIMEOUT); |
|
|
|
k_sleep(K_SECONDS(1)); |
|
|
|
/* Enable RF */ |
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, |
|
NULL, 0, "AT+CFUN=1", &mdata.sem_response, |
|
MDM_CMD_TIMEOUT); |
|
} |
|
|
|
k_sleep(K_SECONDS(1)); |
|
} |
|
|
|
/* query modem RSSI */ |
|
modem_rssi_query_work(NULL); |
|
k_sleep(MDM_WAIT_FOR_RSSI_DELAY); |
|
|
|
counter = 0; |
|
/* wait for RSSI < 0 and > -1000 */ |
|
while (counter++ < MDM_WAIT_FOR_RSSI_COUNT && |
|
(mdata.mdm_rssi >= 0 || |
|
mdata.mdm_rssi <= -1000)) { |
|
modem_rssi_query_work(NULL); |
|
k_sleep(MDM_WAIT_FOR_RSSI_DELAY); |
|
} |
|
|
|
if (mdata.mdm_rssi >= 0 || mdata.mdm_rssi <= -1000) { |
|
retry_count++; |
|
if (retry_count >= MDM_NETWORK_RETRY_COUNT) { |
|
LOG_ERR("Failed network init. Too many attempts!"); |
|
ret = -ENETUNREACH; |
|
goto error; |
|
} |
|
|
|
LOG_ERR("Failed network init. Restarting process."); |
|
goto restart; |
|
} |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_AUTODETECT_VARIANT) |
|
if (mdata.mdm_variant == MDM_VARIANT_UBLOX_U2) { |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_AUTODETECT_APN) |
|
/* setup PDP context definition */ |
|
char cmd[sizeof("AT+UPSD=0,1,\"%s\"")+MDM_APN_LENGTH]; |
|
|
|
snprintk(cmd, sizeof(cmd), "AT+UPSD=0,1,\"%s\"", mdata.mdm_apn); |
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, |
|
NULL, 0, |
|
(const char *)cmd, |
|
&mdata.sem_response, |
|
MDM_REGISTRATION_TIMEOUT); |
|
#endif |
|
ret = modem_cmd_handler_setup_cmds(&mctx.iface, |
|
&mctx.cmd_handler, |
|
post_setup_cmds_u2, |
|
ARRAY_SIZE(post_setup_cmds_u2), |
|
&mdata.sem_response, |
|
MDM_REGISTRATION_TIMEOUT); |
|
} else { |
|
#endif |
|
ret = modem_cmd_handler_setup_cmds(&mctx.iface, |
|
&mctx.cmd_handler, |
|
post_setup_cmds, |
|
ARRAY_SIZE(post_setup_cmds), |
|
&mdata.sem_response, |
|
MDM_REGISTRATION_TIMEOUT); |
|
#if defined(CONFIG_MODEM_UBLOX_SARA_AUTODETECT_VARIANT) |
|
} |
|
#endif |
|
if (ret < 0) { |
|
goto error; |
|
} |
|
|
|
LOG_INF("Network is ready."); |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_RSSI_WORK) |
|
/* start RSSI query */ |
|
k_work_reschedule_for_queue( |
|
&modem_workq, &mdata.rssi_query_work, |
|
K_SECONDS(CONFIG_MODEM_UBLOX_SARA_RSSI_WORK_PERIOD)); |
|
#endif |
|
|
|
error: |
|
return; |
|
} |
|
|
|
/* |
|
* generic socket creation function |
|
* which can be called in bind() or connect() |
|
*/ |
|
static int create_socket(struct modem_socket *sock, const struct sockaddr *addr) |
|
{ |
|
int ret; |
|
static const struct modem_cmd cmd = |
|
MODEM_CMD("+USOCR: ", on_cmd_sockcreate, 1U, ""); |
|
char buf[sizeof("AT+USOCR=#,#####\r")]; |
|
uint16_t local_port = 0U, proto = 6U; |
|
|
|
if (addr) { |
|
if (addr->sa_family == AF_INET6) { |
|
local_port = ntohs(net_sin6(addr)->sin6_port); |
|
} else if (addr->sa_family == AF_INET) { |
|
local_port = ntohs(net_sin(addr)->sin_port); |
|
} |
|
} |
|
|
|
if (sock->ip_proto == IPPROTO_UDP) { |
|
proto = 17U; |
|
} |
|
|
|
if (local_port > 0U) { |
|
snprintk(buf, sizeof(buf), "AT+USOCR=%d,%u", proto, local_port); |
|
} else { |
|
snprintk(buf, sizeof(buf), "AT+USOCR=%d", proto); |
|
} |
|
|
|
/* create socket */ |
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, |
|
&cmd, 1U, buf, |
|
&mdata.sem_response, MDM_CMD_TIMEOUT); |
|
if (ret < 0) { |
|
goto error; |
|
} |
|
|
|
if (sock->ip_proto == IPPROTO_TLS_1_2) { |
|
char atbuf[sizeof("AT+USECPRF=#,#,#######\r")]; |
|
|
|
/* Enable socket security */ |
|
snprintk(atbuf, sizeof(atbuf), "AT+USOSEC=%d,1,%d", sock->id, sock->id); |
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, atbuf, |
|
&mdata.sem_response, MDM_CMD_TIMEOUT); |
|
if (ret < 0) { |
|
goto error; |
|
} |
|
/* Reset the security profile */ |
|
snprintk(atbuf, sizeof(atbuf), "AT+USECPRF=%d", sock->id); |
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, atbuf, |
|
&mdata.sem_response, MDM_CMD_TIMEOUT); |
|
if (ret < 0) { |
|
goto error; |
|
} |
|
/* Validate server cert against the CA. */ |
|
snprintk(atbuf, sizeof(atbuf), "AT+USECPRF=%d,0,1", sock->id); |
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, atbuf, |
|
&mdata.sem_response, MDM_CMD_TIMEOUT); |
|
if (ret < 0) { |
|
goto error; |
|
} |
|
/* Use TLSv1.2 only */ |
|
snprintk(atbuf, sizeof(atbuf), "AT+USECPRF=%d,1,3", sock->id); |
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, atbuf, |
|
&mdata.sem_response, MDM_CMD_TIMEOUT); |
|
if (ret < 0) { |
|
goto error; |
|
} |
|
/* Set root CA filename */ |
|
snprintk(atbuf, sizeof(atbuf), "AT+USECPRF=%d,3,\"ca\"", sock->id); |
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, atbuf, |
|
&mdata.sem_response, MDM_CMD_TIMEOUT); |
|
if (ret < 0) { |
|
goto error; |
|
} |
|
} |
|
|
|
errno = 0; |
|
return 0; |
|
|
|
error: |
|
LOG_ERR("%s ret:%d", buf, ret); |
|
modem_socket_put(&mdata.socket_config, sock->sock_fd); |
|
errno = -ret; |
|
return -1; |
|
} |
|
|
|
/* |
|
* Socket Offload OPS |
|
*/ |
|
|
|
static const struct socket_op_vtable offload_socket_fd_op_vtable; |
|
|
|
static int offload_socket(int family, int type, int proto) |
|
{ |
|
int ret; |
|
|
|
/* defer modem's socket create call to bind() */ |
|
ret = modem_socket_get(&mdata.socket_config, family, type, proto); |
|
if (ret < 0) { |
|
errno = -ret; |
|
return -1; |
|
} |
|
|
|
errno = 0; |
|
return ret; |
|
} |
|
|
|
static int offload_close(void *obj) |
|
{ |
|
struct modem_socket *sock = (struct modem_socket *)obj; |
|
char buf[sizeof("AT+USOCL=#\r")]; |
|
int ret; |
|
|
|
/* make sure socket is allocated and assigned an id */ |
|
if (modem_socket_id_is_assigned(&mdata.socket_config, sock) == false) { |
|
return 0; |
|
} |
|
|
|
if (sock->is_connected || sock->ip_proto == IPPROTO_UDP) { |
|
snprintk(buf, sizeof(buf), "AT+USOCL=%d", sock->id); |
|
|
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, |
|
NULL, 0U, buf, |
|
&mdata.sem_response, MDM_CMD_TIMEOUT); |
|
if (ret < 0) { |
|
LOG_ERR("%s ret:%d", buf, ret); |
|
} |
|
} |
|
|
|
modem_socket_put(&mdata.socket_config, sock->sock_fd); |
|
return 0; |
|
} |
|
|
|
static int offload_bind(void *obj, const struct sockaddr *addr, |
|
socklen_t addrlen) |
|
{ |
|
struct modem_socket *sock = (struct modem_socket *)obj; |
|
|
|
/* save bind address information */ |
|
memcpy(&sock->src, addr, sizeof(*addr)); |
|
|
|
/* make sure we've created the socket */ |
|
if (modem_socket_is_allocated(&mdata.socket_config, sock) == true) { |
|
if (create_socket(sock, addr) < 0) { |
|
return -1; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int offload_connect(void *obj, const struct sockaddr *addr, |
|
socklen_t addrlen) |
|
{ |
|
struct modem_socket *sock = (struct modem_socket *)obj; |
|
int ret; |
|
char buf[sizeof("AT+USOCO=###,!####.####.####.####.####.####.####.####!,#####,#\r")]; |
|
uint16_t dst_port = 0U; |
|
char ip_str[NET_IPV6_ADDR_LEN]; |
|
|
|
if (!addr) { |
|
errno = EINVAL; |
|
return -1; |
|
} |
|
|
|
/* make sure socket has been allocated */ |
|
if (modem_socket_is_allocated(&mdata.socket_config, sock) == false) { |
|
LOG_ERR("Invalid socket_id(%d) from fd:%d", |
|
sock->id, sock->sock_fd); |
|
errno = EINVAL; |
|
return -1; |
|
} |
|
|
|
/* make sure we've created the socket */ |
|
if (modem_socket_id_is_assigned(&mdata.socket_config, sock) == false) { |
|
if (create_socket(sock, NULL) < 0) { |
|
return -1; |
|
} |
|
} |
|
|
|
memcpy(&sock->dst, addr, sizeof(*addr)); |
|
if (addr->sa_family == AF_INET6) { |
|
dst_port = ntohs(net_sin6(addr)->sin6_port); |
|
} else if (addr->sa_family == AF_INET) { |
|
dst_port = ntohs(net_sin(addr)->sin_port); |
|
} else { |
|
errno = EAFNOSUPPORT; |
|
return -1; |
|
} |
|
|
|
/* skip socket connect if UDP */ |
|
if (sock->ip_proto == IPPROTO_UDP) { |
|
errno = 0; |
|
return 0; |
|
} |
|
|
|
ret = modem_context_sprint_ip_addr(addr, ip_str, sizeof(ip_str)); |
|
if (ret != 0) { |
|
errno = -ret; |
|
LOG_ERR("Error formatting IP string %d", ret); |
|
return -1; |
|
} |
|
|
|
snprintk(buf, sizeof(buf), "AT+USOCO=%d,\"%s\",%d", sock->id, |
|
ip_str, dst_port); |
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, |
|
NULL, 0U, buf, |
|
&mdata.sem_response, MDM_CMD_CONN_TIMEOUT); |
|
if (ret < 0) { |
|
LOG_ERR("%s ret:%d", buf, ret); |
|
errno = -ret; |
|
return -1; |
|
} |
|
|
|
sock->is_connected = true; |
|
errno = 0; |
|
return 0; |
|
} |
|
|
|
static ssize_t offload_recvfrom(void *obj, void *buf, size_t len, |
|
int flags, struct sockaddr *from, |
|
socklen_t *fromlen) |
|
{ |
|
struct modem_socket *sock = (struct modem_socket *)obj; |
|
int ret, next_packet_size; |
|
static const struct modem_cmd cmd[] = { |
|
MODEM_CMD("+USORF: ", on_cmd_sockreadfrom, 4U, ","), |
|
MODEM_CMD("+USORD: ", on_cmd_sockread, 2U, ","), |
|
}; |
|
char sendbuf[sizeof("AT+USORF=#,#####\r")]; |
|
struct socket_read_data sock_data; |
|
|
|
if (!buf || len == 0) { |
|
errno = EINVAL; |
|
return -1; |
|
} |
|
|
|
if (flags & ZSOCK_MSG_PEEK) { |
|
errno = ENOTSUP; |
|
return -1; |
|
} |
|
|
|
next_packet_size = modem_socket_next_packet_size(&mdata.socket_config, |
|
sock); |
|
if (!next_packet_size) { |
|
if (flags & ZSOCK_MSG_DONTWAIT) { |
|
errno = EAGAIN; |
|
return -1; |
|
} |
|
|
|
if (!sock->is_connected && sock->ip_proto != IPPROTO_UDP) { |
|
errno = 0; |
|
return 0; |
|
} |
|
|
|
modem_socket_wait_data(&mdata.socket_config, sock); |
|
next_packet_size = modem_socket_next_packet_size( |
|
&mdata.socket_config, sock); |
|
} |
|
|
|
/* |
|
* Binary and ASCII mode allows sending MDM_MAX_DATA_LENGTH bytes to |
|
* the socket in one command |
|
*/ |
|
if (next_packet_size > MDM_MAX_DATA_LENGTH) { |
|
next_packet_size = MDM_MAX_DATA_LENGTH; |
|
} |
|
|
|
snprintk(sendbuf, sizeof(sendbuf), "AT+USO%s=%d,%zd", |
|
sock->ip_proto == IPPROTO_UDP ? "RF" : "RD", sock->id, |
|
len < next_packet_size ? len : next_packet_size); |
|
|
|
/* socket read settings */ |
|
(void)memset(&sock_data, 0, sizeof(sock_data)); |
|
sock_data.recv_buf = buf; |
|
sock_data.recv_buf_len = len; |
|
sock_data.recv_addr = from; |
|
sock->data = &sock_data; |
|
|
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, |
|
cmd, ARRAY_SIZE(cmd), sendbuf, &mdata.sem_response, |
|
MDM_CMD_TIMEOUT); |
|
if (ret < 0) { |
|
errno = -ret; |
|
ret = -1; |
|
goto exit; |
|
} |
|
|
|
/* HACK: use dst address as from */ |
|
if (from && fromlen) { |
|
*fromlen = sizeof(sock->dst); |
|
memcpy(from, &sock->dst, *fromlen); |
|
} |
|
|
|
/* return length of received data */ |
|
errno = 0; |
|
ret = sock_data.recv_read_len; |
|
|
|
exit: |
|
/* clear socket data */ |
|
sock->data = NULL; |
|
return ret; |
|
} |
|
|
|
static ssize_t offload_sendto(void *obj, const void *buf, size_t len, |
|
int flags, const struct sockaddr *to, |
|
socklen_t tolen) |
|
{ |
|
struct iovec msg_iov = { |
|
.iov_base = (void *)buf, |
|
.iov_len = len, |
|
}; |
|
struct msghdr msg = { |
|
.msg_iovlen = 1, |
|
.msg_name = (struct sockaddr *)to, |
|
.msg_namelen = tolen, |
|
.msg_iov = &msg_iov, |
|
}; |
|
|
|
int ret = send_socket_data(obj, &msg, MDM_CMD_TIMEOUT); |
|
if (ret < 0) { |
|
errno = -ret; |
|
return -1; |
|
} |
|
|
|
errno = 0; |
|
return ret; |
|
} |
|
|
|
static int offload_ioctl(void *obj, unsigned int request, va_list args) |
|
{ |
|
switch (request) { |
|
case ZFD_IOCTL_POLL_PREPARE: { |
|
struct zsock_pollfd *pfd; |
|
struct k_poll_event **pev; |
|
struct k_poll_event *pev_end; |
|
|
|
pfd = va_arg(args, struct zsock_pollfd *); |
|
pev = va_arg(args, struct k_poll_event **); |
|
pev_end = va_arg(args, struct k_poll_event *); |
|
|
|
return modem_socket_poll_prepare(&mdata.socket_config, obj, pfd, pev, pev_end); |
|
} |
|
case ZFD_IOCTL_POLL_UPDATE: { |
|
struct zsock_pollfd *pfd; |
|
struct k_poll_event **pev; |
|
|
|
pfd = va_arg(args, struct zsock_pollfd *); |
|
pev = va_arg(args, struct k_poll_event **); |
|
|
|
return modem_socket_poll_update(obj, pfd, pev); |
|
} |
|
|
|
case F_GETFL: |
|
return 0; |
|
|
|
default: |
|
errno = EINVAL; |
|
return -1; |
|
} |
|
} |
|
|
|
static ssize_t offload_read(void *obj, void *buffer, size_t count) |
|
{ |
|
return offload_recvfrom(obj, buffer, count, 0, NULL, 0); |
|
} |
|
|
|
static ssize_t offload_write(void *obj, const void *buffer, size_t count) |
|
{ |
|
return offload_sendto(obj, buffer, count, 0, NULL, 0); |
|
} |
|
|
|
static ssize_t offload_sendmsg(void *obj, const struct msghdr *msg, int flags) |
|
{ |
|
ssize_t sent = 0; |
|
int bkp_iovec_idx; |
|
struct iovec bkp_iovec = {0}; |
|
struct msghdr crafted_msg = { |
|
.msg_name = msg->msg_name, |
|
.msg_namelen = msg->msg_namelen, |
|
}; |
|
size_t full_len = 0; |
|
int ret; |
|
|
|
/* Compute the full length to be send and check for invalid values */ |
|
for (int i = 0; i < msg->msg_iovlen; i++) { |
|
if (!msg->msg_iov[i].iov_base || msg->msg_iov[i].iov_len == 0) { |
|
errno = EINVAL; |
|
return -1; |
|
} |
|
full_len += msg->msg_iov[i].iov_len; |
|
} |
|
|
|
LOG_DBG("msg_iovlen:%zd flags:%d, full_len:%zd", |
|
msg->msg_iovlen, flags, full_len); |
|
|
|
while (full_len > sent) { |
|
int removed = 0; |
|
int i = 0; |
|
|
|
crafted_msg.msg_iovlen = msg->msg_iovlen; |
|
crafted_msg.msg_iov = &msg->msg_iov[0]; |
|
|
|
bkp_iovec_idx = -1; |
|
/* Iterate on iovec to remove the bytes already sent */ |
|
while (removed < sent) { |
|
int to_removed = sent - removed; |
|
|
|
if (to_removed >= msg->msg_iov[i].iov_len) { |
|
crafted_msg.msg_iovlen -= 1; |
|
crafted_msg.msg_iov = &msg->msg_iov[i + 1]; |
|
|
|
removed += msg->msg_iov[i].iov_len; |
|
} else { |
|
/* Backup msg->msg_iov[i] before "removing" |
|
* starting bytes already send. |
|
*/ |
|
bkp_iovec_idx = i; |
|
bkp_iovec.iov_len = msg->msg_iov[i].iov_len; |
|
bkp_iovec.iov_base = msg->msg_iov[i].iov_base; |
|
|
|
/* Update msg->msg_iov[i] to "remove" |
|
* starting bytes already send. |
|
*/ |
|
msg->msg_iov[i].iov_len -= to_removed; |
|
msg->msg_iov[i].iov_base = &(((uint8_t *)msg->msg_iov[i].iov_base)[to_removed]); |
|
|
|
removed += to_removed; |
|
} |
|
|
|
i++; |
|
} |
|
|
|
ret = send_socket_data(obj, &crafted_msg, MDM_CMD_TIMEOUT); |
|
|
|
/* Restore backup iovec when necessary */ |
|
if (bkp_iovec_idx != -1) { |
|
msg->msg_iov[bkp_iovec_idx].iov_len = bkp_iovec.iov_len; |
|
msg->msg_iov[bkp_iovec_idx].iov_base = bkp_iovec.iov_base; |
|
} |
|
|
|
/* Handle send_socket_data() returned value */ |
|
if (ret < 0) { |
|
errno = -ret; |
|
return -1; |
|
} |
|
|
|
sent += ret; |
|
} |
|
|
|
return (ssize_t)sent; |
|
} |
|
|
|
#if defined(CONFIG_NET_SOCKETS_SOCKOPT_TLS) |
|
static int map_credentials(struct modem_socket *sock, const void *optval, socklen_t optlen) |
|
{ |
|
sec_tag_t *sec_tags = (sec_tag_t *)optval; |
|
int ret = 0; |
|
int tags_len; |
|
sec_tag_t tag; |
|
int id; |
|
int i; |
|
struct tls_credential *cert; |
|
|
|
if ((optlen % sizeof(sec_tag_t)) != 0 || (optlen == 0)) { |
|
return -EINVAL; |
|
} |
|
|
|
tags_len = optlen / sizeof(sec_tag_t); |
|
/* For each tag, retrieve the credentials value and type: */ |
|
for (i = 0; i < tags_len; i++) { |
|
tag = sec_tags[i]; |
|
cert = credential_next_get(tag, NULL); |
|
while (cert != NULL) { |
|
switch (cert->type) { |
|
case TLS_CREDENTIAL_CA_CERTIFICATE: |
|
id = 0; |
|
break; |
|
case TLS_CREDENTIAL_NONE: |
|
case TLS_CREDENTIAL_PSK: |
|
case TLS_CREDENTIAL_PSK_ID: |
|
default: |
|
/* Not handled */ |
|
return -EINVAL; |
|
} |
|
struct modem_cmd cmd[] = { |
|
MODEM_CMD("+USECMNG: ", on_cmd_cert_write, 3U, ","), |
|
}; |
|
ret = send_cert(sock, cmd, 1, cert->buf, cert->len, id); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
|
|
cert = credential_next_get(tag, cert); |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
#else |
|
static int map_credentials(struct modem_socket *sock, const void *optval, socklen_t optlen) |
|
{ |
|
return -EINVAL; |
|
} |
|
#endif |
|
|
|
static int offload_setsockopt(void *obj, int level, int optname, |
|
const void *optval, socklen_t optlen) |
|
{ |
|
struct modem_socket *sock = (struct modem_socket *)obj; |
|
|
|
int ret; |
|
|
|
if (IS_ENABLED(CONFIG_NET_SOCKETS_SOCKOPT_TLS) && level == SOL_TLS) { |
|
switch (optname) { |
|
case TLS_SEC_TAG_LIST: |
|
ret = map_credentials(sock, optval, optlen); |
|
break; |
|
case TLS_HOSTNAME: |
|
LOG_WRN("TLS_HOSTNAME option is not supported"); |
|
return -EINVAL; |
|
case TLS_PEER_VERIFY: |
|
if (*(uint32_t *)optval != TLS_PEER_VERIFY_REQUIRED) { |
|
LOG_WRN("Disabling peer verification is not supported"); |
|
return -EINVAL; |
|
} |
|
ret = 0; |
|
break; |
|
default: |
|
return -EINVAL; |
|
} |
|
} else { |
|
return -EINVAL; |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
|
|
static const struct socket_op_vtable offload_socket_fd_op_vtable = { |
|
.fd_vtable = { |
|
.read = offload_read, |
|
.write = offload_write, |
|
.close = offload_close, |
|
.ioctl = offload_ioctl, |
|
}, |
|
.bind = offload_bind, |
|
.connect = offload_connect, |
|
.sendto = offload_sendto, |
|
.recvfrom = offload_recvfrom, |
|
.listen = NULL, |
|
.accept = NULL, |
|
.sendmsg = offload_sendmsg, |
|
.getsockopt = NULL, |
|
.setsockopt = offload_setsockopt, |
|
}; |
|
|
|
static bool offload_is_supported(int family, int type, int proto) |
|
{ |
|
if (family != AF_INET && |
|
family != AF_INET6) { |
|
return false; |
|
} |
|
|
|
if (type != SOCK_DGRAM && |
|
type != SOCK_STREAM) { |
|
return false; |
|
} |
|
|
|
if (proto != IPPROTO_TCP && |
|
proto != IPPROTO_UDP && |
|
proto != IPPROTO_TLS_1_2) { |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
NET_SOCKET_OFFLOAD_REGISTER(ublox_sara_r4, CONFIG_NET_SOCKETS_OFFLOAD_PRIORITY, |
|
AF_UNSPEC, offload_is_supported, offload_socket); |
|
|
|
#if defined(CONFIG_DNS_RESOLVER) |
|
/* TODO: This is a bare-bones implementation of DNS handling |
|
* We ignore most of the hints like ai_family, ai_protocol and ai_socktype. |
|
* Later, we can add additional handling if it makes sense. |
|
*/ |
|
static int offload_getaddrinfo(const char *node, const char *service, |
|
const struct zsock_addrinfo *hints, |
|
struct zsock_addrinfo **res) |
|
{ |
|
static const struct modem_cmd cmd = |
|
MODEM_CMD("+UDNSRN: ", on_cmd_dns, 1U, ","); |
|
uint32_t port = 0U; |
|
int ret; |
|
/* DNS command + 128 bytes for domain name parameter */ |
|
char sendbuf[sizeof("AT+UDNSRN=#,'[]'\r") + 128]; |
|
|
|
/* init result */ |
|
(void)memset(&result, 0, sizeof(result)); |
|
(void)memset(&result_addr, 0, sizeof(result_addr)); |
|
/* FIXME: Hard-code DNS to return only IPv4 */ |
|
result.ai_family = AF_INET; |
|
result_addr.sa_family = AF_INET; |
|
result.ai_addr = &result_addr; |
|
result.ai_addrlen = sizeof(result_addr); |
|
result.ai_canonname = result_canonname; |
|
result_canonname[0] = '\0'; |
|
|
|
if (service) { |
|
port = ATOI(service, 0U, "port"); |
|
if (port < 1 || port > USHRT_MAX) { |
|
return DNS_EAI_SERVICE; |
|
} |
|
} |
|
|
|
if (port > 0U) { |
|
/* FIXME: DNS is hard-coded to return only IPv4 */ |
|
if (result.ai_family == AF_INET) { |
|
net_sin(&result_addr)->sin_port = htons(port); |
|
} |
|
} |
|
|
|
/* check to see if node is an IP address */ |
|
if (net_addr_pton(result.ai_family, node, |
|
&((struct sockaddr_in *)&result_addr)->sin_addr) |
|
== 0) { |
|
*res = &result; |
|
return 0; |
|
} |
|
|
|
/* user flagged node as numeric host, but we failed net_addr_pton */ |
|
if (hints && hints->ai_flags & AI_NUMERICHOST) { |
|
return DNS_EAI_NONAME; |
|
} |
|
|
|
snprintk(sendbuf, sizeof(sendbuf), "AT+UDNSRN=0,\"%s\"", node); |
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, |
|
&cmd, 1U, sendbuf, &mdata.sem_response, |
|
MDM_DNS_TIMEOUT); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
|
|
LOG_DBG("DNS RESULT: %s", |
|
net_addr_ntop(result.ai_family, |
|
&net_sin(&result_addr)->sin_addr, |
|
sendbuf, NET_IPV4_ADDR_LEN)); |
|
|
|
*res = (struct zsock_addrinfo *)&result; |
|
return 0; |
|
} |
|
|
|
static void offload_freeaddrinfo(struct zsock_addrinfo *res) |
|
{ |
|
/* using static result from offload_getaddrinfo() -- no need to free */ |
|
ARG_UNUSED(res); |
|
} |
|
|
|
static const struct socket_dns_offload offload_dns_ops = { |
|
.getaddrinfo = offload_getaddrinfo, |
|
.freeaddrinfo = offload_freeaddrinfo, |
|
}; |
|
#endif |
|
|
|
static int net_offload_dummy_get(sa_family_t family, |
|
enum net_sock_type type, |
|
enum net_ip_protocol ip_proto, |
|
struct net_context **context) |
|
{ |
|
|
|
LOG_ERR("CONFIG_NET_SOCKETS_OFFLOAD must be enabled for this driver"); |
|
|
|
return -ENOTSUP; |
|
} |
|
|
|
/* placeholders, until Zephyr IP stack updated to handle a NULL net_offload */ |
|
static struct net_offload modem_net_offload = { |
|
.get = net_offload_dummy_get, |
|
}; |
|
|
|
#define HASH_MULTIPLIER 37 |
|
static uint32_t hash32(char *str, int len) |
|
{ |
|
uint32_t h = 0; |
|
int i; |
|
|
|
for (i = 0; i < len; ++i) { |
|
h = (h * HASH_MULTIPLIER) + str[i]; |
|
} |
|
|
|
return h; |
|
} |
|
|
|
static inline uint8_t *modem_get_mac(const struct device *dev) |
|
{ |
|
struct modem_data *data = dev->data; |
|
uint32_t hash_value; |
|
|
|
data->mac_addr[0] = 0x00; |
|
data->mac_addr[1] = 0x10; |
|
|
|
/* use IMEI for mac_addr */ |
|
hash_value = hash32(mdata.mdm_imei, strlen(mdata.mdm_imei)); |
|
|
|
UNALIGNED_PUT(hash_value, (uint32_t *)(data->mac_addr + 2)); |
|
|
|
return data->mac_addr; |
|
} |
|
|
|
static int offload_socket(int family, int type, int proto); |
|
|
|
static void modem_net_iface_init(struct net_if *iface) |
|
{ |
|
const struct device *dev = net_if_get_device(iface); |
|
struct modem_data *data = dev->data; |
|
|
|
/* Direct socket offload used instead of net offload: */ |
|
iface->if_dev->offload = &modem_net_offload; |
|
net_if_set_link_addr(iface, modem_get_mac(dev), |
|
sizeof(data->mac_addr), |
|
NET_LINK_ETHERNET); |
|
data->net_iface = iface; |
|
#ifdef CONFIG_DNS_RESOLVER |
|
socket_offload_dns_register(&offload_dns_ops); |
|
#endif |
|
|
|
net_if_socket_offload_set(iface, offload_socket); |
|
} |
|
|
|
static struct offloaded_if_api api_funcs = { |
|
.iface_api.init = modem_net_iface_init, |
|
}; |
|
|
|
static const struct modem_cmd response_cmds[] = { |
|
MODEM_CMD("OK", on_cmd_ok, 0U, ""), /* 3GPP */ |
|
MODEM_CMD("ERROR", on_cmd_error, 0U, ""), /* 3GPP */ |
|
MODEM_CMD("+CME ERROR: ", on_cmd_exterror, 1U, ""), |
|
MODEM_CMD_DIRECT("@", on_prompt), |
|
}; |
|
|
|
static const struct modem_cmd unsol_cmds[] = { |
|
MODEM_CMD("+UUSOCL: ", on_cmd_socknotifyclose, 1U, ""), |
|
MODEM_CMD("+UUSORD: ", on_cmd_socknotifydata, 2U, ","), |
|
MODEM_CMD("+UUSORF: ", on_cmd_socknotifydata, 2U, ","), |
|
MODEM_CMD("+CREG: ", on_cmd_socknotifycreg, 1U, ""), |
|
}; |
|
|
|
static int modem_init(const struct device *dev) |
|
{ |
|
int ret = 0; |
|
|
|
ARG_UNUSED(dev); |
|
|
|
k_sem_init(&mdata.sem_response, 0, 1); |
|
k_sem_init(&mdata.sem_prompt, 0, 1); |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_RSSI_WORK) |
|
/* initialize the work queue */ |
|
k_work_queue_start(&modem_workq, modem_workq_stack, |
|
K_KERNEL_STACK_SIZEOF(modem_workq_stack), |
|
K_PRIO_COOP(7), NULL); |
|
#endif |
|
|
|
/* socket config */ |
|
ret = modem_socket_init(&mdata.socket_config, &mdata.sockets[0], ARRAY_SIZE(mdata.sockets), |
|
MDM_BASE_SOCKET_NUM, false, &offload_socket_fd_op_vtable); |
|
if (ret < 0) { |
|
goto error; |
|
} |
|
|
|
/* cmd handler */ |
|
const struct modem_cmd_handler_config cmd_handler_config = { |
|
.match_buf = &mdata.cmd_match_buf[0], |
|
.match_buf_len = sizeof(mdata.cmd_match_buf), |
|
.buf_pool = &mdm_recv_pool, |
|
.alloc_timeout = K_NO_WAIT, |
|
.eol = "\r", |
|
.user_data = NULL, |
|
.response_cmds = response_cmds, |
|
.response_cmds_len = ARRAY_SIZE(response_cmds), |
|
.unsol_cmds = unsol_cmds, |
|
.unsol_cmds_len = ARRAY_SIZE(unsol_cmds), |
|
}; |
|
|
|
ret = modem_cmd_handler_init(&mctx.cmd_handler, &mdata.cmd_handler_data, |
|
&cmd_handler_config); |
|
if (ret < 0) { |
|
goto error; |
|
} |
|
|
|
/* modem interface */ |
|
const struct modem_iface_uart_config uart_config = { |
|
.rx_rb_buf = &mdata.iface_rb_buf[0], |
|
.rx_rb_buf_len = sizeof(mdata.iface_rb_buf), |
|
.dev = MDM_UART_DEV, |
|
.hw_flow_control = DT_PROP(MDM_UART_NODE, hw_flow_control), |
|
}; |
|
|
|
ret = modem_iface_uart_init(&mctx.iface, &mdata.iface_data, &uart_config); |
|
if (ret < 0) { |
|
goto error; |
|
} |
|
|
|
/* modem data storage */ |
|
mctx.data_manufacturer = mdata.mdm_manufacturer; |
|
mctx.data_model = mdata.mdm_model; |
|
mctx.data_revision = mdata.mdm_revision; |
|
mctx.data_imei = mdata.mdm_imei; |
|
mctx.data_rssi = &mdata.mdm_rssi; |
|
|
|
/* pin setup */ |
|
ret = gpio_pin_configure_dt(&power_gpio, GPIO_OUTPUT); |
|
if (ret < 0) { |
|
LOG_ERR("Failed to configure %s pin", "power"); |
|
goto error; |
|
} |
|
|
|
#if DT_INST_NODE_HAS_PROP(0, mdm_reset_gpios) |
|
ret = gpio_pin_configure_dt(&reset_gpio, GPIO_OUTPUT); |
|
if (ret < 0) { |
|
LOG_ERR("Failed to configure %s pin", "reset"); |
|
goto error; |
|
} |
|
#endif |
|
|
|
#if DT_INST_NODE_HAS_PROP(0, mdm_vint_gpios) |
|
ret = gpio_pin_configure_dt(&vint_gpio, GPIO_INPUT); |
|
if (ret < 0) { |
|
LOG_ERR("Failed to configure %s pin", "vint"); |
|
goto error; |
|
} |
|
#endif |
|
|
|
mctx.driver_data = &mdata; |
|
|
|
ret = modem_context_register(&mctx); |
|
if (ret < 0) { |
|
LOG_ERR("Error registering modem context: %d", ret); |
|
goto error; |
|
} |
|
|
|
/* start RX thread */ |
|
k_thread_create(&modem_rx_thread, modem_rx_stack, |
|
K_KERNEL_STACK_SIZEOF(modem_rx_stack), |
|
modem_rx, |
|
NULL, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT); |
|
|
|
#if defined(CONFIG_MODEM_UBLOX_SARA_RSSI_WORK) |
|
/* init RSSI query */ |
|
k_work_init_delayable(&mdata.rssi_query_work, modem_rssi_query_work); |
|
#endif |
|
|
|
modem_reset(); |
|
|
|
error: |
|
return ret; |
|
} |
|
|
|
NET_DEVICE_DT_INST_OFFLOAD_DEFINE(0, modem_init, NULL, |
|
&mdata, NULL, |
|
CONFIG_MODEM_UBLOX_SARA_R4_INIT_PRIORITY, |
|
&api_funcs, |
|
MDM_MAX_DATA_LENGTH);
|
|
|