You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
238 lines
5.4 KiB
238 lines
5.4 KiB
/* |
|
* Copyright (c) 2019 Tobias Svehagen |
|
* Copyright (c) 2020 Grinn |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include "esp.h" |
|
|
|
#include <logging/log.h> |
|
LOG_MODULE_DECLARE(wifi_esp_at, CONFIG_WIFI_LOG_LEVEL); |
|
|
|
#define RX_NET_PKT_ALLOC_TIMEOUT \ |
|
K_MSEC(CONFIG_WIFI_ESP_AT_RX_NET_PKT_ALLOC_TIMEOUT) |
|
|
|
struct esp_workq_flush_data { |
|
struct k_work work; |
|
struct k_sem sem; |
|
}; |
|
|
|
struct esp_socket *esp_socket_get(struct esp_data *data, |
|
struct net_context *context) |
|
{ |
|
struct esp_socket *sock = data->sockets; |
|
struct esp_socket *sock_end = sock + ARRAY_SIZE(data->sockets); |
|
|
|
for (; sock < sock_end; sock++) { |
|
if (!esp_socket_flags_test_and_set(sock, ESP_SOCK_IN_USE)) { |
|
/* here we should configure all the stuff needed */ |
|
sock->context = context; |
|
context->offload_context = sock; |
|
|
|
sock->connect_cb = NULL; |
|
sock->recv_cb = NULL; |
|
|
|
atomic_inc(&sock->refcount); |
|
|
|
return sock; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
int esp_socket_put(struct esp_socket *sock) |
|
{ |
|
atomic_clear(&sock->flags); |
|
|
|
return 0; |
|
} |
|
|
|
struct esp_socket *esp_socket_ref(struct esp_socket *sock) |
|
{ |
|
atomic_val_t ref; |
|
|
|
do { |
|
ref = atomic_get(&sock->refcount); |
|
if (!ref) { |
|
return NULL; |
|
} |
|
} while (!atomic_cas(&sock->refcount, ref, ref + 1)); |
|
|
|
return sock; |
|
} |
|
|
|
void esp_socket_unref(struct esp_socket *sock) |
|
{ |
|
atomic_val_t ref; |
|
|
|
do { |
|
ref = atomic_get(&sock->refcount); |
|
if (!ref) { |
|
return; |
|
} |
|
} while (!atomic_cas(&sock->refcount, ref, ref - 1)); |
|
|
|
k_sem_give(&sock->sem_free); |
|
} |
|
|
|
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 = i; |
|
atomic_clear(&sock->refcount); |
|
atomic_clear(&sock->flags); |
|
k_mutex_init(&sock->lock); |
|
k_sem_init(&sock->sem_data_ready, 0, 1); |
|
k_work_init(&sock->connect_work, esp_connect_work); |
|
k_work_init(&sock->recvdata_work, esp_recvdata_work); |
|
k_work_init(&sock->close_work, esp_close_work); |
|
k_work_init(&sock->send_work, esp_send_work); |
|
k_fifo_init(&sock->tx_fifo); |
|
} |
|
} |
|
|
|
static struct net_pkt *esp_socket_prepare_pkt(struct esp_socket *sock, |
|
struct net_buf *src, |
|
size_t offset, size_t len) |
|
{ |
|
struct esp_data *data = esp_socket_to_dev(sock); |
|
struct net_buf *frag; |
|
struct net_pkt *pkt; |
|
size_t to_copy; |
|
|
|
pkt = net_pkt_rx_alloc_with_buffer(data->net_iface, len, AF_UNSPEC, |
|
0, RX_NET_PKT_ALLOC_TIMEOUT); |
|
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_set_context(pkt, sock->context); |
|
net_pkt_cursor_init(pkt); |
|
|
|
return pkt; |
|
} |
|
|
|
void esp_socket_rx(struct esp_socket *sock, struct net_buf *buf, |
|
size_t offset, size_t len) |
|
{ |
|
struct net_pkt *pkt; |
|
atomic_val_t flags; |
|
|
|
flags = esp_socket_flags(sock); |
|
|
|
if (!(flags & ESP_SOCK_CONNECTED) || |
|
(flags & ESP_SOCK_CLOSE_PENDING)) { |
|
LOG_DBG("Received data on closed link %d", sock->link_id); |
|
return; |
|
} |
|
|
|
pkt = esp_socket_prepare_pkt(sock, buf, offset, len); |
|
if (!pkt) { |
|
LOG_ERR("Failed to get net_pkt: len %zu", len); |
|
if (esp_socket_type(sock) == SOCK_STREAM) { |
|
if (!esp_socket_flags_test_and_set(sock, |
|
ESP_SOCK_CLOSE_PENDING)) { |
|
esp_socket_work_submit(sock, &sock->close_work); |
|
} |
|
} |
|
return; |
|
} |
|
|
|
#ifdef CONFIG_NET_SOCKETS |
|
/* We need to claim the net_context mutex here so that the ordering of |
|
* net_context and socket mutex claims matches the TX code path. Failure |
|
* to do so can lead to deadlocks. |
|
*/ |
|
if (sock->context->cond.lock) { |
|
k_mutex_lock(sock->context->cond.lock, K_FOREVER); |
|
} |
|
#endif /* CONFIG_NET_SOCKETS */ |
|
k_mutex_lock(&sock->lock, K_FOREVER); |
|
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); |
|
} |
|
k_mutex_unlock(&sock->lock); |
|
#ifdef CONFIG_NET_SOCKETS |
|
if (sock->context->cond.lock) { |
|
k_mutex_unlock(sock->context->cond.lock); |
|
} |
|
#endif /* CONFIG_NET_SOCKETS */ |
|
} |
|
|
|
void esp_socket_close(struct esp_socket *sock) |
|
{ |
|
struct esp_data *dev = esp_socket_to_dev(sock); |
|
char cmd_buf[sizeof("AT+CIPCLOSE=0")]; |
|
int ret; |
|
|
|
snprintk(cmd_buf, sizeof(cmd_buf), "AT+CIPCLOSE=%d", |
|
sock->link_id); |
|
ret = esp_cmd_send(dev, NULL, 0, cmd_buf, 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); |
|
} |
|
} |
|
|
|
static void esp_workq_flush_work(struct k_work *work) |
|
{ |
|
struct esp_workq_flush_data *flush = |
|
CONTAINER_OF(work, struct esp_workq_flush_data, work); |
|
|
|
k_sem_give(&flush->sem); |
|
} |
|
|
|
void esp_socket_workq_stop_and_flush(struct esp_socket *sock) |
|
{ |
|
struct esp_workq_flush_data flush; |
|
|
|
k_work_init(&flush.work, esp_workq_flush_work); |
|
k_sem_init(&flush.sem, 0, 1); |
|
|
|
k_mutex_lock(&sock->lock, K_FOREVER); |
|
esp_socket_flags_set(sock, ESP_SOCK_WORKQ_STOPPED); |
|
__esp_socket_work_submit(sock, &flush.work); |
|
k_mutex_unlock(&sock->lock); |
|
|
|
k_sem_take(&flush.sem, K_FOREVER); |
|
}
|
|
|