/* * Copyright (c) 2020 Analog Life LLC * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT quectel_bg9x #include LOG_MODULE_REGISTER(modem_quectel_bg9x, CONFIG_MODEM_LOG_LEVEL); #include "quectel-bg9x.h" static struct k_thread modem_rx_thread; static struct k_work_q modem_workq; static struct modem_data mdata; static struct modem_context mctx; static const struct socket_op_vtable offload_socket_fd_op_vtable; static K_KERNEL_STACK_DEFINE(modem_rx_stack, CONFIG_MODEM_QUECTEL_BG9X_RX_STACK_SIZE); static K_KERNEL_STACK_DEFINE(modem_workq_stack, CONFIG_MODEM_QUECTEL_BG9X_RX_WORKQ_STACK_SIZE); NET_BUF_POOL_DEFINE(mdm_recv_pool, MDM_RECV_MAX_BUF, MDM_RECV_BUF_SIZE, 0, NULL); static const struct gpio_dt_spec power_gpio = GPIO_DT_SPEC_INST_GET(0, mdm_power_gpios); #if DT_INST_NODE_HAS_PROP(0, mdm_reset_gpios) static const struct gpio_dt_spec reset_gpio = GPIO_DT_SPEC_INST_GET(0, mdm_reset_gpios); #endif #if DT_INST_NODE_HAS_PROP(0, mdm_dtr_gpios) static const struct gpio_dt_spec dtr_gpio = GPIO_DT_SPEC_INST_GET(0, mdm_dtr_gpios); #endif #if DT_INST_NODE_HAS_PROP(0, mdm_wdisable_gpios) static const struct gpio_dt_spec wdisable_gpio = GPIO_DT_SPEC_INST_GET(0, mdm_wdisable_gpios); #endif static inline int digits(int n) { int count = 0; while (n != 0) { n /= 10; ++count; } return count; } static inline uint32_t hash32(char *str, int len) { #define HASH_MULTIPLIER 37 uint32_t h = 0; int i; for (i = 0; i < len; ++i) { h = (h * HASH_MULTIPLIER) + str[i]; } return h; } static inline uint8_t *modem_get_mac(const struct device *dev) { struct modem_data *data = dev->data; uint32_t hash_value; data->mac_addr[0] = 0x00; data->mac_addr[1] = 0x10; /* use IMEI for mac_addr */ hash_value = hash32(mdata.mdm_imei, strlen(mdata.mdm_imei)); UNALIGNED_PUT(hash_value, (uint32_t *)(data->mac_addr + 2)); return data->mac_addr; } /* Func: modem_atoi * Desc: Convert string to long integer, but handle errors */ static int modem_atoi(const char *s, const int err_value, const char *desc, const char *func) { int ret; char *endptr; ret = (int)strtol(s, &endptr, 10); if (!endptr || *endptr != '\0') { LOG_ERR("bad %s '%s' in %s", s, desc, func); return err_value; } return ret; } static inline int find_len(char *data) { char buf[10] = {0}; int i; for (i = 0; i < 10; i++) { if (data[i] == '\r') { break; } buf[i] = data[i]; } return ATOI(buf, 0, "rx_buf"); } /* Func: on_cmd_sockread_common * Desc: Function to successfully read data from the modem on a given socket. */ static int on_cmd_sockread_common(int socket_fd, struct modem_cmd_handler_data *data, uint16_t len) { struct modem_socket *sock = NULL; struct socket_read_data *sock_data; int ret, i; int socket_data_length; int bytes_to_skip; if (!len) { LOG_ERR("Invalid length, Aborting!"); return -EAGAIN; } /* Make sure we still have buf data */ if (!data->rx_buf) { LOG_ERR("Incorrect format! Ignoring data!"); return -EINVAL; } socket_data_length = find_len(data->rx_buf->data); /* No (or not enough) data available on the socket. */ bytes_to_skip = digits(socket_data_length) + 2 + 4; if (socket_data_length <= 0) { LOG_ERR("Length problem (%d). Aborting!", socket_data_length); return -EAGAIN; } /* check to make sure we have all of the data. */ if (net_buf_frags_len(data->rx_buf) < (socket_data_length + bytes_to_skip)) { LOG_DBG("Not enough data -- wait!"); return -EAGAIN; } /* Skip "len" and CRLF */ bytes_to_skip = digits(socket_data_length) + 2; for (i = 0; i < bytes_to_skip; i++) { net_buf_pull_u8(data->rx_buf); } if (!data->rx_buf->len) { data->rx_buf = net_buf_frag_del(NULL, data->rx_buf); } sock = modem_socket_from_fd(&mdata.socket_config, socket_fd); if (!sock) { LOG_ERR("Socket not found! (%d)", socket_fd); ret = -EINVAL; goto exit; } sock_data = (struct socket_read_data *)sock->data; if (!sock_data) { LOG_ERR("Socket data not found! Skip handling (%d)", socket_fd); ret = -EINVAL; goto exit; } ret = net_buf_linearize(sock_data->recv_buf, sock_data->recv_buf_len, data->rx_buf, 0, (uint16_t)socket_data_length); data->rx_buf = net_buf_skip(data->rx_buf, ret); sock_data->recv_read_len = ret; if (ret != socket_data_length) { LOG_ERR("Total copied data is different then received data!" " copied:%d vs. received:%d", ret, socket_data_length); ret = -EINVAL; } exit: /* remove packet from list (ignore errors) */ (void)modem_socket_packet_size_update(&mdata.socket_config, sock, -socket_data_length); /* don't give back semaphore -- OK to follow */ return ret; } /* Func: socket_close * Desc: Function to close the given socket descriptor. */ static void socket_close(struct modem_socket *sock) { char buf[sizeof("AT+QICLOSE=##")] = {0}; int ret; snprintk(buf, sizeof(buf), "AT+QICLOSE=%d", sock->id); /* Tell the modem to close the socket. */ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, buf, &mdata.sem_response, MDM_CMD_TIMEOUT); if (ret < 0) { LOG_ERR("%s ret:%d", buf, ret); } modem_socket_put(&mdata.socket_config, sock->sock_fd); } /* Handler: OK */ MODEM_CMD_DEFINE(on_cmd_ok) { modem_cmd_handler_set_error(data, 0); k_sem_give(&mdata.sem_response); return 0; } /* Handler: ERROR */ MODEM_CMD_DEFINE(on_cmd_error) { modem_cmd_handler_set_error(data, -EIO); k_sem_give(&mdata.sem_response); return 0; } /* Handler: +CME Error: [0] */ MODEM_CMD_DEFINE(on_cmd_exterror) { modem_cmd_handler_set_error(data, -EIO); k_sem_give(&mdata.sem_response); return 0; } /* Handler: +CSQ: [0], [1] */ MODEM_CMD_DEFINE(on_cmd_atcmdinfo_rssi_csq) { int rssi = ATOI(argv[0], 0, "signal_power"); /* Check the RSSI value. */ if (rssi == 31) { mdata.mdm_rssi = -51; } else if (rssi >= 0 && rssi <= 31) { mdata.mdm_rssi = -114 + ((rssi * 2) + 1); } else { mdata.mdm_rssi = -1000; } LOG_INF("RSSI: %d", mdata.mdm_rssi); return 0; } /* Handler: +QIOPEN: [0], [1] */ MODEM_CMD_DEFINE(on_cmd_atcmdinfo_sockopen) { int err = ATOI(argv[1], 0, "sock_err"); LOG_INF("AT+QIOPEN: %d", err); modem_cmd_handler_set_error(data, err); k_sem_give(&mdata.sem_sock_conn); return 0; } /* Handler: */ MODEM_CMD_DEFINE(on_cmd_atcmdinfo_manufacturer) { size_t out_len = net_buf_linearize(mdata.mdm_manufacturer, sizeof(mdata.mdm_manufacturer) - 1, data->rx_buf, 0, len); mdata.mdm_manufacturer[out_len] = '\0'; LOG_INF("Manufacturer: %s", mdata.mdm_manufacturer); return 0; } /* Handler: */ MODEM_CMD_DEFINE(on_cmd_atcmdinfo_model) { size_t out_len = net_buf_linearize(mdata.mdm_model, sizeof(mdata.mdm_model) - 1, data->rx_buf, 0, len); mdata.mdm_model[out_len] = '\0'; /* Log the received information. */ LOG_INF("Model: %s", mdata.mdm_model); return 0; } /* Handler: */ MODEM_CMD_DEFINE(on_cmd_atcmdinfo_revision) { size_t out_len = net_buf_linearize(mdata.mdm_revision, sizeof(mdata.mdm_revision) - 1, data->rx_buf, 0, len); mdata.mdm_revision[out_len] = '\0'; /* Log the received information. */ LOG_INF("Revision: %s", mdata.mdm_revision); return 0; } /* Handler: */ MODEM_CMD_DEFINE(on_cmd_atcmdinfo_imei) { size_t out_len = net_buf_linearize(mdata.mdm_imei, sizeof(mdata.mdm_imei) - 1, data->rx_buf, 0, len); mdata.mdm_imei[out_len] = '\0'; /* Log the received information. */ LOG_INF("IMEI: %s", mdata.mdm_imei); return 0; } #if defined(CONFIG_MODEM_SIM_NUMBERS) /* Handler: */ MODEM_CMD_DEFINE(on_cmd_atcmdinfo_imsi) { size_t out_len = net_buf_linearize(mdata.mdm_imsi, sizeof(mdata.mdm_imsi) - 1, data->rx_buf, 0, len); mdata.mdm_imsi[out_len] = '\0'; /* Log the received information. */ LOG_INF("IMSI: %s", mdata.mdm_imsi); return 0; } /* Handler: */ MODEM_CMD_DEFINE(on_cmd_atcmdinfo_iccid) { size_t out_len; char *p; out_len = net_buf_linearize(mdata.mdm_iccid, sizeof(mdata.mdm_iccid) - 1, data->rx_buf, 0, len); mdata.mdm_iccid[out_len] = '\0'; /* Skip over the +CCID bit, which modems omit. */ if (mdata.mdm_iccid[0] == '+') { p = strchr(mdata.mdm_iccid, ' '); if (p) { out_len = strlen(p + 1); memmove(mdata.mdm_iccid, p + 1, len + 1); } } LOG_INF("ICCID: %s", mdata.mdm_iccid); return 0; } #endif /* #if defined(CONFIG_MODEM_SIM_NUMBERS) */ /* Handler: TX Ready */ MODEM_CMD_DIRECT_DEFINE(on_cmd_tx_ready) { k_sem_give(&mdata.sem_tx_ready); return len; } /* Handler: SEND OK */ MODEM_CMD_DEFINE(on_cmd_send_ok) { modem_cmd_handler_set_error(data, 0); k_sem_give(&mdata.sem_response); return 0; } /* Handler: SEND FAIL */ MODEM_CMD_DEFINE(on_cmd_send_fail) { mdata.sock_written = 0; modem_cmd_handler_set_error(data, -EIO); k_sem_give(&mdata.sem_response); return 0; } /* Handler: Read data */ MODEM_CMD_DEFINE(on_cmd_sock_readdata) { return on_cmd_sockread_common(mdata.sock_fd, data, len); } /* Handler: Data receive indication. */ MODEM_CMD_DEFINE(on_cmd_unsol_recv) { struct modem_socket *sock; int sock_fd; sock_fd = ATOI(argv[0], 0, "sock_fd"); /* Socket pointer from FD. */ sock = modem_socket_from_fd(&mdata.socket_config, sock_fd); if (!sock) { return 0; } /* Data ready indication. */ LOG_INF("Data Receive Indication for socket: %d", sock_fd); modem_socket_data_ready(&mdata.socket_config, sock); return 0; } /* Handler: Socket Close Indication. */ MODEM_CMD_DEFINE(on_cmd_unsol_close) { struct modem_socket *sock; int sock_fd; sock_fd = ATOI(argv[0], 0, "sock_fd"); sock = modem_socket_from_fd(&mdata.socket_config, sock_fd); if (!sock) { return 0; } LOG_INF("Socket Close Indication for socket: %d", sock_fd); /* Tell the modem to close the socket. */ socket_close(sock); LOG_INF("Socket Closed: %d", sock_fd); return 0; } /* Handler: Modem initialization ready. */ MODEM_CMD_DEFINE(on_cmd_unsol_rdy) { k_sem_give(&mdata.sem_response); return 0; } /* Func: send_socket_data * Desc: This function will send "binary" data over the socket object. */ static ssize_t send_socket_data(struct modem_socket *sock, const struct sockaddr *dst_addr, struct modem_cmd *handler_cmds, size_t handler_cmds_len, const char *buf, size_t buf_len, k_timeout_t timeout) { int ret; char send_buf[sizeof("AT+QISEND=##,####")] = {0}; char ctrlz = 0x1A; if (buf_len > MDM_MAX_DATA_LENGTH) { buf_len = MDM_MAX_DATA_LENGTH; } /* Create a buffer with the correct params. */ mdata.sock_written = buf_len; snprintk(send_buf, sizeof(send_buf), "AT+QISEND=%d,%ld", sock->id, (long) buf_len); /* Setup the locks correctly. */ (void)k_sem_take(&mdata.cmd_handler_data.sem_tx_lock, K_FOREVER); k_sem_reset(&mdata.sem_tx_ready); /* Send the Modem command. */ ret = modem_cmd_send_nolock(&mctx.iface, &mctx.cmd_handler, NULL, 0U, send_buf, NULL, K_NO_WAIT); if (ret < 0) { goto exit; } /* set command handlers */ ret = modem_cmd_handler_update_cmds(&mdata.cmd_handler_data, handler_cmds, handler_cmds_len, true); if (ret < 0) { goto exit; } /* Wait for '>' */ ret = k_sem_take(&mdata.sem_tx_ready, K_MSEC(5000)); if (ret < 0) { /* Didn't get the data prompt - Exit. */ LOG_DBG("Timeout waiting for tx"); goto exit; } /* Write all data on the console and send CTRL+Z. */ mctx.iface.write(&mctx.iface, buf, buf_len); mctx.iface.write(&mctx.iface, &ctrlz, 1); /* Wait for 'SEND OK' or 'SEND FAIL' */ k_sem_reset(&mdata.sem_response); ret = k_sem_take(&mdata.sem_response, timeout); if (ret < 0) { LOG_DBG("No send response"); goto exit; } ret = modem_cmd_handler_get_error(&mdata.cmd_handler_data); if (ret != 0) { LOG_DBG("Failed to send data"); } exit: /* unset handler commands and ignore any errors */ (void)modem_cmd_handler_update_cmds(&mdata.cmd_handler_data, NULL, 0U, false); k_sem_give(&mdata.cmd_handler_data.sem_tx_lock); if (ret < 0) { return ret; } /* Return the amount of data written on the socket. */ return mdata.sock_written; } /* Func: offload_sendto * Desc: This function will send data on the socket object. */ static ssize_t offload_sendto(void *obj, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) { int ret; struct modem_socket *sock = (struct modem_socket *) obj; /* Here's how sending data works, * -> We firstly send the "AT+QISEND" command on the given socket and * specify the length of data to be transferred. * -> In response to "AT+QISEND" command, the modem may respond with a * data prompt (>) or not respond at all. If it doesn't respond, we * exit. If it does respond with a data prompt (>), we move forward. * -> We plainly write all data on the UART and terminate by sending a * CTRL+Z. Once the modem receives CTRL+Z, it starts processing the * data and will respond with either "SEND OK", "SEND FAIL" or "ERROR". * Here we are registering handlers for the first two responses. We * already have a handler for the "generic" error response. */ struct modem_cmd cmd[] = { MODEM_CMD_DIRECT(">", on_cmd_tx_ready), MODEM_CMD("SEND OK", on_cmd_send_ok, 0, ","), MODEM_CMD("SEND FAIL", on_cmd_send_fail, 0, ","), }; /* Ensure that valid parameters are passed. */ if (!buf || len == 0) { errno = EINVAL; return -1; } /* UDP is not supported. */ if (sock->ip_proto == IPPROTO_UDP) { errno = ENOTSUP; return -1; } if (!sock->is_connected) { errno = ENOTCONN; return -1; } ret = send_socket_data(sock, to, cmd, ARRAY_SIZE(cmd), buf, len, MDM_CMD_TIMEOUT); if (ret < 0) { errno = -ret; return -1; } /* Data was written successfully. */ errno = 0; return ret; } /* Func: offload_recvfrom * Desc: This function will receive data on the socket object. */ static ssize_t offload_recvfrom(void *obj, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen) { struct modem_socket *sock = (struct modem_socket *)obj; char sendbuf[sizeof("AT+QIRD=##,####")] = {0}; int ret; struct socket_read_data sock_data; /* Modem command to read the data. */ struct modem_cmd data_cmd[] = { MODEM_CMD("+QIRD: ", on_cmd_sock_readdata, 0U, "") }; if (!buf || len == 0) { errno = EINVAL; return -1; } if (flags & ZSOCK_MSG_PEEK) { errno = ENOTSUP; return -1; } snprintk(sendbuf, sizeof(sendbuf), "AT+QIRD=%d,%zd", sock->id, len); /* Socket read settings */ (void) memset(&sock_data, 0, sizeof(sock_data)); sock_data.recv_buf = buf; sock_data.recv_buf_len = len; sock_data.recv_addr = from; sock->data = &sock_data; mdata.sock_fd = sock->sock_fd; /* Tell the modem to give us data (AT+QIRD=id,data_len). */ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, data_cmd, ARRAY_SIZE(data_cmd), sendbuf, &mdata.sem_response, MDM_CMD_TIMEOUT); if (ret < 0) { errno = -ret; ret = -1; goto exit; } /* HACK: use dst address as from */ if (from && fromlen) { *fromlen = sizeof(sock->dst); memcpy(from, &sock->dst, *fromlen); } /* return length of received data */ errno = 0; ret = sock_data.recv_read_len; exit: /* clear socket data */ sock->data = NULL; return ret; } /* Func: offload_read * Desc: This function reads data from the given socket object. */ static ssize_t offload_read(void *obj, void *buffer, size_t count) { return offload_recvfrom(obj, buffer, count, 0, NULL, 0); } /* Func: offload_write * Desc: This function writes data to the given socket object. */ static ssize_t offload_write(void *obj, const void *buffer, size_t count) { return offload_sendto(obj, buffer, count, 0, NULL, 0); } /* Func: offload_ioctl * Desc: Function call to handle various misc requests. */ static int offload_ioctl(void *obj, unsigned int request, va_list args) { switch (request) { case ZFD_IOCTL_POLL_PREPARE: { struct zsock_pollfd *pfd; struct k_poll_event **pev; struct k_poll_event *pev_end; pfd = va_arg(args, struct zsock_pollfd *); pev = va_arg(args, struct k_poll_event **); pev_end = va_arg(args, struct k_poll_event *); return modem_socket_poll_prepare(&mdata.socket_config, obj, pfd, pev, pev_end); } case ZFD_IOCTL_POLL_UPDATE: { struct zsock_pollfd *pfd; struct k_poll_event **pev; pfd = va_arg(args, struct zsock_pollfd *); pev = va_arg(args, struct k_poll_event **); return modem_socket_poll_update(obj, pfd, pev); } default: errno = EINVAL; return -1; } } /* Func: offload_connect * Desc: This function will connect with a provided TCP. */ static int offload_connect(void *obj, const struct sockaddr *addr, socklen_t addrlen) { struct modem_socket *sock = (struct modem_socket *) obj; uint16_t dst_port = 0; char *protocol = "TCP"; struct modem_cmd cmd[] = { MODEM_CMD("+QIOPEN: ", on_cmd_atcmdinfo_sockopen, 2U, ",") }; char buf[sizeof("AT+QIOPEN=#,#,'###','###'," "####.####.####.####.####.####.####.####,######," "0,0")] = {0}; int ret; char ip_str[NET_IPV6_ADDR_LEN]; /* Verify socket has been allocated */ if (modem_socket_is_allocated(&mdata.socket_config, sock) == false) { LOG_ERR("Invalid socket_id(%d) from fd:%d", sock->id, sock->sock_fd); errno = EINVAL; return -1; } if (sock->is_connected == true) { LOG_ERR("Socket is already connected!! socket_id(%d), socket_fd:%d", sock->id, sock->sock_fd); errno = EISCONN; return -1; } /* Find the correct destination port. */ if (addr->sa_family == AF_INET6) { dst_port = ntohs(net_sin6(addr)->sin6_port); } else if (addr->sa_family == AF_INET) { dst_port = ntohs(net_sin(addr)->sin_port); } /* UDP is not supported. */ if (sock->ip_proto == IPPROTO_UDP) { errno = ENOTSUP; return -1; } k_sem_reset(&mdata.sem_sock_conn); ret = modem_context_sprint_ip_addr(addr, ip_str, sizeof(ip_str)); if (ret != 0) { LOG_ERR("Error formatting IP string %d", ret); LOG_ERR("Closing the socket!!!"); socket_close(sock); errno = -ret; return -1; } /* Formulate the complete string. */ snprintk(buf, sizeof(buf), "AT+QIOPEN=%d,%d,\"%s\",\"%s\",%d,0,0", 1, sock->id, protocol, ip_str, dst_port); /* Send out the command. */ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, buf, &mdata.sem_response, K_SECONDS(1)); if (ret < 0) { LOG_ERR("%s ret:%d", buf, ret); LOG_ERR("Closing the socket!!!"); socket_close(sock); errno = -ret; return -1; } /* set command handlers */ ret = modem_cmd_handler_update_cmds(&mdata.cmd_handler_data, cmd, ARRAY_SIZE(cmd), true); if (ret < 0) { goto exit; } /* Wait for QI+OPEN */ ret = k_sem_take(&mdata.sem_sock_conn, MDM_CMD_CONN_TIMEOUT); if (ret < 0) { LOG_ERR("Timeout waiting for socket open"); LOG_ERR("Closing the socket!!!"); socket_close(sock); goto exit; } ret = modem_cmd_handler_get_error(&mdata.cmd_handler_data); if (ret != 0) { LOG_ERR("Closing the socket!!!"); socket_close(sock); goto exit; } /* Connected successfully. */ sock->is_connected = true; errno = 0; return 0; exit: (void) modem_cmd_handler_update_cmds(&mdata.cmd_handler_data, NULL, 0U, false); errno = -ret; return -1; } /* Func: offload_close * Desc: This function closes the connection with the remote client and * frees the socket. */ static int offload_close(void *obj) { struct modem_socket *sock = (struct modem_socket *) obj; /* Make sure socket is allocated */ if (modem_socket_is_allocated(&mdata.socket_config, sock) == false) { return 0; } /* Close the socket only if it is connected. */ if (sock->is_connected) { socket_close(sock); } return 0; } /* Func: offload_sendmsg * Desc: This function sends messages to the modem. */ static ssize_t offload_sendmsg(void *obj, const struct msghdr *msg, int flags) { ssize_t sent = 0; int rc; LOG_DBG("msg_iovlen:%zd flags:%d", msg->msg_iovlen, flags); for (int i = 0; i < msg->msg_iovlen; i++) { const char *buf = msg->msg_iov[i].iov_base; size_t len = msg->msg_iov[i].iov_len; while (len > 0) { rc = offload_sendto(obj, buf, len, flags, msg->msg_name, msg->msg_namelen); if (rc < 0) { if (rc == -EAGAIN) { k_sleep(MDM_SENDMSG_SLEEP); } else { sent = rc; break; } } else { sent += rc; buf += rc; len -= rc; } } } return (ssize_t) sent; } /* Func: modem_rx * Desc: Thread to process all messages received from the Modem. */ static void modem_rx(void *p1, void *p2, void *p3) { ARG_UNUSED(p1); ARG_UNUSED(p2); ARG_UNUSED(p3); while (true) { /* Wait for incoming data */ modem_iface_uart_rx_wait(&mctx.iface, K_FOREVER); modem_cmd_handler_process(&mctx.cmd_handler, &mctx.iface); } } /* Func: modem_rssi_query_work * Desc: Routine to get Modem RSSI. */ static void modem_rssi_query_work(struct k_work *work) { struct modem_cmd cmd = MODEM_CMD("+CSQ: ", on_cmd_atcmdinfo_rssi_csq, 2U, ","); static char *send_cmd = "AT+CSQ"; int ret; /* query modem RSSI */ ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, &cmd, 1U, send_cmd, &mdata.sem_response, MDM_CMD_TIMEOUT); if (ret < 0) { LOG_ERR("AT+CSQ ret:%d", ret); } /* Re-start RSSI query work */ if (work) { k_work_reschedule_for_queue(&modem_workq, &mdata.rssi_query_work, K_SECONDS(RSSI_TIMEOUT_SECS)); } } /* Func: pin_init * Desc: Boot up the Modem. */ static void pin_init(void) { #if !DT_INST_NODE_HAS_PROP(0, mdm_reset_gpios) int ret = k_sem_take(&mdata.sem_pin_busy, K_SECONDS(3)); if (ret < 0) { LOG_DBG("Timeout pin_init()"); } #endif /* !DT_INST_NODE_HAS_PROP(0, mdm_reset_gpios) */ LOG_INF("Setting Modem Pins"); #if DT_INST_NODE_HAS_PROP(0, mdm_wdisable_gpios) LOG_INF("Deactivate W Disable"); gpio_pin_set_dt(&wdisable_gpio, 0); k_sleep(K_MSEC(250)); #endif /* NOTE: Per the BG95 document, the Reset pin is internally connected to the * Power key pin. */ /* MDM_POWER -> 1 for 500-1000 msec. */ gpio_pin_set_dt(&power_gpio, 1); k_sleep(K_MSEC(750)); /* MDM_POWER -> 0 and wait for ~2secs as UART remains in "inactive" state * for some time after the power signal is enabled. */ gpio_pin_set_dt(&power_gpio, 0); k_sleep(K_SECONDS(2)); LOG_INF("... Done!"); #if !DT_INST_NODE_HAS_PROP(0, mdm_reset_gpios) k_sem_give(&mdata.sem_pin_busy); #endif /* !DT_INST_NODE_HAS_PROP(0, mdm_reset_gpios) */ } MODEM_CMD_DEFINE(on_cmd_unsol_normal_power_down) { LOG_INF("Modem powering off. Re-power modem..."); pin_init(); return 0; } static const struct modem_cmd response_cmds[] = { MODEM_CMD("OK", on_cmd_ok, 0U, ""), MODEM_CMD("ERROR", on_cmd_error, 0U, ""), MODEM_CMD("+CME ERROR: ", on_cmd_exterror, 1U, ""), }; static const struct modem_cmd unsol_cmds[] = { MODEM_CMD("+QIURC: \"recv\",", on_cmd_unsol_recv, 1U, ""), MODEM_CMD("+QIURC: \"closed\",", on_cmd_unsol_close, 1U, ""), MODEM_CMD(MDM_UNSOL_RDY, on_cmd_unsol_rdy, 0U, ""), MODEM_CMD("NORMAL POWER DOWN", on_cmd_unsol_normal_power_down, 0U, ""), }; /* Commands sent to the modem to set it up at boot time. */ static const struct setup_cmd setup_cmds[] = { SETUP_CMD_NOHANDLE("ATE0"), SETUP_CMD_NOHANDLE("ATH"), SETUP_CMD_NOHANDLE("AT+CMEE=1"), /* Commands to read info from the modem (things like IMEI, Model etc). */ SETUP_CMD("AT+CGMI", "", on_cmd_atcmdinfo_manufacturer, 0U, ""), SETUP_CMD("AT+CGMM", "", on_cmd_atcmdinfo_model, 0U, ""), SETUP_CMD("AT+CGMR", "", on_cmd_atcmdinfo_revision, 0U, ""), SETUP_CMD("AT+CGSN", "", on_cmd_atcmdinfo_imei, 0U, ""), #if defined(CONFIG_MODEM_SIM_NUMBERS) SETUP_CMD("AT+CIMI", "", on_cmd_atcmdinfo_imsi, 0U, ""), SETUP_CMD("AT+QCCID", "", on_cmd_atcmdinfo_iccid, 0U, ""), #endif /* #if defined(CONFIG_MODEM_SIM_NUMBERS) */ SETUP_CMD_NOHANDLE("AT+QICSGP=1,1,\"" MDM_APN "\",\"" MDM_USERNAME "\",\"" MDM_PASSWORD "\",1"), }; /* Func: modem_pdp_context_active * Desc: This helper function is called from modem_setup, and is * used to open the PDP context. If there is trouble activating the * PDP context, we try to deactivate and reactivate MDM_PDP_ACT_RETRY_COUNT times. * If it fails, we return an error. */ static int modem_pdp_context_activate(void) { int ret; int retry_count = 0; ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, "AT+QIACT=1", &mdata.sem_response, MDM_CMD_TIMEOUT); /* If there is trouble activating the PDP context, we try to deactivate/reactive it. */ while (ret == -EIO && retry_count < MDM_PDP_ACT_RETRY_COUNT) { ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, "AT+QIDEACT=1", &mdata.sem_response, MDM_CMD_TIMEOUT); /* If there's any error for AT+QIDEACT, restart the module. */ if (ret != 0) { return ret; } ret = modem_cmd_send(&mctx.iface, &mctx.cmd_handler, NULL, 0U, "AT+QIACT=1", &mdata.sem_response, MDM_CMD_TIMEOUT); retry_count++; } if (ret == -EIO && retry_count >= MDM_PDP_ACT_RETRY_COUNT) { LOG_ERR("Retried activating/deactivating too many times."); } return ret; } /* Func: modem_setup * Desc: This function is used to setup the modem from zero. The idea * is that this function will be called right after the modem is * powered on to do the stuff necessary to talk to the modem. */ static int modem_setup(void) { int ret = 0, counter; int rssi_retry_count = 0, init_retry_count = 0; /* Setup the pins to ensure that Modem is enabled. */ pin_init(); restart: counter = 0; /* stop RSSI delay work */ k_work_cancel_delayable(&mdata.rssi_query_work); /* Let the modem respond. */ LOG_INF("Waiting for modem to respond"); ret = k_sem_take(&mdata.sem_response, MDM_MAX_BOOT_TIME); if (ret < 0) { LOG_ERR("Timeout waiting for RDY"); goto error; } /* Run setup commands on the modem. */ ret = modem_cmd_handler_setup_cmds(&mctx.iface, &mctx.cmd_handler, setup_cmds, ARRAY_SIZE(setup_cmds), &mdata.sem_response, MDM_REGISTRATION_TIMEOUT); if (ret < 0) { goto error; } restart_rssi: /* query modem RSSI */ modem_rssi_query_work(NULL); k_sleep(MDM_WAIT_FOR_RSSI_DELAY); /* Keep trying to read RSSI until we get a valid value - Eventually, exit. */ while (counter++ < MDM_WAIT_FOR_RSSI_COUNT && (mdata.mdm_rssi >= 0 || mdata.mdm_rssi <= -1000)) { modem_rssi_query_work(NULL); k_sleep(MDM_WAIT_FOR_RSSI_DELAY); } /* Is the RSSI invalid ? */ if (mdata.mdm_rssi >= 0 || mdata.mdm_rssi <= -1000) { rssi_retry_count++; if (rssi_retry_count >= MDM_NETWORK_RETRY_COUNT) { LOG_ERR("Failed network init. Too many attempts!"); ret = -ENETUNREACH; goto error; } /* Try again! */ LOG_ERR("Failed network init. Restarting process."); counter = 0; goto restart_rssi; } /* Network is ready - Start RSSI work in the background. */ LOG_INF("Network is ready."); k_work_reschedule_for_queue(&modem_workq, &mdata.rssi_query_work, K_SECONDS(RSSI_TIMEOUT_SECS)); /* Once the network is ready, we try to activate the PDP context. */ ret = modem_pdp_context_activate(); if (ret < 0 && init_retry_count++ < MDM_INIT_RETRY_COUNT) { LOG_ERR("Error activating modem with pdp context"); goto restart; } error: return ret; } static const struct socket_op_vtable offload_socket_fd_op_vtable = { .fd_vtable = { .read = offload_read, .write = offload_write, .close = offload_close, .ioctl = offload_ioctl, }, .bind = NULL, .connect = offload_connect, .sendto = offload_sendto, .recvfrom = offload_recvfrom, .listen = NULL, .accept = NULL, .sendmsg = offload_sendmsg, .getsockopt = NULL, .setsockopt = NULL, }; static int offload_socket(int family, int type, int proto); /* Setup the Modem NET Interface. */ static void modem_net_iface_init(struct net_if *iface) { const struct device *dev = net_if_get_device(iface); struct modem_data *data = dev->data; /* Direct socket offload used instead of net offload: */ net_if_set_link_addr(iface, modem_get_mac(dev), sizeof(data->mac_addr), NET_LINK_ETHERNET); data->net_iface = iface; net_if_socket_offload_set(iface, offload_socket); } static struct offloaded_if_api api_funcs = { .iface_api.init = modem_net_iface_init, }; static bool offload_is_supported(int family, int type, int proto) { if (family != AF_INET && family != AF_INET6) { return false; } if (type != SOCK_STREAM) { return false; } if (proto != IPPROTO_TCP) { return false; } return true; } static int offload_socket(int family, int type, int proto) { int ret; /* defer modem's socket create call to bind() */ ret = modem_socket_get(&mdata.socket_config, family, type, proto); if (ret < 0) { errno = -ret; return -1; } errno = 0; return ret; } static int modem_init(const struct device *dev) { int ret; ARG_UNUSED(dev); #if !DT_INST_NODE_HAS_PROP(0, mdm_reset_gpios) k_sem_init(&mdata.sem_pin_busy, 1, 1); #endif /* !DT_INST_NODE_HAS_PROP(0, mdm_reset_gpios) */ k_sem_init(&mdata.sem_response, 0, 1); k_sem_init(&mdata.sem_tx_ready, 0, 1); k_sem_init(&mdata.sem_sock_conn, 0, 1); k_work_queue_start(&modem_workq, modem_workq_stack, K_KERNEL_STACK_SIZEOF(modem_workq_stack), K_PRIO_COOP(7), NULL); /* socket config */ ret = modem_socket_init(&mdata.socket_config, &mdata.sockets[0], ARRAY_SIZE(mdata.sockets), MDM_BASE_SOCKET_NUM, true, &offload_socket_fd_op_vtable); if (ret < 0) { goto error; } /* cmd handler setup */ const struct modem_cmd_handler_config cmd_handler_config = { .match_buf = &mdata.cmd_match_buf[0], .match_buf_len = sizeof(mdata.cmd_match_buf), .buf_pool = &mdm_recv_pool, .alloc_timeout = BUF_ALLOC_TIMEOUT, .eol = "\r\n", .user_data = NULL, .response_cmds = response_cmds, .response_cmds_len = ARRAY_SIZE(response_cmds), .unsol_cmds = unsol_cmds, .unsol_cmds_len = ARRAY_SIZE(unsol_cmds), }; ret = modem_cmd_handler_init(&mctx.cmd_handler, &mdata.cmd_handler_data, &cmd_handler_config); if (ret < 0) { goto error; } /* modem interface */ const struct modem_iface_uart_config uart_config = { .rx_rb_buf = &mdata.iface_rb_buf[0], .rx_rb_buf_len = sizeof(mdata.iface_rb_buf), .dev = MDM_UART_DEV, .hw_flow_control = DT_PROP(MDM_UART_NODE, hw_flow_control), }; ret = modem_iface_uart_init(&mctx.iface, &mdata.iface_data, &uart_config); if (ret < 0) { goto error; } /* modem data storage */ mctx.data_manufacturer = mdata.mdm_manufacturer; mctx.data_model = mdata.mdm_model; mctx.data_revision = mdata.mdm_revision; mctx.data_imei = mdata.mdm_imei; #if defined(CONFIG_MODEM_SIM_NUMBERS) mctx.data_imsi = mdata.mdm_imsi; mctx.data_iccid = mdata.mdm_iccid; #endif /* #if defined(CONFIG_MODEM_SIM_NUMBERS) */ mctx.data_rssi = &mdata.mdm_rssi; /* pin setup */ ret = gpio_pin_configure_dt(&power_gpio, GPIO_OUTPUT_LOW); if (ret < 0) { LOG_ERR("Failed to configure %s pin", "power"); goto error; } #if DT_INST_NODE_HAS_PROP(0, mdm_reset_gpios) ret = gpio_pin_configure_dt(&reset_gpio, GPIO_OUTPUT_LOW); if (ret < 0) { LOG_ERR("Failed to configure %s pin", "reset"); goto error; } #endif #if DT_INST_NODE_HAS_PROP(0, mdm_dtr_gpios) ret = gpio_pin_configure_dt(&dtr_gpio, GPIO_OUTPUT_LOW); if (ret < 0) { LOG_ERR("Failed to configure %s pin", "dtr"); goto error; } #endif #if DT_INST_NODE_HAS_PROP(0, mdm_wdisable_gpios) ret = gpio_pin_configure_dt(&wdisable_gpio, GPIO_OUTPUT_LOW); if (ret < 0) { LOG_ERR("Failed to configure %s pin", "wdisable"); goto error; } #endif /* modem context setup */ mctx.driver_data = &mdata; ret = modem_context_register(&mctx); if (ret < 0) { LOG_ERR("Error registering modem context: %d", ret); goto error; } /* start RX thread */ k_thread_create(&modem_rx_thread, modem_rx_stack, K_KERNEL_STACK_SIZEOF(modem_rx_stack), modem_rx, NULL, NULL, NULL, K_PRIO_COOP(7), 0, K_NO_WAIT); /* Init RSSI query */ k_work_init_delayable(&mdata.rssi_query_work, modem_rssi_query_work); return modem_setup(); error: return ret; } /* Register the device with the Networking stack. */ NET_DEVICE_DT_INST_OFFLOAD_DEFINE(0, modem_init, NULL, &mdata, NULL, CONFIG_MODEM_QUECTEL_BG9X_INIT_PRIORITY, &api_funcs, MDM_MAX_DATA_LENGTH); /* Register NET sockets. */ NET_SOCKET_OFFLOAD_REGISTER(quectel_bg9x, CONFIG_NET_SOCKETS_OFFLOAD_PRIORITY, AF_UNSPEC, offload_is_supported, offload_socket);