diff --git a/drivers/wifi/CMakeLists.txt b/drivers/wifi/CMakeLists.txt index 015902c6828992..27775027b11196 100644 --- a/drivers/wifi/CMakeLists.txt +++ b/drivers/wifi/CMakeLists.txt @@ -3,3 +3,4 @@ add_subdirectory_ifdef(CONFIG_WIFI_WINC1500 winc1500) add_subdirectory_ifdef(CONFIG_WIFI_SIMPLELINK simplelink) add_subdirectory_ifdef(CONFIG_WIFI_ESWIFI eswifi) +add_subdirectory_ifdef(CONFIG_WIFI_ESP esp) diff --git a/drivers/wifi/Kconfig b/drivers/wifi/Kconfig index aa2bbdf095d665..0785bfb476b85b 100644 --- a/drivers/wifi/Kconfig +++ b/drivers/wifi/Kconfig @@ -32,5 +32,6 @@ config WIFI_OFFLOAD source "drivers/wifi/winc1500/Kconfig.winc1500" source "drivers/wifi/simplelink/Kconfig.simplelink" source "drivers/wifi/eswifi/Kconfig.eswifi" +source "drivers/wifi/esp/Kconfig.esp" endif # WIFI diff --git a/drivers/wifi/esp/CMakeLists.txt b/drivers/wifi/esp/CMakeLists.txt new file mode 100644 index 00000000000000..d16b485d5072a2 --- /dev/null +++ b/drivers/wifi/esp/CMakeLists.txt @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: Apache-2.0 + +if(CONFIG_WIFI_ESP) + zephyr_include_directories(./) + + zephyr_library_include_directories( + ${ZEPHYR_BASE}/drivers/modem + ) + + zephyr_sources( + esp.c + esp_socket.c + esp_offload.c + ) +endif() diff --git a/drivers/wifi/esp/Kconfig.esp b/drivers/wifi/esp/Kconfig.esp new file mode 100644 index 00000000000000..8a93bb3b15b51a --- /dev/null +++ b/drivers/wifi/esp/Kconfig.esp @@ -0,0 +1,70 @@ +# Copyright (c) 2019 Tobias Svehagen +# SPDX-License-Identifier: Apache-2.0 + +menuconfig WIFI_ESP + bool "Espressif ESP8266 and ESP32 support" + select MODEM + select MODEM_CONTEXT + select MODEM_CMD_HANDLER + select MODEM_IFACE_UART + select NET_L2_WIFI_MGMT + select WIFI_OFFLOAD + +if WIFI_ESP + +config WIFI_ESP_NAME + string "Driver name" + default "esp-wifi-modem" + +config WIFI_ESP_RX_STACK_SIZE + int "Stack size for the Espressif esp wifi driver RX thread" + default 1024 + help + This stack is used by the Espressif ESP RX thread. + +config WIFI_ESP_RX_THREAD_PRIORITY + int "Priority of RX thread" + default 7 + help + Priority of thread used for processing RX data. + +config WIFI_ESP_WORKQ_STACK_SIZE + int "Stack size for the esp driver work queue" + default 2048 + help + This stack is used by the work queue to pass off net_pkt data + to the rest of the network stack, letting the rx thread continue + processing data. + +config WIFI_ESP_WORKQ_THREAD_PRIORITY + int "Priority of work queue thread" + default 7 + help + Priority of thread used for processing driver work queue items. + +config WIFI_ESP_PASSIVE_TCP + bool "Use passive TCP" + help + This lets the ESP handle the TCP window so that data can flow + at a rate that the driver can handle. Without this, data might get + lost if the driver cannot empty the device buffer quickly enough. + +choice + prompt "AT version" + default WIFI_ESP_AT_VERSION_2_0 + help + Select which version of AT command set should be used. + +config WIFI_ESP_AT_VERSION_1_7 + bool "AT version 1.7" + help + Use AT command set version 1.7. + +config WIFI_ESP_AT_VERSION_2_0 + bool "AT version 2.0" + help + Use AT command set version 2.0. + +endchoice + +endif # WIFI_ESP diff --git a/drivers/wifi/esp/esp.c b/drivers/wifi/esp/esp.c new file mode 100644 index 00000000000000..34e2468d57d43b --- /dev/null +++ b/drivers/wifi/esp/esp.c @@ -0,0 +1,874 @@ +/* + * Copyright (c) 2019 Tobias Svehagen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define LOG_LEVEL CONFIG_WIFI_LOG_LEVEL +#include +LOG_MODULE_REGISTER(wifi_esp); + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "esp.h" + +/* pin settings */ +enum modem_control_pins { +#if defined(DT_INST_0_ESPRESSIF_ESP_WIFI_RESET_GPIOS_PIN) + WIFI_RESET, +#endif + NUM_PINS, +}; + +static struct modem_pin modem_pins[] = { +#if defined(DT_INST_0_ESPRESSIF_ESP_WIFI_RESET_GPIOS_PIN) + MODEM_PIN(DT_INST_0_ESPRESSIF_ESP_WIFI_RESET_GPIOS_CONTROLLER, + DT_INST_0_ESPRESSIF_ESP_WIFI_RESET_GPIOS_PIN, + GPIO_OUTPUT), +#endif +}; + +NET_BUF_POOL_DEFINE(mdm_recv_pool, MDM_RECV_MAX_BUF, MDM_RECV_BUF_SIZE, + 0, NULL); + +/* RX thread structures */ +K_THREAD_STACK_DEFINE(esp_rx_stack, + CONFIG_WIFI_ESP_RX_STACK_SIZE); +struct k_thread esp_rx_thread; + +/* RX thread work queue */ +K_THREAD_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 device *dev) +{ + struct esp_data *data = dev->driver_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; +} + +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], 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:"" + * +CIPSTA:gateway:"" + * +CIPSTA:netmask:"" + */ +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 *data = CONTAINER_OF(work, struct esp_data, + ip_addr_work); + struct modem_cmd cmds[] = { + MODEM_CMD("+"_CIPSTA":", on_cmd_cipsta, 2U, ":"), + }; + + modem_cmd_send(&data->mctx.iface, &data->mctx.cmd_handler, + cmds, ARRAY_SIZE(cmds), "AT+"_CIPSTA"?", + &data->sem_response, ESP_CMD_TIMEOUT); + + /* update interface addresses */ + net_if_ipv4_set_gw(data->net_iface, &data->gw); + net_if_ipv4_set_netmask(data->net_iface, &data->nm); + net_if_ipv4_addr_add(data->net_iface, &data->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; + u8_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; + u8_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 TCP: "+IPD,,\r\n" + * Other: "+IPD,,:" + */ +#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; + u8_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 TCP, the +IPD command ends with \r\n */ + if (IS_ENABLED(CONFIG_WIFI_ESP_PASSIVE_TCP) && + sock->ip_proto == IPPROTO_TCP) { + 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 (IS_ENABLED(CONFIG_WIFI_ESP_PASSIVE_TCP) && + sock->ip_proto == IPPROTO_TCP) { + 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); + + 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(struct device *dev, scan_result_cb_t cb) +{ + struct esp_data *data = dev->driver_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(struct device *dev, + struct wifi_connect_req_params *params) +{ + struct esp_data *data = dev->driver_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(struct device *dev) +{ + struct esp_data *data = dev->driver_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(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->driver_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(struct device *dev) +{ + struct esp_data *data = dev->driver_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"), +#if defined(CONFIG_WIFI_ESP_AT_VERSION_2_0) + SETUP_CMD_NOHANDLE("AT+CWAUTOCONN=0"), +#endif + SETUP_CMD_NOHANDLE("AT+UART_CUR="_UART_CUR), + /* enable multiple socket support */ + SETUP_CMD_NOHANDLE("AT+CIPMUX=1"), + SETUP_CMD_NOHANDLE("AT+"_CWMODE"=1"), + /* only need ecn,ssid,rssi,channel */ + SETUP_CMD_NOHANDLE("AT+CWLAPOPT=0,23"), +#if defined(CONFIG_WIFI_ESP_PASSIVE_TCP) + 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 defined(DT_INST_0_ESPRESSIF_ESP_WIFI_RESET_GPIOS_PIN) + modem_pin_write(&dev->mctx, WIFI_RESET, 0); + k_sleep(K_MSEC(100)); + modem_pin_write(&dev->mctx, WIFI_RESET, 1); +#else + int retries = 3; + + while (retries--) { + ret = modem_cmd_send(&dev->mctx.iface, &dev->mctx.cmd_handler, + NULL, 0, "AT+RST", &dev->sem_response, + K_MSEC(100)); + 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) +{ + struct device *dev = net_if_get_device(iface); + struct esp_data *data = dev->driver_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(struct device *dev) +{ + struct esp_data *data = dev->driver_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_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_THREAD_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_0_ESPRESSIF_ESP_BUS_NAME); + 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_THREAD_STACK_SIZEOF(esp_rx_stack), + (k_thread_entry_t)esp_rx, + dev, 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, &esp_driver_data, NULL, + CONFIG_WIFI_INIT_PRIORITY, &esp_api, + ESP_MTU); diff --git a/drivers/wifi/esp/esp.h b/drivers/wifi/esp/esp.h new file mode 100644 index 00000000000000..56a72163a088f2 --- /dev/null +++ b/drivers/wifi/esp/esp.h @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2019 Tobias Svehagen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_WIFI_ESP_H_ +#define ZEPHYR_INCLUDE_DRIVERS_WIFI_ESP_H_ + +#include +#include +#include +#include +#include +#include + +#include "modem_context.h" +#include "modem_cmd_handler.h" +#include "modem_iface_uart.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Define the commands that differ between the AT versions */ +#if defined(CONFIG_WIFI_ESP_AT_VERSION_1_7) +#define _CWMODE "CWMODE_CUR" +#define _CWSAP "CWSAP_CUR" +#define _CWJAP "CWJAP_CUR" +#define _CIPSTA "CIPSTA_CUR" +#define _CIPSTAMAC "CIPSTAMAC_CUR" +#else +#define _CWMODE "CWMODE" +#define _CWSAP "CWSAP" +#define _CWJAP "CWJAP" +#define _CIPSTA "CIPSTA" +#define _CIPSTAMAC "CIPSTAMAC" +#endif + +#if DT_INST_0_ESPRESSIF_ESP_WIFI_UART_FLOW_CONTROL == 1 +#define _FLOW_CONTROL "3" +#else +#define _FLOW_CONTROL "0" +#endif + +#define _UART_CUR \ + STRINGIFY(DT_INST_0_ESPRESSIF_ESP_WIFI_UART_SPEED)",8,1,0,"_FLOW_CONTROL + +#define CONN_CMD_MAX_LEN (sizeof("AT+"_CWJAP"=\"\",\"\"") + \ + WIFI_SSID_MAX_LEN + WIFI_PSK_MAX_LEN) + +#define ESP_MAX_SOCKETS 5 + +/* Maximum amount that can be sent with CIPSEND and read with CIPRECVDATA */ +#define ESP_MTU 2048 +#define CIPRECVDATA_MAX_LEN ESP_MTU + +#define INVALID_LINK_ID 255 + +#define MDM_RING_BUF_SIZE 1024 +#define MDM_RECV_MAX_BUF 30 +#define MDM_RECV_BUF_SIZE 128 +#define CMD_BUF_ALLOC_TIMEOUT K_SECONDS(1) + +#define ESP_CMD_TIMEOUT K_SECONDS(10) +#define ESP_SCAN_TIMEOUT K_SECONDS(10) +#define ESP_CONNECT_TIMEOUT K_SECONDS(20) +#define ESP_INIT_TIMEOUT K_SECONDS(10) + +extern struct esp_data esp_driver_data; + +enum esp_socket_flags { + ESP_SOCK_IN_USE = BIT(1), + ESP_SOCK_CONNECTING = BIT(2), + ESP_SOCK_CONNECTED = BIT(3) +}; + +struct esp_socket { + /* internal */ + u8_t idx; + u8_t link_id; + u8_t flags; + + /* socket info */ + sa_family_t family; + enum net_sock_type type; + enum net_ip_protocol ip_proto; + struct sockaddr src; + struct sockaddr dst; + + /* for +CIPRECVDATA */ + size_t bytes_avail; + + /* packets */ + struct k_fifo fifo_rx_pkt; + struct net_pkt *tx_pkt; + + /* sem */ + struct k_sem sem_data_ready; + + /* work */ + struct k_work connect_work; + struct k_work send_work; + struct k_work recv_work; + struct k_work recvdata_work; + + /* net context */ + struct net_context *context; + net_context_connect_cb_t connect_cb; + net_context_send_cb_t send_cb; + net_context_recv_cb_t recv_cb; + + /* callback data */ + void *conn_user_data; + void *send_user_data; + void *recv_user_data; +}; + +enum esp_data_flag { + EDF_STA_CONNECTING = BIT(1), + EDF_STA_CONNECTED = BIT(2) +}; + +/* driver data */ +struct esp_data { + struct net_if *net_iface; + + u8_t flags; + + char conn_cmd[CONN_CMD_MAX_LEN]; + + /* addresses */ + struct in_addr ip; + struct in_addr gw; + struct in_addr nm; + u8_t mac_addr[6]; + + /* modem context */ + struct modem_context mctx; + + /* modem interface */ + struct modem_iface_uart_data iface_data; + u8_t iface_isr_buf[MDM_RECV_BUF_SIZE]; + u8_t iface_rb_buf[MDM_RING_BUF_SIZE]; + + /* modem cmds */ + struct modem_cmd_handler_data cmd_handler_data; + u8_t cmd_read_buf[MDM_RECV_BUF_SIZE]; + u8_t cmd_match_buf[MDM_RECV_BUF_SIZE]; + + /* socket data */ + struct esp_socket sockets[ESP_MAX_SOCKETS]; + struct esp_socket *rx_sock; + + /* work */ + struct k_work_q workq; + struct k_work init_work; + struct k_delayed_work ip_addr_work; + struct k_work scan_work; + struct k_work connect_work; + + scan_result_cb_t scan_cb; + + /* response semaphore */ + struct k_sem sem_tx_ready; + struct k_sem sem_response; + struct k_sem sem_if_up; +}; + +int esp_offload_init(struct net_if *iface); + +struct net_pkt *esp_prepare_pkt(struct esp_data *dev, struct net_buf *src, + size_t offset, size_t len); +struct esp_socket *esp_socket_get(); +int esp_socket_put(struct esp_socket *sock); +struct esp_socket *esp_socket_from_link_id(struct esp_data *data, + u8_t link_id); +void esp_socket_init(struct esp_data *data); + +static inline struct esp_data *esp_socket_to_dev(struct esp_socket *sock) +{ + return CONTAINER_OF(sock - sock->idx, struct esp_data, sockets); +} + +static inline bool esp_socket_in_use(struct esp_socket *sock) +{ + return (sock->flags & ESP_SOCK_IN_USE) != 0; +} + +static inline bool esp_socket_connected(struct esp_socket *sock) +{ + return (sock->flags & ESP_SOCK_CONNECTED) != 0; +} + +static inline void esp_flag_set(struct esp_data *dev, + enum esp_data_flag flag) +{ + dev->flags |= flag; +} + +static inline void esp_flag_clear(struct esp_data *dev, + enum esp_data_flag flag) +{ + dev->flags &= (~flag); +} + +static inline bool esp_flag_is_set(struct esp_data *dev, + enum esp_data_flag flag) +{ + return (dev->flags & flag) != 0; +} + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_WIFI_ESP_H_ */ diff --git a/drivers/wifi/esp/esp_offload.c b/drivers/wifi/esp/esp_offload.c new file mode 100644 index 00000000000000..05fc4a52dd3e2d --- /dev/null +++ b/drivers/wifi/esp/esp_offload.c @@ -0,0 +1,696 @@ +/* + * Copyright (c) 2019 Tobias Svehagen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define LOG_LEVEL CONFIG_WIFI_LOG_LEVEL +#include +LOG_MODULE_REGISTER(wifi_esp_offload); + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "esp.h" + +static int esp_bind(struct net_context *context, const struct sockaddr *addr, + socklen_t addrlen) +{ + struct esp_socket *sock; + + sock = (struct esp_socket *)context->offload_context; + + sock->src.sa_family = addr->sa_family; + + if (IS_ENABLED(CONFIG_NET_IPV4) && addr->sa_family == AF_INET) { + net_ipaddr_copy(&net_sin(&sock->src)->sin_addr, + &net_sin(addr)->sin_addr); + net_sin(&sock->src)->sin_port = net_sin(addr)->sin_port; + } else { + return -EAFNOSUPPORT; + } + + return 0; +} + +static int esp_listen(struct net_context *context, int backlog) +{ + return -ENOTSUP; +} + +static int _sock_connect(struct esp_data *dev, struct esp_socket *sock) +{ + char addr_str[NET_IPV4_ADDR_LEN]; + char connect_msg[100]; + int ret; + + if (!esp_flag_is_set(dev, EDF_STA_CONNECTED)) { + return -ENETUNREACH; + } + + if (sock->ip_proto == IPPROTO_TCP) { + net_addr_ntop(sock->dst.sa_family, + &net_sin(&sock->dst)->sin_addr, + addr_str, sizeof(addr_str)); + snprintk(connect_msg, sizeof(connect_msg), + "AT+CIPSTART=%d,\"TCP\",\"%s\",%d,7200", + sock->link_id, addr_str, + ntohs(net_sin(&sock->dst)->sin_port)); + } else { + net_addr_ntop(sock->dst.sa_family, + &net_sin(&sock->dst)->sin_addr, + addr_str, sizeof(addr_str)); + snprintk(connect_msg, sizeof(connect_msg), + "AT+CIPSTART=%d,\"UDP\",\"%s\",%d", + sock->link_id, addr_str, + ntohs(net_sin(&sock->dst)->sin_port)); + } + + LOG_DBG("link %d, ip_proto %s, addr %s", sock->link_id, + sock->ip_proto == IPPROTO_TCP ? "TCP" : "UDP", + log_strdup(addr_str)); + + ret = modem_cmd_send(&dev->mctx.iface, &dev->mctx.cmd_handler, + NULL, 0, connect_msg, &dev->sem_response, + ESP_CMD_TIMEOUT); + if (ret == 0) { + sock->flags |= ESP_SOCK_CONNECTED; + } else if (ret == -ETIMEDOUT) { + /* FIXME: + * What if the connection finishes after we return from + * here? The caller might think that it can discard the + * socket. Set some flag to indicate that the link should + * be closed if it ever connects? + */ + } + + return ret; +} + +static void esp_connect_work(struct k_work *work) +{ + struct esp_socket *sock; + struct esp_data *dev; + int ret; + + sock = CONTAINER_OF(work, struct esp_socket, connect_work); + dev = esp_socket_to_dev(sock); + + if (!esp_socket_in_use(sock)) { + LOG_DBG("Socket %d not in use", sock->idx); + return; + } + + ret = _sock_connect(dev, sock); + + if (sock->connect_cb) { + sock->connect_cb(sock->context, ret, sock->conn_user_data); + } + +} + +static int esp_connect(struct net_context *context, + const struct sockaddr *addr, + socklen_t addrlen, + net_context_connect_cb_t cb, + s32_t timeout, + void *user_data) +{ + struct esp_socket *sock; + struct esp_data *dev; + int ret; + + sock = (struct esp_socket *)context->offload_context; + dev = esp_socket_to_dev(sock); + + LOG_DBG("link %d, timeout %d", sock->link_id, timeout); + + if (!IS_ENABLED(CONFIG_NET_IPV4) || addr->sa_family != AF_INET) { + return -EAFNOSUPPORT; + } + + if (esp_socket_connected(sock)) { + return -EISCONN; + } + + sock->dst = *addr; + sock->connect_cb = cb; + sock->conn_user_data = user_data; + + if (timeout == K_NO_WAIT) { + k_work_submit_to_queue(&dev->workq, &sock->connect_work); + return 0; + } + + ret = _sock_connect(dev, sock); + + if (esp_socket_connected(sock) && sock->tx_pkt) { + k_work_submit_to_queue(&dev->workq, &sock->send_work); + } + + if (ret != -ETIMEDOUT && cb) { + cb(context, ret, user_data); + } + + return ret; +} + +static int esp_accept(struct net_context *context, + net_tcp_accept_cb_t cb, s32_t timeout, + void *user_data) +{ + return -ENOTSUP; +} + +MODEM_CMD_DIRECT_DEFINE(on_cmd_tx_ready) +{ + struct esp_data *dev = CONTAINER_OF(data, struct esp_data, + cmd_handler_data); + + k_sem_give(&dev->sem_tx_ready); + return len; +} + +MODEM_CMD_DEFINE(on_cmd_send_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; +} + +MODEM_CMD_DEFINE(on_cmd_send_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 int _sock_send(struct esp_data *dev, struct esp_socket *sock) +{ + char cmd_buf[64], addr_str[NET_IPV4_ADDR_LEN]; + int ret, write_len, pkt_len; + struct net_buf *frag; + struct modem_cmd cmds[] = { + MODEM_CMD_DIRECT(">", on_cmd_tx_ready), + MODEM_CMD("SEND OK", on_cmd_send_ok, 0U, ""), + MODEM_CMD("SEND FAIL", on_cmd_send_fail, 0U, ""), + }; + + if (!esp_flag_is_set(dev, EDF_STA_CONNECTED)) { + return -ENETUNREACH; + } + + pkt_len = net_pkt_get_len(sock->tx_pkt); + + LOG_DBG("link %d, len %d", sock->link_id, pkt_len); + + if (sock->ip_proto == IPPROTO_TCP) { + snprintk(cmd_buf, sizeof(cmd_buf), + "AT+CIPSEND=%d,%d", sock->link_id, pkt_len); + } else { + net_addr_ntop(sock->dst.sa_family, + &net_sin(&sock->dst)->sin_addr, + addr_str, sizeof(addr_str)); + snprintk(cmd_buf, sizeof(cmd_buf), + "AT+CIPSEND=%d,%d,\"%s\",%d", + sock->link_id, pkt_len, addr_str, + ntohs(net_sin(&sock->dst)->sin_port)); + } + + k_sem_take(&dev->cmd_handler_data.sem_tx_lock, K_FOREVER); + k_sem_reset(&dev->sem_tx_ready); + + ret = modem_cmd_send_nolock(&dev->mctx.iface, &dev->mctx.cmd_handler, + NULL, 0, cmd_buf, &dev->sem_response, + ESP_CMD_TIMEOUT); + if (ret < 0) { + LOG_DBG("Failed to send command"); + goto out; + } + + ret = modem_cmd_handler_update_cmds(&dev->cmd_handler_data, + cmds, ARRAY_SIZE(cmds), + true); + if (ret < 0) { + goto out; + } + + /* + * After modem handlers have been updated the receive buffer + * needs to be processed again since there might now be a match. + */ + k_sem_give(&dev->iface_data.rx_sem); + + /* Wait for '>' */ + ret = k_sem_take(&dev->sem_tx_ready, 5000); + if (ret < 0) { + LOG_DBG("Timeout waiting for tx"); + goto out; + } + + frag = sock->tx_pkt->frags; + while (frag && pkt_len) { + write_len = MIN(pkt_len, frag->len); + dev->mctx.iface.write(&dev->mctx.iface, frag->data, write_len); + pkt_len -= write_len; + frag = frag->frags; + } + + /* Wait for 'SEND OK' or 'SEND FAIL' */ + k_sem_reset(&dev->sem_response); + ret = k_sem_take(&dev->sem_response, ESP_CMD_TIMEOUT); + if (ret < 0) { + LOG_DBG("No send response"); + goto out; + } + + ret = modem_cmd_handler_get_error(&dev->cmd_handler_data); + if (ret != 0) { + LOG_DBG("Failed to send data"); + } + +out: + (void)modem_cmd_handler_update_cmds(&dev->cmd_handler_data, + NULL, 0U, false); + k_sem_give(&dev->cmd_handler_data.sem_tx_lock); + + net_pkt_unref(sock->tx_pkt); + sock->tx_pkt = NULL; + + return ret; +} + +static void esp_send_work(struct k_work *work) +{ + struct esp_socket *sock; + struct esp_data *dev; + int ret = 0; + + sock = CONTAINER_OF(work, struct esp_socket, send_work); + dev = esp_socket_to_dev(sock); + + if (!esp_socket_in_use(sock)) { + LOG_DBG("Socket %d not in use", sock->idx); + return; + } + + ret = _sock_send(dev, sock); + if (ret < 0) { + LOG_ERR("Failed to send data: link %d, ret %d", sock->link_id, + ret); + } + + if (sock->send_cb) { + sock->send_cb(sock->context, ret, sock->send_user_data); + } +} + +static int esp_sendto(struct net_pkt *pkt, + const struct sockaddr *dst_addr, + socklen_t addrlen, + net_context_send_cb_t cb, + s32_t timeout, + void *user_data) +{ + struct net_context *context; + struct esp_socket *sock; + struct esp_data *dev; + int ret = 0; + + context = pkt->context; + sock = (struct esp_socket *)context->offload_context; + dev = esp_socket_to_dev(sock); + + LOG_DBG("link %d, timeout %d", sock->link_id, timeout); + + if (sock->tx_pkt) { + return -EBUSY; + } + + if (sock->type == SOCK_STREAM) { + if (!esp_socket_connected(sock)) { + return -ENOTCONN; + } + } else { + if (!esp_socket_connected(sock)) { + if (!dst_addr) { + return -ENOTCONN; + } + + /* Use a timeout of 5000 ms here even though the + * timeout parameter might be different. We want to + * have a valid link id before proceeding. + */ + ret = esp_connect(context, dst_addr, addrlen, NULL, + K_SECONDS(5), NULL); + if (ret < 0) { + return ret; + } + } else if (dst_addr && memcmp(dst_addr, &sock->dst, addrlen)) { + /* This might be unexpected behaviour but the ESP + * doesn't support changing endpoint. + */ + return -EISCONN; + } + } + + sock->tx_pkt = pkt; + sock->send_cb = cb; + sock->send_user_data = user_data; + + if (timeout == K_NO_WAIT) { + k_work_submit_to_queue(&dev->workq, &sock->send_work); + return 0; + } + + /* + * FIXME: + * In _modem_cmd_send() in modem_cmd_handler.c it can happen that a + * response, eg 'OK', is received before k_sem_reset(sem) is called. + * If the sending thread can be preempted, the command handler could + * run and call k_sem_give(). This will cause a timeout and the send + * will fail. This can be avoided by locking the scheduler. Maybe this + * should be done in _modem_cmd_send() instead. + */ + k_sched_lock(); + ret = _sock_send(dev, sock); + k_sched_unlock(); + + if (ret < 0) { + LOG_ERR("Failed to send data: link %d, ret %d", sock->link_id, + ret); + } + + if (cb) { + cb(context, ret, user_data); + } + + return ret; +} + +static int esp_send(struct net_pkt *pkt, + net_context_send_cb_t cb, + s32_t timeout, + void *user_data) +{ + return esp_sendto(pkt, NULL, 0, cb, timeout, user_data); +} + +#define CIPRECVDATA_CMD_MIN_LEN (sizeof("+CIPRECVDATA,L:") - 1) +#define CIPRECVDATA_CMD_MAX_LEN (sizeof("+CIPRECVDATA,LLLL:") - 1) +MODEM_CMD_DIRECT_DEFINE(on_cmd_ciprecvdata) +{ + char *endptr, cmd_buf[CIPRECVDATA_CMD_MAX_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; + + dev = CONTAINER_OF(data, struct esp_data, cmd_handler_data); + + sock = dev->rx_sock; + + frags_len = net_buf_frags_len(data->rx_buf); + if (frags_len < CIPRECVDATA_CMD_MIN_LEN) { + ret = -EAGAIN; + goto out; + } + + match_len = net_buf_linearize(cmd_buf, CIPRECVDATA_CMD_MAX_LEN, + data->rx_buf, 0, CIPRECVDATA_CMD_MAX_LEN); + + cmd_buf[match_len] = 0; + + data_len = strtol(&cmd_buf[len], &endptr, 10); + if (endptr == &cmd_buf[len] || + (*endptr == 0 && match_len >= CIPRECVDATA_CMD_MAX_LEN) || + data_len > sock->bytes_avail) { + LOG_ERR("Invalid cmd: %s", log_strdup(cmd_buf)); + ret = len; + goto out; + } else if (*endptr == 0) { + ret = -EAGAIN; + goto out; + } else if (*endptr != ':') { + LOG_ERR("Invalid end of cmd: 0x%02x != 0x%02x", *endptr, ':'); + ret = len; + goto out; + } + + *endptr = 0; + + /* data_offset is the offset to where the actual data starts */ + data_offset = strlen(cmd_buf) + 1; + + /* FIXME: Inefficient way of waiting for data */ + if (data_offset + data_len > frags_len) { + ret = -EAGAIN; + goto out; + } + + sock->bytes_avail -= data_len; + ret = data_offset + data_len; + + 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; +} + +static void esp_recvdata_work(struct k_work *work) +{ + struct esp_socket *sock; + struct esp_data *dev; + int len = CIPRECVDATA_MAX_LEN, ret; + char cmd[32]; + struct modem_cmd cmds[] = { + MODEM_CMD_DIRECT("+CIPRECVDATA,", on_cmd_ciprecvdata), + }; + + sock = CONTAINER_OF(work, struct esp_socket, recvdata_work); + dev = esp_socket_to_dev(sock); + + if (!esp_socket_in_use(sock)) { + LOG_DBG("Socket %d not in use", sock->idx); + return; + } + + LOG_DBG("%d bytes available on link %d", sock->bytes_avail, + sock->link_id); + + if (sock->bytes_avail == 0) { + LOG_WRN("No data available on link %d", sock->link_id); + return; + } else if (len > sock->bytes_avail) { + len = sock->bytes_avail; + } + + dev->rx_sock = sock; + + snprintk(cmd, sizeof(cmd), "AT+CIPRECVDATA=%d,%d", sock->link_id, len); + + ret = modem_cmd_send(&dev->mctx.iface, &dev->mctx.cmd_handler, + cmds, ARRAY_SIZE(cmds), cmd, &dev->sem_response, + ESP_CMD_TIMEOUT); + if (ret < 0) { + LOG_ERR("Error during rx: link %d, ret %d", sock->link_id, + ret); + } else if (sock->bytes_avail > 0) { + k_work_submit_to_queue(&dev->workq, &sock->recvdata_work); + } +} + + +static void esp_recv_work(struct k_work *work) +{ + struct esp_socket *sock; + struct esp_data *dev; + struct net_pkt *pkt; + + sock = CONTAINER_OF(work, struct esp_socket, recv_work); + dev = esp_socket_to_dev(sock); + + if (!esp_socket_in_use(sock)) { + LOG_DBG("Socket %d not in use", sock->idx); + return; + } + + pkt = k_fifo_get(&sock->fifo_rx_pkt, K_NO_WAIT); + while (pkt) { + if (sock->recv_cb) { + sock->recv_cb(sock->context, pkt, NULL, NULL, + 0, sock->recv_user_data); + k_sem_give(&sock->sem_data_ready); + } else { + /* Discard */ + net_pkt_unref(pkt); + } + + pkt = k_fifo_get(&sock->fifo_rx_pkt, K_NO_WAIT); + } + + /* Should we notify that the socket has been closed? */ + if (!esp_socket_connected(sock) && sock->bytes_avail == 0 && + sock->recv_cb) { + sock->recv_cb(sock->context, NULL, NULL, NULL, 0, + sock->recv_user_data); + k_sem_give(&sock->sem_data_ready); + } +} + +static int esp_recv(struct net_context *context, + net_context_recv_cb_t cb, + s32_t timeout, + void *user_data) +{ + struct esp_socket *sock; + struct esp_data *dev; + int ret; + + sock = (struct esp_socket *)context->offload_context; + dev = esp_socket_to_dev(sock); + + LOG_DBG("link_id %d, timeout %d, cb 0x%x, data 0x%x", sock->link_id, + timeout, (int)cb, (int)user_data); + + sock->recv_cb = cb; + sock->recv_user_data = user_data; + k_sem_reset(&sock->sem_data_ready); + + if (timeout == K_NO_WAIT) { + return 0; + } + + ret = k_sem_take(&sock->sem_data_ready, timeout); + + sock->recv_cb = NULL; + + return ret; +} + +static int esp_put(struct net_context *context) +{ + struct esp_socket *sock; + struct esp_data *dev; + struct net_pkt *pkt; + char cmd_buf[16]; + int ret; + + sock = (struct esp_socket *)context->offload_context; + dev = esp_socket_to_dev(sock); + + if (esp_socket_connected(sock)) { + snprintk(cmd_buf, sizeof(cmd_buf), "AT+CIPCLOSE=%d", + sock->link_id); + ret = modem_cmd_send(&dev->mctx.iface, &dev->mctx.cmd_handler, + NULL, 0, cmd_buf, &dev->sem_response, + ESP_CMD_TIMEOUT); + if (ret < 0) { + /* FIXME: + * If link doesn't close correctly here, esp_get could + * allocate a socket with an already open link. + */ + LOG_ERR("Failed to close link %d, ret %d", + sock->link_id, ret); + } + } + + sock->connect_cb = NULL; + sock->recv_cb = NULL; + sock->send_cb = NULL; + sock->tx_pkt = NULL; + + /* Drain rxfifo */ + for (pkt = k_fifo_get(&sock->fifo_rx_pkt, K_NO_WAIT); + pkt != NULL; + pkt = k_fifo_get(&sock->fifo_rx_pkt, K_NO_WAIT)) { + net_pkt_unref(pkt); + } + + esp_socket_put(sock); + + return 0; +} + +static int esp_get(sa_family_t family, + enum net_sock_type type, + enum net_ip_protocol ip_proto, + struct net_context **context) +{ + struct esp_socket *sock; + struct esp_data *dev; + + LOG_DBG(""); + + if (family != AF_INET) { + return -EAFNOSUPPORT; + } + + /* FIXME: + * iface has not yet been assigned to context so there is currently + * no way to know which interface to operate on. Therefore this driver + * only supports one device node. + */ + dev = &esp_driver_data; + + sock = esp_socket_get(dev); + if (sock == NULL) { + return -ENOMEM; + } + + k_work_init(&sock->connect_work, esp_connect_work); + k_work_init(&sock->send_work, esp_send_work); + k_work_init(&sock->recv_work, esp_recv_work); + k_work_init(&sock->recvdata_work, esp_recvdata_work); + sock->family = family; + sock->type = type; + sock->ip_proto = ip_proto; + sock->context = *context; + (*context)->offload_context = sock; + + return 0; +} + +static struct net_offload esp_offload = { + .get = esp_get, + .bind = esp_bind, + .listen = esp_listen, + .connect = esp_connect, + .accept = esp_accept, + .send = esp_send, + .sendto = esp_sendto, + .recv = esp_recv, + .put = esp_put, +}; + +int esp_offload_init(struct net_if *iface) +{ + iface->if_dev->offload = &esp_offload; + + return 0; +} diff --git a/drivers/wifi/esp/esp_socket.c b/drivers/wifi/esp/esp_socket.c new file mode 100644 index 00000000000000..b933bcf9b9ee2d --- /dev/null +++ b/drivers/wifi/esp/esp_socket.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2019 Tobias Svehagen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "esp.h" + +/* esp_data->mtx_sock should be held */ +struct esp_socket *esp_socket_get(struct esp_data *data) +{ + struct esp_socket *sock; + int i; + + for (i = 0; i < ARRAY_SIZE(data->sockets); ++i) { + sock = &data->sockets[i]; + if (!esp_socket_in_use(sock)) { + break; + } + } + + if (esp_socket_in_use(sock)) { + return NULL; + } + + sock->link_id = i; + sock->flags |= ESP_SOCK_IN_USE; + + return sock; +} + +/* esp_data->mtx_sock should be held */ +int esp_socket_put(struct esp_socket *sock) +{ + sock->flags = 0; + sock->link_id = INVALID_LINK_ID; + return 0; +} + +struct esp_socket *esp_socket_from_link_id(struct esp_data *data, + u8_t link_id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(data->sockets); ++i) { + if (esp_socket_in_use(&data->sockets[i]) && + data->sockets[i].link_id == link_id) { + return &data->sockets[i]; + } + } + + return NULL; +} + +void esp_socket_init(struct esp_data *data) +{ + struct esp_socket *sock; + int i; + + for (i = 0; i < ARRAY_SIZE(data->sockets); ++i) { + sock = &data->sockets[i]; + sock->idx = i; + sock->link_id = INVALID_LINK_ID; + sock->flags = 0; + k_sem_init(&sock->sem_data_ready, 0, 1); + k_fifo_init(&sock->fifo_rx_pkt); + } +} diff --git a/dts/bindings/wifi/espressif,esp.yaml b/dts/bindings/wifi/espressif,esp.yaml new file mode 100644 index 00000000000000..c53a058639317f --- /dev/null +++ b/dts/bindings/wifi/espressif,esp.yaml @@ -0,0 +1,23 @@ +# Copyright (c) 2019 Tobias Svehagen +# SPDX-License-Identifier: Apache-2.0 + +description: Espressif ESP8266/ESP32 WiFi modem (AT Commands) + +compatible: "espressif,esp" + +include: uart-device.yaml + +properties: + label: + required: true + + wifi-reset-gpios: + type: phandle-array + required: false + + wifi-uart-speed: + type: int + required: true + + wifi-uart-flow-control: + type: boolean