zephyr/drivers/modem/ublox-sara-r4.c
Keith Packard 87367afa80 drivers/modem: Increase sprintk buffers to hold parameter types
These drivers use stack buffers to hold AT command strings which are
generated at runtime using sprintk. The buffers are only sized for the
expected range of values, not the full possible range given the datatypes
involved. Values outside this expected range could cause a buffer overflow.

To mitigate this, increase the size of each buffef to hold the full range
of each parameter type.

Signed-off-by: Keith Packard <keithp@keithp.com>
2022-06-09 11:33:09 +02:00

2238 lines
54 KiB
C

/*
* 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/zephyr.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <fcntl.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_offload.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", log_strdup(s), log_strdup(desc),
log_strdup(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\"", log_strdup(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;
}
mctx.iface.write(&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 = k_sem_take(&mdata.sem_response, timeout);
if (ret == 0) {
ret = modem_cmd_handler_get_error(&mdata.cmd_handler_data);
} else if (ret == -EAGAIN) {
ret = -ETIMEDOUT;
}
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;
}
/* slight pause per spec so that @ prompt is received */
k_sleep(MDM_PROMPT_CMD_DELAY);
mctx.iface.write(&mctx.iface, cert_data, cert_len);
k_sem_reset(&mdata.sem_response);
ret = k_sem_take(&mdata.sem_response, K_MSEC(1000));
if (ret == 0) {
ret = modem_cmd_handler_get_error(&mdata.cmd_handler_data);
} else if (ret == -EAGAIN) {
ret = -ETIMEDOUT;
}
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", log_strdup(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", log_strdup(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", log_strdup(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", log_strdup(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", log_strdup(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;
/* look up new socket by special id */
sock = modem_socket_from_newid(&mdata.socket_config);
if (sock) {
sock->id = ATOI(argv[0],
mdata.socket_config.base_socket_num - 1,
"socket_id");
/* on error give up modem socket */
if (sock->id == mdata.socket_config.base_socket_num - 1) {
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", log_strdup(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)
{
while (true) {
/* wait for incoming data */
k_sem_take(&mdata.iface_data.rx_sem, K_FOREVER);
mctx.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 buf[sizeof("AT+USECPRF=#,#,#######\r")];
/* Enable socket security */
snprintk(buf, sizeof(buf), "AT+USOSEC=%d,1,%d", sock->id, sock->id);
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, buf,
&mdata.sem_response, MDM_CMD_TIMEOUT);
if (ret < 0) {
goto error;
}
/* Reset the security profile */
snprintk(buf, sizeof(buf), "AT+USECPRF=%d", sock->id);
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, buf,
&mdata.sem_response, MDM_CMD_TIMEOUT);
if (ret < 0) {
goto error;
}
/* Validate server cert against the CA. */
snprintk(buf, sizeof(buf), "AT+USECPRF=%d,0,1", sock->id);
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, buf,
&mdata.sem_response, MDM_CMD_TIMEOUT);
if (ret < 0) {
goto error;
}
/* Use TLSv1.2 only */
snprintk(buf, sizeof(buf), "AT+USECPRF=%d,1,3", sock->id);
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, buf,
&mdata.sem_response, MDM_CMD_TIMEOUT);
if (ret < 0) {
goto error;
}
/* Set root CA filename */
snprintk(buf, sizeof(buf), "AT+USECPRF=%d,3,\"ca\"", sock->id);
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, buf,
&mdata.sem_response, MDM_CMD_TIMEOUT);
if (ret < 0) {
goto error;
}
}
errno = 0;
return 0;
error:
LOG_ERR("%s ret:%d", log_strdup(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 we assigned an id */
if (sock->id < mdata.socket_config.base_socket_num) {
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", log_strdup(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 (sock->id == mdata.socket_config.sockets_len + 1) {
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;
}
if (sock->id < mdata.socket_config.base_socket_num - 1) {
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 (sock->id == mdata.socket_config.sockets_len + 1) {
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", log_strdup(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",
log_strdup(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 */
res = NULL;
}
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 net_if_api api_funcs = {
.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 */
mdata.socket_config.sockets = &mdata.sockets[0];
mdata.socket_config.sockets_len = ARRAY_SIZE(mdata.sockets);
mdata.socket_config.base_socket_num = MDM_BASE_SOCKET_NUM;
ret = modem_socket_init(&mdata.socket_config,
&offload_socket_fd_op_vtable);
if (ret < 0) {
goto error;
}
/* cmd handler */
mdata.cmd_handler_data.cmds[CMD_RESP] = response_cmds;
mdata.cmd_handler_data.cmds_len[CMD_RESP] = ARRAY_SIZE(response_cmds);
mdata.cmd_handler_data.cmds[CMD_UNSOL] = unsol_cmds;
mdata.cmd_handler_data.cmds_len[CMD_UNSOL] = ARRAY_SIZE(unsol_cmds);
mdata.cmd_handler_data.match_buf = &mdata.cmd_match_buf[0];
mdata.cmd_handler_data.match_buf_len = sizeof(mdata.cmd_match_buf);
mdata.cmd_handler_data.buf_pool = &mdm_recv_pool;
mdata.cmd_handler_data.alloc_timeout = K_NO_WAIT;
mdata.cmd_handler_data.eol = "\r";
ret = modem_cmd_handler_init(&mctx.cmd_handler,
&mdata.cmd_handler_data);
if (ret < 0) {
goto error;
}
/* modem interface */
mdata.iface_data.hw_flow_control = DT_PROP(MDM_UART_NODE,
hw_flow_control);
mdata.iface_data.rx_rb_buf = &mdata.iface_rb_buf[0];
mdata.iface_data.rx_rb_buf_len = sizeof(mdata.iface_rb_buf);
ret = modem_iface_uart_init(&mctx.iface, &mdata.iface_data,
MDM_UART_DEV);
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),
(k_thread_entry_t) 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);