zephyr/drivers/modem/wncm14a2a.c
Flavio Ceolin c4f7faea10 random: Include header where it is used
Unit tests were failing to build because random header was included by
kernel_includes.h. The problem is that rand32.h includes a generated
file that is either not generated or not included when building unit
tests. Also, it is better to limit the scope of this file to where it is
used.

Signed-off-by: Flavio Ceolin <flavio.ceolin@intel.com>
2020-07-08 21:05:36 -04:00

1854 lines
43 KiB
C

/*
* 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 <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.h>
#include <drivers/gpio.h>
#include <device.h>
#include <init.h>
#include <random/rand32.h>
#include <net/net_context.h>
#include <net/net_if.h>
#include <net/net_offload.h>
#include <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_NAME DT_INST_BUS_LABEL(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_THREAD_STACK_DEFINE(wncm14a2a_rx_stack,
CONFIG_MODEM_WNCM14A2A_RX_STACK_SIZE);
struct k_thread wncm14a2a_rx_thread;
/* RX thread work queue */
K_THREAD_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 */
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_delayed_work 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];
/* 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,%u,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_ctx.data_rssi = atoi(value);
LOG_INF("RSSI: %d", ictx.mdm_ctx.data_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 %u!",
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 */
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 ouptut 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_delayed_work_submit_to_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_delayed_work_cancel(&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_ctx.data_rssi <= -1000 ||
ictx.mdm_ctx.data_rssi == 0)) {
/* stop RSSI delay work */
k_delayed_work_cancel(&ictx.rssi_query_work);
wncm14a2a_rssi_query_work(NULL);
k_sleep(K_SECONDS(2));
}
if (ictx.mdm_ctx.data_rssi <= -1000 || ictx.mdm_ctx.data_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(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_q_start(&wncm14a2a_workq,
wncm14a2a_workq_stack,
K_THREAD_STACK_SIZEOF(wncm14a2a_workq_stack),
K_PRIO_COOP(7));
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;
ictx.mdm_ctx.data_imei = ictx.mdm_imei;
ret = mdm_receiver_register(&ictx.mdm_ctx, MDM_UART_DEV_NAME,
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_THREAD_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_delayed_work_init(&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 = MIN(360, MAX(timeout_sec, 30));
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 < 0) {
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;
sock->context->connect_cb = NULL;
sock->context->recv_cb = NULL;
sock->context->send_cb = NULL;
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(struct device *dev)
{
struct wncm14a2a_iface_ctx *ctx = dev->driver_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)
{
struct device *dev = net_if_get_device(iface);
struct wncm14a2a_iface_ctx *ctx = dev->driver_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_OFFLOAD_INIT(modem_wncm14a2a, "MODEM_WNCM14A2A",
wncm14a2a_init, device_pm_control_nop, &ictx,
NULL, CONFIG_MODEM_WNCM14A2A_INIT_PRIORITY, &api_funcs,
MDM_MAX_DATA_LENGTH);