drivers: modem: Added simcom sim7080 modem driver.
Implemented driver for the simcom sim7080 modem.
This driver features Socket offloading, TCP, UDP, DNS,
SMS, GPS and FTP.
Signed-off-by: Lukas Gehreke <lk.gehreke@gmail.com>
diff --git a/drivers/modem/simcom-sim7080.c b/drivers/modem/simcom-sim7080.c
new file mode 100644
index 0000000..dc9c4e5
--- /dev/null
+++ b/drivers/modem/simcom-sim7080.c
@@ -0,0 +1,2384 @@
+/*
+ * Copyright (C) 2021 metraTec GmbH
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#define DT_DRV_COMPAT simcom_sim7080
+
+#include <logging/log.h>
+LOG_MODULE_REGISTER(modem_simcom_sim7080, CONFIG_MODEM_LOG_LEVEL);
+
+#include <drivers/modem/simcom-sim7080.h>
+#include "simcom-sim7080.h"
+
+#define SMS_TP_UDHI_HEADER 0x40
+
+static struct k_thread modem_rx_thread;
+static struct k_work_q modem_workq;
+static struct sim7080_data mdata;
+static struct modem_context mctx;
+static const struct socket_op_vtable offload_socket_fd_op_vtable;
+
+static struct zsock_addrinfo dns_result;
+static struct sockaddr dns_result_addr;
+static char dns_result_canonname[DNS_MAX_NAME_SIZE + 1];
+
+static struct sim7080_gnss_data gnss_data;
+
+static K_KERNEL_STACK_DEFINE(modem_rx_stack, CONFIG_MODEM_SIMCOM_SIM7080_RX_STACK_SIZE);
+static K_KERNEL_STACK_DEFINE(modem_workq_stack, CONFIG_MODEM_SIMCOM_SIM7080_RX_WORKQ_STACK_SIZE);
+NET_BUF_POOL_DEFINE(mdm_recv_pool, MDM_RECV_MAX_BUF, MDM_RECV_BUF_SIZE, 0, NULL);
+
+static void socket_close(struct modem_socket *sock);
+const struct socket_dns_offload offload_dns_ops;
+
+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 sim7080_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;
+}
+
+/* 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 sim7080_data *data = dev->data;
+
+ net_if_set_link_addr(iface, modem_get_mac(dev), sizeof(data->mac_addr), NET_LINK_ETHERNET);
+
+ data->netif = iface;
+
+ socket_offload_dns_register(&offload_dns_ops);
+}
+
+/**
+ * Changes the operating state of the sim7080.
+ *
+ * @param state The new state.
+ */
+static void change_state(enum sim7080_state state)
+{
+ LOG_DBG("Changing state to (%d)", state);
+ mdata.state = state;
+}
+
+/**
+ * Get the current operating state of the sim7080.
+ *
+ * @return The current state.
+ */
+static enum sim7080_state get_state(void)
+{
+ return mdata.state;
+}
+
+/*
+ * Parses the +CAOPEN command and gives back the
+ * connect semaphore.
+ */
+MODEM_CMD_DEFINE(on_cmd_caopen)
+{
+ int result = atoi(argv[1]);
+
+ LOG_INF("+CAOPEN: %d", result);
+ modem_cmd_handler_set_error(data, result);
+ return 0;
+}
+
+/*
+ * Unlock the tx ready semaphore if '> ' is received.
+ */
+MODEM_CMD_DIRECT_DEFINE(on_cmd_tx_ready)
+{
+ k_sem_give(&mdata.sem_tx_ready);
+ return len;
+}
+
+/*
+ * Connects an modem socket. Protocol can either be TCP or UDP.
+ */
+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;
+ struct modem_cmd cmd[] = { MODEM_CMD("+CAOPEN: ", on_cmd_caopen, 2U, ",") };
+ char buf[sizeof("AT+CAOPEN: #,#,#####,#xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx#,####")];
+ char ip_str[NET_IPV6_ADDR_LEN];
+ int ret;
+
+ /* Modem is not attached to the network. */
+ if (get_state() != SIM7080_STATE_NETWORKING) {
+ return -EAGAIN;
+ }
+
+ 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! id: %d, fd: %d", sock->id, sock->sock_fd);
+ errno = EISCONN;
+ return -1;
+ }
+
+ /* get the 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);
+ }
+
+ /* Get protocol */
+ protocol = (sock->type == SOCK_STREAM) ? "TCP" : "UDP";
+
+ ret = modem_context_sprint_ip_addr(addr, ip_str, sizeof(ip_str));
+ if (ret != 0) {
+ LOG_ERR("Failed to format IP!");
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ret = snprintk(buf, sizeof(buf), "AT+CAOPEN=%d,%d,\"%s\",\"%s\",%d", 0, sock->sock_fd,
+ protocol, ip_str, dst_port);
+ if (ret < 0) {
+ LOG_ERR("Failed to build connect command. ID: %d, FD: %d", sock->id, sock->sock_fd);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, cmd, ARRAY_SIZE(cmd), buf,
+ &mdata.sem_response, MDM_CONNECT_TIMEOUT);
+ if (ret < 0) {
+ LOG_ERR("%s ret: %d", log_strdup(buf), ret);
+ socket_close(sock);
+ goto error;
+ }
+
+ ret = modem_cmd_handler_get_error(&mdata.cmd_handler_data);
+ if (ret != 0) {
+ LOG_ERR("Closing the socket!");
+ socket_close(sock);
+ goto error;
+ }
+
+ sock->is_connected = true;
+ errno = 0;
+ return 0;
+error:
+ errno = -ret;
+ return -1;
+}
+
+/*
+ * Send data over a given socket.
+ *
+ * First we signal the module that we want to send data over a socket.
+ * This is done by sending AT+CASEND=<sockfd>,<nbytes>\r\n.
+ * If The module is ready to send data it will send back
+ * an UNTERMINATED promt '> '. After that data can be sent to the modem.
+ * As terminating byte a STRG+Z (0x1A) is sent. The module will
+ * then send a OK or ERROR.
+ */
+static ssize_t offload_sendto(void *obj, const void *buf, size_t len, int flags,
+ const struct sockaddr *dest_addr, socklen_t addrlen)
+{
+ int ret;
+ struct modem_socket *sock = (struct modem_socket *)obj;
+ char send_buf[sizeof("AT+CASEND=#,####")] = { 0 };
+ char ctrlz = 0x1A;
+
+ /* Modem is not attached to the network. */
+ if (get_state() != SIM7080_STATE_NETWORKING) {
+ LOG_ERR("Modem currently not attached to the network!");
+ return -EAGAIN;
+ }
+
+ /* Do some sanity checks. */
+ if (!buf || len == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* Socket has to be connected. */
+ if (!sock->is_connected) {
+ errno = ENOTCONN;
+ return -1;
+ }
+
+ /* Only send up to MTU bytes. */
+ if (len > MDM_MAX_DATA_LENGTH) {
+ len = MDM_MAX_DATA_LENGTH;
+ }
+
+ ret = snprintk(send_buf, sizeof(send_buf), "AT+CASEND=%d,%ld", sock->sock_fd, (long)len);
+ if (ret < 0) {
+ LOG_ERR("Failed to build send command!!");
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /* Make sure only one send can be done at a time. */
+ k_sem_take(&mdata.cmd_handler_data.sem_tx_lock, K_FOREVER);
+ k_sem_reset(&mdata.sem_tx_ready);
+
+ /* Send CASEND */
+ mdata.current_sock_written = len;
+ ret = modem_cmd_send_nolock(&mctx.iface, &mctx.cmd_handler, NULL, 0U, send_buf, NULL,
+ K_NO_WAIT);
+ if (ret < 0) {
+ LOG_ERR("Failed to send CASEND!!");
+ goto exit;
+ }
+
+ /* Wait for '> ' */
+ ret = k_sem_take(&mdata.sem_tx_ready, K_SECONDS(2));
+ if (ret < 0) {
+ LOG_ERR("Timeout while waiting for tx");
+ goto exit;
+ }
+
+ /* Send data */
+ mctx.iface.write(&mctx.iface, buf, len);
+ mctx.iface.write(&mctx.iface, &ctrlz, 1);
+
+ /* Wait for the OK */
+ k_sem_reset(&mdata.sem_response);
+ ret = k_sem_take(&mdata.sem_response, MDM_CMD_TIMEOUT);
+ if (ret < 0) {
+ LOG_ERR("Timeout waiting for OK");
+ }
+
+exit:
+ k_sem_give(&mdata.cmd_handler_data.sem_tx_lock);
+ /* Data was successfully sent */
+
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+
+ errno = 0;
+ return mdata.current_sock_written;
+}
+
+/*
+ * Read data from a given socket.
+ *
+ * The response has the form +CARECV: <length>,data\r\nOK\r\n
+ */
+static int sockread_common(int sockfd, struct modem_cmd_handler_data *data, int socket_data_length,
+ uint16_t len)
+{
+ struct modem_socket *sock;
+ struct socket_read_data *sock_data;
+ int ret, packet_size;
+
+ if (!len) {
+ LOG_ERR("Invalid length, aborting");
+ return -EAGAIN;
+ }
+
+ if (!data->rx_buf) {
+ LOG_ERR("Incorrect format! Ignoring data!");
+ return -EINVAL;
+ }
+
+ if (socket_data_length <= 0) {
+ LOG_ERR("Length error (%d)", socket_data_length);
+ return -EAGAIN;
+ }
+
+ if (net_buf_frags_len(data->rx_buf) < socket_data_length) {
+ LOG_DBG("Not enough data -- wait!");
+ return -EAGAIN;
+ }
+
+ sock = modem_socket_from_fd(&mdata.socket_config, sockfd);
+ if (!sock) {
+ LOG_ERR("Socket not found! (%d)", sockfd);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ sock_data = (struct socket_read_data *)sock->data;
+ if (!sock_data) {
+ LOG_ERR("Socket data not found! (%d)", sockfd);
+ 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;
+ goto exit;
+ }
+
+exit:
+ /* Indication only sets length to a dummy value. */
+ packet_size = modem_socket_next_packet_size(&mdata.socket_config, sock);
+ modem_socket_packet_size_update(&mdata.socket_config, sock, -packet_size);
+ return ret;
+}
+
+/*
+ * Handler for carecv response.
+ */
+MODEM_CMD_DEFINE(on_cmd_carecv)
+{
+ return sockread_common(mdata.current_sock_fd, data, atoi(argv[0]), len);
+}
+
+/*
+ * Read data from a given socket.
+ */
+static ssize_t offload_recvfrom(void *obj, void *buf, size_t max_len, int flags,
+ struct sockaddr *src_addr, socklen_t *addrlen)
+{
+ struct modem_socket *sock = (struct modem_socket *)obj;
+ char sendbuf[sizeof("AT+CARECV=##,####")];
+ int ret, packet_size;
+ struct socket_read_data sock_data;
+
+ struct modem_cmd data_cmd[] = { MODEM_CMD("+CARECV: ", on_cmd_carecv, 1U, ",") };
+
+ /* Modem is not attached to the network. */
+ if (get_state() != SIM7080_STATE_NETWORKING) {
+ LOG_ERR("Modem currently not attached to the network!");
+ return -EAGAIN;
+ }
+
+ if (!buf || max_len == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (flags & ZSOCK_MSG_PEEK) {
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ packet_size = modem_socket_next_packet_size(&mdata.socket_config, sock);
+ if (!packet_size) {
+ if (flags & ZSOCK_MSG_DONTWAIT) {
+ errno = EAGAIN;
+ return -1;
+ }
+
+ modem_socket_wait_data(&mdata.socket_config, sock);
+ packet_size = modem_socket_next_packet_size(&mdata.socket_config, sock);
+ }
+
+ max_len = (max_len > MDM_MAX_DATA_LENGTH) ? MDM_MAX_DATA_LENGTH : max_len;
+ snprintk(sendbuf, sizeof(sendbuf), "AT+CARECV=%d,%zd", sock->sock_fd, max_len);
+
+ memset(&sock_data, 0, sizeof(sock_data));
+ sock_data.recv_buf = buf;
+ sock_data.recv_buf_len = max_len;
+ sock_data.recv_addr = src_addr;
+ sock->data = &sock_data;
+ mdata.current_sock_fd = sock->sock_fd;
+
+ 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 src */
+ if (src_addr && addrlen) {
+ *addrlen = sizeof(sock->dst);
+ memcpy(src_addr, &sock->dst, *addrlen);
+ }
+
+ errno = 0;
+ ret = sock_data.recv_read_len;
+
+exit:
+ /* clear socket data */
+ mdata.current_sock_fd = -1;
+ sock->data = NULL;
+ return ret;
+}
+
+/*
+ * Sends messages to the modem.
+ */
+static ssize_t offload_sendmsg(void *obj, const struct msghdr *msg, int flags)
+{
+ ssize_t sent = 0;
+ const char *buf;
+ size_t len;
+ int ret;
+
+ /* Modem is not attached to the network. */
+ if (get_state() != SIM7080_STATE_NETWORKING) {
+ LOG_ERR("Modem currently not attached to the network!");
+ return -EAGAIN;
+ }
+
+ for (int i = 0; i < msg->msg_iovlen; i++) {
+ buf = msg->msg_iov[i].iov_base;
+ len = msg->msg_iov[i].iov_len;
+
+ while (len > 0) {
+ ret = offload_sendto(obj, buf, len, flags, msg->msg_name, msg->msg_namelen);
+ if (ret < 0) {
+ if (ret == -EAGAIN) {
+ k_sleep(K_SECONDS(1));
+ } else {
+ sent = ret;
+ break;
+ }
+ } else {
+ sent += ret;
+ buf += ret;
+ len -= ret;
+ }
+ }
+ }
+
+ return sent;
+}
+
+/*
+ * Closes a given socket.
+ */
+static void socket_close(struct modem_socket *sock)
+{
+ char buf[sizeof("AT+CACLOSE=##")];
+ int ret;
+
+ snprintk(buf, sizeof(buf), "AT+CACLOSE=%d", sock->sock_fd);
+
+ 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);
+}
+
+/*
+ * Offloads read by reading from a given socket.
+ */
+static ssize_t offload_read(void *obj, void *buffer, size_t count)
+{
+ return offload_recvfrom(obj, buffer, count, 0, NULL, 0);
+}
+
+/*
+ * Offloads write by writing to a given socket.
+ */
+static ssize_t offload_write(void *obj, const void *buffer, size_t count)
+{
+ return offload_sendto(obj, buffer, count, 0, NULL, 0);
+}
+
+/*
+ * Offloads close by terminating the connection and freeing the socket.
+ */
+static int offload_close(void *obj)
+{
+ struct modem_socket *sock = (struct modem_socket *)obj;
+
+ /* Modem is not attached to the network. */
+ if (get_state() != SIM7080_STATE_NETWORKING) {
+ LOG_ERR("Modem currently not attached to the network!");
+ return -EAGAIN;
+ }
+
+ /* 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;
+}
+
+/*
+ * Polls a given socket.
+ */
+static int offload_poll(struct zsock_pollfd *fds, int nfds, int msecs)
+{
+ int i;
+ void *obj;
+
+ /* Modem is not attached to the network. */
+ if (get_state() != SIM7080_STATE_NETWORKING) {
+ LOG_ERR("Modem currently not attached to the network!");
+ return -EAGAIN;
+ }
+
+ /* 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);
+}
+
+/*
+ * Offloads ioctl. Only supported ioctl is poll_offload.
+ */
+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;
+ }
+}
+
+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,
+};
+
+/*
+ * Parses the dns response from the modem.
+ *
+ * Response on success:
+ * +CDNSGIP: 1,<domain name>,<IPv4>[,<IPv6>]
+ *
+ * Response on failure:
+ * +CDNSGIP: 0,<err>
+ */
+MODEM_CMD_DEFINE(on_cmd_cdnsgip)
+{
+ int state;
+ char ips[256];
+ size_t out_len;
+ int ret = -1;
+
+ state = atoi(argv[0]);
+ if (state == 0) {
+ LOG_ERR("DNS lookup failed with error %s", log_strdup(argv[1]));
+ goto exit;
+ }
+
+ /* Offset to skip the leading " */
+ out_len = net_buf_linearize(ips, sizeof(ips) - 1, data->rx_buf, 1, len);
+ ips[out_len] = '\0';
+
+ /* find trailing " */
+ char *ipv4 = strstr(ips, "\"");
+
+ if (!ipv4) {
+ LOG_ERR("Malformed DNS response!!");
+ goto exit;
+ }
+
+ *ipv4 = '\0';
+ net_addr_pton(dns_result.ai_family, ips,
+ &((struct sockaddr_in *)&dns_result_addr)->sin_addr);
+ ret = 0;
+
+exit:
+ k_sem_give(&mdata.sem_dns);
+ return ret;
+}
+
+/*
+ * Perform a dns lookup.
+ */
+static int offload_getaddrinfo(const char *node, const char *service,
+ const struct zsock_addrinfo *hints, struct zsock_addrinfo **res)
+{
+ struct modem_cmd cmd[] = { MODEM_CMD("+CDNSGIP: ", on_cmd_cdnsgip, 2U, ",") };
+ char sendbuf[sizeof("AT+CDNSGIP=\"\",##,#####") + 128];
+ uint32_t port = 0;
+ int ret;
+
+ /* Modem is not attached to the network. */
+ if (get_state() != SIM7080_STATE_NETWORKING) {
+ LOG_ERR("Modem currently not attached to the network!");
+ return DNS_EAI_AGAIN;
+ }
+
+ /* init result */
+ (void)memset(&dns_result, 0, sizeof(dns_result));
+ (void)memset(&dns_result_addr, 0, sizeof(dns_result_addr));
+
+ /* Currently only support IPv4. */
+ dns_result.ai_family = AF_INET;
+ dns_result_addr.sa_family = AF_INET;
+ dns_result.ai_addr = &dns_result_addr;
+ dns_result.ai_addrlen = sizeof(dns_result_addr);
+ dns_result.ai_canonname = dns_result_canonname;
+ dns_result_canonname[0] = '\0';
+
+ if (service) {
+ port = atoi(service);
+ if (port < 1 || port > USHRT_MAX)
+ return DNS_EAI_SERVICE;
+ }
+
+ if (port > 0U) {
+ if (dns_result.ai_family == AF_INET)
+ net_sin(&dns_result_addr)->sin_port = htons(port);
+ }
+
+ /* Check if node is an IP address */
+ if (net_addr_pton(dns_result.ai_family, node,
+ &((struct sockaddr_in *)&dns_result_addr)->sin_addr) == 0) {
+ *res = &dns_result;
+ return 0;
+ }
+
+ /* user flagged node as numeric host, but we failed net_addr_pton */
+ if (hints && hints->ai_flags & AI_NUMERICHOST) {
+ return DNS_EAI_NONAME;
+ }
+
+ snprintk(sendbuf, sizeof(sendbuf), "AT+CDNSGIP=\"%s\",10,20000", node);
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, cmd, ARRAY_SIZE(cmd), sendbuf,
+ &mdata.sem_dns, MDM_DNS_TIMEOUT);
+ if (ret < 0) {
+ return ret;
+ }
+
+ *res = (struct zsock_addrinfo *)&dns_result;
+ return 0;
+}
+
+/*
+ * Free addrinfo structure.
+ */
+static void offload_freeaddrinfo(struct zsock_addrinfo *res)
+{
+ /* No need to free static memory. */
+ res = NULL;
+}
+
+/*
+ * DNS vtable.
+ */
+const struct socket_dns_offload offload_dns_ops = {
+ .getaddrinfo = offload_getaddrinfo,
+ .freeaddrinfo = offload_freeaddrinfo,
+};
+
+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;
+
+ ret = modem_socket_get(&mdata.socket_config, family, type, proto);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+
+ errno = 0;
+ return ret;
+}
+
+/*
+ * 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);
+ }
+}
+
+MODEM_CMD_DEFINE(on_cmd_ok)
+{
+ modem_cmd_handler_set_error(data, 0);
+ k_sem_give(&mdata.sem_response);
+ return 0;
+}
+
+MODEM_CMD_DEFINE(on_cmd_error)
+{
+ modem_cmd_handler_set_error(data, -EIO);
+ k_sem_give(&mdata.sem_response);
+ return 0;
+}
+
+MODEM_CMD_DEFINE(on_cmd_exterror)
+{
+ modem_cmd_handler_set_error(data, -EIO);
+ k_sem_give(&mdata.sem_response);
+ return 0;
+}
+
+/*
+ * Handles pdp context urc.
+ *
+ * The urc has the form +APP PDP: <index>,<state>.
+ * State can either be ACTIVE for activation or
+ * DEACTIVE if disabled.
+ */
+MODEM_CMD_DEFINE(on_urc_app_pdp)
+{
+ mdata.pdp_active = strcmp(argv[1], "ACTIVE") == 0;
+ LOG_INF("PDP context: %u", mdata.pdp_active);
+ k_sem_give(&mdata.sem_response);
+ return 0;
+}
+
+MODEM_CMD_DEFINE(on_urc_sms)
+{
+ LOG_INF("SMS: %s", log_strdup(argv[0]));
+ return 0;
+}
+
+/*
+ * Handles socket data notification.
+ *
+ * The sim modem sends and unsolicited +CADATAIND: <cid>
+ * if data can be read from a socket.
+ */
+MODEM_CMD_DEFINE(on_urc_cadataind)
+{
+ struct modem_socket *sock;
+ int sock_fd;
+
+ sock_fd = atoi(argv[0]);
+
+ sock = modem_socket_from_fd(&mdata.socket_config, sock_fd);
+ if (!sock) {
+ return 0;
+ }
+
+ /* Modem does not tell packet size. Set dummy for receive. */
+ modem_socket_packet_size_update(&mdata.socket_config, sock, 1);
+
+ LOG_INF("Data available on socket: %d", sock_fd);
+ modem_socket_data_ready(&mdata.socket_config, sock);
+
+ return 0;
+}
+
+/*
+ * Handles the castate response.
+ *
+ * +CASTATE: <cid>,<state>
+ *
+ * Cid is the connection id (socket fd) and
+ * state can be:
+ * 0 - Closed by remote server or error
+ * 1 - Connected to remote server
+ * 2 - Listening
+ */
+MODEM_CMD_DEFINE(on_urc_castate)
+{
+ struct modem_socket *sock;
+ int sockfd, state;
+
+ sockfd = atoi(argv[0]);
+ state = atoi(argv[1]);
+
+ sock = modem_socket_from_fd(&mdata.socket_config, sockfd);
+ if (!sock) {
+ return 0;
+ }
+
+ /* Only continue if socket was closed. */
+ if (state != 0) {
+ return 0;
+ }
+
+ LOG_INF("Socket close indication for socket: %d", sockfd);
+
+ sock->is_connected = false;
+ LOG_INF("Socket closed: %d", sockfd);
+
+ return 0;
+}
+
+/**
+ * Handles the ftpget urc.
+ *
+ * +FTPGET: <mode>,<error>
+ *
+ * Mode can be 1 for opening a session and
+ * reporting that data is available or 2 for
+ * reading data. This urc handler will only handle
+ * mode 1 because 2 will not occur as urc.
+ *
+ * Error can be either:
+ * - 1 for data available/opened session.
+ * - 0 If transfer is finished.
+ * - >0 for some error.
+ */
+MODEM_CMD_DEFINE(on_urc_ftpget)
+{
+ int error = atoi(argv[0]);
+
+ LOG_INF("+FTPGET: 1,%d", error);
+
+ /* Transfer finished. */
+ if (error == 0) {
+ mdata.ftp.state = SIM7080_FTP_CONNECTION_STATE_FINISHED;
+ } else if (error == 1) {
+ mdata.ftp.state = SIM7080_FTP_CONNECTION_STATE_CONNECTED;
+ } else {
+ mdata.ftp.state = SIM7080_FTP_CONNECTION_STATE_ERROR;
+ }
+
+ k_sem_give(&mdata.sem_ftp);
+
+ return 0;
+}
+
+/*
+ * Read manufacurer identification.
+ */
+MODEM_CMD_DEFINE(on_cmd_cgmi)
+{
+ 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;
+}
+
+/*
+ * Read model identification.
+ */
+MODEM_CMD_DEFINE(on_cmd_cgmm)
+{
+ 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_INF("Model: %s", log_strdup(mdata.mdm_model));
+ return 0;
+}
+
+/*
+ * Read software release.
+ *
+ * Response will be in format RESPONSE: <revision>.
+ */
+MODEM_CMD_DEFINE(on_cmd_cgmr)
+{
+ size_t out_len;
+ char *p;
+
+ out_len = net_buf_linearize(mdata.mdm_revision, sizeof(mdata.mdm_revision) - 1,
+ data->rx_buf, 0, len);
+ mdata.mdm_revision[out_len] = '\0';
+
+ /* The module prepends a Revision: */
+ p = strchr(mdata.mdm_revision, ':');
+ if (p) {
+ out_len = strlen(p + 1);
+ memmove(mdata.mdm_revision, p + 1, out_len + 1);
+ }
+
+ LOG_INF("Revision: %s", log_strdup(mdata.mdm_revision));
+ return 0;
+}
+
+/*
+ * Read serial number identification.
+ */
+MODEM_CMD_DEFINE(on_cmd_cgsn)
+{
+ 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_INF("IMEI: %s", log_strdup(mdata.mdm_imei));
+ return 0;
+}
+
+#if defined(CONFIG_MODEM_SIM_NUMBERS)
+/*
+ * Read international mobile subscriber identity.
+ */
+MODEM_CMD_DEFINE(on_cmd_cimi)
+{
+ 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;
+}
+
+/*
+ * Read iccid.
+ */
+MODEM_CMD_DEFINE(on_cmd_ccid)
+{
+ size_t out_len = net_buf_linearize(mdata.mdm_iccid, sizeof(mdata.mdm_iccid) - 1,
+ data->rx_buf, 0, len);
+ mdata.mdm_iccid[out_len] = '\0';
+
+ /* Log the received information. */
+ LOG_INF("ICCID: %s", log_strdup(mdata.mdm_iccid));
+ return 0;
+}
+#endif /* defined(CONFIG_MODEM_SIM_NUMBERS) */
+
+/*
+ * Parses the non urc C(E)REG and updates registration status.
+ */
+MODEM_CMD_DEFINE(on_cmd_cereg)
+{
+ mdata.mdm_registration = atoi(argv[1]);
+ LOG_INF("CREG: %u", mdata.mdm_registration);
+ return 0;
+}
+
+MODEM_CMD_DEFINE(on_cmd_cpin)
+{
+ mdata.cpin_ready = strcmp(argv[0], "READY") == 0;
+ LOG_INF("CPIN: %d", mdata.cpin_ready);
+ return 0;
+}
+
+MODEM_CMD_DEFINE(on_cmd_cgatt)
+{
+ mdata.mdm_cgatt = atoi(argv[0]);
+ LOG_INF("CGATT: %d", mdata.mdm_cgatt);
+ return 0;
+}
+
+/*
+ * Handler for RSSI query.
+ *
+ * +CSQ: <rssi>,<ber>
+ * rssi: 0,-115dBm; 1,-111dBm; 2...30,-110...-54dBm; 31,-52dBm or greater.
+ * 99, ukn
+ * ber: Not used.
+ */
+MODEM_CMD_DEFINE(on_cmd_csq)
+{
+ int rssi = atoi(argv[0]);
+
+ if (rssi == 0) {
+ mdata.mdm_rssi = -115;
+ } else if (rssi == 1) {
+ mdata.mdm_rssi = -111;
+ } else if (rssi > 1 && rssi < 31) {
+ mdata.mdm_rssi = -114 + 2 * rssi;
+ } else if (rssi == 31) {
+ mdata.mdm_rssi = -52;
+ } else {
+ mdata.mdm_rssi = -1000;
+ }
+
+ LOG_INF("RSSI: %d", mdata.mdm_rssi);
+ return 0;
+}
+
+/*
+ * Queries modem RSSI.
+ *
+ * If a work queue parameter is provided query work will
+ * be scheduled. Otherwise rssi is queried once.
+ */
+static void modem_rssi_query_work(struct k_work *work)
+{
+ struct modem_cmd cmd[] = { MODEM_CMD("+CSQ: ", on_cmd_csq, 2U, ",") };
+ static char *send_cmd = "AT+CSQ";
+ int ret;
+
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, cmd, ARRAY_SIZE(cmd), send_cmd,
+ &mdata.sem_response, MDM_CMD_TIMEOUT);
+ if (ret < 0) {
+ LOG_ERR("AT+CSQ ret:%d", ret);
+ }
+
+ if (work) {
+ k_work_reschedule_for_queue(&modem_workq, &mdata.rssi_query_work,
+ K_SECONDS(RSSI_TIMEOUT_SECS));
+ }
+}
+
+/*
+ * Possible responses by the sim7080.
+ */
+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, ""),
+ MODEM_CMD_DIRECT(">", on_cmd_tx_ready),
+};
+
+/*
+ * Possible unsolicited commands.
+ */
+static const struct modem_cmd unsolicited_cmds[] = {
+ MODEM_CMD("+APP PDP: ", on_urc_app_pdp, 2U, ","),
+ MODEM_CMD("SMS ", on_urc_sms, 1U, ""),
+ MODEM_CMD("+CADATAIND: ", on_urc_cadataind, 1U, ""),
+ MODEM_CMD("+CASTATE: ", on_urc_castate, 2U, ","),
+ MODEM_CMD("+FTPGET: 1,", on_urc_ftpget, 1U, ""),
+};
+
+/*
+ * Activates the pdp context
+ */
+static int modem_pdp_activate(void)
+{
+ int counter;
+ int ret = 0;
+#if defined(CONFIG_MODEM_SIMCOM_SIM7080_RAT_GSM)
+ const char *buf = "AT+CREG?";
+ struct modem_cmd cmds[] = { MODEM_CMD("+CREG: ", on_cmd_cereg, 2U, ",") };
+#else
+ const char *buf = "AT+CEREG?";
+ struct modem_cmd cmds[] = { MODEM_CMD("+CEREG: ", on_cmd_cereg, 2U, ",") };
+#endif /* defined(CONFIG_MODEM_SIMCOM_SIM7080_RAT_GSM) */
+
+ struct modem_cmd cgatt_cmd[] = { MODEM_CMD("+CGATT: ", on_cmd_cgatt, 1U, "") };
+
+ counter = 0;
+ while (counter++ < MDM_MAX_CGATT_WAITS && mdata.mdm_cgatt != 1) {
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, cgatt_cmd,
+ ARRAY_SIZE(cgatt_cmd), "AT+CGATT?", &mdata.sem_response,
+ MDM_CMD_TIMEOUT);
+ if (ret < 0) {
+ LOG_ERR("Failed to query cgatt!!");
+ return -1;
+ }
+
+ k_sleep(K_SECONDS(1));
+ }
+
+ if (counter >= MDM_MAX_CGATT_WAITS) {
+ LOG_WRN("Network attach failed!!");
+ return -1;
+ }
+
+ if (!mdata.cpin_ready || mdata.mdm_cgatt != 1) {
+ LOG_ERR("Fatal: Modem is not attached to GPRS network!!");
+ return -1;
+ }
+
+ LOG_INF("Waiting for network");
+
+ /* Wait until the module is registered to the network.
+ * Registration will be set by urc.
+ */
+ counter = 0;
+ while (counter++ < MDM_MAX_CEREG_WAITS && mdata.mdm_registration != 1 &&
+ mdata.mdm_registration != 5) {
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, cmds, ARRAY_SIZE(cmds), buf,
+ &mdata.sem_response, MDM_CMD_TIMEOUT);
+ if (ret < 0) {
+ LOG_ERR("Failed to query registration!!");
+ return -1;
+ }
+
+ k_sleep(K_SECONDS(1));
+ }
+
+ if (counter >= MDM_MAX_CEREG_WAITS) {
+ LOG_WRN("Network registration failed!");
+ ret = -1;
+ goto error;
+ }
+
+ /* Set dual stack mode (IPv4/IPv6) */
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0, "AT+CNCFG=0,0",
+ &mdata.sem_response, MDM_CMD_TIMEOUT);
+ if (ret < 0) {
+ LOG_ERR("Could not configure pdp context!");
+ goto error;
+ }
+
+ /*
+ * Now activate the pdp context and wait for confirmation.
+ */
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0, "AT+CNACT=0,1",
+ &mdata.sem_response, MDM_CMD_TIMEOUT);
+ if (ret < 0) {
+ LOG_ERR("Could not activate PDP context.");
+ goto error;
+ }
+
+ ret = k_sem_take(&mdata.sem_response, MDM_PDP_TIMEOUT);
+ if (ret < 0 || mdata.pdp_active == false) {
+ LOG_ERR("Failed to activate PDP context.");
+ ret = -1;
+ goto error;
+ }
+
+ LOG_INF("Network active.");
+
+error:
+ return ret;
+}
+
+/*
+ * Toggles the modems power pin.
+ */
+static void modem_pwrkey(void)
+{
+ /* Power pin should be high for 1.5 seconds. */
+ modem_pin_write(&mctx, 0, 1);
+ k_sleep(K_MSEC(1500));
+ modem_pin_write(&mctx, 0, 0);
+ k_sleep(K_SECONDS(5));
+}
+
+/*
+ * Commands to be sent at setup.
+ */
+static const struct setup_cmd setup_cmds[] = {
+ SETUP_CMD_NOHANDLE("ATH"),
+ SETUP_CMD("AT+CGMI", "", on_cmd_cgmi, 0U, ""),
+ SETUP_CMD("AT+CGMM", "", on_cmd_cgmm, 0U, ""),
+ SETUP_CMD("AT+CGMR", "", on_cmd_cgmr, 0U, ""),
+ SETUP_CMD("AT+CGSN", "", on_cmd_cgsn, 0U, ""),
+#if defined(CONFIG_MODEM_SIM_NUMBERS)
+ SETUP_CMD("AT+CIMI", "", on_cmd_cimi, 0U, ""),
+ SETUP_CMD("AT+CCID", "", on_cmd_ccid, 0U, ""),
+#endif /* defined(CONFIG_MODEM_SIM_NUMBERS) */
+#if defined(CONFIG_MODEM_SIMCOM_SIM7080_RAT_NB1)
+ SETUP_CMD_NOHANDLE("AT+CNMP=38"),
+ SETUP_CMD_NOHANDLE("AT+CMNB=2"),
+ SETUP_CMD_NOHANDLE("AT+CBANDCFG=\"NB-IOT\"," MDM_LTE_BANDS),
+#endif /* defined(CONFIG_MODEM_SIMCOM_SIM7080_RAT_NB1) */
+#if defined(CONFIG_MODEM_SIMCOM_SIM7080_RAT_M1)
+ SETUP_CMD_NOHANDLE("AT+CNMP=38"),
+ SETUP_CMD_NOHANDLE("AT+CMNB=1"),
+ SETUP_CMD_NOHANDLE("AT+CBANDCFG=\"CAT-M\"," MDM_LTE_BANDS),
+#endif /* defined(CONFIG_MODEM_SIMCOM_SIM7080_RAT_M1) */
+#if defined(CONFIG_MODEM_SIMCOM_SIM7080_RAT_GSM)
+ SETUP_CMD_NOHANDLE("AT+CNMP=13"),
+#endif /* defined(CONFIG_MODEM_SIMCOM_SIM7080_RAT_GSM) */
+ SETUP_CMD("AT+CPIN?", "+CPIN: ", on_cmd_cpin, 1U, ""),
+};
+
+/**
+ * Performs the autobaud sequence until modem answers or limit is reached.
+ *
+ * @return On successful boot 0 is returned. Otherwise <0 is returned.
+ */
+static int modem_autobaud(void)
+{
+ int boot_tries = 0;
+ int counter = 0;
+ int ret;
+
+ while (boot_tries++ <= MDM_BOOT_TRIES) {
+ modem_pwrkey();
+
+ /*
+ * The sim7080 has a autobaud function.
+ * On startup multiple AT's are sent until
+ * a OK is received.
+ */
+ counter = 0;
+ while (counter < MDM_MAX_AUTOBAUD) {
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, "AT",
+ &mdata.sem_response, K_MSEC(500));
+
+ /* OK was received. */
+ if (ret == 0) {
+ /* Disable echo */
+ return modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U,
+ "ATE0", &mdata.sem_response, K_SECONDS(2));
+ }
+
+ counter++;
+ }
+ }
+
+ return -1;
+}
+
+/**
+ * Get the next parameter from the gnss phrase.
+ *
+ * @param src The source string supported on first call.
+ * @param delim The delimeter of the parameter list.
+ * @param saveptr Pointer for subsequent parses.
+ * @return On success a pointer to the parameter. On failure
+ * or end of string NULL is returned.
+ *
+ * This function is used instead of strtok because strtok would
+ * skip empty parameters, which is not desired. The modem may
+ * omit parameters which could lead to a incorrect parse.
+ */
+static char *gnss_get_next_param(char *src, const char *delim, char **saveptr)
+{
+ char *start, *del;
+
+ if (src) {
+ start = src;
+ } else {
+ start = *saveptr;
+ }
+
+ /* Illegal start string. */
+ if (!start) {
+ return NULL;
+ }
+
+ /* End of string reached. */
+ if (*start == '\0' || *start == '\r') {
+ return NULL;
+ }
+
+ del = strstr(start, delim);
+ if (!del) {
+ return NULL;
+ }
+
+ *del = '\0';
+ *saveptr = del + 1;
+
+ if (del == start) {
+ return NULL;
+ }
+
+ return start;
+}
+
+static void gnss_skip_param(char **saveptr)
+{
+ gnss_get_next_param(NULL, ",", saveptr);
+}
+
+/**
+ * Splits float parameters of the CGNSINF response on '.'
+ *
+ * @param src Null terminated string containing the float.
+ * @param f1 Resulting number part of the float.
+ * @param f2 Resulting fraction part of the float.
+ * @return 0 if parsing was successful. Otherwise <0 is returned.
+ *
+ * If the number part of the float is negative f1 and f2 will be
+ * negative too.
+ */
+static int gnss_split_on_dot(const char *src, int32_t *f1, int32_t *f2)
+{
+ char *dot = strchr(src, '.');
+
+ if (!dot) {
+ return -1;
+ }
+
+ *dot = '\0';
+
+ *f1 = (int32_t)strtol(src, NULL, 10);
+ *f2 = (int32_t)strtol(dot + 1, NULL, 10);
+
+ if (*f1 < 0) {
+ *f2 = -*f2;
+ }
+
+ return 0;
+}
+
+/**
+ * Parses cgnsinf response into the gnss_data structure.
+ *
+ * @param gps_buf Null terminated buffer containing the response.
+ * @return 0 on successful parse. Otherwise <0 is returned.
+ */
+static int parse_cgnsinf(char *gps_buf)
+{
+ char *saveptr;
+ int ret;
+ int32_t number, fraction;
+
+ char *run_status = gnss_get_next_param(gps_buf, ",", &saveptr);
+
+ if (run_status == NULL) {
+ goto error;
+ } else if (*run_status != '1') {
+ goto error;
+ }
+
+ char *fix_status = gnss_get_next_param(NULL, ",", &saveptr);
+
+ if (fix_status == NULL) {
+ goto error;
+ } else if (*fix_status != '1') {
+ goto error;
+ }
+
+ char *utc = gnss_get_next_param(NULL, ",", &saveptr);
+
+ if (utc == NULL) {
+ goto error;
+ }
+
+ char *lat = gnss_get_next_param(NULL, ",", &saveptr);
+
+ if (lat == NULL) {
+ goto error;
+ }
+
+ char *lon = gnss_get_next_param(NULL, ",", &saveptr);
+
+ if (lon == NULL) {
+ goto error;
+ }
+
+ char *alt = gnss_get_next_param(NULL, ",", &saveptr);
+ char *speed = gnss_get_next_param(NULL, ",", &saveptr);
+ char *course = gnss_get_next_param(NULL, ",", &saveptr);
+
+ /* discard fix mode and reserved*/
+ gnss_skip_param(&saveptr);
+ gnss_skip_param(&saveptr);
+
+ char *hdop = gnss_get_next_param(NULL, ",", &saveptr);
+
+ if (hdop == NULL) {
+ goto error;
+ }
+
+ gnss_data.run_status = 1;
+ gnss_data.fix_status = 1;
+
+ strncpy(gnss_data.utc, utc, sizeof(gnss_data.utc));
+
+ ret = gnss_split_on_dot(lat, &number, &fraction);
+ if (ret != 0) {
+ goto error;
+ }
+ gnss_data.lat = number * 10000000 + fraction * 10;
+
+ ret = gnss_split_on_dot(lon, &number, &fraction);
+ if (ret != 0) {
+ goto error;
+ }
+ gnss_data.lon = number * 10000000 + fraction * 10;
+
+ if (alt) {
+ ret = gnss_split_on_dot(alt, &number, &fraction);
+ if (ret != 0) {
+ goto error;
+ }
+ gnss_data.alt = number * 1000 + fraction;
+ } else {
+ gnss_data.alt = 0;
+ }
+
+ ret = gnss_split_on_dot(hdop, &number, &fraction);
+ if (ret != 0) {
+ goto error;
+ }
+ gnss_data.hdop = number * 100 + fraction * 10;
+
+ if (course) {
+ ret = gnss_split_on_dot(course, &number, &fraction);
+ if (ret != 0) {
+ goto error;
+ }
+ gnss_data.cog = number * 100 + fraction * 10;
+ } else {
+ gnss_data.cog = 0;
+ }
+
+ if (speed) {
+ ret = gnss_split_on_dot(speed, &number, &fraction);
+ if (ret != 0) {
+ goto error;
+ }
+ gnss_data.kmh = number * 10 + fraction / 10;
+ } else {
+ gnss_data.kmh = 0;
+ }
+
+ return 0;
+error:
+ memset(&gnss_data, 0, sizeof(gnss_data));
+ return -1;
+}
+
+/*
+ * Parses the +CGNSINF Gnss response.
+ *
+ * The CGNSINF command has the following parameters but
+ * not all parameters are set by the module:
+ *
+ * +CGNSINF: <GNSS run status>,<Fix status>,<UTC date & Time>,
+ * <Latitude>,<Longitude>,<MSL Altitude>,<Speed Over Ground>,
+ * <Course Over Ground>,<Fix Mode>,<Reserved1>,<HDOP>,<PDOP>,
+ * <VDOP>,<Reserved2>,<GNSS Satellites in View>,<Reserved3>,
+ * <HPA>,<VPA>
+ *
+ */
+MODEM_CMD_DEFINE(on_cmd_cgnsinf)
+{
+ char gps_buf[MDM_GNSS_PARSER_MAX_LEN];
+ size_t out_len = net_buf_linearize(gps_buf, sizeof(gps_buf) - 1, data->rx_buf, 0, len);
+
+ gps_buf[out_len] = '\0';
+ return parse_cgnsinf(gps_buf);
+}
+
+int mdm_sim7080_query_gnss(struct sim7080_gnss_data *data)
+{
+ int ret;
+ struct modem_cmd cmds[] = { MODEM_CMD("+CGNSINF: ", on_cmd_cgnsinf, 0U, NULL) };
+
+ if (get_state() != SIM7080_STATE_GNSS) {
+ LOG_ERR("GNSS functionality is not enabled!!");
+ return -1;
+ }
+
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, cmds, ARRAY_SIZE(cmds), "AT+CGNSINF",
+ &mdata.sem_response, K_SECONDS(2));
+ if (ret < 0) {
+ return ret;
+ }
+
+ if (!gnss_data.run_status || !gnss_data.fix_status) {
+ return -EAGAIN;
+ }
+
+ if (data) {
+ memcpy(data, &gnss_data, sizeof(gnss_data));
+ }
+
+ memset(&gnss_data, 0, sizeof(gnss_data));
+
+ return ret;
+}
+
+int mdm_sim7080_start_gnss(void)
+{
+ int ret;
+
+ change_state(SIM7080_STATE_INIT);
+ k_work_cancel_delayable(&mdata.rssi_query_work);
+
+ ret = modem_autobaud();
+ if (ret < 0) {
+ LOG_ERR("Failed to start modem!!");
+ return -1;
+ }
+
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, "AT+CGNSCOLD",
+ &mdata.sem_response, K_SECONDS(2));
+ if (ret < 0) {
+ return -1;
+ }
+
+ change_state(SIM7080_STATE_GNSS);
+ return 0;
+}
+
+/**
+ * Parse the +FTPGET response.
+ *
+ * +FTPGET: <mode>,<len>
+ *
+ * Mode is hard set to 2.
+ *
+ * Length is the number of bytes following (the ftp data).
+ */
+MODEM_CMD_DEFINE(on_cmd_ftpget)
+{
+ int nbytes = atoi(argv[0]);
+ int bytes_to_skip;
+ size_t out_len;
+
+ if (nbytes == 0) {
+ mdata.ftp.nread = 0;
+ return 0;
+ }
+
+ /* Skip length parameter and trailing \r\n */
+ bytes_to_skip = strlen(argv[0]) + 2;
+
+ /* Wait until data is ready.
+ * >= to ensure buffer is not empty after skip.
+ */
+ if (net_buf_frags_len(data->rx_buf) <= nbytes + bytes_to_skip) {
+ return -EAGAIN;
+ }
+
+ out_len = net_buf_linearize(mdata.ftp.read_buffer, mdata.ftp.nread, data->rx_buf,
+ bytes_to_skip, nbytes);
+ if (out_len != nbytes) {
+ LOG_WRN("FTP read size differs!");
+ }
+ data->rx_buf = net_buf_skip(data->rx_buf, nbytes + bytes_to_skip);
+
+ mdata.ftp.nread = nbytes;
+
+ return 0;
+}
+
+int mdm_sim7080_ftp_get_read(char *dst, size_t *size)
+{
+ int ret;
+ char buffer[sizeof("AT+FTPGET=#,######")];
+ struct modem_cmd cmds[] = { MODEM_CMD("+FTPGET: 2,", on_cmd_ftpget, 1U, "") };
+
+ /* Some error occurred. */
+ if (mdata.ftp.state == SIM7080_FTP_CONNECTION_STATE_ERROR ||
+ mdata.ftp.state == SIM7080_FTP_CONNECTION_STATE_INITIAL) {
+ return SIM7080_FTP_RC_ERROR;
+ }
+
+ /* Setup buffer. */
+ mdata.ftp.read_buffer = dst;
+ mdata.ftp.nread = *size;
+
+ /* Read ftp data. */
+ ret = snprintk(buffer, sizeof(buffer), "AT+FTPGET=2,%zu", *size);
+ if (ret < 0) {
+ *size = 0;
+ return SIM7080_FTP_RC_ERROR;
+ }
+
+ /* Wait for data from the server. */
+ k_sem_take(&mdata.sem_ftp, K_MSEC(200));
+
+ if (mdata.ftp.state == SIM7080_FTP_CONNECTION_STATE_FINISHED) {
+ *size = 0;
+ return SIM7080_FTP_RC_FINISHED;
+ } else if (mdata.ftp.state == SIM7080_FTP_CONNECTION_STATE_ERROR) {
+ *size = 0;
+ return SIM7080_FTP_RC_ERROR;
+ }
+
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, cmds, ARRAY_SIZE(cmds), buffer,
+ &mdata.sem_response, MDM_CMD_TIMEOUT);
+ if (ret < 0) {
+ *size = 0;
+ return SIM7080_FTP_RC_ERROR;
+ }
+
+ /* Set read size. */
+ *size = mdata.ftp.nread;
+
+ return SIM7080_FTP_RC_OK;
+}
+
+int mdm_sim7080_ftp_get_start(const char *server, const char *user, const char *passwd,
+ const char *file, const char *path)
+{
+ int ret;
+ char buffer[256];
+
+ /* Start network. */
+ ret = mdm_sim7080_start_network();
+ if (ret < 0) {
+ LOG_ERR("Failed to start network for FTP!");
+ return -1;
+ }
+
+ /* Set connection id for ftp. */
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, "AT+FTPCID=0",
+ &mdata.sem_response, MDM_CMD_TIMEOUT);
+ if (ret < 0) {
+ LOG_WRN("Failed to set FTP Cid!");
+ return -1;
+ }
+
+ /* Set ftp server. */
+ ret = snprintk(buffer, sizeof(buffer), "AT+FTPSERV=\"%s\"", server);
+ if (ret < 0) {
+ LOG_WRN("Failed to build command!");
+ return -1;
+ }
+
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, buffer, &mdata.sem_response,
+ MDM_CMD_TIMEOUT);
+ if (ret < 0) {
+ LOG_WRN("Failed to set FTP Cid!");
+ return -1;
+ }
+
+ /* Set ftp user. */
+ ret = snprintk(buffer, sizeof(buffer), "AT+FTPUN=\"%s\"", user);
+ if (ret < 0) {
+ LOG_WRN("Failed to build command!");
+ return -1;
+ }
+
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, buffer, &mdata.sem_response,
+ MDM_CMD_TIMEOUT);
+ if (ret < 0) {
+ LOG_WRN("Failed to set ftp user!");
+ return -1;
+ }
+
+ /* Set ftp password. */
+ ret = snprintk(buffer, sizeof(buffer), "AT+FTPPW=\"%s\"", passwd);
+ if (ret < 0) {
+ LOG_WRN("Failed to build command!");
+ return -1;
+ }
+
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, buffer, &mdata.sem_response,
+ MDM_CMD_TIMEOUT);
+ if (ret < 0) {
+ LOG_WRN("Failed to set ftp password!");
+ return -1;
+ }
+
+ /* Set ftp filename. */
+ ret = snprintk(buffer, sizeof(buffer), "AT+FTPGETNAME=\"%s\"", file);
+ if (ret < 0) {
+ LOG_WRN("Failed to build command!");
+ return -1;
+ }
+
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, buffer, &mdata.sem_response,
+ MDM_CMD_TIMEOUT);
+ if (ret < 0) {
+ LOG_WRN("Failed to set ftp filename!");
+ return -1;
+ }
+
+ /* Set ftp filename. */
+ ret = snprintk(buffer, sizeof(buffer), "AT+FTPGETNAME=\"%s\"", file);
+ if (ret < 0) {
+ LOG_WRN("Failed to build command!");
+ return -1;
+ }
+
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, buffer, &mdata.sem_response,
+ MDM_CMD_TIMEOUT);
+ if (ret < 0) {
+ LOG_WRN("Failed to set ftp filename!");
+ return -1;
+ }
+
+ /* Set ftp path. */
+ ret = snprintk(buffer, sizeof(buffer), "AT+FTPGETPATH=\"%s\"", path);
+ if (ret < 0) {
+ LOG_WRN("Failed to build command!");
+ return -1;
+ }
+
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, buffer, &mdata.sem_response,
+ MDM_CMD_TIMEOUT);
+ if (ret < 0) {
+ LOG_WRN("Failed to set ftp path!");
+ return -1;
+ }
+
+ /* Initialize ftp variables. */
+ mdata.ftp.read_buffer = NULL;
+ mdata.ftp.nread = 0;
+ mdata.ftp.state = SIM7080_FTP_CONNECTION_STATE_INITIAL;
+
+ /* Start the ftp session. */
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, "AT+FTPGET=1",
+ &mdata.sem_ftp, MDM_CMD_TIMEOUT);
+ if (ret < 0) {
+ LOG_WRN("Failed to start session!");
+ return -1;
+ }
+
+ if (mdata.ftp.state != SIM7080_FTP_CONNECTION_STATE_CONNECTED) {
+ LOG_WRN("Session state is not connected!");
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Decode readable hex to "real" hex.
+ */
+static uint8_t mdm_pdu_decode_ascii(char byte)
+{
+ if ((byte >= '0') && (byte <= '9'))
+ return byte - '0';
+ else if ((byte >= 'A') && (byte <= 'F'))
+ return byte - 'A' + 10;
+ else if ((byte >= 'a') && (byte <= 'f'))
+ return byte - 'a' + 10;
+ else
+ return 255;
+}
+
+/**
+ * Reads "byte" from pdu.
+ *
+ * @param pdu pdu to read from.
+ * @param index index of "byte".
+ *
+ * Sim module "encodes" one pdu byte as two human readable bytes
+ * this functions squashes these two bytes into one.
+ */
+static uint8_t mdm_pdu_read_byte(const char *pdu, size_t index)
+{
+ return (mdm_pdu_decode_ascii(pdu[index * 2]) << 4 |
+ mdm_pdu_decode_ascii(pdu[index * 2 + 1]));
+}
+
+/**
+ * Decodes time from pdu.
+ *
+ * @param pdu pdu to read from.
+ * @param index index of "byte".
+ */
+static uint8_t mdm_pdu_read_time(const char *pdu, size_t index)
+{
+ return (mdm_pdu_decode_ascii(pdu[index * 2]) +
+ mdm_pdu_decode_ascii(pdu[index * 2 + 1]) * 10);
+}
+
+/**
+ * Decode a sms from pdu mode.
+ */
+static int mdm_decode_pdu(const char *pdu, size_t pdu_len, struct sim7080_sms *target_buf)
+{
+ size_t index;
+
+ /*
+ * GSM_03.38 to Unicode conversion table
+ */
+ const short enc7_basic[128] = {
+ '@', 0xA3, '$', 0xA5, 0xE8, 0xE9, 0xF9, 0xEC, 0xF2, 0xE7,
+ '\n', 0xD8, 0xF8, '\r', 0xC5, 0xF8, 0x0394, '_', 0x03A6, 0x0393,
+ 0x039B, 0x03A9, 0x03A0, 0x03A8, 0x03A3, 0x0398, 0x039E, '\x1b', 0xC6, 0xE6,
+ 0xDF, 0xC9, ' ', '!', '\"', '#', 0xA4, '%', '&', '\'',
+ '(', ')', '*', '+', ',', '-', '.', '/', '0', '1',
+ '2', '3', '4', '5', '6', '7', '8', '9', ':', ';',
+ '<', '=', '>', '?', 0xA1, 'A', 'B', 'C', 'D', 'E',
+ 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
+ 'Z', 0xC4, 0xD6, 0xD1, 0xDC, 0xA7, 0xBF, 'a', 'b', 'c',
+ 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+ 'x', 'y', 'z', 0xE4, 0xF6, 0xF1, 0xFC, 0xE0
+ };
+
+ /* two bytes in pdu are on real byte */
+ pdu_len = (pdu_len / 2);
+
+ /* first byte of pdu is length of trailing SMSC information
+ * skip it by setting index to SMSC length + 1.
+ */
+ index = mdm_pdu_read_byte(pdu, 0) + 1;
+
+ if (index >= pdu_len) {
+ return -1;
+ }
+
+ /* read first octet */
+ target_buf->first_octet = mdm_pdu_read_byte(pdu, index++);
+
+ if (index >= pdu_len) {
+ return -1;
+ }
+
+ /* pdu_index now points to the address field.
+ * first byte of addr field is the addr length -> skip it.
+ * address type is not included in addr len -> add +1.
+ * address is coded in semi octets
+ * + addr_len/2 if even
+ * + addr_len/2 + 1 if odd
+ */
+ uint8_t addr_len = mdm_pdu_read_byte(pdu, index);
+
+ index += ((addr_len % 2) == 0) ? (addr_len / 2) + 2 : (addr_len / 2) + 3;
+
+ if (index >= pdu_len) {
+ return -1;
+ }
+
+ /* read protocol idenifier */
+ target_buf->tp_pid = mdm_pdu_read_byte(pdu, index++);
+
+ if (index >= pdu_len) {
+ return -1;
+ }
+
+ /* read coding scheme */
+ uint8_t tp_dcs = mdm_pdu_read_byte(pdu, index++);
+
+ /* parse date and time */
+ if ((index + 7) >= pdu_len) {
+ return -1;
+ }
+
+ target_buf->time.year = mdm_pdu_read_time(pdu, index++);
+ target_buf->time.month = mdm_pdu_read_time(pdu, index++);
+ target_buf->time.day = mdm_pdu_read_time(pdu, index++);
+ target_buf->time.hour = mdm_pdu_read_time(pdu, index++);
+ target_buf->time.minute = mdm_pdu_read_time(pdu, index++);
+ target_buf->time.second = mdm_pdu_read_time(pdu, index++);
+ target_buf->time.timezone = mdm_pdu_read_time(pdu, index++);
+
+ /* Read user data length */
+ uint8_t tp_udl = mdm_pdu_read_byte(pdu, index++);
+
+ /* Discard header */
+ uint8_t header_skip = 0;
+
+ if (target_buf->first_octet & SMS_TP_UDHI_HEADER) {
+ uint8_t tp_udhl = mdm_pdu_read_byte(pdu, index);
+
+ index += tp_udhl + 1;
+ header_skip = tp_udhl + 1;
+
+ if (index >= pdu_len) {
+ return -1;
+ }
+ }
+
+ /* Read data according to type set in TP-DCS */
+ if (tp_dcs == 0x00) {
+ /* 7 bit GSM coding */
+ uint8_t fill_level = 0;
+ uint16_t buf = 0;
+
+ if (target_buf->first_octet & SMS_TP_UDHI_HEADER) {
+ /* Initial fill because septets are aligned to
+ * septet boundary after header
+ */
+ uint8_t fill_bits = 7 - ((header_skip * 8) % 7);
+
+ if (fill_bits == 7) {
+ fill_bits = 0;
+ }
+
+ buf = mdm_pdu_read_byte(pdu, index++);
+
+ fill_level = 8 - fill_bits;
+ }
+
+ uint16_t data_index = 0;
+
+ for (unsigned int idx = 0; idx < tp_udl; idx++) {
+ if (fill_level < 7) {
+ uint8_t octet = mdm_pdu_read_byte(pdu, index++);
+
+ buf &= ((1 << fill_level) - 1);
+ buf |= (octet << fill_level);
+ fill_level += 8;
+ }
+
+ /*
+ * Convert 7-bit encoded data to Unicode and
+ * then to UTF-8
+ */
+ short letter = enc7_basic[buf & 0x007f];
+
+ if (letter < 0x0080) {
+ target_buf->data[data_index++] = letter & 0x007f;
+ } else if (letter < 0x0800) {
+ target_buf->data[data_index++] = 0xc0 | ((letter & 0x07c0) >> 6);
+ target_buf->data[data_index++] = 0x80 | ((letter & 0x003f) >> 0);
+ }
+ buf >>= 7;
+ fill_level -= 7;
+ }
+ target_buf->data_len = data_index;
+ } else if (tp_dcs == 0x04) {
+ /* 8 bit binary coding */
+ for (int idx = 0; idx < tp_udl - header_skip; idx++) {
+ target_buf->data[idx] = mdm_pdu_read_byte(pdu, index++);
+ }
+ target_buf->data_len = tp_udl;
+ } else if (tp_dcs == 0x08) {
+ /* Unicode (16 bit per character) */
+ for (int idx = 0; idx < tp_udl - header_skip; idx++) {
+ target_buf->data[idx] = mdm_pdu_read_byte(pdu, index++);
+ }
+ target_buf->data_len = tp_udl;
+ } else {
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Check if given char sequence is crlf.
+ *
+ * @param c The char sequence.
+ * @param len Total length of the fragment.
+ * @return @c true if char sequence is crlf.
+ * Otherwise @c false is returned.
+ */
+static bool is_crlf(uint8_t *c, uint8_t len)
+{
+ /* crlf does not fit. */
+ if (len < 2) {
+ return false;
+ }
+
+ return c[0] == '\r' && c[1] == '\n';
+}
+
+/**
+ * Find terminating crlf in a netbuffer.
+ *
+ * @param buf The netbuffer.
+ * @param skip Bytes to skip before search.
+ * @return Length of the returned fragment or 0 if not found.
+ */
+static size_t net_buf_find_crlf(struct net_buf *buf, size_t skip)
+{
+ size_t len = 0, pos = 0;
+ struct net_buf *frag = buf;
+
+ /* Skip to the start. */
+ while (frag && skip >= frag->len) {
+ skip -= frag->len;
+ frag = frag->frags;
+ }
+
+ /* Need to wait for more data. */
+ if (!frag) {
+ return 0;
+ }
+
+ pos = skip;
+
+ while (frag && !is_crlf(frag->data + pos, frag->len - pos)) {
+ if (pos + 1 >= frag->len) {
+ len += frag->len;
+ frag = frag->frags;
+ pos = 0U;
+ } else {
+ pos++;
+ }
+ }
+
+ if (frag && is_crlf(frag->data + pos, frag->len - pos)) {
+ len += pos;
+ return len - skip;
+ }
+
+ return 0;
+}
+
+/**
+ * Parses list sms and add them to buffer.
+ * Format is:
+ *
+ * +CMGL: <index>,<stat>,,<length><CR><LF><pdu><CR><LF>
+ * +CMGL: <index>,<stat>,,<length><CR><LF><pdu><CR><LF>
+ * ...
+ * OK
+ */
+MODEM_CMD_DEFINE(on_cmd_cmgl)
+{
+ int sms_index, sms_stat, ret;
+ char pdu_buffer[256];
+ size_t out_len, sms_len, param_len;
+ struct sim7080_sms *sms;
+
+ sms_index = atoi(argv[0]);
+ sms_stat = atoi(argv[1]);
+
+ /* Get the length of the "length" parameter.
+ * The last parameter will be stuck in the netbuffer.
+ * It is not the actual length of the trailing pdu so
+ * we have to search the next crlf.
+ */
+ param_len = net_buf_find_crlf(data->rx_buf, 0);
+ if (param_len == 0) {
+ LOG_INF("No <CR><LF>");
+ return -EAGAIN;
+ }
+
+ /* Get actual trailing pdu len. +2 to skip crlf. */
+ sms_len = net_buf_find_crlf(data->rx_buf, param_len + 2);
+ if (sms_len == 0) {
+ return -EAGAIN;
+ }
+
+ /* Skip to start of pdu. */
+ data->rx_buf = net_buf_skip(data->rx_buf, param_len + 2);
+
+ out_len = net_buf_linearize(pdu_buffer, sizeof(pdu_buffer) - 1, data->rx_buf, 0, sms_len);
+ pdu_buffer[out_len] = '\0';
+
+ data->rx_buf = net_buf_skip(data->rx_buf, sms_len);
+
+ /* No buffer specified. */
+ if (!mdata.sms_buffer) {
+ return 0;
+ }
+
+ /* No space left in buffer. */
+ if (mdata.sms_buffer_pos >= mdata.sms_buffer->nsms) {
+ return 0;
+ }
+
+ sms = &mdata.sms_buffer->sms[mdata.sms_buffer_pos];
+
+ ret = mdm_decode_pdu(pdu_buffer, out_len, sms);
+ if (ret < 0) {
+ return 0;
+ }
+
+ sms->stat = sms_stat;
+ sms->index = sms_index;
+ sms->data[sms->data_len] = '\0';
+
+ mdata.sms_buffer_pos++;
+
+ return 0;
+}
+
+int mdm_sim7080_read_sms(struct sim7080_sms_buffer *buffer)
+{
+ int ret;
+ struct modem_cmd cmds[] = { MODEM_CMD("+CMGL: ", on_cmd_cmgl, 4U, ",\r") };
+
+ mdata.sms_buffer = buffer;
+ mdata.sms_buffer_pos = 0;
+
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, cmds, ARRAY_SIZE(cmds), "AT+CMGL=4",
+ &mdata.sem_response, K_SECONDS(20));
+ if (ret < 0) {
+ return -1;
+ }
+
+ return mdata.sms_buffer_pos;
+}
+
+int mdm_sim7080_delete_sms(uint16_t index)
+{
+ int ret;
+ char buf[sizeof("AT+CMGD=#####")] = { 0 };
+
+ ret = snprintk(buf, sizeof(buf), "AT+CMGD=%u", index);
+ if (ret < 0) {
+ return -1;
+ }
+
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0, buf, &mdata.sem_response,
+ K_SECONDS(5));
+ if (ret < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Does the modem setup by starting it and
+ * bringing the modem to a PDP active state.
+ */
+static int modem_setup(void)
+{
+ int ret = 0;
+ int counter = 0;
+
+ k_work_cancel_delayable(&mdata.rssi_query_work);
+
+ ret = modem_autobaud();
+ if (ret < 0) {
+ LOG_ERR("Booting modem failed!!");
+ goto error;
+ }
+
+ ret = modem_cmd_handler_setup_cmds(&mctx.iface, &mctx.cmd_handler, setup_cmds,
+ ARRAY_SIZE(setup_cmds), &mdata.sem_response,
+ MDM_REGISTRATION_TIMEOUT);
+ if (ret < 0) {
+ LOG_ERR("Failed to send init commands!");
+ goto error;
+ }
+
+ k_sleep(K_SECONDS(3));
+
+ /* Wait for acceptable rssi values. */
+ modem_rssi_query_work(NULL);
+ k_sleep(MDM_WAIT_FOR_RSSI_DELAY);
+
+ counter = 0;
+ while (counter++ < MDM_WAIT_FOR_RSSI_COUNT &&
+ (mdata.mdm_rssi >= 0 || mdata.mdm_rssi <= -1000)) {
+ modem_rssi_query_work(NULL);
+ k_sleep(MDM_WAIT_FOR_RSSI_DELAY);
+ }
+
+ if (mdata.mdm_rssi >= 0 || mdata.mdm_rssi <= -1000) {
+ LOG_ERR("Network not reachable!!");
+ ret = -ENETUNREACH;
+ goto error;
+ }
+
+ ret = modem_pdp_activate();
+ if (ret < 0) {
+ goto error;
+ }
+
+ k_work_reschedule_for_queue(&modem_workq, &mdata.rssi_query_work,
+ K_SECONDS(RSSI_TIMEOUT_SECS));
+
+ change_state(SIM7080_STATE_NETWORKING);
+
+error:
+ return ret;
+}
+
+int mdm_sim7080_start_network(void)
+{
+ change_state(SIM7080_STATE_INIT);
+ return modem_setup();
+}
+
+int mdm_sim7080_power_on(void)
+{
+ return modem_autobaud();
+}
+
+int mdm_sim7080_power_off(void)
+{
+ int tries = 5;
+ int autobaud_tries;
+ int ret = 0;
+
+ k_work_cancel_delayable(&mdata.rssi_query_work);
+
+ /* Check if module is already off. */
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, "AT", &mdata.sem_response,
+ K_MSEC(1000));
+ if (ret < 0) {
+ change_state(SIM7080_STATE_OFF);
+ return 0;
+ }
+
+ while (tries--) {
+ modem_pwrkey();
+
+ autobaud_tries = 5;
+
+ while (autobaud_tries--) {
+ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, "AT",
+ &mdata.sem_response, K_MSEC(500));
+ if (ret == 0) {
+ break;
+ }
+ }
+
+ if (ret < 0) {
+ change_state(SIM7080_STATE_OFF);
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+const char *mdm_sim7080_get_manufacturer(void)
+{
+ return mdata.mdm_manufacturer;
+}
+
+const char *mdm_sim7080_get_model(void)
+{
+ return mdata.mdm_model;
+}
+
+const char *mdm_sim7080_get_revision(void)
+{
+ return mdata.mdm_revision;
+}
+
+const char *mdm_sim7080_get_imei(void)
+{
+ return mdata.mdm_imei;
+}
+
+/*
+ * Initializes modem handlers and context.
+ * After successful init this function calls
+ * modem_setup.
+ */
+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_dns, 0, 1);
+ k_sem_init(&mdata.sem_ftp, 0, 1);
+ k_work_queue_start(&modem_workq, modem_workq_stack,
+ K_KERNEL_STACK_SIZEOF(modem_workq_stack), K_PRIO_COOP(7), NULL);
+
+ /* Assume the modem is not registered to the network. */
+ mdata.mdm_registration = 0;
+ mdata.cpin_ready = false;
+ mdata.pdp_active = false;
+
+ mdata.sms_buffer = NULL;
+ mdata.sms_buffer_pos = 0;
+
+ /* 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;
+ }
+
+ change_state(SIM7080_STATE_INIT);
+
+ /* Command 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] = unsolicited_cmds;
+ mdata.cmd_handler_data.cmds_len[CMD_UNSOL] = ARRAY_SIZE(unsolicited_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;
+ }
+
+ /* Uart handler. */
+ 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;
+ }
+
+ mdata.current_sock_fd = -1;
+ mdata.current_sock_written = 0;
+
+ mdata.ftp.read_buffer = NULL;
+ mdata.ftp.nread = 0;
+ mdata.ftp.state = SIM7080_FTP_CONNECTION_STATE_INITIAL;
+
+ /* 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) */
+ mctx.data_rssi = &mdata.mdm_rssi;
+
+ mctx.pins = modem_pins;
+ mctx.pins_len = ARRAY_SIZE(modem_pins);
+ mctx.driver_data = &mdata;
+
+ memset(&gnss_data, 0, sizeof(gnss_data));
+
+ ret = modem_context_register(&mctx);
+ if (ret < 0) {
+ LOG_ERR("Error registering modem context: %d", ret);
+ goto error;
+ }
+
+ 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 device with the networking stack. */
+NET_DEVICE_DT_INST_OFFLOAD_DEFINE(0, modem_init, NULL, &mdata, NULL,
+ CONFIG_MODEM_SIMCOM_SIM7080_INIT_PRIORITY, &api_funcs,
+ MDM_MAX_DATA_LENGTH);
+
+NET_SOCKET_REGISTER(simcom_sim7080, MDM_SOCKET_PRIO, AF_UNSPEC, offload_is_supported,
+ offload_socket);