| /* |
| * Copyright (c) 2019 Tobias Svehagen |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT espressif_esp |
| |
| #define LOG_LEVEL CONFIG_WIFI_LOG_LEVEL |
| #include <logging/log.h> |
| LOG_MODULE_REGISTER(wifi_esp); |
| |
| #include <kernel.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <zephyr.h> |
| #include <device.h> |
| #include <init.h> |
| #include <stdlib.h> |
| |
| #include <drivers/gpio.h> |
| |
| #include <net/net_if.h> |
| #include <net/net_offload.h> |
| #include <net/wifi_mgmt.h> |
| |
| #include "esp.h" |
| |
| /* pin settings */ |
| enum modem_control_pins { |
| #if DT_INST_NODE_HAS_PROP(0, wifi_reset_gpios) |
| WIFI_RESET, |
| #endif |
| NUM_PINS, |
| }; |
| |
| static struct modem_pin modem_pins[] = { |
| #if DT_INST_NODE_HAS_PROP(0, wifi_reset_gpios) |
| MODEM_PIN(DT_INST_GPIO_LABEL(0, wifi_reset_gpios), |
| DT_INST_GPIO_PIN(0, wifi_reset_gpios), |
| DT_INST_GPIO_FLAGS(0, wifi_reset_gpios) | GPIO_OUTPUT), |
| #endif |
| }; |
| |
| NET_BUF_POOL_DEFINE(mdm_recv_pool, MDM_RECV_MAX_BUF, MDM_RECV_BUF_SIZE, |
| 0, NULL); |
| |
| /* RX thread structures */ |
| K_KERNEL_STACK_DEFINE(esp_rx_stack, |
| CONFIG_WIFI_ESP_RX_STACK_SIZE); |
| struct k_thread esp_rx_thread; |
| |
| /* RX thread work queue */ |
| K_KERNEL_STACK_DEFINE(esp_workq_stack, |
| CONFIG_WIFI_ESP_WORKQ_STACK_SIZE); |
| |
| struct esp_data esp_driver_data; |
| |
| /* |
| * Modem Response Command Handlers |
| */ |
| |
| /* Handler: OK */ |
| MODEM_CMD_DEFINE(on_cmd_ok) |
| { |
| struct esp_data *dev = CONTAINER_OF(data, struct esp_data, |
| cmd_handler_data); |
| |
| modem_cmd_handler_set_error(data, 0); |
| k_sem_give(&dev->sem_response); |
| |
| return 0; |
| } |
| |
| /* Handler: ERROR */ |
| MODEM_CMD_DEFINE(on_cmd_error) |
| { |
| struct esp_data *dev = CONTAINER_OF(data, struct esp_data, |
| cmd_handler_data); |
| |
| modem_cmd_handler_set_error(data, -EIO); |
| k_sem_give(&dev->sem_response); |
| |
| return 0; |
| } |
| |
| /* RX thread */ |
| static void esp_rx(struct esp_data *data) |
| { |
| while (true) { |
| /* wait for incoming data */ |
| k_sem_take(&data->iface_data.rx_sem, K_FOREVER); |
| |
| data->mctx.cmd_handler.process(&data->mctx.cmd_handler, |
| &data->mctx.iface); |
| |
| /* give up time if we have a solid stream of data */ |
| k_yield(); |
| } |
| } |
| |
| static char *str_unquote(char *str) |
| { |
| char *end; |
| |
| if (str[0] != '"') { |
| return str; |
| } |
| |
| str++; |
| |
| end = strrchr(str, '"'); |
| if (end != NULL) { |
| *end = 0; |
| } |
| |
| return str; |
| } |
| |
| /* +CIPSTAMAC:"xx:xx:xx:xx:xx:xx" */ |
| MODEM_CMD_DEFINE(on_cmd_cipstamac) |
| { |
| struct esp_data *dev = CONTAINER_OF(data, struct esp_data, |
| cmd_handler_data); |
| char *mac; |
| |
| mac = str_unquote(argv[0]); |
| net_bytes_from_str(dev->mac_addr, sizeof(dev->mac_addr), mac); |
| |
| return 0; |
| } |
| |
| /* +CWLAP:(sec,ssid,rssi,channel) */ |
| MODEM_CMD_DEFINE(on_cmd_cwlap) |
| { |
| struct esp_data *dev = CONTAINER_OF(data, struct esp_data, |
| cmd_handler_data); |
| struct wifi_scan_result res = { 0 }; |
| int i; |
| |
| i = strtol(&argv[0][1], NULL, 10); |
| if (i == 0) { |
| res.security = WIFI_SECURITY_TYPE_NONE; |
| } else { |
| res.security = WIFI_SECURITY_TYPE_PSK; |
| } |
| |
| argv[1] = str_unquote(argv[1]); |
| i = strlen(argv[1]); |
| if (i > sizeof(res.ssid)) { |
| i = sizeof(res.ssid); |
| } |
| |
| memcpy(res.ssid, argv[1], i); |
| res.ssid_length = i; |
| res.rssi = strtol(argv[2], NULL, 10); |
| res.channel = strtol(argv[3], NULL, 10); |
| |
| if (dev->scan_cb) { |
| dev->scan_cb(dev->net_iface, 0, &res); |
| } |
| |
| return 0; |
| } |
| |
| static struct modem_cmd response_cmds[] = { |
| MODEM_CMD("OK", on_cmd_ok, 0U, ""), /* 3GPP */ |
| MODEM_CMD("ERROR", on_cmd_error, 0U, ""), /* 3GPP */ |
| }; |
| |
| MODEM_CMD_DEFINE(on_cmd_wifi_connected) |
| { |
| struct esp_data *dev = CONTAINER_OF(data, struct esp_data, |
| cmd_handler_data); |
| |
| if (esp_flag_is_set(dev, EDF_STA_CONNECTED)) { |
| return 0; |
| } |
| |
| esp_flag_set(dev, EDF_STA_CONNECTED); |
| wifi_mgmt_raise_connect_result_event(dev->net_iface, 0); |
| |
| return 0; |
| } |
| |
| MODEM_CMD_DEFINE(on_cmd_wifi_disconnected) |
| { |
| struct esp_data *dev = CONTAINER_OF(data, struct esp_data, |
| cmd_handler_data); |
| |
| if (!esp_flag_is_set(dev, EDF_STA_CONNECTED)) { |
| return 0; |
| } |
| |
| esp_flag_clear(dev, EDF_STA_CONNECTED); |
| net_if_ipv4_addr_rm(dev->net_iface, &dev->ip); |
| wifi_mgmt_raise_disconnect_result_event(dev->net_iface, 0); |
| |
| return 0; |
| } |
| |
| /* |
| * +CIPSTA:ip:"<ip>" |
| * +CIPSTA:gateway:"<ip>" |
| * +CIPSTA:netmask:"<ip>" |
| */ |
| MODEM_CMD_DEFINE(on_cmd_cipsta) |
| { |
| struct esp_data *dev = CONTAINER_OF(data, struct esp_data, |
| cmd_handler_data); |
| char *ip; |
| |
| ip = str_unquote(argv[1]); |
| |
| if (!strcmp(argv[0], "ip")) { |
| net_addr_pton(AF_INET, ip, &dev->ip); |
| } else if (!strcmp(argv[0], "gateway")) { |
| net_addr_pton(AF_INET, ip, &dev->gw); |
| } else if (!strcmp(argv[0], "netmask")) { |
| net_addr_pton(AF_INET, ip, &dev->nm); |
| } else { |
| LOG_WRN("Unknown IP type %s", log_strdup(argv[0])); |
| } |
| |
| return 0; |
| } |
| |
| static void esp_ip_addr_work(struct k_work *work) |
| { |
| struct esp_data *dev = CONTAINER_OF(work, struct esp_data, |
| ip_addr_work); |
| int ret; |
| |
| struct modem_cmd cmds[] = { |
| MODEM_CMD("+"_CIPSTA":", on_cmd_cipsta, 2U, ":"), |
| }; |
| |
| ret = modem_cmd_send(&dev->mctx.iface, &dev->mctx.cmd_handler, |
| cmds, ARRAY_SIZE(cmds), "AT+"_CIPSTA"?", |
| &dev->sem_response, ESP_CMD_TIMEOUT); |
| if (ret < 0) { |
| LOG_WRN("Failed to query IP settings: ret %d", ret); |
| k_delayed_work_submit_to_queue(&dev->workq, &dev->ip_addr_work, |
| K_SECONDS(5)); |
| return; |
| } |
| |
| /* update interface addresses */ |
| net_if_ipv4_set_gw(dev->net_iface, &dev->gw); |
| net_if_ipv4_set_netmask(dev->net_iface, &dev->nm); |
| net_if_ipv4_addr_add(dev->net_iface, &dev->ip, NET_ADDR_DHCP, 0); |
| } |
| |
| MODEM_CMD_DEFINE(on_cmd_got_ip) |
| { |
| struct esp_data *dev = CONTAINER_OF(data, struct esp_data, |
| cmd_handler_data); |
| |
| k_delayed_work_submit_to_queue(&dev->workq, &dev->ip_addr_work, |
| K_SECONDS(1)); |
| |
| return 0; |
| } |
| |
| MODEM_CMD_DEFINE(on_cmd_connect) |
| { |
| struct esp_socket *sock; |
| struct esp_data *dev; |
| uint8_t link_id; |
| |
| link_id = data->match_buf[0] - '0'; |
| |
| dev = CONTAINER_OF(data, struct esp_data, cmd_handler_data); |
| sock = esp_socket_from_link_id(dev, link_id); |
| if (sock == NULL) { |
| LOG_ERR("No socket for link %d", link_id); |
| } |
| |
| return 0; |
| } |
| |
| MODEM_CMD_DEFINE(on_cmd_closed) |
| { |
| struct esp_socket *sock; |
| struct esp_data *dev; |
| uint8_t link_id; |
| |
| link_id = data->match_buf[0] - '0'; |
| |
| dev = CONTAINER_OF(data, struct esp_data, cmd_handler_data); |
| sock = esp_socket_from_link_id(dev, link_id); |
| if (sock == NULL) { |
| LOG_ERR("No socket for link %d", link_id); |
| return 0; |
| } |
| |
| if (!esp_socket_connected(sock)) { |
| LOG_WRN("Link %d already closed", link_id); |
| return 0; |
| } |
| |
| sock->flags &= ~(ESP_SOCK_CONNECTED); |
| k_work_submit_to_queue(&dev->workq, &sock->recv_work); |
| |
| return 0; |
| } |
| |
| struct net_pkt *esp_prepare_pkt(struct esp_data *dev, struct net_buf *src, |
| size_t offset, size_t len) |
| { |
| struct net_buf *frag; |
| struct net_pkt *pkt; |
| size_t to_copy; |
| |
| pkt = net_pkt_rx_alloc_with_buffer(dev->net_iface, len, AF_UNSPEC, |
| 0, K_MSEC(100)); |
| if (!pkt) { |
| return NULL; |
| } |
| |
| frag = src; |
| |
| /* find the right fragment to start copying from */ |
| while (frag && offset >= frag->len) { |
| offset -= frag->len; |
| frag = frag->frags; |
| } |
| |
| /* traverse the fragment chain until len bytes are copied */ |
| while (frag && len > 0) { |
| to_copy = MIN(len, frag->len - offset); |
| if (net_pkt_write(pkt, frag->data + offset, to_copy) != 0) { |
| net_pkt_unref(pkt); |
| return NULL; |
| } |
| |
| /* to_copy is always <= len */ |
| len -= to_copy; |
| frag = frag->frags; |
| |
| /* after the first iteration, this value will be 0 */ |
| offset = 0; |
| } |
| |
| net_pkt_cursor_init(pkt); |
| |
| return pkt; |
| } |
| |
| /* |
| * Passive mode: "+IPD,<id>,<len>\r\n" |
| * Other: "+IPD,<id>,<len>:<data>" |
| */ |
| #define MIN_IPD_LEN (sizeof("+IPD,I,LE") - 1) |
| #define MAX_IPD_LEN (sizeof("+IPD,I,LLLLE") - 1) |
| MODEM_CMD_DIRECT_DEFINE(on_cmd_ipd) |
| { |
| char *endptr, end, ipd_buf[MAX_IPD_LEN + 1]; |
| int data_offset, data_len, ret; |
| size_t match_len, frags_len; |
| struct esp_socket *sock; |
| struct esp_data *dev; |
| struct net_pkt *pkt; |
| uint8_t link_id; |
| |
| dev = CONTAINER_OF(data, struct esp_data, cmd_handler_data); |
| |
| frags_len = net_buf_frags_len(data->rx_buf); |
| |
| /* Wait until minimum cmd length is available */ |
| if (frags_len < MIN_IPD_LEN) { |
| ret = -EAGAIN; |
| goto out; |
| } |
| |
| match_len = net_buf_linearize(ipd_buf, MAX_IPD_LEN, |
| data->rx_buf, 0, MAX_IPD_LEN); |
| |
| ipd_buf[match_len] = 0; |
| if (ipd_buf[len] != ',' || ipd_buf[len + 2] != ',') { |
| LOG_ERR("Invalid IPD: %s", log_strdup(ipd_buf)); |
| ret = len; |
| goto out; |
| } |
| |
| link_id = ipd_buf[len + 1] - '0'; |
| sock = esp_socket_from_link_id(dev, link_id); |
| if (sock == NULL) { |
| LOG_ERR("No socket for link %d", link_id); |
| ret = len; |
| goto out; |
| } |
| |
| /* When using passive mode, the +IPD command ends with \r\n */ |
| if (ESP_PROTO_PASSIVE(sock->ip_proto)) { |
| end = '\r'; |
| } else { |
| end = ':'; |
| } |
| |
| data_len = strtol(&ipd_buf[len + 3], &endptr, 10); |
| if (endptr == &ipd_buf[len + 3] || |
| (*endptr == 0 && match_len >= MAX_IPD_LEN)) { |
| /* Invalid */ |
| LOG_ERR("Invalid IPD len: %s", log_strdup(ipd_buf)); |
| ret = len; |
| goto out; |
| } else if (*endptr == 0) { |
| ret = -EAGAIN; |
| goto out; |
| } else if (*endptr != end) { |
| LOG_ERR("Invalid cmd end 0x%02x, expected 0x%02x", *endptr, |
| end); |
| ret = len; |
| goto out; |
| } |
| |
| *endptr = 0; |
| data_offset = strlen(ipd_buf) + 1; |
| |
| /* |
| * When using passive TCP, the data itself is not included in the +IPD |
| * command but must be polled with AT+CIPRECVDATA. |
| */ |
| if (ESP_PROTO_PASSIVE(sock->ip_proto)) { |
| sock->bytes_avail = data_len; |
| k_work_submit_to_queue(&dev->workq, &sock->recvdata_work); |
| ret = data_offset; |
| goto out; |
| } |
| |
| /* Do we have the whole message? */ |
| if (data_offset + data_len > frags_len) { |
| ret = -EAGAIN; |
| goto out; |
| } |
| |
| ret = data_offset + data_len; /* Skip */ |
| |
| pkt = esp_prepare_pkt(dev, data->rx_buf, data_offset, data_len); |
| if (!pkt) { |
| /* FIXME: Should probably terminate connection */ |
| LOG_ERR("Failed to get net_pkt: len %d", data_len); |
| goto out; |
| } |
| |
| k_fifo_put(&sock->fifo_rx_pkt, pkt); |
| k_work_submit_to_queue(&dev->workq, &sock->recv_work); |
| |
| out: |
| return ret; |
| } |
| |
| MODEM_CMD_DEFINE(on_cmd_busy_sending) |
| { |
| LOG_WRN("Busy sending"); |
| return 0; |
| } |
| |
| MODEM_CMD_DEFINE(on_cmd_busy_processing) |
| { |
| LOG_WRN("Busy processing"); |
| return 0; |
| } |
| |
| /* |
| * The 'ready' command is sent when device has booted and is ready to receive |
| * commands. It is only expected after a reset of the device. |
| */ |
| MODEM_CMD_DEFINE(on_cmd_ready) |
| { |
| struct esp_data *dev = CONTAINER_OF(data, struct esp_data, |
| cmd_handler_data); |
| k_sem_give(&dev->sem_if_ready); |
| |
| if (net_if_is_up(dev->net_iface)) { |
| net_if_down(dev->net_iface); |
| LOG_ERR("Unexpected reset"); |
| } |
| |
| if (esp_flag_is_set(dev, EDF_STA_CONNECTING)) { |
| esp_flag_clear(dev, EDF_STA_CONNECTING); |
| wifi_mgmt_raise_connect_result_event(dev->net_iface, -1); |
| } else if (esp_flag_is_set(dev, EDF_STA_CONNECTED)) { |
| esp_flag_clear(dev, EDF_STA_CONNECTED); |
| wifi_mgmt_raise_disconnect_result_event(dev->net_iface, 0); |
| } |
| |
| net_if_ipv4_addr_rm(dev->net_iface, &dev->ip); |
| k_work_submit_to_queue(&dev->workq, &dev->init_work); |
| |
| return 0; |
| } |
| |
| static struct modem_cmd unsol_cmds[] = { |
| MODEM_CMD("WIFI CONNECTED", on_cmd_wifi_connected, 0U, ""), |
| MODEM_CMD("WIFI DISCONNECT", on_cmd_wifi_disconnected, 0U, ""), |
| MODEM_CMD("WIFI GOT IP", on_cmd_got_ip, 0U, ""), |
| MODEM_CMD("0,CONNECT", on_cmd_connect, 0U, ""), |
| MODEM_CMD("1,CONNECT", on_cmd_connect, 0U, ""), |
| MODEM_CMD("2,CONNECT", on_cmd_connect, 0U, ""), |
| MODEM_CMD("3,CONNECT", on_cmd_connect, 0U, ""), |
| MODEM_CMD("4,CONNECT", on_cmd_connect, 0U, ""), |
| MODEM_CMD("0,CLOSED", on_cmd_closed, 0U, ""), |
| MODEM_CMD("1,CLOSED", on_cmd_closed, 0U, ""), |
| MODEM_CMD("2,CLOSED", on_cmd_closed, 0U, ""), |
| MODEM_CMD("3,CLOSED", on_cmd_closed, 0U, ""), |
| MODEM_CMD("4,CLOSED", on_cmd_closed, 0U, ""), |
| MODEM_CMD("busy s...", on_cmd_busy_sending, 0U, ""), |
| MODEM_CMD("busy p...", on_cmd_busy_processing, 0U, ""), |
| MODEM_CMD("ready", on_cmd_ready, 0U, ""), |
| MODEM_CMD_DIRECT("+IPD", on_cmd_ipd), |
| }; |
| |
| static void esp_mgmt_scan_work(struct k_work *work) |
| { |
| struct esp_data *dev; |
| int ret; |
| struct modem_cmd cmds[] = { |
| MODEM_CMD("+CWLAP:", on_cmd_cwlap, 4U, ","), |
| }; |
| |
| dev = CONTAINER_OF(work, struct esp_data, scan_work); |
| |
| ret = modem_cmd_send(&dev->mctx.iface, &dev->mctx.cmd_handler, |
| cmds, ARRAY_SIZE(cmds), "AT+CWLAP", |
| &dev->sem_response, ESP_SCAN_TIMEOUT); |
| if (ret < 0) { |
| LOG_ERR("Failed to scan: ret %d", ret); |
| } |
| |
| dev->scan_cb(dev->net_iface, 0, NULL); |
| dev->scan_cb = NULL; |
| } |
| |
| static int esp_mgmt_scan(const struct device *dev, scan_result_cb_t cb) |
| { |
| struct esp_data *data = dev->data; |
| |
| if (data->scan_cb != NULL) { |
| return -EINPROGRESS; |
| } |
| |
| if (!net_if_is_up(data->net_iface)) { |
| return -EIO; |
| } |
| |
| data->scan_cb = cb; |
| |
| k_work_submit_to_queue(&data->workq, &data->scan_work); |
| |
| return 0; |
| }; |
| |
| MODEM_CMD_DEFINE(on_cmd_fail) |
| { |
| struct esp_data *dev = CONTAINER_OF(data, struct esp_data, |
| cmd_handler_data); |
| |
| modem_cmd_handler_set_error(data, -EIO); |
| k_sem_give(&dev->sem_response); |
| |
| return 0; |
| } |
| |
| static void esp_mgmt_connect_work(struct k_work *work) |
| { |
| struct esp_data *dev; |
| int ret; |
| struct modem_cmd cmds[] = { |
| MODEM_CMD("FAIL", on_cmd_fail, 0U, ""), |
| }; |
| |
| dev = CONTAINER_OF(work, struct esp_data, connect_work); |
| |
| ret = modem_cmd_send(&dev->mctx.iface, &dev->mctx.cmd_handler, |
| cmds, ARRAY_SIZE(cmds), dev->conn_cmd, |
| &dev->sem_response, ESP_CONNECT_TIMEOUT); |
| |
| memset(dev->conn_cmd, 0, sizeof(dev->conn_cmd)); |
| |
| if (ret < 0) { |
| if (esp_flag_is_set(dev, EDF_STA_CONNECTED)) { |
| esp_flag_clear(dev, EDF_STA_CONNECTED); |
| wifi_mgmt_raise_disconnect_result_event(dev->net_iface, |
| 0); |
| } else { |
| wifi_mgmt_raise_connect_result_event(dev->net_iface, |
| ret); |
| } |
| } else if (!esp_flag_is_set(dev, EDF_STA_CONNECTED)) { |
| esp_flag_set(dev, EDF_STA_CONNECTED); |
| wifi_mgmt_raise_connect_result_event(dev->net_iface, 0); |
| } |
| |
| esp_flag_clear(dev, EDF_STA_CONNECTING); |
| } |
| |
| static int esp_mgmt_connect(const struct device *dev, |
| struct wifi_connect_req_params *params) |
| { |
| struct esp_data *data = dev->data; |
| int len; |
| |
| if (!net_if_is_up(data->net_iface)) { |
| return -EIO; |
| } |
| |
| if (esp_flag_is_set(data, EDF_STA_CONNECTED) || |
| esp_flag_is_set(data, EDF_STA_CONNECTING)) { |
| return -EALREADY; |
| } |
| |
| esp_flag_set(data, EDF_STA_CONNECTING); |
| |
| len = snprintk(data->conn_cmd, sizeof(data->conn_cmd), |
| "AT+"_CWJAP"=\""); |
| memcpy(&data->conn_cmd[len], params->ssid, params->ssid_length); |
| len += params->ssid_length; |
| |
| if (params->security == WIFI_SECURITY_TYPE_PSK) { |
| len += snprintk(&data->conn_cmd[len], |
| sizeof(data->conn_cmd) - len, "\",\""); |
| memcpy(&data->conn_cmd[len], params->psk, params->psk_length); |
| len += params->psk_length; |
| } |
| |
| len += snprintk(&data->conn_cmd[len], sizeof(data->conn_cmd) - len, |
| "\""); |
| |
| k_work_submit_to_queue(&data->workq, &data->connect_work); |
| |
| return 0; |
| } |
| |
| static int esp_mgmt_disconnect(const struct device *dev) |
| { |
| struct esp_data *data = dev->data; |
| int ret; |
| |
| ret = modem_cmd_send(&data->mctx.iface, &data->mctx.cmd_handler, |
| NULL, 0, "AT+CWQAP", &data->sem_response, |
| ESP_CMD_TIMEOUT); |
| |
| return ret; |
| } |
| |
| static int esp_mgmt_ap_enable(const struct device *dev, |
| struct wifi_connect_req_params *params) |
| { |
| char cmd[sizeof("AT+"_CWSAP"=\"\",\"\",xx,x") + WIFI_SSID_MAX_LEN + |
| WIFI_PSK_MAX_LEN]; |
| struct esp_data *data = dev->data; |
| int ecn = 0, len, ret; |
| |
| ret = modem_cmd_send(&data->mctx.iface, &data->mctx.cmd_handler, |
| NULL, 0, "AT+"_CWMODE"=3", &data->sem_response, |
| ESP_CMD_TIMEOUT); |
| if (ret < 0) { |
| LOG_ERR("Failed to enable AP mode, ret %d", ret); |
| return ret; |
| } |
| |
| len = snprintk(cmd, sizeof(cmd), "AT+"_CWSAP"=\""); |
| memcpy(&cmd[len], params->ssid, params->ssid_length); |
| len += params->ssid_length; |
| |
| if (params->security == WIFI_SECURITY_TYPE_PSK) { |
| len += snprintk(&cmd[len], sizeof(cmd) - len, "\",\""); |
| memcpy(&cmd[len], params->psk, params->psk_length); |
| len += params->psk_length; |
| ecn = 3; |
| } else { |
| len += snprintk(&cmd[len], sizeof(cmd) - len, "\",\""); |
| } |
| |
| snprintk(&cmd[len], sizeof(cmd) - len, "\",%d,%d", params->channel, |
| ecn); |
| |
| ret = modem_cmd_send(&data->mctx.iface, &data->mctx.cmd_handler, |
| NULL, 0, cmd, &data->sem_response, |
| ESP_CMD_TIMEOUT); |
| |
| return ret; |
| } |
| |
| static int esp_mgmt_ap_disable(const struct device *dev) |
| { |
| struct esp_data *data = dev->data; |
| int ret; |
| |
| ret = modem_cmd_send(&data->mctx.iface, &data->mctx.cmd_handler, |
| NULL, 0, "AT+"_CWMODE"=1", &data->sem_response, |
| ESP_CMD_TIMEOUT); |
| |
| return ret; |
| } |
| |
| static void esp_init_work(struct k_work *work) |
| { |
| struct esp_data *dev; |
| int ret; |
| static struct setup_cmd setup_cmds[] = { |
| SETUP_CMD_NOHANDLE("AT"), |
| /* turn off echo */ |
| SETUP_CMD_NOHANDLE("ATE0"), |
| SETUP_CMD_NOHANDLE("AT+UART_CUR="_UART_CUR), |
| SETUP_CMD_NOHANDLE("AT+"_CWMODE"=1"), |
| /* enable multiple socket support */ |
| SETUP_CMD_NOHANDLE("AT+CIPMUX=1"), |
| /* only need ecn,ssid,rssi,channel */ |
| SETUP_CMD_NOHANDLE("AT+CWLAPOPT=0,23"), |
| #if defined(CONFIG_WIFI_ESP_AT_VERSION_2_0) |
| SETUP_CMD_NOHANDLE("AT+CWAUTOCONN=0"), |
| #endif |
| #if defined(CONFIG_WIFI_ESP_PASSIVE_MODE) |
| SETUP_CMD_NOHANDLE("AT+CIPRECVMODE=1"), |
| #endif |
| SETUP_CMD("AT+"_CIPSTAMAC"?", "+"_CIPSTAMAC":", |
| on_cmd_cipstamac, 1U, ""), |
| }; |
| |
| dev = CONTAINER_OF(work, struct esp_data, init_work); |
| |
| ret = modem_cmd_handler_setup_cmds(&dev->mctx.iface, |
| &dev->mctx.cmd_handler, setup_cmds, |
| ARRAY_SIZE(setup_cmds), |
| &dev->sem_response, |
| ESP_INIT_TIMEOUT); |
| if (ret < 0) { |
| LOG_ERR("Init failed %d", ret); |
| return; |
| } |
| |
| net_if_set_link_addr(dev->net_iface, dev->mac_addr, |
| sizeof(dev->mac_addr), NET_LINK_ETHERNET); |
| |
| LOG_INF("ESP Wi-Fi ready"); |
| |
| net_if_up(dev->net_iface); |
| |
| k_sem_give(&dev->sem_if_up); |
| } |
| |
| static void esp_reset(struct esp_data *dev) |
| { |
| int ret; |
| |
| if (net_if_is_up(dev->net_iface)) { |
| net_if_down(dev->net_iface); |
| } |
| |
| #if DT_INST_NODE_HAS_PROP(0, wifi_reset_gpios) |
| modem_pin_write(&dev->mctx, WIFI_RESET, 1); |
| k_sleep(K_MSEC(100)); |
| modem_pin_write(&dev->mctx, WIFI_RESET, 0); |
| #else |
| int retries = 3; |
| |
| while (retries--) { |
| ret = modem_cmd_send(&dev->mctx.iface, &dev->mctx.cmd_handler, |
| NULL, 0, "AT+RST", &dev->sem_if_ready, |
| K_MSEC(CONFIG_WIFI_ESP_RESET_TIMEOUT)); |
| if (ret == 0 || ret != -ETIMEDOUT) { |
| break; |
| } |
| } |
| |
| if (ret < 0) { |
| LOG_ERR("Failed to reset device: %d", ret); |
| return; |
| } |
| #endif |
| |
| LOG_INF("Waiting for interface to come up"); |
| |
| ret = k_sem_take(&dev->sem_if_up, ESP_INIT_TIMEOUT); |
| if (ret == -EAGAIN) { |
| LOG_ERR("Timeout waiting for interface"); |
| } |
| } |
| |
| static void esp_iface_init(struct net_if *iface) |
| { |
| const struct device *dev = net_if_get_device(iface); |
| struct esp_data *data = dev->data; |
| |
| net_if_flag_set(iface, NET_IF_NO_AUTO_START); |
| data->net_iface = iface; |
| esp_offload_init(iface); |
| esp_reset(data); |
| } |
| |
| static const struct net_wifi_mgmt_offload esp_api = { |
| .iface_api.init = esp_iface_init, |
| .scan = esp_mgmt_scan, |
| .connect = esp_mgmt_connect, |
| .disconnect = esp_mgmt_disconnect, |
| .ap_enable = esp_mgmt_ap_enable, |
| .ap_disable = esp_mgmt_ap_disable, |
| }; |
| |
| static int esp_init(const struct device *dev) |
| { |
| struct esp_data *data = dev->data; |
| int ret = 0; |
| |
| k_sem_init(&data->sem_tx_ready, 0, 1); |
| k_sem_init(&data->sem_response, 0, 1); |
| k_sem_init(&data->sem_if_ready, 0, 1); |
| k_sem_init(&data->sem_if_up, 0, 1); |
| |
| k_work_init(&data->init_work, esp_init_work); |
| k_delayed_work_init(&data->ip_addr_work, esp_ip_addr_work); |
| k_work_init(&data->scan_work, esp_mgmt_scan_work); |
| k_work_init(&data->connect_work, esp_mgmt_connect_work); |
| |
| esp_socket_init(data); |
| |
| /* initialize the work queue */ |
| k_work_q_start(&data->workq, esp_workq_stack, |
| K_KERNEL_STACK_SIZEOF(esp_workq_stack), |
| K_PRIO_COOP(CONFIG_WIFI_ESP_WORKQ_THREAD_PRIORITY)); |
| k_thread_name_set(&data->workq.thread, "esp_workq"); |
| |
| /* cmd handler */ |
| data->cmd_handler_data.cmds[CMD_RESP] = response_cmds; |
| data->cmd_handler_data.cmds_len[CMD_RESP] = ARRAY_SIZE(response_cmds); |
| data->cmd_handler_data.cmds[CMD_UNSOL] = unsol_cmds; |
| data->cmd_handler_data.cmds_len[CMD_UNSOL] = ARRAY_SIZE(unsol_cmds); |
| data->cmd_handler_data.read_buf = &data->cmd_read_buf[0]; |
| data->cmd_handler_data.read_buf_len = sizeof(data->cmd_read_buf); |
| data->cmd_handler_data.match_buf = &data->cmd_match_buf[0]; |
| data->cmd_handler_data.match_buf_len = sizeof(data->cmd_match_buf); |
| data->cmd_handler_data.buf_pool = &mdm_recv_pool; |
| data->cmd_handler_data.alloc_timeout = CMD_BUF_ALLOC_TIMEOUT; |
| data->cmd_handler_data.eol = "\r\n"; |
| ret = modem_cmd_handler_init(&data->mctx.cmd_handler, |
| &data->cmd_handler_data); |
| if (ret < 0) { |
| goto error; |
| } |
| |
| /* modem interface */ |
| data->iface_data.isr_buf = &data->iface_isr_buf[0]; |
| data->iface_data.isr_buf_len = sizeof(data->iface_isr_buf); |
| data->iface_data.rx_rb_buf = &data->iface_rb_buf[0]; |
| data->iface_data.rx_rb_buf_len = sizeof(data->iface_rb_buf); |
| ret = modem_iface_uart_init(&data->mctx.iface, &data->iface_data, |
| DT_INST_BUS_LABEL(0)); |
| if (ret < 0) { |
| goto error; |
| } |
| |
| /* pin setup */ |
| data->mctx.pins = modem_pins; |
| data->mctx.pins_len = ARRAY_SIZE(modem_pins); |
| |
| data->mctx.driver_data = data; |
| |
| ret = modem_context_register(&data->mctx); |
| if (ret < 0) { |
| LOG_ERR("Error registering modem context: %d", ret); |
| goto error; |
| } |
| |
| /* start RX thread */ |
| k_thread_create(&esp_rx_thread, esp_rx_stack, |
| K_KERNEL_STACK_SIZEOF(esp_rx_stack), |
| (k_thread_entry_t)esp_rx, |
| data, NULL, NULL, |
| K_PRIO_COOP(CONFIG_WIFI_ESP_RX_THREAD_PRIORITY), 0, |
| K_NO_WAIT); |
| k_thread_name_set(&esp_rx_thread, "esp_rx"); |
| |
| error: |
| return ret; |
| } |
| |
| NET_DEVICE_OFFLOAD_INIT(wifi_esp, CONFIG_WIFI_ESP_NAME, |
| esp_init, device_pm_control_nop, &esp_driver_data, NULL, |
| CONFIG_WIFI_INIT_PRIORITY, &esp_api, |
| ESP_MTU); |