When creating a socket, all of the registered socket implementation are processed in a sequence, allowing to find appropriate socket implementation for specified family/type/protocol. So far however, the order of processing was not clearly defined, leaving ambiguity if multiple implmentations supported the same set of parameters. Fix this, by registering socket priority along with implementation. This makes the processing order of particular socket implementations explicit, giving more flexibility to the user, for example when it's neeed to prioritze one implementation over another if they support the same set of parameters. Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
1207 lines
30 KiB
C
1207 lines
30 KiB
C
/*
|
|
* Copyright (c) 2020 Analog Life LLC
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT quectel_bg9x
|
|
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(modem_quectel_bg9x, CONFIG_MODEM_LOG_LEVEL);
|
|
|
|
#include "quectel-bg9x.h"
|
|
|
|
static struct k_thread modem_rx_thread;
|
|
static struct k_work_q modem_workq;
|
|
static struct modem_data mdata;
|
|
static struct modem_context mctx;
|
|
static const struct socket_op_vtable offload_socket_fd_op_vtable;
|
|
|
|
static K_KERNEL_STACK_DEFINE(modem_rx_stack, CONFIG_MODEM_QUECTEL_BG9X_RX_STACK_SIZE);
|
|
static K_KERNEL_STACK_DEFINE(modem_workq_stack, CONFIG_MODEM_QUECTEL_BG9X_RX_WORKQ_STACK_SIZE);
|
|
NET_BUF_POOL_DEFINE(mdm_recv_pool, MDM_RECV_MAX_BUF, MDM_RECV_BUF_SIZE, 0, NULL);
|
|
|
|
static inline int digits(int n)
|
|
{
|
|
int count = 0;
|
|
|
|
while (n != 0) {
|
|
n /= 10;
|
|
++count;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static inline uint32_t hash32(char *str, int len)
|
|
{
|
|
#define HASH_MULTIPLIER 37
|
|
|
|
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;
|
|
}
|
|
|
|
/* Func: modem_atoi
|
|
* Desc: Convert string to long integer, but handle errors
|
|
*/
|
|
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;
|
|
}
|
|
|
|
static inline int find_len(char *data)
|
|
{
|
|
char buf[10] = {0};
|
|
int i;
|
|
|
|
for (i = 0; i < 10; i++) {
|
|
if (data[i] == '\r')
|
|
break;
|
|
|
|
buf[i] = data[i];
|
|
}
|
|
|
|
return ATOI(buf, 0, "rx_buf");
|
|
}
|
|
|
|
/* Func: on_cmd_sockread_common
|
|
* Desc: Function to successfully read data from the modem on a given socket.
|
|
*/
|
|
static int on_cmd_sockread_common(int socket_fd,
|
|
struct modem_cmd_handler_data *data,
|
|
uint16_t len)
|
|
{
|
|
struct modem_socket *sock = NULL;
|
|
struct socket_read_data *sock_data;
|
|
int ret, i;
|
|
int socket_data_length;
|
|
int bytes_to_skip;
|
|
|
|
if (!len) {
|
|
LOG_ERR("Invalid length, Aborting!");
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/* Make sure we still have buf data */
|
|
if (!data->rx_buf) {
|
|
LOG_ERR("Incorrect format! Ignoring data!");
|
|
return -EINVAL;
|
|
}
|
|
|
|
socket_data_length = find_len(data->rx_buf->data);
|
|
|
|
/* No (or not enough) data available on the socket. */
|
|
bytes_to_skip = digits(socket_data_length) + 2 + 4;
|
|
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. */
|
|
if (net_buf_frags_len(data->rx_buf) < (socket_data_length + bytes_to_skip)) {
|
|
LOG_DBG("Not enough data -- wait!");
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/* Skip "len" and CRLF */
|
|
bytes_to_skip = digits(socket_data_length) + 2;
|
|
for (i = 0; i < bytes_to_skip; i++) {
|
|
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_fd(&mdata.socket_config, socket_fd);
|
|
if (!sock) {
|
|
LOG_ERR("Socket not found! (%d)", socket_fd);
|
|
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_fd);
|
|
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;
|
|
}
|
|
|
|
/* Func: socket_close
|
|
* Desc: Function to close the given socket descriptor.
|
|
*/
|
|
static void socket_close(struct modem_socket *sock)
|
|
{
|
|
char buf[sizeof("AT+QICLOSE=##")] = {0};
|
|
int ret;
|
|
|
|
snprintk(buf, sizeof(buf), "AT+QICLOSE=%d", sock->sock_fd);
|
|
|
|
/* Tell the modem to close the socket. */
|
|
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);
|
|
}
|
|
|
|
/* Handler: OK */
|
|
MODEM_CMD_DEFINE(on_cmd_ok)
|
|
{
|
|
modem_cmd_handler_set_error(data, 0);
|
|
k_sem_give(&mdata.sem_response);
|
|
return 0;
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
modem_cmd_handler_set_error(data, -EIO);
|
|
k_sem_give(&mdata.sem_response);
|
|
return 0;
|
|
}
|
|
|
|
/* Handler: +CSQ: <signal_power>[0], <qual>[1] */
|
|
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_rssi_csq)
|
|
{
|
|
int rssi = ATOI(argv[0], 0, "signal_power");
|
|
|
|
/* Check the RSSI value. */
|
|
if (rssi == 31) {
|
|
mctx.data_rssi = -51;
|
|
} else if (rssi >= 0 && rssi <= 31) {
|
|
mctx.data_rssi = -114 + ((rssi * 2) + 1);
|
|
} else {
|
|
mctx.data_rssi = -1000;
|
|
}
|
|
|
|
LOG_INF("RSSI: %d", mctx.data_rssi);
|
|
return 0;
|
|
}
|
|
|
|
/* Handler: +QIOPEN: <connect_id>[0], <err>[1] */
|
|
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_sockopen)
|
|
{
|
|
int err = ATOI(argv[1], 0, "sock_err");
|
|
|
|
LOG_INF("AT+QIOPEN: %d", err);
|
|
modem_cmd_handler_set_error(data, err);
|
|
k_sem_give(&mdata.sem_sock_conn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Handler: <manufacturer> */
|
|
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_manufacturer)
|
|
{
|
|
size_t 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 = net_buf_linearize(mdata.mdm_model,
|
|
sizeof(mdata.mdm_model) - 1,
|
|
data->rx_buf, 0, len);
|
|
mdata.mdm_model[out_len] = '\0';
|
|
|
|
/* Log the received information. */
|
|
LOG_INF("Model: %s", log_strdup(mdata.mdm_model));
|
|
return 0;
|
|
}
|
|
|
|
/* Handler: <rev> */
|
|
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_revision)
|
|
{
|
|
size_t 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 the received information. */
|
|
LOG_INF("Revision: %s", log_strdup(mdata.mdm_revision));
|
|
return 0;
|
|
}
|
|
|
|
/* Handler: <IMEI> */
|
|
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_imei)
|
|
{
|
|
size_t 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 the received information. */
|
|
LOG_INF("IMEI: %s", log_strdup(mdata.mdm_imei));
|
|
return 0;
|
|
}
|
|
|
|
#if defined(CONFIG_MODEM_SIM_NUMBERS)
|
|
/* Handler: <IMSI> */
|
|
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_imsi)
|
|
{
|
|
size_t 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 the received information. */
|
|
LOG_INF("IMSI: %s", log_strdup(mdata.mdm_imsi));
|
|
return 0;
|
|
}
|
|
|
|
/* Handler: <ICCID> */
|
|
MODEM_CMD_DEFINE(on_cmd_atcmdinfo_iccid)
|
|
{
|
|
size_t out_len;
|
|
char *p;
|
|
|
|
out_len = net_buf_linearize(mdata.mdm_iccid, sizeof(mdata.mdm_iccid) - 1,
|
|
data->rx_buf, 0, len);
|
|
mdata.mdm_iccid[out_len] = '\0';
|
|
|
|
/* Skip over the +CCID bit, which modems omit. */
|
|
if (mdata.mdm_iccid[0] == '+') {
|
|
p = strchr(mdata.mdm_iccid, ' ');
|
|
if (p) {
|
|
out_len = strlen(p + 1);
|
|
memmove(mdata.mdm_iccid, p + 1, len + 1);
|
|
}
|
|
}
|
|
|
|
LOG_INF("ICCID: %s", log_strdup(mdata.mdm_iccid));
|
|
return 0;
|
|
}
|
|
#endif /* #if defined(CONFIG_MODEM_SIM_NUMBERS) */
|
|
|
|
/* Handler: TX Ready */
|
|
MODEM_CMD_DIRECT_DEFINE(on_cmd_tx_ready)
|
|
{
|
|
k_sem_give(&mdata.sem_tx_ready);
|
|
return len;
|
|
}
|
|
|
|
/* Handler: SEND OK */
|
|
MODEM_CMD_DEFINE(on_cmd_send_ok)
|
|
{
|
|
modem_cmd_handler_set_error(data, 0);
|
|
k_sem_give(&mdata.sem_response);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Handler: SEND FAIL */
|
|
MODEM_CMD_DEFINE(on_cmd_send_fail)
|
|
{
|
|
mdata.sock_written = 0;
|
|
modem_cmd_handler_set_error(data, -EIO);
|
|
k_sem_give(&mdata.sem_response);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Handler: Read data */
|
|
MODEM_CMD_DEFINE(on_cmd_sock_readdata)
|
|
{
|
|
return on_cmd_sockread_common(mdata.sock_fd, data, len);
|
|
}
|
|
|
|
/* Handler: Data receive indication. */
|
|
MODEM_CMD_DEFINE(on_cmd_unsol_recv)
|
|
{
|
|
struct modem_socket *sock;
|
|
int sock_fd;
|
|
|
|
sock_fd = ATOI(argv[0], 0, "sock_fd");
|
|
|
|
/* Socket pointer from FD. */
|
|
sock = modem_socket_from_fd(&mdata.socket_config, sock_fd);
|
|
if (!sock) {
|
|
return 0;
|
|
}
|
|
|
|
/* Data ready indication. */
|
|
LOG_INF("Data Receive Indication for socket: %d", sock_fd);
|
|
modem_socket_data_ready(&mdata.socket_config, sock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Handler: Socket Close Indication. */
|
|
MODEM_CMD_DEFINE(on_cmd_unsol_close)
|
|
{
|
|
struct modem_socket *sock;
|
|
int sock_fd;
|
|
|
|
sock_fd = ATOI(argv[0], 0, "sock_fd");
|
|
sock = modem_socket_from_fd(&mdata.socket_config, sock_fd);
|
|
if (!sock) {
|
|
return 0;
|
|
}
|
|
|
|
LOG_INF("Socket Close Indication for socket: %d", sock_fd);
|
|
|
|
/* Tell the modem to close the socket. */
|
|
socket_close(sock);
|
|
LOG_INF("Socket Closed: %d", sock_fd);
|
|
return 0;
|
|
}
|
|
|
|
/* Handler: Modem initialization ready. */
|
|
MODEM_CMD_DEFINE(on_cmd_unsol_rdy)
|
|
{
|
|
k_sem_give(&mdata.sem_response);
|
|
return 0;
|
|
}
|
|
|
|
/* Func: send_socket_data
|
|
* Desc: This function will send "binary" data over the socket object.
|
|
*/
|
|
static ssize_t send_socket_data(struct modem_socket *sock,
|
|
const struct sockaddr *dst_addr,
|
|
struct modem_cmd *handler_cmds,
|
|
size_t handler_cmds_len,
|
|
const char *buf, size_t buf_len,
|
|
k_timeout_t timeout)
|
|
{
|
|
int ret;
|
|
char send_buf[sizeof("AT+QISEND=##,####")] = {0};
|
|
char ctrlz = 0x1A;
|
|
|
|
if (buf_len > MDM_MAX_DATA_LENGTH) {
|
|
buf_len = MDM_MAX_DATA_LENGTH;
|
|
}
|
|
|
|
/* Create a buffer with the correct params. */
|
|
mdata.sock_written = buf_len;
|
|
snprintk(send_buf, sizeof(send_buf), "AT+QISEND=%d,%ld", sock->sock_fd, (long) buf_len);
|
|
|
|
/* Setup the locks correctly. */
|
|
k_sem_take(&mdata.cmd_handler_data.sem_tx_lock, K_FOREVER);
|
|
k_sem_reset(&mdata.sem_tx_ready);
|
|
|
|
/* Send the Modem command. */
|
|
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;
|
|
}
|
|
|
|
/* Wait for '>' */
|
|
ret = k_sem_take(&mdata.sem_tx_ready, K_MSEC(5000));
|
|
if (ret < 0) {
|
|
/* Didn't get the data prompt - Exit. */
|
|
LOG_DBG("Timeout waiting for tx");
|
|
goto exit;
|
|
}
|
|
|
|
/* Write all data on the console and send CTRL+Z. */
|
|
mctx.iface.write(&mctx.iface, buf, buf_len);
|
|
mctx.iface.write(&mctx.iface, &ctrlz, 1);
|
|
|
|
/* Wait for 'SEND OK' or 'SEND FAIL' */
|
|
k_sem_reset(&mdata.sem_response);
|
|
ret = k_sem_take(&mdata.sem_response, timeout);
|
|
if (ret < 0) {
|
|
LOG_DBG("No send response");
|
|
goto exit;
|
|
}
|
|
|
|
ret = modem_cmd_handler_get_error(&mdata.cmd_handler_data);
|
|
if (ret != 0) {
|
|
LOG_DBG("Failed to send data");
|
|
}
|
|
|
|
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 the amount of data written on the socket. */
|
|
return mdata.sock_written;
|
|
}
|
|
|
|
/* Func: offload_sendto
|
|
* Desc: This function will send data on the socket object.
|
|
*/
|
|
static ssize_t offload_sendto(void *obj, const void *buf, size_t len,
|
|
int flags, const struct sockaddr *to,
|
|
socklen_t tolen)
|
|
{
|
|
int ret;
|
|
struct modem_socket *sock = (struct modem_socket *) obj;
|
|
|
|
/* Here's how sending data works,
|
|
* -> We firstly send the "AT+QISEND" command on the given socket and
|
|
* specify the length of data to be transferred.
|
|
* -> In response to "AT+QISEND" command, the modem may respond with a
|
|
* data prompt (>) or not respond at all. If it doesn't respond, we
|
|
* exit. If it does respond with a data prompt (>), we move forward.
|
|
* -> We plainly write all data on the UART and terminate by sending a
|
|
* CTRL+Z. Once the modem receives CTRL+Z, it starts processing the
|
|
* data and will respond with either "SEND OK", "SEND FAIL" or "ERROR".
|
|
* Here we are registering handlers for the first two responses. We
|
|
* already have a handler for the "generic" error response.
|
|
*/
|
|
struct modem_cmd cmd[] = {
|
|
MODEM_CMD_DIRECT(">", on_cmd_tx_ready),
|
|
MODEM_CMD("SEND OK", on_cmd_send_ok, 0, ","),
|
|
MODEM_CMD("SEND FAIL", on_cmd_send_fail, 0, ","),
|
|
};
|
|
|
|
/* Ensure that valid parameters are passed. */
|
|
if (!buf || len == 0) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
/* UDP is not supported. */
|
|
if (sock->ip_proto == IPPROTO_UDP) {
|
|
errno = ENOTSUP;
|
|
return -1;
|
|
}
|
|
|
|
if (!sock->is_connected) {
|
|
errno = ENOTCONN;
|
|
return -1;
|
|
}
|
|
|
|
ret = send_socket_data(sock, to, cmd, ARRAY_SIZE(cmd), buf, len,
|
|
MDM_CMD_TIMEOUT);
|
|
if (ret < 0) {
|
|
errno = -ret;
|
|
return -1;
|
|
}
|
|
|
|
/* Data was written successfully. */
|
|
errno = 0;
|
|
return ret;
|
|
}
|
|
|
|
/* Func: offload_recvfrom
|
|
* Desc: This function will receive data on the socket object.
|
|
*/
|
|
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;
|
|
char sendbuf[sizeof("AT+QIRD=##,####")] = {0};
|
|
int ret;
|
|
struct socket_read_data sock_data;
|
|
|
|
/* Modem command to read the data. */
|
|
struct modem_cmd data_cmd[] = { MODEM_CMD("+QIRD: ", on_cmd_sock_readdata, 0U, "") };
|
|
|
|
if (!buf || len == 0) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
if (flags & ZSOCK_MSG_PEEK) {
|
|
errno = ENOTSUP;
|
|
return -1;
|
|
}
|
|
|
|
snprintk(sendbuf, sizeof(sendbuf), "AT+QIRD=%d,%zd", sock->sock_fd, len);
|
|
|
|
/* 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;
|
|
mdata.sock_fd = sock->sock_fd;
|
|
|
|
/* Tell the modem to give us data (AT+QIRD=sock_fd,data_len). */
|
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler,
|
|
data_cmd, ARRAY_SIZE(data_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;
|
|
}
|
|
|
|
/* Func: offload_read
|
|
* Desc: This function reads data from the given socket object.
|
|
*/
|
|
static ssize_t offload_read(void *obj, void *buffer, size_t count)
|
|
{
|
|
return offload_recvfrom(obj, buffer, count, 0, NULL, 0);
|
|
}
|
|
|
|
/* Func: offload_write
|
|
* Desc: This function writes data to the given socket object.
|
|
*/
|
|
static ssize_t offload_write(void *obj, const void *buffer, size_t count)
|
|
{
|
|
return offload_sendto(obj, buffer, count, 0, NULL, 0);
|
|
}
|
|
|
|
/* Func: offload_poll
|
|
* Desc: This function polls on a given socket object.
|
|
*/
|
|
static int offload_poll(struct zsock_pollfd *fds, int nfds, int msecs)
|
|
{
|
|
int i;
|
|
void *obj;
|
|
|
|
/* Only accept modem sockets. */
|
|
for (i = 0; i < nfds; i++) {
|
|
if (fds[i].fd < 0) {
|
|
continue;
|
|
}
|
|
|
|
/* If vtable matches, then it's modem socket. */
|
|
obj = z_get_fd_obj(fds[i].fd,
|
|
(const struct fd_op_vtable *) &offload_socket_fd_op_vtable,
|
|
EINVAL);
|
|
if (obj == NULL) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return modem_socket_poll(&mdata.socket_config, fds, nfds, msecs);
|
|
}
|
|
|
|
/* Func: offload_ioctl
|
|
* Desc: Function call to handle various misc requests.
|
|
*/
|
|
static int offload_ioctl(void *obj, unsigned int request, va_list args)
|
|
{
|
|
switch (request) {
|
|
case ZFD_IOCTL_POLL_PREPARE:
|
|
return -EXDEV;
|
|
|
|
case ZFD_IOCTL_POLL_UPDATE:
|
|
return -EOPNOTSUPP;
|
|
|
|
case ZFD_IOCTL_POLL_OFFLOAD:
|
|
{
|
|
/* Poll on the given socket. */
|
|
struct zsock_pollfd *fds;
|
|
int nfds, timeout;
|
|
|
|
fds = va_arg(args, struct zsock_pollfd *);
|
|
nfds = va_arg(args, int);
|
|
timeout = va_arg(args, int);
|
|
|
|
return offload_poll(fds, nfds, timeout);
|
|
}
|
|
|
|
default:
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* Func: offload_connect
|
|
* Desc: This function will connect with a provided TCP.
|
|
*/
|
|
static int offload_connect(void *obj, const struct sockaddr *addr,
|
|
socklen_t addrlen)
|
|
{
|
|
struct modem_socket *sock = (struct modem_socket *) obj;
|
|
uint16_t dst_port = 0;
|
|
char *protocol = "TCP";
|
|
struct modem_cmd cmd[] = { MODEM_CMD("+QIOPEN: ", on_cmd_atcmdinfo_sockopen, 2U, ",") };
|
|
char buf[sizeof("AT+QIOPEN=#,##,###,####.####.####.####,######")] = {0};
|
|
int ret;
|
|
|
|
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;
|
|
}
|
|
|
|
if (sock->is_connected == true) {
|
|
LOG_ERR("Socket is already connected!! socket_id(%d), socket_fd:%d",
|
|
sock->id, sock->sock_fd);
|
|
errno = EISCONN;
|
|
return -1;
|
|
}
|
|
|
|
/* Find the correct destination port. */
|
|
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);
|
|
}
|
|
|
|
/* UDP is not supported. */
|
|
if (sock->ip_proto == IPPROTO_UDP) {
|
|
errno = ENOTSUP;
|
|
return -1;
|
|
}
|
|
|
|
k_sem_reset(&mdata.sem_sock_conn);
|
|
|
|
/* Formulate the complete string. */
|
|
snprintk(buf, sizeof(buf), "AT+QIOPEN=%d,%d,\"%s\",\"%s\",%d,0,0", 1, sock->sock_fd, protocol,
|
|
modem_context_sprint_ip_addr(addr), dst_port);
|
|
|
|
/* Send out the command. */
|
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler,
|
|
NULL, 0U, buf,
|
|
&mdata.sem_response, K_SECONDS(1));
|
|
if (ret < 0) {
|
|
LOG_ERR("%s ret:%d", log_strdup(buf), ret);
|
|
LOG_ERR("Closing the socket!!!");
|
|
socket_close(sock);
|
|
errno = -ret;
|
|
return -1;
|
|
}
|
|
|
|
/* set command handlers */
|
|
ret = modem_cmd_handler_update_cmds(&mdata.cmd_handler_data,
|
|
cmd, ARRAY_SIZE(cmd), true);
|
|
if (ret < 0) {
|
|
goto exit;
|
|
}
|
|
|
|
/* Wait for QI+OPEN */
|
|
ret = k_sem_take(&mdata.sem_sock_conn, MDM_CMD_CONN_TIMEOUT);
|
|
if (ret < 0) {
|
|
LOG_ERR("Timeout waiting for socket open");
|
|
LOG_ERR("Closing the socket!!!");
|
|
socket_close(sock);
|
|
goto exit;
|
|
}
|
|
|
|
ret = modem_cmd_handler_get_error(&mdata.cmd_handler_data);
|
|
if (ret != 0) {
|
|
LOG_ERR("Closing the socket!!!");
|
|
socket_close(sock);
|
|
goto exit;
|
|
}
|
|
|
|
/* Connected successfully. */
|
|
sock->is_connected = true;
|
|
errno = 0;
|
|
return 0;
|
|
|
|
exit:
|
|
(void) modem_cmd_handler_update_cmds(&mdata.cmd_handler_data,
|
|
NULL, 0U, false);
|
|
errno = -ret;
|
|
return -1;
|
|
}
|
|
|
|
/* Func: offload_close
|
|
* Desc: This function closes the connection with the remote client and
|
|
* frees the socket.
|
|
*/
|
|
static int offload_close(void *obj)
|
|
{
|
|
struct modem_socket *sock = (struct modem_socket *) obj;
|
|
|
|
/* Make sure we assigned an id */
|
|
if (sock->id < mdata.socket_config.base_socket_num) {
|
|
return 0;
|
|
}
|
|
|
|
/* Close the socket only if it is connected. */
|
|
if (sock->is_connected) {
|
|
socket_close(sock);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Func: offload_sendmsg
|
|
* Desc: This function sends messages to the modem.
|
|
*/
|
|
static ssize_t offload_sendmsg(void *obj, const struct msghdr *msg, int flags)
|
|
{
|
|
ssize_t sent = 0;
|
|
int rc;
|
|
|
|
LOG_DBG("msg_iovlen:%zd flags:%d", msg->msg_iovlen, flags);
|
|
|
|
for (int i = 0; i < msg->msg_iovlen; i++) {
|
|
const char *buf = msg->msg_iov[i].iov_base;
|
|
size_t len = msg->msg_iov[i].iov_len;
|
|
|
|
while (len > 0) {
|
|
rc = offload_sendto(obj, buf, len, flags,
|
|
msg->msg_name, msg->msg_namelen);
|
|
if (rc < 0) {
|
|
if (rc == -EAGAIN) {
|
|
k_sleep(MDM_SENDMSG_SLEEP);
|
|
} else {
|
|
sent = rc;
|
|
break;
|
|
}
|
|
} else {
|
|
sent += rc;
|
|
buf += rc;
|
|
len -= rc;
|
|
}
|
|
}
|
|
}
|
|
|
|
return (ssize_t) sent;
|
|
}
|
|
|
|
/* Func: modem_rx
|
|
* Desc: Thread to process all messages received from the Modem.
|
|
*/
|
|
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);
|
|
}
|
|
}
|
|
|
|
/* Func: modem_rssi_query_work
|
|
* Desc: Routine to get Modem RSSI.
|
|
*/
|
|
static void modem_rssi_query_work(struct k_work *work)
|
|
{
|
|
struct modem_cmd cmd = MODEM_CMD("+CSQ: ", on_cmd_atcmdinfo_rssi_csq, 2U, ",");
|
|
static char *send_cmd = "AT+CSQ";
|
|
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+CSQ ret:%d", ret);
|
|
}
|
|
|
|
/* Re-start RSSI query work */
|
|
if (work) {
|
|
k_work_reschedule_for_queue(&modem_workq,
|
|
&mdata.rssi_query_work,
|
|
K_SECONDS(RSSI_TIMEOUT_SECS));
|
|
}
|
|
}
|
|
|
|
/* Func: pin_init
|
|
* Desc: Boot up the Modem.
|
|
*/
|
|
static void pin_init(void)
|
|
{
|
|
LOG_INF("Setting Modem Pins");
|
|
|
|
#if DT_INST_NODE_HAS_PROP(0, mdm_wdisable_gpios)
|
|
LOG_INF("Deactivate W Disable");
|
|
modem_pin_write(&mctx, MDM_WDISABLE, 0);
|
|
k_sleep(K_MSEC(250));
|
|
#endif
|
|
|
|
/* NOTE: Per the BG95 document, the Reset pin is internally connected to the
|
|
* Power key pin.
|
|
*/
|
|
|
|
/* MDM_POWER -> 1 for 500-1000 msec. */
|
|
modem_pin_write(&mctx, MDM_POWER, 1);
|
|
k_sleep(K_MSEC(750));
|
|
|
|
/* MDM_POWER -> 0 and wait for ~2secs as UART remains in "inactive" state
|
|
* for some time after the power signal is enabled.
|
|
*/
|
|
modem_pin_write(&mctx, MDM_POWER, 0);
|
|
k_sleep(K_SECONDS(2));
|
|
|
|
LOG_INF("... Done!");
|
|
}
|
|
|
|
static const struct modem_cmd response_cmds[] = {
|
|
MODEM_CMD("OK", on_cmd_ok, 0U, ""),
|
|
MODEM_CMD("ERROR", on_cmd_error, 0U, ""),
|
|
MODEM_CMD("+CME ERROR: ", on_cmd_exterror, 1U, ""),
|
|
};
|
|
|
|
static const struct modem_cmd unsol_cmds[] = {
|
|
MODEM_CMD("+QIURC: \"recv\",", on_cmd_unsol_recv, 1U, ""),
|
|
MODEM_CMD("+QIURC: \"closed\",", on_cmd_unsol_close, 1U, ""),
|
|
MODEM_CMD("RDY", on_cmd_unsol_rdy, 0U, ""),
|
|
};
|
|
|
|
/* Commands sent to the modem to set it up at boot time. */
|
|
static const struct setup_cmd setup_cmds[] = {
|
|
SETUP_CMD_NOHANDLE("ATE0"),
|
|
SETUP_CMD_NOHANDLE("ATH"),
|
|
SETUP_CMD_NOHANDLE("AT+CMEE=1"),
|
|
|
|
/* Commands to read info from the modem (things like IMEI, Model etc). */
|
|
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, ""),
|
|
#if defined(CONFIG_MODEM_SIM_NUMBERS)
|
|
SETUP_CMD("AT+CIMI", "", on_cmd_atcmdinfo_imsi, 0U, ""),
|
|
SETUP_CMD("AT+QCCID", "", on_cmd_atcmdinfo_iccid, 0U, ""),
|
|
#endif /* #if defined(CONFIG_MODEM_SIM_NUMBERS) */
|
|
SETUP_CMD_NOHANDLE("AT+QICSGP=1,1,\"" MDM_APN "\",\"" MDM_USERNAME "\", \"" MDM_PASSWORD "\",1"),
|
|
};
|
|
|
|
/* Func: modem_pdp_context_active
|
|
* Desc: This helper function is called from modem_setup, and is
|
|
* used to open the PDP context. If there is trouble activating the
|
|
* PDP context, we try to deactive and reactive MDM_PDP_ACT_RETRY_COUNT times.
|
|
* If it fails, we return an error.
|
|
*/
|
|
static int modem_pdp_context_activate(void)
|
|
{
|
|
int ret;
|
|
int retry_count = 0;
|
|
|
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler,
|
|
NULL, 0U, "AT+QIACT=1", &mdata.sem_response,
|
|
MDM_CMD_TIMEOUT);
|
|
|
|
/* If there is trouble activating the PDP context, we try to deactivate/reactive it. */
|
|
while (ret == -EIO && retry_count < MDM_PDP_ACT_RETRY_COUNT) {
|
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler,
|
|
NULL, 0U, "AT+QIDEACT=1", &mdata.sem_response,
|
|
MDM_CMD_TIMEOUT);
|
|
|
|
/* If there's any error for AT+QIDEACT, restart the module. */
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
|
|
ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler,
|
|
NULL, 0U, "AT+QIACT=1", &mdata.sem_response,
|
|
MDM_CMD_TIMEOUT);
|
|
|
|
retry_count++;
|
|
}
|
|
|
|
if (ret == -EIO && retry_count >= MDM_PDP_ACT_RETRY_COUNT) {
|
|
LOG_ERR("Retried activating/deactivating too many times.");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Func: modem_setup
|
|
* Desc: This function is used to setup the modem from zero. The idea
|
|
* is that this function will be called right after the modem is
|
|
* powered on to do the stuff necessary to talk to the modem.
|
|
*/
|
|
static int modem_setup(void)
|
|
{
|
|
int ret = 0, counter;
|
|
int rssi_retry_count = 0, init_retry_count = 0;
|
|
|
|
/* Setup the pins to ensure that Modem is enabled. */
|
|
pin_init();
|
|
|
|
restart:
|
|
|
|
counter = 0;
|
|
|
|
/* stop RSSI delay work */
|
|
k_work_cancel_delayable(&mdata.rssi_query_work);
|
|
|
|
/* Let the modem respond. */
|
|
LOG_INF("Waiting for modem to respond");
|
|
ret = k_sem_take(&mdata.sem_response, MDM_MAX_BOOT_TIME);
|
|
if (ret < 0) {
|
|
LOG_ERR("Timeout waiting for RDY");
|
|
goto error;
|
|
}
|
|
|
|
/* Run setup commands on the modem. */
|
|
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;
|
|
}
|
|
|
|
restart_rssi:
|
|
|
|
/* query modem RSSI */
|
|
modem_rssi_query_work(NULL);
|
|
k_sleep(MDM_WAIT_FOR_RSSI_DELAY);
|
|
|
|
/* Keep trying to read RSSI until we get a valid value - Eventually, exit. */
|
|
while (counter++ < MDM_WAIT_FOR_RSSI_COUNT &&
|
|
(mctx.data_rssi >= 0 || mctx.data_rssi <= -1000)) {
|
|
modem_rssi_query_work(NULL);
|
|
k_sleep(MDM_WAIT_FOR_RSSI_DELAY);
|
|
}
|
|
|
|
/* Is the RSSI invalid ? */
|
|
if (mctx.data_rssi >= 0 || mctx.data_rssi <= -1000) {
|
|
rssi_retry_count++;
|
|
|
|
if (rssi_retry_count >= MDM_NETWORK_RETRY_COUNT) {
|
|
LOG_ERR("Failed network init. Too many attempts!");
|
|
ret = -ENETUNREACH;
|
|
goto error;
|
|
}
|
|
|
|
/* Try again! */
|
|
LOG_ERR("Failed network init. Restarting process.");
|
|
counter = 0;
|
|
goto restart_rssi;
|
|
}
|
|
|
|
/* Network is ready - Start RSSI work in the background. */
|
|
LOG_INF("Network is ready.");
|
|
k_work_reschedule_for_queue(&modem_workq, &mdata.rssi_query_work,
|
|
K_SECONDS(RSSI_TIMEOUT_SECS));
|
|
|
|
/* Once the network is ready, we try to activate the PDP context. */
|
|
ret = modem_pdp_context_activate();
|
|
if (ret < 0 && init_retry_count++ < MDM_INIT_RETRY_COUNT) {
|
|
LOG_ERR("Error activating modem with pdp context");
|
|
goto restart;
|
|
}
|
|
|
|
error:
|
|
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 = NULL,
|
|
.connect = offload_connect,
|
|
.sendto = offload_sendto,
|
|
.recvfrom = offload_recvfrom,
|
|
.listen = NULL,
|
|
.accept = NULL,
|
|
.sendmsg = offload_sendmsg,
|
|
.getsockopt = NULL,
|
|
.setsockopt = NULL,
|
|
};
|
|
|
|
/* Setup the Modem NET Interface. */
|
|
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: */
|
|
net_if_set_link_addr(iface, modem_get_mac(dev),
|
|
sizeof(data->mac_addr),
|
|
NET_LINK_ETHERNET);
|
|
data->net_iface = iface;
|
|
}
|
|
|
|
static struct net_if_api api_funcs = {
|
|
.init = modem_net_iface_init,
|
|
};
|
|
|
|
static bool offload_is_supported(int family, int type, int proto)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
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 modem_init(const struct device *dev)
|
|
{
|
|
int ret; ARG_UNUSED(dev);
|
|
|
|
k_sem_init(&mdata.sem_response, 0, 1);
|
|
k_sem_init(&mdata.sem_tx_ready, 0, 1);
|
|
k_sem_init(&mdata.sem_sock_conn, 0, 1);
|
|
k_work_queue_start(&modem_workq, modem_workq_stack,
|
|
K_KERNEL_STACK_SIZEOF(modem_workq_stack),
|
|
K_PRIO_COOP(7), NULL);
|
|
|
|
/* 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 = BUF_ALLOC_TIMEOUT;
|
|
mdata.cmd_handler_data.eol = "\r\n";
|
|
ret = modem_cmd_handler_init(&mctx.cmd_handler, &mdata.cmd_handler_data);
|
|
if (ret < 0) {
|
|
goto error;
|
|
}
|
|
|
|
/* modem interface */
|
|
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;
|
|
#if defined(CONFIG_MODEM_SIM_NUMBERS)
|
|
mctx.data_imsi = mdata.mdm_imsi;
|
|
mctx.data_iccid = mdata.mdm_iccid;
|
|
#endif /* #if defined(CONFIG_MODEM_SIM_NUMBERS) */
|
|
|
|
/* pin setup */
|
|
mctx.pins = modem_pins;
|
|
mctx.pins_len = ARRAY_SIZE(modem_pins);
|
|
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);
|
|
|
|
/* Init RSSI query */
|
|
k_work_init_delayable(&mdata.rssi_query_work, modem_rssi_query_work);
|
|
return modem_setup();
|
|
|
|
error:
|
|
return ret;
|
|
}
|
|
|
|
/* Register the device with the Networking stack. */
|
|
NET_DEVICE_DT_INST_OFFLOAD_DEFINE(0, modem_init, NULL,
|
|
&mdata, NULL,
|
|
CONFIG_MODEM_QUECTEL_BG9X_INIT_PRIORITY,
|
|
&api_funcs, MDM_MAX_DATA_LENGTH);
|
|
|
|
/* Register NET sockets. */
|
|
NET_SOCKET_REGISTER(quectel_bg9x, NET_SOCKET_DEFAULT_PRIO, AF_UNSPEC,
|
|
offload_is_supported, offload_socket);
|