drivers: wifi: Add ESP8266 and ESP32 wifi modem driver
This adds support for the Espressif ESP8266 and ESP32 devices to be used as peripherals on a UART. There are two main AT command versions that can be selected, 1.7 and 2.0. Since they behave a bit different it is important to select the one that matches the used in the firmware on your device. When downloading large amounts of data it is highly recommended to enable CONFIG_ESP_PASSIVE_TCP and flow control on the UART so that data is not lost due to UART speed or receive buffer size. Currently unsupported: - Changing UDP endpoint with a sendto() - Bind to a specific local port - Server socket operations, ie listen() and accept() Official AT firmware for ESP8266 and ESP32 can be found at: https://github.com/espressif/esp-at Signed-off-by: Tobias Svehagen <tobias.svehagen@gmail.com>
This commit is contained in:
parent
aa85756bf5
commit
7b5f6bc660
9 changed files with 1967 additions and 0 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
15
drivers/wifi/esp/CMakeLists.txt
Normal file
15
drivers/wifi/esp/CMakeLists.txt
Normal file
|
@ -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()
|
70
drivers/wifi/esp/Kconfig.esp
Normal file
70
drivers/wifi/esp/Kconfig.esp
Normal file
|
@ -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
|
874
drivers/wifi/esp/esp.c
Normal file
874
drivers/wifi/esp/esp.c
Normal file
|
@ -0,0 +1,874 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Tobias Svehagen
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#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 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:"<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 *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,<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;
|
||||
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);
|
217
drivers/wifi/esp/esp.h
Normal file
217
drivers/wifi/esp/esp.h
Normal file
|
@ -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 <kernel.h>
|
||||
#include <net/net_context.h>
|
||||
#include <net/net_if.h>
|
||||
#include <net/net_ip.h>
|
||||
#include <net/net_pkt.h>
|
||||
#include <net/wifi_mgmt.h>
|
||||
|
||||
#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_ */
|
696
drivers/wifi/esp/esp_offload.c
Normal file
696
drivers/wifi/esp/esp_offload.c
Normal file
|
@ -0,0 +1,696 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Tobias Svehagen
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#define LOG_LEVEL CONFIG_WIFI_LOG_LEVEL
|
||||
#include <logging/log.h>
|
||||
LOG_MODULE_REGISTER(wifi_esp_offload);
|
||||
|
||||
#include <zephyr.h>
|
||||
#include <kernel.h>
|
||||
#include <device.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <net/net_pkt.h>
|
||||
#include <net/net_if.h>
|
||||
#include <net/net_offload.h>
|
||||
|
||||
#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;
|
||||
}
|
70
drivers/wifi/esp/esp_socket.c
Normal file
70
drivers/wifi/esp/esp_socket.c
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Tobias Svehagen
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
23
dts/bindings/wifi/espressif,esp.yaml
Normal file
23
dts/bindings/wifi/espressif,esp.yaml
Normal file
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue