diff --git a/CODEOWNERS b/CODEOWNERS index 91d4007b17..45c00ddffa 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -295,7 +295,10 @@ /drivers/misc/ @tejlmand /drivers/misc/ft8xx/ @hubertmis /drivers/modem/hl7800.c @LairdCP/zephyr +/drivers/modem/simcom-sim7080.c @lgehreke +/drivers/modem/simcom-sim7080.h @lgehreke /drivers/modem/Kconfig.hl7800 @LairdCP/zephyr +/drivers/modem/Kconfig.simcom-sim7080 @lgehreke /drivers/pcie/ @dcpleung @nashif @jhedberg /drivers/peci/ @albertofloyd @franciscomunoz @scottwcpg /drivers/pinctrl/ @gmarull diff --git a/drivers/modem/CMakeLists.txt b/drivers/modem/CMakeLists.txt index 83e2dd88bd..a83890fc21 100644 --- a/drivers/modem/CMakeLists.txt +++ b/drivers/modem/CMakeLists.txt @@ -38,3 +38,8 @@ if (CONFIG_MODEM_HL7800) zephyr_library_include_directories(${ZEPHYR_BASE}/subsys/net/ip) zephyr_library_sources(hl7800.c) endif() + +if (CONFIG_MODEM_SIM7080) + zephyr_library_include_directories(${ZEPHYR_BASE}/subsys/net/ip) + zephyr_library_sources(simcom-sim7080.c) +endif() diff --git a/drivers/modem/Kconfig b/drivers/modem/Kconfig index 1a851d64f4..5c7272ef8a 100644 --- a/drivers/modem/Kconfig +++ b/drivers/modem/Kconfig @@ -145,5 +145,6 @@ source "drivers/modem/Kconfig.wncm14a2a" source "drivers/modem/Kconfig.gsm" source "drivers/modem/Kconfig.hl7800" +source "drivers/modem/Kconfig.simcom-sim7080" endif # MODEM diff --git a/drivers/modem/Kconfig.simcom-sim7080 b/drivers/modem/Kconfig.simcom-sim7080 new file mode 100644 index 0000000000..d70aa756b0 --- /dev/null +++ b/drivers/modem/Kconfig.simcom-sim7080 @@ -0,0 +1,73 @@ +# Simcom sim7080 driver options + +# Copyright (C) 2021 metraTec GmbH +# SPDX-License-Identifier: Apache-2.0 + +config MODEM_SIM7080 + bool "Sim7080 Driver" + select MODEM_CONTEXT + select MODEM_CMD_HANDLER + select MODEM_IFACE_UART + select MODEM_SOCKET + select NET_OFFLOAD + select NET_SOCKETS_OFFLOAD + imply GPIO + help + Enables the driver for the Sim7080 modem. + +if MODEM_SIM7080 + +config MODEM_SIMCOM_SIM7080_RX_STACK_SIZE + int "Stack size for the simcom sim7080 modem driver rx thread" + default 1028 + help + This stack is used by the simcom SIM7080 RX thread. + +config MODEM_SIMCOM_SIM7080_RX_WORKQ_STACK_SIZE + int "Stack size for the simcom sim7080 modem driver work queue" + default 2048 + help + This stack is used by the work queue. + +config MODEM_SIMCOM_SIM7080_INIT_PRIORITY + int "simcom sim7080 driver init priority" + default 80 + help + simcom sim7080 druver initialization priority. + +config MODEM_SIMCOM_SIM7080_LTE_BANDS + string "LTE bands the driver can use" + default "8,20,28" + help + Comma separated list of usable lte bands. + +config MODEM_SIMCOM_SIM7080_APN + string "APN for establishing a network connection" + default "internet" + help + This setting is used to set the APN name for the network connection + context. This value is specific to the network provider and may + need to be changed. + +choice MODEM_SIMCOM_SIM7080_RAT + bool "Radio Access Technology Mode" + default MODEM_SIMCOM_SIM7080_RAT_NB1 + +config MODEM_SIMCOM_SIM7080_RAT_NB1 + bool "NB-IoT" + help + Enable LTE NB-IoT mode. + +config MODEM_SIMCOM_SIM7080_RAT_M1 + bool "Cat-M1" + help + Enable Cat-M1 mode. + +config MODEM_SIMCOM_SIM7080_RAT_GSM + bool "GSM" + help + Enable GSM mode. + +endchoice + +endif # MODEM_SIM7080 diff --git a/drivers/modem/simcom-sim7080.c b/drivers/modem/simcom-sim7080.c new file mode 100644 index 0000000000..dc9c4e5a26 --- /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 +LOG_MODULE_REGISTER(modem_simcom_sim7080, CONFIG_MODEM_LOG_LEVEL); + +#include +#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=,\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: ,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,,[,] + * + * Response on failure: + * +CDNSGIP: 0, + */ +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: ,. + * 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: + * 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 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 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: . + */ +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: 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: ,,, + * ,,,, + * ,,,,, + * ,,,, + * , + * + */ +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 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: ,,, + * +CMGL: ,,, + * ... + * 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 "); + 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); diff --git a/drivers/modem/simcom-sim7080.h b/drivers/modem/simcom-sim7080.h new file mode 100644 index 0000000000..9627e6e34f --- /dev/null +++ b/drivers/modem/simcom-sim7080.h @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2021 metraTec GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef SIMCOM_SIM7080_H +#define SIMCOM_SIM7080_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "modem_context.h" +#include "modem_cmd_handler.h" +#include "modem_iface_uart.h" +#include "modem_socket.h" + +#define MDM_UART_DEV DEVICE_DT_GET(DT_INST_BUS(0)) +#define MDM_MAX_DATA_LENGTH 1024 +#define MDM_RECV_BUF_SIZE 1024 +#define MDM_MAX_SOCKETS 5 +#define MDM_BASE_SOCKET_NUM 0 +#define MDM_RECV_MAX_BUF 30 +#define BUF_ALLOC_TIMEOUT K_SECONDS(1) +#define MDM_CMD_TIMEOUT K_SECONDS(10) +#define MDM_REGISTRATION_TIMEOUT K_SECONDS(180) +#define MDM_CONNECT_TIMEOUT K_SECONDS(90) +#define MDM_PDP_TIMEOUT K_SECONDS(120) +#define MDM_DNS_TIMEOUT K_SECONDS(210) +#define MDM_WAIT_FOR_RSSI_DELAY K_SECONDS(2) +#define MDM_WAIT_FOR_RSSI_COUNT 30 +#define MDM_MAX_AUTOBAUD 5 +#define MDM_MAX_CEREG_WAITS 40 +#define MDM_MAX_CGATT_WAITS 40 +#define MDM_BOOT_TRIES 4 +#define MDM_GNSS_PARSER_MAX_LEN 128 +#define MDM_APN CONFIG_MODEM_SIMCOM_SIM7080_APN +#define MDM_LTE_BANDS CONFIG_MODEM_SIMCOM_SIM7080_LTE_BANDS +#define RSSI_TIMEOUT_SECS 30 +#define MDM_SOCKET_PRIO 40 + +/* + * Default length of modem data. + */ +#define MDM_MANUFACTURER_LENGTH 12 +#define MDM_MODEL_LENGTH 16 +#define MDM_REVISION_LENGTH 64 +#define MDM_IMEI_LENGTH 16 +#define MDM_IMSI_LENGTH 16 +#define MDM_ICCID_LENGTH 32 + +enum sim7080_state { + SIM7080_STATE_INIT = 0, + SIM7080_STATE_NETWORKING, + SIM7080_STATE_GNSS, + SIM7080_STATE_OFF, +}; + +/* Possible states of the ftp connection. */ +enum sim7080_ftp_connection_state { + /* Not connected yet. */ + SIM7080_FTP_CONNECTION_STATE_INITIAL = 0, + /* Connected and still data available. */ + SIM7080_FTP_CONNECTION_STATE_CONNECTED, + /* All data transferred. */ + SIM7080_FTP_CONNECTION_STATE_FINISHED, + /* Something went wrong. */ + SIM7080_FTP_CONNECTION_STATE_ERROR, +}; + +/* + * Driver data. + */ +struct sim7080_data { + /* + * Network interface of the sim module. + */ + struct net_if *netif; + uint8_t mac_addr[6]; + /* + * Uart interface of the modem. + */ + struct modem_iface_uart_data iface_data; + uint8_t iface_rb_buf[MDM_MAX_DATA_LENGTH]; + /* + * Modem command handler. + */ + struct modem_cmd_handler_data cmd_handler_data; + uint8_t cmd_match_buf[MDM_RECV_BUF_SIZE + 1]; + /* + * Modem socket data. + */ + struct modem_socket_config socket_config; + struct modem_socket sockets[MDM_MAX_SOCKETS]; + /* + * Current state of the modem. + */ + enum sim7080_state state; + /* + * RSSI work + */ + struct k_work_delayable rssi_query_work; + /* + * Information over the modem. + */ + char mdm_manufacturer[MDM_MANUFACTURER_LENGTH]; + char mdm_model[MDM_MODEL_LENGTH]; + char mdm_revision[MDM_REVISION_LENGTH]; + char mdm_imei[MDM_IMEI_LENGTH]; +#if defined(CONFIG_MODEM_SIM_NUMBERS) + char mdm_imsi[MDM_IMSI_LENGTH]; + char mdm_iccid[MDM_ICCID_LENGTH]; +#endif /* #if defined(CONFIG_MODEM_SIM_NUMBERS) */ + int mdm_rssi; + /* + * Current operating socket and statistics. + */ + int current_sock_fd; + int current_sock_written; + /* + * Network registration of the modem. + */ + uint8_t mdm_registration; + /* + * Whether gprs is attached or detached. + */ + uint8_t mdm_cgatt; + /* + * If the sim card is ready or not. + */ + bool cpin_ready; + /* + * Flag if the PDP context is active. + */ + bool pdp_active; + /* SMS buffer structure provided by read. */ + struct sim7080_sms_buffer *sms_buffer; + /* Position in the sms buffer. */ + uint8_t sms_buffer_pos; + /* Ftp related variables. */ + struct { + /* User buffer for ftp data. */ + char *read_buffer; + /* Length of the read buffer/number of bytes read. */ + size_t nread; + /* State of the ftp connection. */ + enum sim7080_ftp_connection_state state; + } ftp; + /* + * Semaphore(s). + */ + struct k_sem sem_response; + struct k_sem sem_tx_ready; + struct k_sem sem_dns; + struct k_sem sem_ftp; +}; + +/* + * Pin definitions + */ +static struct modem_pin modem_pins[] = { MODEM_PIN( + DT_INST_GPIO_LABEL(0, mdm_power_gpios), DT_INST_GPIO_PIN(0, mdm_power_gpios), + DT_INST_GPIO_FLAGS(0, mdm_power_gpios) | GPIO_OUTPUT_LOW) }; + +/* + * Socket read callback data. + */ +struct socket_read_data { + char *recv_buf; + size_t recv_buf_len; + struct sockaddr *recv_addr; + uint16_t recv_read_len; +}; + +#endif /* SIMCOM_SIM7080_H */ diff --git a/dts/bindings/modem/simcom,sim7080.yaml b/dts/bindings/modem/simcom,sim7080.yaml new file mode 100644 index 0000000000..f66cee254c --- /dev/null +++ b/dts/bindings/modem/simcom,sim7080.yaml @@ -0,0 +1,16 @@ +# Copyright (C) 2021 metraTec GmbH +# SPDX-License-Identifier: Apache-2.0 + +description: Simcom Sim7080 modem + +compatible: "simcom,sim7080" + +include: uart-device.yaml + +properties: + label: + required: true + + mdm-power-gpios: + type: phandle-array + required: true diff --git a/dts/bindings/vendor-prefixes.txt b/dts/bindings/vendor-prefixes.txt index 352177db29..9d0cf7b29a 100644 --- a/dts/bindings/vendor-prefixes.txt +++ b/dts/bindings/vendor-prefixes.txt @@ -540,6 +540,7 @@ silex-insight Silex Insight siliconfile Siliconfile Technologies lnc. siliconmitus Silicon Mitus, Inc. siemens Siemens AG +simcom SIMCom Wireless Solutions Co., LTD simtek Cypress Semiconductor Corporation (Simtek Corporation) sinlinx Sinlinx Electronics Technology Co., LTD sinovoip SinoVoip Co., Ltd diff --git a/include/drivers/modem/simcom-sim7080.h b/include/drivers/modem/simcom-sim7080.h new file mode 100644 index 0000000000..1117f1d33d --- /dev/null +++ b/include/drivers/modem/simcom-sim7080.h @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2021 metraTec GmbH + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_MODEM_SIMCOM_SIM7080_H +#define ZEPHYR_INCLUDE_DRIVERS_MODEM_SIMCOM_SIM7080_H + +#include + +#include + +#define SIM7080_GNSS_DATA_UTC_LEN 20 +#define SIM7080_SMS_MAX_LEN 160 + +struct sim7080_gnss_data { + /** + * Whether gnss is powered or not. + */ + bool run_status; + /** + * Whether fix is acquired or not. + */ + bool fix_status; + /** + * UTC in format yyyyMMddhhmmss.sss + */ + char utc[SIM7080_GNSS_DATA_UTC_LEN]; + /** + * Latitude in 10^-7 degree. + */ + int32_t lat; + /** + * Longitude in 10^-7 degree. + */ + int32_t lon; + /** + * Altitude in mm. + */ + int32_t alt; + /** + * Horizontal dilution of precision in 10^-2. + */ + uint16_t hdop; + /** + * Course over ground un 10^-2 degree. + */ + uint16_t cog; + /** + * Speed in 10^-1 km/h. + */ + uint16_t kmh; +}; + +/** + * Possible sms states in memory. + */ +enum sim7080_sms_stat { + SIM7080_SMS_STAT_REC_UNREAD = 0, + SIM7080_SMS_STAT_REC_READ, + SIM7080_SMS_STAT_STO_UNSENT, + SIM7080_SMS_STAT_STO_SENT, + SIM7080_SMS_STAT_ALL, +}; + +/** + * Possible ftp return codes. + */ +enum sim7080_ftp_rc { + /* Operation finished correctly. */ + SIM7080_FTP_RC_OK = 0, + /* Session finished. */ + SIM7080_FTP_RC_FINISHED, + /* An error occurred. */ + SIM7080_FTP_RC_ERROR, +}; + +/** + * Buffer structure for sms. + */ +struct sim7080_sms { + /* First octet of the sms. */ + uint8_t first_octet; + /* Message protocol identifier. */ + uint8_t tp_pid; + /* Status of the sms in memory. */ + enum sim7080_sms_stat stat; + /* Index of the sms in memory. */ + uint16_t index; + /* Time the sms was received. */ + struct { + uint8_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t timezone; + } time; + /* Buffered sms. */ + char data[SIM7080_SMS_MAX_LEN + 1]; + /* Length of the sms in buffer. */ + uint8_t data_len; +}; + +/** + * Buffer structure for sms reads. + */ +struct sim7080_sms_buffer { + /* sms structures to read to. */ + struct sim7080_sms *sms; + /* Number of sms structures. */ + uint8_t nsms; +}; + +/** + * @brief Power on the Sim7080. + * + * @return 0 on success. Otherwise -1 is returned. + */ +int mdm_sim7080_power_on(void); + +/** + * @brief Power off the Sim7080. + * + * @return 0 on success. Otherwise -1 is returned. + */ +int mdm_sim7080_power_off(void); + +/** + * @brief Starts the modem in network operation mode. + * + * @return 0 on success. Otherwise <0 is returned. + */ +int mdm_sim7080_start_network(void); + +/** + * @brief Starts the modem in gnss operation mode. + * + * @return 0 on success. Otherwise <0 is returned. + */ +int mdm_sim7080_start_gnss(void); + +/** + * @brief Query gnss position form the modem. + * + * @return 0 on success. If no fix is acquired yet -EAGAIN is returned. + * Otherwise <0 is returned. + */ +int mdm_sim7080_query_gnss(struct sim7080_gnss_data *data); + +/** + * Get the sim7080 manufacturer. + */ +const char *mdm_sim7080_get_manufacturer(void); + +/** + * Get the sim7080 model information. + */ +const char *mdm_sim7080_get_model(void); + +/** + * Get the sim7080 revision. + */ +const char *mdm_sim7080_get_revision(void); + +/** + * Get the sim7080 imei number. + */ +const char *mdm_sim7080_get_imei(void); + +/** + * Read sms from sim module. + * + * @param buffer Buffer structure for sms. + * @return Number of sms read on success. Otherwise -1 is returned. + * + * @note The buffer structure needs to be initialized to + * the size of the sms buffer. When this function finishes + * successful, nsms will be set to the number of sms read. + * If the whole structure is filled a subsequent read may + * be needed. + */ +int mdm_sim7080_read_sms(struct sim7080_sms_buffer *buffer); + +/** + * Delete a sms at a given index. + * + * @param index The index of the sms in memory. + * @return 0 on success. Otherwise -1 is returned. + */ +int mdm_sim7080_delete_sms(uint16_t index); + +/** + * Start a ftp get session. + * + * @param server The ftp servers address. + * @param user User name for the ftp server. + * @param passwd Password for the ftp user. + * @param file File to be downloaded. + * @param path Path to the file on the server. + * @return 0 if the session was started. Otherwise -1 is returned. + */ +int mdm_sim7080_ftp_get_start(const char *server, const char *user, const char *passwd, + const char *file, const char *path); + +/** + * Read data from a ftp get session. + * + * @param dst The destination buffer. + * @param size Initialize to the size of dst. Gets set to the nuber + * of bytes actually read. + * @return According sim7080_ftp_rc. + */ +int mdm_sim7080_ftp_get_read(char *dst, size_t *size); + +#endif /* ZEPHYR_INCLUDE_DRIVERS_MODEM_SIMCOM_SIM7080_H */ diff --git a/tests/drivers/build_all/modem/modem_simcom_sim7080.conf b/tests/drivers/build_all/modem/modem_simcom_sim7080.conf new file mode 100644 index 0000000000..3af21cfcec --- /dev/null +++ b/tests/drivers/build_all/modem/modem_simcom_sim7080.conf @@ -0,0 +1,8 @@ +CONFIG_TEST=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_SERIAL=y +CONFIG_TEST_UART=y +CONFIG_NETWORKING=y +CONFIG_NET_SOCKETS=y +CONFIG_MODEM=y +CONFIG_MODEM_SIM7080=y diff --git a/tests/drivers/build_all/modem/testcase.yaml b/tests/drivers/build_all/modem/testcase.yaml index fdc192e153..a43ab30a8e 100644 --- a/tests/drivers/build_all/modem/testcase.yaml +++ b/tests/drivers/build_all/modem/testcase.yaml @@ -4,10 +4,13 @@ common: tests: drivers.modem.build: extra_args: CONF_FILE=modem.conf - platform_exclude: serpente particle_boron rak5010_nrf52840 litex_vexriscv + platform_exclude: serpente particle_boron rak5010_nrf52840 litex_vexriscv ip_k66f drivers.modem.ublox_sara.build: extra_args: CONF_FILE=modem_ublox_sara.conf - platform_exclude: serpente pinnacle_100_dvk litex_vexriscv + platform_exclude: serpente pinnacle_100_dvk litex_vexriscv ip_k66f + drivers.modem.simcom_sim7080.build: + extra_args: CONF_FILE=modem_simcom_sim7080.conf + platform_exclude: serpente pinnacle_100_dvk litex_vexriscv ip_k66f drivers.modem.quectel_bg9x.build: extra_args: CONF_FILE=modem_quectel_bg9x.conf - platform_exclude: serpente pinnacle_100_dvk litex_vexriscv + platform_exclude: serpente pinnacle_100_dvk litex_vexriscv ip_k66f diff --git a/tests/drivers/build_all/modem/uart.dtsi b/tests/drivers/build_all/modem/uart.dtsi index d9ccedf671..9b2377ca21 100644 --- a/tests/drivers/build_all/modem/uart.dtsi +++ b/tests/drivers/build_all/modem/uart.dtsi @@ -37,6 +37,13 @@ test_sara_r4: sara_r4 { mdm-reset-gpios = <&test_gpio 0 0>; }; +test_simcom_sim7080: sim7080 { + compatible = "simcom,sim7080"; + label = "simcom,sim7080"; + + mdm-power-gpios = <&test_gpio 0 0>; +}; + test_quectel_bg9x: quectel_bg9x { compatible = "quectel,bg9x"; label = "quectel,bg9x";