blob: c84415b3767d7457f6ff64f6968716f00ab99200 [file] [log] [blame]
/*
* Copyright (c) 2018 Foundries.io
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT wnc_m14a2a
#define LOG_DOMAIN modem_wncm14a2a
#define LOG_LEVEL CONFIG_MODEM_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(LOG_DOMAIN);
#include <zephyr/types.h>
#include <stddef.h>
#include <stdlib.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 <zephyr/random/rand32.h>
#include <zephyr/net/net_context.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_offload.h>
#include <zephyr/net/net_pkt.h>
#if defined(CONFIG_NET_IPV6)
#include "ipv6.h"
#endif
#if defined(CONFIG_NET_IPV4)
#include "ipv4.h"
#endif
#if defined(CONFIG_NET_UDP)
#include "udp_internal.h"
#endif
#include "modem_receiver.h"
/* Uncomment the #define below to enable a hexdump of all incoming
* data from the modem receiver
*/
/* #define ENABLE_VERBOSE_MODEM_RECV_HEXDUMP 1 */
struct mdm_control_pinconfig {
char *dev_name;
gpio_pin_t pin;
gpio_flags_t flags;
};
#define PINCONFIG(name_, pin_, flags_) { \
.dev_name = name_, \
.pin = pin_, \
.flags = flags_ \
}
/* pin settings */
enum mdm_control_pins {
MDM_BOOT_MODE_SEL = 0,
MDM_POWER,
MDM_KEEP_AWAKE,
MDM_RESET,
SHLD_3V3_1V8_SIG_TRANS_ENA,
#if DT_INST_NODE_HAS_PROP(0, mdm_send_ok_gpios)
MDM_SEND_OK,
#endif
MAX_MDM_CONTROL_PINS,
};
static const struct mdm_control_pinconfig pinconfig[] = {
/* MDM_BOOT_MODE_SEL */
PINCONFIG(DT_INST_GPIO_LABEL(0, mdm_boot_mode_sel_gpios),
DT_INST_GPIO_PIN(0, mdm_boot_mode_sel_gpios),
DT_INST_GPIO_FLAGS(0, mdm_boot_mode_sel_gpios)),
/* MDM_POWER */
PINCONFIG(DT_INST_GPIO_LABEL(0, mdm_power_gpios),
DT_INST_GPIO_PIN(0, mdm_power_gpios),
DT_INST_GPIO_FLAGS(0, mdm_power_gpios)),
/* MDM_KEEP_AWAKE */
PINCONFIG(DT_INST_GPIO_LABEL(0, mdm_keep_awake_gpios),
DT_INST_GPIO_PIN(0, mdm_keep_awake_gpios),
DT_INST_GPIO_FLAGS(0, mdm_keep_awake_gpios)),
/* MDM_RESET */
PINCONFIG(DT_INST_GPIO_LABEL(0, mdm_reset_gpios),
DT_INST_GPIO_PIN(0, mdm_reset_gpios),
DT_INST_GPIO_FLAGS(0, mdm_reset_gpios)),
/* SHLD_3V3_1V8_SIG_TRANS_ENA */
PINCONFIG(DT_INST_GPIO_LABEL(0, mdm_shld_trans_ena_gpios),
DT_INST_GPIO_PIN(0, mdm_shld_trans_ena_gpios),
DT_INST_GPIO_FLAGS(0, mdm_shld_trans_ena_gpios)),
#if DT_INST_NODE_HAS_PROP(0, mdm_send_ok_gpios)
/* MDM_SEND_OK */
PINCONFIG(DT_INST_GPIO_LABEL(0, mdm_send_ok_gpios),
DT_INST_GPIO_PIN(0, mdm_send_ok_gpios),
DT_INST_GPIO_FLAGS(0, mdm_send_ok_gpios)),
#endif
};
#define MDM_UART_DEV DEVICE_DT_GET(DT_INST_BUS(0))
#define MDM_BOOT_MODE_SPECIAL 0
#define MDM_BOOT_MODE_NORMAL 1
#define MDM_POWER_ENABLE 0
#define MDM_POWER_DISABLE 1
#define MDM_KEEP_AWAKE_DISABLED 0
#define MDM_KEEP_AWAKE_ENABLED 1
#define MDM_RESET_NOT_ASSERTED 0
#define MDM_RESET_ASSERTED 1
#define SHLD_3V3_1V8_SIG_TRANS_DISABLED 0
#define SHLD_3V3_1V8_SIG_TRANS_ENABLED 1
#define MDM_SEND_OK_ENABLED 0
#define MDM_SEND_OK_DISABLED 1
#define MDM_CMD_TIMEOUT (5 * MSEC_PER_SEC)
#define MDM_CMD_SEND_TIMEOUT (10 * MSEC_PER_SEC)
#define MDM_CMD_CONN_TIMEOUT (31 * MSEC_PER_SEC)
#define MDM_MAX_DATA_LENGTH 1500
#define MDM_RECV_MAX_BUF 30
#define MDM_RECV_BUF_SIZE 128
#define MDM_MAX_SOCKETS 6
#define BUF_ALLOC_TIMEOUT K_SECONDS(1)
#define CMD_HANDLER(cmd_, cb_) { \
.cmd = cmd_, \
.cmd_len = (uint16_t)sizeof(cmd_)-1, \
.func = on_cmd_ ## cb_ \
}
#define MDM_MANUFACTURER_LENGTH 10
#define MDM_MODEL_LENGTH 16
#define MDM_REVISION_LENGTH 64
#define MDM_IMEI_LENGTH 16
#define RSSI_TIMEOUT_SECS 30
NET_BUF_POOL_DEFINE(mdm_recv_pool, MDM_RECV_MAX_BUF, MDM_RECV_BUF_SIZE,
0, NULL);
static uint8_t mdm_recv_buf[MDM_MAX_DATA_LENGTH];
/* RX thread structures */
K_KERNEL_STACK_DEFINE(wncm14a2a_rx_stack,
CONFIG_MODEM_WNCM14A2A_RX_STACK_SIZE);
struct k_thread wncm14a2a_rx_thread;
/* RX thread work queue */
K_KERNEL_STACK_DEFINE(wncm14a2a_workq_stack,
CONFIG_MODEM_WNCM14A2A_RX_WORKQ_STACK_SIZE);
static struct k_work_q wncm14a2a_workq;
struct wncm14a2a_socket {
struct net_context *context;
sa_family_t family;
enum net_sock_type type;
enum net_ip_protocol ip_proto;
struct sockaddr src;
struct sockaddr dst;
int socket_id;
/** semaphore */
struct k_sem sock_send_sem;
/** socket callbacks */
struct k_work recv_cb_work;
net_context_recv_cb_t recv_cb;
struct net_pkt *recv_pkt;
void *recv_user_data;
};
struct wncm14a2a_iface_ctx {
struct net_if *iface;
uint8_t mac_addr[6];
/* GPIO PORT devices */
const struct device *gpio_port_dev[MAX_MDM_CONTROL_PINS];
/* RX specific attributes */
struct mdm_receiver_context mdm_ctx;
/* socket data */
struct wncm14a2a_socket sockets[MDM_MAX_SOCKETS];
int last_socket_id;
int last_error;
/* semaphores */
struct k_sem response_sem;
/* RSSI work */
struct k_work_delayable rssi_query_work;
/* 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];
int mdm_rssi;
/* modem state */
int ev_csps;
int ev_rrcstate;
};
struct cmd_handler {
const char *cmd;
uint16_t cmd_len;
void (*func)(struct net_buf **buf, uint16_t len);
};
static struct wncm14a2a_iface_ctx ictx;
static void wncm14a2a_read_rx(struct net_buf **buf);
/*** Verbose Debugging Functions ***/
#if defined(ENABLE_VERBOSE_MODEM_RECV_HEXDUMP)
static inline void hexdump(const uint8_t *packet, size_t length)
{
char output[sizeof("xxxxyyyy xxxxyyyy")];
int n = 0, k = 0;
uint8_t byte;
while (length--) {
if (n % 16 == 0) {
printk(" %08X ", n);
}
byte = *packet++;
printk("%02X ", byte);
if (byte < 0x20 || byte > 0x7f) {
output[k++] = '.';
} else {
output[k++] = byte;
}
n++;
if (n % 8 == 0) {
if (n % 16 == 0) {
output[k] = '\0';
printk(" [%s]\n", output);
k = 0;
} else {
printk(" ");
}
}
}
if (n % 16) {
int i;
output[k] = '\0';
for (i = 0; i < (16 - (n % 16)); i++) {
printk(" ");
}
if ((n % 16) < 8) {
printk(" "); /* one extra delimiter after 8 chars */
}
printk(" [%s]\n", output);
}
}
#else
#define hexdump(...)
#endif
static struct wncm14a2a_socket *socket_get(void)
{
int i;
struct wncm14a2a_socket *sock = NULL;
for (i = 0; i < MDM_MAX_SOCKETS; i++) {
if (!ictx.sockets[i].context) {
sock = &ictx.sockets[i];
break;
}
}
return sock;
}
static struct wncm14a2a_socket *socket_from_id(int socket_id)
{
int i;
struct wncm14a2a_socket *sock = NULL;
if (socket_id < 1) {
return NULL;
}
for (i = 0; i < MDM_MAX_SOCKETS; i++) {
if (ictx.sockets[i].socket_id == socket_id) {
sock = &ictx.sockets[i];
break;
}
}
return sock;
}
static void socket_put(struct wncm14a2a_socket *sock)
{
if (!sock) {
return;
}
sock->context = NULL;
sock->socket_id = 0;
(void)memset(&sock->src, 0, sizeof(struct sockaddr));
(void)memset(&sock->dst, 0, sizeof(struct sockaddr));
}
char *wncm14a2a_sprint_ip_addr(const struct sockaddr *addr)
{
static char buf[NET_IPV6_ADDR_LEN];
#if defined(CONFIG_NET_IPV6)
if (addr->sa_family == AF_INET6) {
return net_addr_ntop(AF_INET6, &net_sin6(addr)->sin6_addr,
buf, sizeof(buf));
} else
#endif
#if defined(CONFIG_NET_IPV4)
if (addr->sa_family == AF_INET) {
return net_addr_ntop(AF_INET, &net_sin(addr)->sin_addr,
buf, sizeof(buf));
} else
#endif
{
LOG_ERR("Unknown IP address family:%d", addr->sa_family);
return NULL;
}
}
/* Send an AT command with a series of response handlers */
static int send_at_cmd(struct wncm14a2a_socket *sock,
const uint8_t *data, int timeout)
{
int ret;
ictx.last_error = 0;
LOG_DBG("OUT: [%s]", data);
mdm_receiver_send(&ictx.mdm_ctx, data, strlen(data));
mdm_receiver_send(&ictx.mdm_ctx, "\r\n", 2);
if (timeout == 0) {
return 0;
}
if (!sock) {
k_sem_reset(&ictx.response_sem);
ret = k_sem_take(&ictx.response_sem, K_MSEC(timeout));
} else {
k_sem_reset(&sock->sock_send_sem);
ret = k_sem_take(&sock->sock_send_sem, K_MSEC(timeout));
}
if (ret == 0) {
ret = ictx.last_error;
} else if (ret == -EAGAIN) {
ret = -ETIMEDOUT;
}
return ret;
}
static int send_data(struct wncm14a2a_socket *sock, struct net_pkt *pkt)
{
int ret;
struct net_buf *frag;
char buf[sizeof("AT@SOCKWRITE=#,####,1\r")];
if (!sock) {
return -EINVAL;
}
ictx.last_error = 0;
frag = pkt->frags;
/* use SOCKWRITE with binary mode formatting */
snprintk(buf, sizeof(buf), "AT@SOCKWRITE=%d,%zu,1\r",
sock->socket_id, net_buf_frags_len(frag));
mdm_receiver_send(&ictx.mdm_ctx, buf, strlen(buf));
/* Loop through packet data and send */
while (frag) {
mdm_receiver_send(&ictx.mdm_ctx,
frag->data, frag->len);
frag = frag->frags;
}
mdm_receiver_send(&ictx.mdm_ctx, "\r\n", 2);
k_sem_reset(&sock->sock_send_sem);
ret = k_sem_take(&sock->sock_send_sem, K_MSEC(MDM_CMD_SEND_TIMEOUT));
if (ret == 0) {
ret = ictx.last_error;
} else if (ret == -EAGAIN) {
ret = -ETIMEDOUT;
}
return ret;
}
/*** NET_BUF HELPERS ***/
static bool is_crlf(uint8_t c)
{
if (c == '\n' || c == '\r') {
return true;
} else {
return false;
}
}
static void net_buf_skipcrlf(struct net_buf **buf)
{
/* chop off any /n or /r */
while (*buf && is_crlf(*(*buf)->data)) {
net_buf_pull_u8(*buf);
if (!(*buf)->len) {
*buf = net_buf_frag_del(NULL, *buf);
}
}
}
static uint16_t net_buf_findcrlf(struct net_buf *buf, struct net_buf **frag,
uint16_t *offset)
{
uint16_t len = 0U, pos = 0U;
while (buf && !is_crlf(*(buf->data + pos))) {
if (pos + 1 >= buf->len) {
len += buf->len;
buf = buf->frags;
pos = 0U;
} else {
pos++;
}
}
if (buf && is_crlf(*(buf->data + pos))) {
len += pos;
*offset = pos;
*frag = buf;
return len;
}
return 0;
}
/*** UDP / TCP Helper Function ***/
/* Setup IP header data to be used by some network applications.
* While much is dummy data, some fields such as dst, port and family are
* important.
* Return the IP + protocol header length.
*/
static int pkt_setup_ip_data(struct net_pkt *pkt,
struct wncm14a2a_socket *sock)
{
int hdr_len = 0;
uint16_t src_port = 0U, dst_port = 0U;
#if defined(CONFIG_NET_IPV6)
if (net_pkt_family(pkt) == AF_INET6) {
if (net_ipv6_create(
pkt,
&((struct sockaddr_in6 *)&sock->dst)->sin6_addr,
&((struct sockaddr_in6 *)&sock->src)->sin6_addr)) {
return -1;
}
src_port = ntohs(net_sin6(&sock->src)->sin6_port);
dst_port = ntohs(net_sin6(&sock->dst)->sin6_port);
hdr_len = sizeof(struct net_ipv6_hdr);
} else
#endif
#if defined(CONFIG_NET_IPV4)
if (net_pkt_family(pkt) == AF_INET) {
if (net_ipv4_create(
pkt,
&((struct sockaddr_in *)&sock->dst)->sin_addr,
&((struct sockaddr_in *)&sock->src)->sin_addr)) {
return -1;
}
src_port = ntohs(net_sin(&sock->src)->sin_port);
dst_port = ntohs(net_sin(&sock->dst)->sin_port);
hdr_len = sizeof(struct net_ipv4_hdr);
} else
#endif
{
/* no error here as hdr_len is checked later for 0 value */
}
#if defined(CONFIG_NET_UDP)
if (sock->ip_proto == IPPROTO_UDP) {
if (net_udp_create(pkt, dst_port, src_port)) {
return -1;
}
hdr_len += NET_UDPH_LEN;
} else
#endif
#if defined(CONFIG_NET_TCP)
if (sock->ip_proto == IPPROTO_TCP) {
NET_PKT_DATA_ACCESS_DEFINE(tcp_access, struct net_tcp_hdr);
struct net_tcp_hdr *tcp;
tcp = (struct net_tcp_hdr *)net_pkt_get_data(pkt, &tcp_access);
if (!tcp) {
return -1;
}
(void)memset(tcp, 0, NET_TCPH_LEN);
/* Setup TCP header */
tcp->src_port = dst_port;
tcp->dst_port = src_port;
if (net_pkt_set_data(pkt, &tcp_access)) {
return -1;
}
hdr_len += NET_TCPH_LEN;
} else
#endif /* CONFIG_NET_TCP */
{
/* no error here as hdr_len is checked later for 0 value */
}
return hdr_len;
}
/*** MODEM RESPONSE HANDLERS ***/
/* Last Socket ID Handler */
static void on_cmd_atcmdecho(struct net_buf **buf, uint16_t len)
{
char value[2];
/* make sure only a single digit is picked up for socket_id */
value[0] = net_buf_pull_u8(*buf);
ictx.last_socket_id = atoi(value);
}
/* Echo Handler for commands without related sockets */
static void on_cmd_atcmdecho_nosock(struct net_buf **buf, uint16_t len)
{
/* clear last_socket_id */
ictx.last_socket_id = 0;
}
static void on_cmd_atcmdinfo_manufacturer(struct net_buf **buf, uint16_t len)
{
size_t out_len;
out_len = net_buf_linearize(ictx.mdm_manufacturer,
sizeof(ictx.mdm_manufacturer) - 1,
*buf, 0, len);
ictx.mdm_manufacturer[out_len] = 0;
LOG_INF("Manufacturer: %s", ictx.mdm_manufacturer);
}
static void on_cmd_atcmdinfo_model(struct net_buf **buf, uint16_t len)
{
size_t out_len;
out_len = net_buf_linearize(ictx.mdm_model,
sizeof(ictx.mdm_model) - 1,
*buf, 0, len);
ictx.mdm_model[out_len] = 0;
LOG_INF("Model: %s", ictx.mdm_model);
}
static void on_cmd_atcmdinfo_revision(struct net_buf **buf, uint16_t len)
{
size_t out_len;
out_len = net_buf_linearize(ictx.mdm_revision,
sizeof(ictx.mdm_revision) - 1,
*buf, 0, len);
ictx.mdm_revision[out_len] = 0;
LOG_INF("Revision: %s", ictx.mdm_revision);
}
static void on_cmd_atcmdecho_nosock_imei(struct net_buf **buf, uint16_t len)
{
struct net_buf *frag = NULL;
uint16_t offset;
size_t out_len;
/* make sure IMEI data is received */
if (len < MDM_IMEI_LENGTH) {
LOG_DBG("Waiting for data");
/* wait for more data */
k_sleep(K_MSEC(500));
wncm14a2a_read_rx(buf);
}
net_buf_skipcrlf(buf);
if (!*buf) {
LOG_DBG("Unable to find IMEI (net_buf_skipcrlf)");
return;
}
frag = NULL;
len = net_buf_findcrlf(*buf, &frag, &offset);
if (!frag) {
LOG_DBG("Unable to find IMEI (net_buf_findcrlf)");
return;
}
out_len = net_buf_linearize(ictx.mdm_imei, sizeof(ictx.mdm_imei) - 1,
*buf, 0, len);
ictx.mdm_imei[out_len] = 0;
LOG_INF("IMEI: %s", ictx.mdm_imei);
}
/* Handler: %MEAS: RSSI:Reported= -68, Ant0= -63, Ant1= -251 */
static void on_cmd_atcmdinfo_rssi(struct net_buf **buf, uint16_t len)
{
int start = 0, i = 0;
size_t value_size;
char value[64];
value_size = sizeof(value);
(void)memset(value, 0, value_size);
while (*buf && len > 0 && i < value_size) {
value[i] = net_buf_pull_u8(*buf);
if (!(*buf)->len) {
*buf = net_buf_frag_del(NULL, *buf);
}
/* 2nd "=" marks the beginning of the RSSI value */
if (start < 2) {
if (value[i] == '=') {
start++;
}
continue;
}
/* "," marks the end of the RSSI value */
if (value[i] == ',') {
value[i] = '\0';
break;
}
i++;
}
if (i > 0) {
ictx.mdm_rssi = atoi(value);
LOG_INF("RSSI: %d", ictx.mdm_rssi);
} else {
LOG_WRN("Bad format found for RSSI");
}
}
/* Handler: OK */
static void on_cmd_sockok(struct net_buf **buf, uint16_t len)
{
struct wncm14a2a_socket *sock = NULL;
ictx.last_error = 0;
sock = socket_from_id(ictx.last_socket_id);
if (!sock) {
k_sem_give(&ictx.response_sem);
} else {
k_sem_give(&sock->sock_send_sem);
}
}
/* Handler: ERROR */
static void on_cmd_sockerror(struct net_buf **buf, uint16_t len)
{
struct wncm14a2a_socket *sock = NULL;
ictx.last_error = -EIO;
sock = socket_from_id(ictx.last_socket_id);
if (!sock) {
k_sem_give(&ictx.response_sem);
} else {
k_sem_give(&sock->sock_send_sem);
}
}
/* Handler: @EXTERR:<exterror_id> */
static void on_cmd_sockexterror(struct net_buf **buf, uint16_t len)
{
char value[8];
size_t out_len;
struct wncm14a2a_socket *sock = NULL;
out_len = net_buf_linearize(value, sizeof(value) - 1, *buf, 0, len);
value[out_len] = 0;
ictx.last_error = -atoi(value);
LOG_ERR("@EXTERR:%d", ictx.last_error);
sock = socket_from_id(ictx.last_socket_id);
if (!sock) {
k_sem_give(&ictx.response_sem);
} else {
k_sem_give(&sock->sock_send_sem);
}
}
/* Handler: @SOCKDIAL:<status> */
static void on_cmd_sockdial(struct net_buf **buf, uint16_t len)
{
char value[8];
size_t out_len;
out_len = net_buf_linearize(value, sizeof(value) - 1, *buf, 0, len);
value[out_len] = 0;
ictx.last_error = atoi(value);
k_sem_give(&ictx.response_sem);
}
/* Handler: @SOCKCREAT:<socket_id> */
static void on_cmd_sockcreat(struct net_buf **buf, uint16_t len)
{
char value[2];
struct wncm14a2a_socket *sock = NULL;
/* look up new socket by special id */
sock = socket_from_id(MDM_MAX_SOCKETS + 1);
if (sock) {
/* make sure only a single digit is picked up for socket_id */
value[0] = net_buf_pull_u8(*buf);
sock->socket_id = atoi(value);
}
/* don't give back semaphore -- OK to follow */
}
/* Handler: @SOCKWRITE:<actual_length> */
static void on_cmd_sockwrite(struct net_buf **buf, uint16_t len)
{
char value[8];
size_t out_len;
int write_len;
struct wncm14a2a_socket *sock = NULL;
/* TODO: check against what we wanted to send */
out_len = net_buf_linearize(value, sizeof(value) - 1, *buf, 0, len);
value[out_len] = 0;
write_len = atoi(value);
if (write_len <= 0) {
return;
}
sock = socket_from_id(ictx.last_socket_id);
if (sock) {
k_sem_give(&sock->sock_send_sem);
}
}
static void sockreadrecv_cb_work(struct k_work *work)
{
struct wncm14a2a_socket *sock = NULL;
struct net_pkt *pkt;
sock = CONTAINER_OF(work, struct wncm14a2a_socket, recv_cb_work);
/* return data */
pkt = sock->recv_pkt;
sock->recv_pkt = NULL;
if (sock->recv_cb) {
sock->recv_cb(sock->context, pkt, NULL, NULL,
0, sock->recv_user_data);
} else {
net_pkt_unref(pkt);
}
}
/* Handler: @SOCKREAD:<actual_length>,"<hex encoded binary>" */
static void on_cmd_sockread(struct net_buf **buf, uint16_t len)
{
struct wncm14a2a_socket *sock = NULL;
uint8_t c = 0U;
int i, actual_length, hdr_len = 0;
size_t value_size;
char value[10];
/* first comma marks the end of actual_length */
i = 0;
value_size = sizeof(value);
(void)memset(value, 0, value_size);
while (*buf && i < value_size - 1) {
value[i++] = net_buf_pull_u8(*buf);
len--;
if (!(*buf)->len) {
*buf = net_buf_frag_del(NULL, *buf);
}
if (value[i-1] == ',') {
i--;
break;
}
}
/* make sure we still have buf data, the last pulled character was
* a comma and that the next char in the buffer is a quote.
*/
if (!*buf || value[i] != ',' || *(*buf)->data != '\"') {
LOG_ERR("Incorrect format! Ignoring data!");
return;
}
/* clear the comma */
value[i] = '\0';
actual_length = atoi(value);
/* skip quote */
len--;
net_buf_pull_u8(*buf);
if (!(*buf)->len) {
*buf = net_buf_frag_del(NULL, *buf);
}
/* check that we have enough data */
if (!*buf || len > (actual_length * 2) + 1) {
LOG_ERR("Incorrect format! Ignoring data!");
return;
}
sock = socket_from_id(ictx.last_socket_id);
if (!sock) {
LOG_ERR("Socket not found! (%d)", ictx.last_socket_id);
return;
}
/* allocate an RX pkt */
sock->recv_pkt = net_pkt_rx_alloc_with_buffer(
net_context_get_iface(sock->context),
actual_length, sock->family, sock->ip_proto,
BUF_ALLOC_TIMEOUT);
if (!sock->recv_pkt) {
LOG_ERR("Failed net_pkt_get_reserve_rx!");
return;
}
/* set pkt data */
net_pkt_set_context(sock->recv_pkt, sock->context);
/* add IP / protocol headers */
hdr_len = pkt_setup_ip_data(sock->recv_pkt, sock);
/* move hex encoded data from the buffer to the recv_pkt */
for (i = 0; i < actual_length * 2; i++) {
char c2 = *(*buf)->data;
if (isdigit(c2)) {
c += c2 - '0';
} else if (isalpha(c2)) {
c += c2 - (isupper(c2) ? 'A' - 10 : 'a' - 10);
} else {
/* TODO: unexpected input! skip? */
}
if (i % 2) {
if (net_pkt_write_u8(sock->recv_pkt, c)) {
LOG_ERR("Unable to add data! Aborting!");
net_pkt_unref(sock->recv_pkt);
sock->recv_pkt = NULL;
return;
}
c = 0U;
} else {
c = c << 4;
}
/* pull data from buf and advance to the next frag if needed */
net_buf_pull_u8(*buf);
if (!(*buf)->len) {
*buf = net_buf_frag_del(NULL, *buf);
}
}
net_pkt_cursor_init(sock->recv_pkt);
net_pkt_set_overwrite(sock->recv_pkt, true);
if (hdr_len > 0) {
net_pkt_skip(sock->recv_pkt, hdr_len);
}
/* Let's do the callback processing in a different work queue in
* case the app takes a long time.
*/
k_work_submit_to_queue(&wncm14a2a_workq, &sock->recv_cb_work);
}
/* Handler: @SOCKDATAIND: <socket_id>,<session_status>,<left_bytes> */
static void on_cmd_sockdataind(struct net_buf **buf, uint16_t len)
{
int socket_id, session_status, left_bytes;
size_t out_len;
char *delim1, *delim2;
char value[sizeof("#,#,#####\r")];
char sendbuf[sizeof("AT@SOCKREAD=-#####,-#####\r")];
struct wncm14a2a_socket *sock = NULL;
out_len = net_buf_linearize(value, sizeof(value) - 1, *buf, 0, len);
value[out_len] = 0;
/* First comma separator marks the end of socket_id */
delim1 = strchr(value, ',');
if (!delim1) {
LOG_ERR("Missing 1st comma");
return;
}
*delim1++ = '\0';
socket_id = atoi(value);
/* Second comma separator marks the end of session_status */
/* TODO: ignore for now, but maybe this is useful? */
delim2 = strchr(delim1, ',');
if (!delim2) {
LOG_ERR("Missing 2nd comma");
return;
}
*delim2++ = '\0';
session_status = atoi(delim1);
/* Third param is for left_bytes */
/* TODO: ignore for now because we ask for max data len
* but maybe this is useful in the future?
*/
left_bytes = atoi(delim2);
sock = socket_from_id(socket_id);
if (!sock) {
LOG_ERR("Unable to find socket_id:%d", socket_id);
return;
}
if (left_bytes > 0) {
LOG_DBG("socket_id:%d left_bytes:%d", socket_id, left_bytes);
snprintk(sendbuf, sizeof(sendbuf), "AT@SOCKREAD=%d,%d",
sock->socket_id, left_bytes);
/* We entered this trigger due to an unsolicited modem response.
* When we send the AT@SOCKREAD command it won't generate an
* "OK" response directly. The modem will respond with
* "@SOCKREAD ..." and the data requested and then "OK" or
* "ERROR". Let's not wait here by passing in a timeout to
* send_at_cmd(). Instead, when the resulting response is
* received, we trigger on_cmd_sockread() to handle it.
*/
send_at_cmd(sock, sendbuf, 0);
}
}
static void on_cmd_socknotifyev(struct net_buf **buf, uint16_t len)
{
char value[40];
size_t out_len;
int p1 = 0, p2 = 0;
out_len = net_buf_linearize(value, sizeof(value) - 1, *buf, 0, len);
value[out_len] = 0;
/* walk value till 1st quote */
while (p1 < len && value[p1] != '\"') {
p1++;
}
if (value[p1] != '\"') {
/* 1st quote not found */
return;
}
p1++;
p2 = p1;
while (p2 < len && value[p2] != '\"') {
p2++;
}
if (value[p2] != '\"') {
/* 2nd quote not found */
return;
}
/* clear quote */
value[p2] = '\0';
p2++;
/* skip comma if present */
if (value[p2] == ',') {
p2++;
}
/* CSPS: 0: Moved to PS mode, 1: Moved to CS/PS mode */
if (!strncmp(&value[p1], "CSPS", 4)) {
ictx.ev_csps = atoi(&value[p2]);
/* This also signifies that RRCSTATE = 1 */
ictx.ev_rrcstate = 1;
LOG_DBG("CSPS:%d", ictx.ev_csps);
/* RRCSTATE: 0: RRC Idle, 1: RRC Connected, 2: RRC Unknown */
} else if (!strncmp(&value[p1], "RRCSTATE", 8)) {
ictx.ev_rrcstate = atoi(&value[p2]);
LOG_DBG("RRCSTATE:%d", ictx.ev_rrcstate);
} else if (!strncmp(&value[p1], "LTIME", 5)) {
/* local time from network */
LOG_INF("LTIME:%s", &value[p2]);
} else if (!strncmp(&value[p1], "SIB1", 4)) {
/* do nothing? */
LOG_DBG("SIB1");
} else {
LOG_DBG("UNHANDLED: [%s:%s]", &value[p1], &value[p2]);
}
}
static int net_buf_ncmp(struct net_buf *buf, const uint8_t *s2, size_t n)
{
struct net_buf *frag = buf;
uint16_t offset = 0U;
while ((n > 0) && (*(frag->data + offset) == *s2) && (*s2 != '\0')) {
if (offset == frag->len) {
if (!frag->frags) {
break;
}
frag = frag->frags;
offset = 0U;
} else {
offset++;
}
s2++;
n--;
}
return (n == 0) ? 0 : (*(frag->data + offset) - *s2);
}
static inline struct net_buf *read_rx_allocator(k_timeout_t timeout,
void *user_data)
{
return net_buf_alloc((struct net_buf_pool *)user_data, timeout);
}
static void wncm14a2a_read_rx(struct net_buf **buf)
{
uint8_t uart_buffer[MDM_RECV_BUF_SIZE];
size_t bytes_read = 0;
int ret;
uint16_t rx_len;
/* read all of the data from mdm_receiver */
while (true) {
ret = mdm_receiver_recv(&ictx.mdm_ctx,
uart_buffer,
sizeof(uart_buffer),
&bytes_read);
if (ret < 0 || bytes_read == 0) {
/* mdm_receiver buffer is empty */
break;
}
hexdump(uart_buffer, bytes_read);
/* make sure we have storage */
if (!*buf) {
*buf = net_buf_alloc(&mdm_recv_pool, BUF_ALLOC_TIMEOUT);
if (!*buf) {
LOG_ERR("Can't allocate RX data! "
"Skipping data!");
break;
}
}
rx_len = net_buf_append_bytes(*buf, bytes_read, uart_buffer,
BUF_ALLOC_TIMEOUT,
read_rx_allocator,
&mdm_recv_pool);
if (rx_len < bytes_read) {
LOG_ERR("Data was lost! read %u of %zu!",
rx_len, bytes_read);
}
}
}
/* RX thread */
static void wncm14a2a_rx(void)
{
struct net_buf *rx_buf = NULL;
struct net_buf *frag = NULL;
int i;
uint16_t offset, len;
static const struct cmd_handler handlers[] = {
/* NON-SOCKET COMMAND ECHOES to clear last_socket_id */
CMD_HANDLER("ATE1", atcmdecho_nosock),
CMD_HANDLER("AT%PDNSET=", atcmdecho_nosock),
CMD_HANDLER("ATI", atcmdecho_nosock),
CMD_HANDLER("AT+CGSN", atcmdecho_nosock_imei),
CMD_HANDLER("AT%MEAS=", atcmdecho_nosock),
CMD_HANDLER("AT@INTERNET=", atcmdecho_nosock),
CMD_HANDLER("AT@SOCKDIAL=", atcmdecho_nosock),
CMD_HANDLER("AT@SOCKCREAT=", atcmdecho_nosock),
/* SOCKET COMMAND ECHOES for last_socket_id processing */
CMD_HANDLER("AT@SOCKCONN=", atcmdecho),
CMD_HANDLER("AT@SOCKWRITE=", atcmdecho),
CMD_HANDLER("AT@SOCKREAD=", atcmdecho),
CMD_HANDLER("AT@SOCKCLOSE=", atcmdecho),
/* MODEM Information */
CMD_HANDLER("Manufacturer: ", atcmdinfo_manufacturer),
CMD_HANDLER("Model: ", atcmdinfo_model),
CMD_HANDLER("Revision: ", atcmdinfo_revision),
CMD_HANDLER("%MEAS: RSSI:", atcmdinfo_rssi),
/* SOLICITED SOCKET RESPONSES */
CMD_HANDLER("OK", sockok),
CMD_HANDLER("ERROR", sockerror),
CMD_HANDLER("@EXTERR:", sockexterror),
CMD_HANDLER("@SOCKDIAL:", sockdial),
CMD_HANDLER("@SOCKCREAT:", sockcreat),
CMD_HANDLER("@OCKCREAT:", sockcreat), /* seeing this a lot */
CMD_HANDLER("@SOCKWRITE:", sockwrite),
CMD_HANDLER("@SOCKREAD:", sockread),
/* UNSOLICITED SOCKET RESPONSES */
CMD_HANDLER("@SOCKDATAIND:", sockdataind),
CMD_HANDLER("%NOTIFYEV:", socknotifyev),
};
while (true) {
/* wait for incoming data */
(void)k_sem_take(&ictx.mdm_ctx.rx_sem, K_FOREVER);
wncm14a2a_read_rx(&rx_buf);
while (rx_buf) {
net_buf_skipcrlf(&rx_buf);
if (!rx_buf) {
break;
}
frag = NULL;
len = net_buf_findcrlf(rx_buf, &frag, &offset);
if (!frag) {
break;
}
/* look for matching data handlers */
i = -1;
for (i = 0; i < ARRAY_SIZE(handlers); i++) {
if (net_buf_ncmp(rx_buf, handlers[i].cmd,
handlers[i].cmd_len) == 0) {
/* found a matching handler */
LOG_DBG("MATCH %s (len:%u)",
handlers[i].cmd, len);
/* skip cmd_len */
rx_buf = net_buf_skip(rx_buf,
handlers[i].cmd_len);
/* locate next cr/lf */
frag = NULL;
len = net_buf_findcrlf(rx_buf,
&frag, &offset);
if (!frag) {
break;
}
/* call handler */
if (handlers[i].func) {
handlers[i].func(&rx_buf, len);
}
frag = NULL;
/* make sure buf still has data */
if (!rx_buf) {
break;
}
/*
* We've handled the current line
* and need to exit the "search for
* handler loop". Let's skip any
* "extra" data and look for the next
* CR/LF, leaving us ready for the
* next handler search. Ignore the
* length returned.
*/
(void)net_buf_findcrlf(rx_buf,
&frag, &offset);
break;
}
}
if (frag && rx_buf) {
/* clear out processed line (buffers) */
while (frag && rx_buf != frag) {
rx_buf = net_buf_frag_del(NULL, rx_buf);
}
net_buf_pull(rx_buf, offset);
}
}
/* give up time if we have a solid stream of data */
k_yield();
}
}
static int modem_pin_init(void)
{
LOG_INF("Setting Modem Pins");
/* Hard reset the modem (>5 seconds required)
* (doesn't go through the signal level translator)
*/
LOG_DBG("MDM_RESET_PIN -> ASSERTED");
gpio_pin_set_raw(ictx.gpio_port_dev[MDM_RESET],
pinconfig[MDM_RESET].pin, MDM_RESET_ASSERTED);
k_sleep(K_SECONDS(7));
LOG_DBG("MDM_RESET_PIN -> NOT_ASSERTED");
gpio_pin_set_raw(ictx.gpio_port_dev[MDM_RESET],
pinconfig[MDM_RESET].pin, MDM_RESET_NOT_ASSERTED);
/* disable signal level translator (necessary
* for the modem to boot properly). All signals
* except mdm_reset go through the level translator
* and have internal pull-up/down in the module. While
* the level translator is disabled, these pins will
* be in the correct state.
*/
LOG_DBG("SIG_TRANS_ENA_PIN -> DISABLED");
gpio_pin_set_raw(ictx.gpio_port_dev[SHLD_3V3_1V8_SIG_TRANS_ENA],
pinconfig[SHLD_3V3_1V8_SIG_TRANS_ENA].pin,
SHLD_3V3_1V8_SIG_TRANS_DISABLED);
/* While the level translator is disabled and output pins
* are tristated, make sure the inputs are in the same state
* as the WNC Module pins so that when the level translator is
* enabled, there are no differences.
*/
LOG_DBG("MDM_BOOT_MODE_SEL_PIN -> NORMAL");
gpio_pin_set_raw(ictx.gpio_port_dev[MDM_BOOT_MODE_SEL],
pinconfig[MDM_BOOT_MODE_SEL].pin,
MDM_BOOT_MODE_NORMAL);
LOG_DBG("MDM_POWER_PIN -> ENABLE");
gpio_pin_set_raw(ictx.gpio_port_dev[MDM_POWER],
pinconfig[MDM_POWER].pin,
MDM_POWER_ENABLE);
LOG_DBG("MDM_KEEP_AWAKE_PIN -> ENABLED");
gpio_pin_set_raw(ictx.gpio_port_dev[MDM_KEEP_AWAKE],
pinconfig[MDM_KEEP_AWAKE].pin,
MDM_KEEP_AWAKE_ENABLED);
#if DT_INST_NODE_HAS_PROP(0, mdm_send_ok_gpios)
LOG_DBG("MDM_SEND_OK_PIN -> ENABLED");
gpio_pin_set_raw(ictx.gpio_port_dev[MDM_SEND_OK],
pinconfig[MDM_SEND_OK].pin,
MDM_SEND_OK_ENABLED);
#endif
/* wait for the WNC Module to perform its initial boot correctly */
k_sleep(K_SECONDS(1));
/* Enable the level translator.
* The input pins should now be the same as how the M14A module is
* driving them with internal pull ups/downs.
* When enabled, there will be no changes in the above 4 pins...
*/
LOG_DBG("SIG_TRANS_ENA_PIN -> ENABLED");
gpio_pin_set_raw(ictx.gpio_port_dev[SHLD_3V3_1V8_SIG_TRANS_ENA],
pinconfig[SHLD_3V3_1V8_SIG_TRANS_ENA].pin,
SHLD_3V3_1V8_SIG_TRANS_ENABLED);
LOG_INF("... Done!");
return 0;
}
static void modem_wakeup_pin_fix(void)
{
/* AT&T recommend toggling the KEEP_AWAKE signal to reduce missed
* UART characters.
*/
LOG_DBG("Toggling MDM_KEEP_AWAKE_PIN to avoid missed characters");
k_sleep(K_MSEC(20));
LOG_DBG("MDM_KEEP_AWAKE_PIN -> DISABLED");
gpio_pin_set_raw(ictx.gpio_port_dev[MDM_KEEP_AWAKE],
pinconfig[MDM_KEEP_AWAKE].pin,
MDM_KEEP_AWAKE_DISABLED);
k_sleep(K_SECONDS(2));
LOG_DBG("MDM_KEEP_AWAKE_PIN -> ENABLED");
gpio_pin_set_raw(ictx.gpio_port_dev[MDM_KEEP_AWAKE],
pinconfig[MDM_KEEP_AWAKE].pin,
MDM_KEEP_AWAKE_ENABLED);
k_sleep(K_MSEC(20));
}
static void wncm14a2a_rssi_query_work(struct k_work *work)
{
int ret;
/* query modem RSSI */
ret = send_at_cmd(NULL, "AT%MEAS=\"23\"", MDM_CMD_TIMEOUT);
if (ret < 0) {
LOG_ERR("AT%%MEAS ret:%d", ret);
}
/* re-start RSSI query work */
k_work_reschedule_for_queue(&wncm14a2a_workq, &ictx.rssi_query_work,
K_SECONDS(RSSI_TIMEOUT_SECS));
}
static void wncm14a2a_modem_reset(void)
{
int ret = 0, retry_count = 0, counter = 0;
/* bring down network interface */
net_if_flag_clear(ictx.iface, NET_IF_UP);
restart:
/* stop RSSI delay work */
k_work_cancel_delayable(&ictx.rssi_query_work);
modem_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 = send_at_cmd(NULL, "AT", MDM_CMD_TIMEOUT);
if (ret < 0 && ret != -ETIMEDOUT) {
break;
}
}
if (ret < 0) {
LOG_ERR("MODEM WAIT LOOP ERROR: %d", ret);
goto error;
}
LOG_INF("Setting modem to always stay awake");
modem_wakeup_pin_fix();
ret = send_at_cmd(NULL, "ATE1", MDM_CMD_TIMEOUT);
if (ret < 0) {
LOG_ERR("ATE1 ret:%d", ret);
goto error;
}
ret = send_at_cmd(NULL, "AT%PDNSET=1,\"" CONFIG_MODEM_WNCM14A2A_APN_NAME
"\",\"IPV4V6\"", MDM_CMD_TIMEOUT);
if (ret < 0) {
LOG_ERR("AT%%PDNSET ret:%d", ret);
goto error;
}
/* query modem info */
LOG_INF("Querying modem information");
ret = send_at_cmd(NULL, "ATI", MDM_CMD_TIMEOUT);
if (ret < 0) {
LOG_ERR("ATI ret:%d", ret);
goto error;
}
/* query modem IMEI */
ret = send_at_cmd(NULL, "AT+CGSN", MDM_CMD_TIMEOUT);
if (ret < 0) {
LOG_ERR("AT+CGSN ret:%d", ret);
goto error;
}
LOG_INF("Waiting for network");
/* query modem RSSI */
wncm14a2a_rssi_query_work(NULL);
k_sleep(K_SECONDS(2));
counter = 0;
/* wait for RSSI > -1000 and != 0 */
while (counter++ < 15 &&
(ictx.mdm_rssi <= -1000 ||
ictx.mdm_rssi == 0)) {
/* stop RSSI delay work */
k_work_cancel_delayable(&ictx.rssi_query_work);
wncm14a2a_rssi_query_work(NULL);
k_sleep(K_SECONDS(2));
}
if (ictx.mdm_rssi <= -1000 || ictx.mdm_rssi == 0) {
retry_count++;
if (retry_count > 3) {
LOG_ERR("Failed network init. Too many attempts!");
ret = -ENETUNREACH;
goto error;
}
LOG_ERR("Failed network init. Restarting process.");
goto restart;
}
LOG_INF("Network is ready.");
ret = send_at_cmd(NULL, "AT@INTERNET=1", MDM_CMD_TIMEOUT);
if (ret < 0) {
LOG_ERR("AT@INTERNET ret:%d", ret);
goto error;
}
ret = send_at_cmd(NULL, "AT@SOCKDIAL=1", MDM_CMD_TIMEOUT);
if (ret < 0) {
LOG_ERR("SOCKDIAL=1 CHECK ret:%d", ret);
/* don't report this as an error, we retry later */
ret = 0;
}
/* Set iface up */
net_if_up(ictx.iface);
error:
return;
}
static int wncm14a2a_init(const struct device *dev)
{
int i, ret = 0;
ARG_UNUSED(dev);
/* check for valid pinconfig */
__ASSERT(ARRAY_SIZE(pinconfig) == MAX_MDM_CONTROL_PINS,
"Incorrect modem pinconfig!");
(void)memset(&ictx, 0, sizeof(ictx));
for (i = 0; i < MDM_MAX_SOCKETS; i++) {
k_work_init(&ictx.sockets[i].recv_cb_work,
sockreadrecv_cb_work);
k_sem_init(&ictx.sockets[i].sock_send_sem, 0, 1);
}
k_sem_init(&ictx.response_sem, 0, 1);
/* initialize the work queue */
k_work_queue_start(&wncm14a2a_workq, wncm14a2a_workq_stack,
K_KERNEL_STACK_SIZEOF(wncm14a2a_workq_stack),
K_PRIO_COOP(7), NULL);
ictx.last_socket_id = 0;
/* setup port devices and pin directions */
for (i = 0; i < MAX_MDM_CONTROL_PINS; i++) {
ictx.gpio_port_dev[i] =
device_get_binding(pinconfig[i].dev_name);
if (!ictx.gpio_port_dev[i]) {
LOG_ERR("gpio port (%s) not found!",
pinconfig[i].dev_name);
return -ENODEV;
}
gpio_pin_configure(ictx.gpio_port_dev[i], pinconfig[i].pin,
pinconfig[i].flags | GPIO_OUTPUT);
}
/* Set modem data storage */
ictx.mdm_ctx.data_manufacturer = ictx.mdm_manufacturer;
ictx.mdm_ctx.data_model = ictx.mdm_model;
ictx.mdm_ctx.data_revision = ictx.mdm_revision;
#ifdef CONFIG_MODEM_SIM_NUMBERS
ictx.mdm_ctx.data_imei = ictx.mdm_imei;
#endif
ictx.mdm_ctx.data_rssi = &ictx.mdm_rssi;
ret = mdm_receiver_register(&ictx.mdm_ctx, MDM_UART_DEV,
mdm_recv_buf, sizeof(mdm_recv_buf));
if (ret < 0) {
LOG_ERR("Error registering modem receiver (%d)!", ret);
goto error;
}
/* start RX thread */
k_thread_create(&wncm14a2a_rx_thread, wncm14a2a_rx_stack,
K_KERNEL_STACK_SIZEOF(wncm14a2a_rx_stack),
(k_thread_entry_t) wncm14a2a_rx,
NULL, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT);
/* init RSSI query */
k_work_init_delayable(&ictx.rssi_query_work, wncm14a2a_rssi_query_work);
wncm14a2a_modem_reset();
error:
return ret;
}
/*** OFFLOAD FUNCTIONS ***/
static int offload_get(sa_family_t family,
enum net_sock_type type,
enum net_ip_protocol ip_proto,
struct net_context **context)
{
int ret;
char buf[sizeof("AT@SOCKCREAT=###,#\r")];
struct wncm14a2a_socket *sock = NULL;
/* new socket */
sock = socket_get();
if (!sock) {
return -ENOMEM;
}
(*context)->offload_context = sock;
sock->family = family;
sock->type = type;
sock->ip_proto = ip_proto;
sock->context = *context;
sock->socket_id = MDM_MAX_SOCKETS + 1; /* socket # needs assigning */
snprintk(buf, sizeof(buf), "AT@SOCKCREAT=%d,%d", type,
family == AF_INET ? 0 : 1);
ret = send_at_cmd(NULL, buf, MDM_CMD_TIMEOUT);
if (ret < 0) {
LOG_ERR("AT@SOCKCREAT ret:%d", ret);
socket_put(sock);
}
return ret;
}
static int offload_bind(struct net_context *context,
const struct sockaddr *addr,
socklen_t addrlen)
{
struct wncm14a2a_socket *sock = NULL;
if (!context) {
return -EINVAL;
}
sock = (struct wncm14a2a_socket *)context->offload_context;
if (!sock) {
LOG_ERR("Can't locate socket for net_ctx:%p!", context);
return -EINVAL;
}
/* save bind address information */
sock->src.sa_family = addr->sa_family;
#if defined(CONFIG_NET_IPV6)
if (addr->sa_family == AF_INET6) {
net_ipaddr_copy(&net_sin6(&sock->src)->sin6_addr,
&net_sin6(addr)->sin6_addr);
net_sin6(&sock->src)->sin6_port = net_sin6(addr)->sin6_port;
} else
#endif
#if defined(CONFIG_NET_IPV4)
if (addr->sa_family == AF_INET) {
net_ipaddr_copy(&net_sin(&sock->src)->sin_addr,
&net_sin(addr)->sin_addr);
net_sin(&sock->src)->sin_port = net_sin(addr)->sin_port;
} else
#endif
{
return -EPFNOSUPPORT;
}
return 0;
}
static int offload_listen(struct net_context *context, int backlog)
{
/* NOT IMPLEMENTED */
return -ENOTSUP;
}
static int offload_connect(struct net_context *context,
const struct sockaddr *addr,
socklen_t addrlen,
net_context_connect_cb_t cb,
int32_t timeout,
void *user_data)
{
int ret, dst_port = -1;
int32_t timeout_sec = -1; /* if not changed, this will be min timeout */
char buf[sizeof("AT@SOCKCONN=#,###.###.###.###,#####,#####\r")];
struct wncm14a2a_socket *sock;
if (timeout > 0) {
timeout_sec = timeout / MSEC_PER_SEC;
}
if (!context || !addr) {
return -EINVAL;
}
sock = (struct wncm14a2a_socket *)context->offload_context;
if (!sock) {
LOG_ERR("Can't locate socket for net_ctx:%p!", context);
return -EINVAL;
}
if (sock->socket_id < 1) {
LOG_ERR("Invalid socket_id(%d) for net_ctx:%p!",
sock->socket_id, context);
return -EINVAL;
}
sock->dst.sa_family = addr->sa_family;
#if defined(CONFIG_NET_IPV6)
if (addr->sa_family == AF_INET6) {
net_ipaddr_copy(&net_sin6(&sock->dst)->sin6_addr,
&net_sin6(addr)->sin6_addr);
dst_port = ntohs(net_sin6(addr)->sin6_port);
net_sin6(&sock->dst)->sin6_port = dst_port;
} else
#endif
#if defined(CONFIG_NET_IPV4)
if (addr->sa_family == AF_INET) {
net_ipaddr_copy(&net_sin(&sock->dst)->sin_addr,
&net_sin(addr)->sin_addr);
dst_port = ntohs(net_sin(addr)->sin_port);
net_sin(&sock->dst)->sin_port = dst_port;
} else
#endif
{
return -EINVAL;
}
if (dst_port < 0) {
LOG_ERR("Invalid port: %d", dst_port);
return -EINVAL;
}
/*
* AT@SOCKCONN timeout param has minimum value of 30 seconds and
* maximum value of 360 seconds, otherwise an error is generated
*/
timeout_sec = CLAMP(timeout_sec, 30, 360);
snprintk(buf, sizeof(buf), "AT@SOCKCONN=%d,\"%s\",%d,%d",
sock->socket_id, wncm14a2a_sprint_ip_addr(addr),
dst_port, timeout_sec);
ret = send_at_cmd(sock, buf, MDM_CMD_CONN_TIMEOUT);
if (!ret) {
net_context_set_state(sock->context, NET_CONTEXT_CONNECTED);
} else {
LOG_ERR("AT@SOCKCONN ret:%d", ret);
}
if (cb) {
cb(context, ret, user_data);
}
return ret;
}
static int offload_accept(struct net_context *context,
net_tcp_accept_cb_t cb,
int32_t timeout,
void *user_data)
{
/* NOT IMPLEMENTED */
return -ENOTSUP;
}
static int offload_sendto(struct net_pkt *pkt,
const struct sockaddr *dst_addr,
socklen_t addrlen,
net_context_send_cb_t cb,
int32_t timeout,
void *user_data)
{
struct net_context *context = net_pkt_context(pkt);
struct wncm14a2a_socket *sock;
int ret = 0;
if (!context) {
return -EINVAL;
}
sock = (struct wncm14a2a_socket *)context->offload_context;
if (!sock) {
LOG_ERR("Can't locate socket for net_ctx:%p!", context);
return -EINVAL;
}
ret = send_data(sock, pkt);
if (ret < 0) {
LOG_ERR("send_data error: %d", ret);
} else {
net_pkt_unref(pkt);
}
if (cb) {
cb(context, ret, user_data);
}
return ret;
}
static int offload_send(struct net_pkt *pkt,
net_context_send_cb_t cb,
int32_t timeout,
void *user_data)
{
struct net_context *context = net_pkt_context(pkt);
socklen_t addrlen;
addrlen = 0;
#if defined(CONFIG_NET_IPV6)
if (net_pkt_family(pkt) == AF_INET6) {
addrlen = sizeof(struct sockaddr_in6);
} else
#endif /* CONFIG_NET_IPV6 */
#if defined(CONFIG_NET_IPV4)
if (net_pkt_family(pkt) == AF_INET) {
addrlen = sizeof(struct sockaddr_in);
} else
#endif /* CONFIG_NET_IPV4 */
{
return -EPFNOSUPPORT;
}
return offload_sendto(pkt, &context->remote, addrlen, cb,
timeout, user_data);
}
static int offload_recv(struct net_context *context,
net_context_recv_cb_t cb,
int32_t timeout,
void *user_data)
{
struct wncm14a2a_socket *sock;
if (!context) {
return -EINVAL;
}
sock = (struct wncm14a2a_socket *)context->offload_context;
if (!sock) {
LOG_ERR("Can't locate socket for net_ctx:%p!", context);
return -EINVAL;
}
sock->recv_cb = cb;
sock->recv_user_data = user_data;
return 0;
}
static int offload_put(struct net_context *context)
{
struct wncm14a2a_socket *sock;
char buf[sizeof("AT@SOCKCLOSE=#\r")];
int ret;
if (!context) {
return -EINVAL;
}
sock = (struct wncm14a2a_socket *)context->offload_context;
if (!sock) {
/* socket was already closed? Exit quietly here. */
return 0;
}
snprintk(buf, sizeof(buf), "AT@SOCKCLOSE=%d", sock->socket_id);
ret = send_at_cmd(sock, buf, MDM_CMD_TIMEOUT);
if (ret < 0) {
LOG_ERR("AT@SOCKCLOSE ret:%d", ret);
}
/* clear last_socket_id */
ictx.last_socket_id = 0;
socket_put(sock);
net_context_unref(context);
if (sock->type == SOCK_STREAM) {
/* TCP contexts are referenced twice,
* once for the app and once for the stack.
* Since TCP stack is not used for offload,
* unref a second time.
*/
net_context_unref(context);
}
return 0;
}
static struct net_offload offload_funcs = {
.get = offload_get,
.bind = offload_bind,
.listen = offload_listen, /* TODO */
.connect = offload_connect,
.accept = offload_accept, /* TODO */
.send = offload_send,
.sendto = offload_sendto,
.recv = offload_recv,
.put = offload_put,
};
static inline uint8_t *wncm14a2a_get_mac(const struct device *dev)
{
struct wncm14a2a_iface_ctx *ctx = dev->data;
ctx->mac_addr[0] = 0x00;
ctx->mac_addr[1] = 0x10;
UNALIGNED_PUT(sys_cpu_to_be32(sys_rand32_get()),
(uint32_t *)(ctx->mac_addr + 2));
return ctx->mac_addr;
}
static void offload_iface_init(struct net_if *iface)
{
const struct device *dev = net_if_get_device(iface);
struct wncm14a2a_iface_ctx *ctx = dev->data;
iface->if_dev->offload = &offload_funcs;
net_if_set_link_addr(iface, wncm14a2a_get_mac(dev),
sizeof(ctx->mac_addr),
NET_LINK_ETHERNET);
ctx->iface = iface;
}
static struct net_if_api api_funcs = {
.init = offload_iface_init,
};
NET_DEVICE_DT_INST_OFFLOAD_DEFINE(0, wncm14a2a_init, NULL,
&ictx, NULL,
CONFIG_MODEM_WNCM14A2A_INIT_PRIORITY,
&api_funcs,
MDM_MAX_DATA_LENGTH);