| /** |
| * Copyright (c) 2018 Linaro |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| #define LOG_LEVEL CONFIG_WIFI_LOG_LEVEL |
| #include <logging/log.h> |
| LOG_MODULE_REGISTER(wifi_eswifi_offload); |
| |
| #include <zephyr.h> |
| #include <kernel.h> |
| #include <device.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| |
| #include <net/net_pkt.h> |
| #include <net/net_if.h> |
| |
| #include "eswifi.h" |
| |
| static inline int __select_socket(struct eswifi_dev *eswifi, u8_t idx) |
| { |
| snprintf(eswifi->buf, sizeof(eswifi->buf), "P0=%d\r", idx); |
| return eswifi_at_cmd(eswifi, eswifi->buf); |
| } |
| |
| static int __read_data(struct eswifi_dev *eswifi, size_t len, char **data) |
| { |
| char cmd[] = "R0\r"; |
| char size[] = "R1=9999\r"; |
| char timeout[] = "R2=30000\r"; |
| int ret; |
| |
| /* Set max read size */ |
| snprintf(size, sizeof(size), "R1=%u\r", len); |
| ret = eswifi_at_cmd(eswifi, size); |
| if (ret < 0) { |
| LOG_ERR("Unable to set read size"); |
| return -EIO; |
| } |
| |
| /* Set timeout */ |
| snprintf(timeout, sizeof(timeout), "R2=%u\r", 30); /* 30 ms */ |
| ret = eswifi_at_cmd(eswifi, timeout); |
| if (ret < 0) { |
| LOG_ERR("Unable to set timeout"); |
| return -EIO; |
| } |
| |
| return eswifi_at_cmd_rsp(eswifi, cmd, data); |
| } |
| |
| static inline |
| struct eswifi_dev *eswifi_socket_to_dev(struct eswifi_off_socket *socket) |
| { |
| return CONTAINER_OF(socket - socket->index, struct eswifi_dev, socket); |
| } |
| |
| static void eswifi_off_read_work(struct k_work *work) |
| { |
| struct eswifi_off_socket *socket; |
| struct eswifi_dev *eswifi; |
| struct net_pkt *pkt; |
| int err, len; |
| char *data; |
| |
| LOG_DBG(""); |
| |
| socket = CONTAINER_OF(work, struct eswifi_off_socket, read_work); |
| eswifi = eswifi_socket_to_dev(socket); |
| |
| eswifi_lock(eswifi); |
| |
| if (socket->state != ESWIFI_SOCKET_STATE_CONNECTED) { |
| goto done; |
| } |
| |
| __select_socket(eswifi, socket->index); |
| |
| len = __read_data(eswifi, 1460, &data); /* 1460 is max size */ |
| if (len <= 0 || !socket->recv_cb) { |
| goto done; |
| } |
| |
| LOG_ERR("payload sz = %d", len); |
| |
| pkt = net_pkt_rx_alloc_with_buffer(eswifi->iface, len, |
| AF_UNSPEC, 0, K_NO_WAIT); |
| if (!pkt) { |
| LOG_ERR("Cannot allocate rx packet"); |
| goto done; |
| } |
| |
| if (!net_pkt_write(pkt, data, len)) { |
| LOG_WRN("Incomplete buffer copy"); |
| } |
| |
| socket->recv_cb(socket->context, pkt, |
| NULL, NULL, 0, socket->user_data); |
| k_sem_give(&socket->read_sem); |
| k_yield(); |
| |
| done: |
| err = k_delayed_work_submit_to_queue(&eswifi->work_q, |
| &socket->read_work, |
| 500); |
| if (err) { |
| LOG_ERR("Rescheduling socket read error"); |
| } |
| |
| eswifi_unlock(eswifi); |
| } |
| |
| static int eswifi_off_bind(struct net_context *context, |
| const struct sockaddr *addr, |
| socklen_t addrlen) |
| { |
| struct eswifi_off_socket *socket = context->offload_context; |
| struct eswifi_dev *eswifi = eswifi_by_iface_idx(context->iface); |
| int err; |
| |
| if (addr->sa_family != AF_INET) { |
| LOG_ERR("Only AF_INET is supported!"); |
| return -EPFNOSUPPORT; |
| } |
| |
| LOG_DBG(""); |
| |
| eswifi_lock(eswifi); |
| |
| __select_socket(eswifi, socket->index); |
| |
| /* Set Local Port */ |
| snprintf(eswifi->buf, sizeof(eswifi->buf), "P2=%d\r", |
| (u16_t)sys_be16_to_cpu(net_sin(addr)->sin_port)); |
| err = eswifi_at_cmd(eswifi, eswifi->buf); |
| if (err < 0) { |
| LOG_ERR("Unable to set local port"); |
| eswifi_unlock(eswifi); |
| return -EIO; |
| } |
| |
| eswifi_unlock(eswifi); |
| |
| return 0; |
| } |
| |
| static int eswifi_off_listen(struct net_context *context, int backlog) |
| { |
| struct eswifi_off_socket *socket = context->offload_context; |
| struct eswifi_dev *eswifi = eswifi_by_iface_idx(context->iface); |
| char cmd[] = "P5=1\r"; |
| int err; |
| |
| /* TODO */ |
| LOG_ERR(""); |
| |
| eswifi_lock(eswifi); |
| |
| __select_socket(eswifi, socket->index); |
| |
| /* Start TCP Server */ |
| err = eswifi_at_cmd(eswifi, cmd); |
| if (err < 0) { |
| LOG_ERR("Unable to start TCP server"); |
| } |
| |
| eswifi_unlock(eswifi); |
| |
| return err; |
| } |
| |
| static int __eswifi_off_connect(struct eswifi_dev *eswifi, |
| struct eswifi_off_socket *socket) |
| { |
| struct sockaddr *addr = &socket->peer_addr; |
| struct in_addr *sin_addr = &net_sin(addr)->sin_addr; |
| int err; |
| |
| LOG_DBG(""); |
| |
| __select_socket(eswifi, socket->index); |
| |
| /* Set Remote IP */ |
| snprintf(eswifi->buf, sizeof(eswifi->buf), "P3=%u.%u.%u.%u\r", |
| sin_addr->s4_addr[0], sin_addr->s4_addr[1], |
| sin_addr->s4_addr[2], sin_addr->s4_addr[3]); |
| |
| err = eswifi_at_cmd(eswifi, eswifi->buf); |
| if (err < 0) { |
| LOG_ERR("Unable to set remote ip"); |
| return -EIO; |
| } |
| |
| /* Set Remote Port */ |
| snprintf(eswifi->buf, sizeof(eswifi->buf), "P4=%d\r", |
| (u16_t)sys_be16_to_cpu(net_sin(addr)->sin_port)); |
| err = eswifi_at_cmd(eswifi, eswifi->buf); |
| if (err < 0) { |
| LOG_ERR("Unable to set remote port"); |
| return -EIO; |
| } |
| |
| /* Start TCP client */ |
| snprintf(eswifi->buf, sizeof(eswifi->buf), "P6=1\r"); |
| err = eswifi_at_cmd(eswifi, eswifi->buf); |
| if (err < 0) { |
| LOG_ERR("Unable to connect"); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static void eswifi_off_connect_work(struct k_work *work) |
| { |
| struct eswifi_off_socket *socket; |
| net_context_connect_cb_t cb; |
| struct net_context *context; |
| struct eswifi_dev *eswifi; |
| void *user_data; |
| int err; |
| |
| socket = CONTAINER_OF(work, struct eswifi_off_socket, connect_work); |
| eswifi = eswifi_socket_to_dev(socket); |
| |
| eswifi_lock(eswifi); |
| |
| cb = socket->conn_cb; |
| context = socket->context; |
| user_data = socket->user_data; |
| |
| err = __eswifi_off_connect(eswifi, socket); |
| if (!err) { |
| socket->state = ESWIFI_SOCKET_STATE_CONNECTED; |
| } else { |
| socket->state = ESWIFI_SOCKET_STATE_NONE; |
| } |
| |
| eswifi_unlock(eswifi); |
| |
| if (cb) { |
| cb(context, err, user_data); |
| } |
| } |
| |
| static int eswifi_off_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 eswifi_off_socket *socket = context->offload_context; |
| struct eswifi_dev *eswifi = eswifi_by_iface_idx(context->iface); |
| int err; |
| |
| LOG_DBG("timeout=%d", timeout); |
| |
| if (addr->sa_family != AF_INET) { |
| LOG_ERR("Only AF_INET is supported!"); |
| return -EPFNOSUPPORT; |
| } |
| |
| eswifi_lock(eswifi); |
| |
| if (socket->state != ESWIFI_SOCKET_STATE_NONE) { |
| eswifi_unlock(eswifi); |
| return -EBUSY; |
| } |
| |
| socket->peer_addr = *addr; |
| socket->user_data = user_data; |
| socket->state = ESWIFI_SOCKET_STATE_CONNECTING; |
| |
| if (timeout == K_NO_WAIT) { |
| /* async */ |
| k_work_submit_to_queue(&eswifi->work_q, &socket->connect_work); |
| eswifi_unlock(eswifi); |
| return 0; |
| } |
| |
| err = __eswifi_off_connect(eswifi, socket); |
| if (!err) { |
| socket->state = ESWIFI_SOCKET_STATE_CONNECTED; |
| } else { |
| socket->state = ESWIFI_SOCKET_STATE_NONE; |
| } |
| |
| eswifi_unlock(eswifi); |
| |
| if (cb) { |
| cb(context, err, user_data); |
| } |
| |
| return err; |
| } |
| |
| static int eswifi_off_accept(struct net_context *context, |
| net_tcp_accept_cb_t cb, s32_t timeout, |
| void *user_data) |
| { |
| /* TODO */ |
| LOG_DBG(""); |
| return -ENOTSUP; |
| } |
| |
| static int __eswifi_off_send_pkt(struct eswifi_dev *eswifi, |
| struct eswifi_off_socket *socket) |
| { |
| struct net_pkt *pkt = socket->tx_pkt; |
| unsigned int bytes; |
| int err, offset; |
| |
| LOG_DBG(""); |
| |
| if (!pkt) { |
| return -EINVAL; |
| } |
| |
| bytes = net_pkt_get_len(pkt); |
| |
| __select_socket(eswifi, socket->index); |
| |
| /* header */ |
| snprintf(eswifi->buf, sizeof(eswifi->buf), "S3=%u\r", bytes); |
| offset = strlen(eswifi->buf); |
| |
| /* copy payload */ |
| if (net_pkt_read(pkt, &eswifi->buf[offset], bytes)) { |
| return -ENOBUFS; |
| } |
| |
| offset += bytes; |
| |
| err = eswifi_request(eswifi, eswifi->buf, offset + 1, |
| eswifi->buf, sizeof(eswifi->buf)); |
| if (err < 0) { |
| LOG_ERR("Unable to send data"); |
| return -EIO; |
| } |
| |
| net_pkt_unref(pkt); |
| |
| return 0; |
| } |
| |
| static void eswifi_off_send_work(struct k_work *work) |
| { |
| struct eswifi_off_socket *socket; |
| net_context_send_cb_t cb; |
| struct net_context *context; |
| struct eswifi_dev *eswifi; |
| void *user_data; |
| int err; |
| |
| socket = CONTAINER_OF(work, struct eswifi_off_socket, connect_work); |
| eswifi = eswifi_socket_to_dev(socket); |
| |
| eswifi_lock(eswifi); |
| |
| user_data = socket->user_data; |
| cb = socket->send_cb; |
| context = socket->context; |
| |
| err = __eswifi_off_send_pkt(eswifi, socket); |
| socket->tx_pkt = NULL; |
| |
| eswifi_unlock(eswifi); |
| |
| if (cb) { |
| cb(context, err, user_data); |
| } |
| } |
| |
| static int eswifi_off_send(struct net_pkt *pkt, |
| net_context_send_cb_t cb, |
| s32_t timeout, |
| void *user_data) |
| { |
| struct eswifi_off_socket *socket = pkt->context->offload_context; |
| struct eswifi_dev *eswifi = eswifi_by_iface_idx(socket->context->iface); |
| int err; |
| |
| LOG_DBG("timeout=%d", timeout); |
| |
| eswifi_lock(eswifi); |
| |
| if (socket->state != ESWIFI_SOCKET_STATE_CONNECTED) { |
| eswifi_unlock(eswifi); |
| return -ENOTCONN; |
| } |
| |
| if (socket->tx_pkt) { |
| eswifi_unlock(eswifi); |
| return -EBUSY; |
| } |
| socket->tx_pkt = pkt; |
| |
| if (timeout == K_NO_WAIT) { |
| socket->user_data = user_data; |
| socket->send_cb = cb; |
| |
| k_work_submit_to_queue(&eswifi->work_q, &socket->send_work); |
| |
| eswifi_unlock(eswifi); |
| |
| return 0; |
| } |
| |
| err = __eswifi_off_send_pkt(eswifi, socket); |
| socket->tx_pkt = NULL; |
| |
| eswifi_unlock(eswifi); |
| |
| if (cb) { |
| cb(socket->context, err, user_data); |
| } |
| |
| return err; |
| } |
| |
| static int eswifi_off_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) |
| { |
| /* TODO */ |
| LOG_DBG(""); |
| return -ENOTSUP; |
| } |
| |
| static int eswifi_off_recv(struct net_context *context, |
| net_context_recv_cb_t cb, |
| s32_t timeout, |
| void *user_data) |
| { |
| struct eswifi_off_socket *socket = context->offload_context; |
| struct eswifi_dev *eswifi = eswifi_by_iface_idx(context->iface); |
| int err; |
| |
| |
| LOG_DBG(""); |
| |
| eswifi_lock(eswifi); |
| socket->recv_cb = cb; |
| socket->user_data = user_data; |
| k_sem_reset(&socket->read_sem); |
| eswifi_unlock(eswifi); |
| |
| if (timeout == K_NO_WAIT) |
| return 0; |
| |
| err = k_sem_take(&socket->read_sem, timeout); |
| |
| /* Unregister cakkback */ |
| eswifi_lock(eswifi); |
| socket->recv_cb = NULL; |
| eswifi_unlock(eswifi); |
| |
| return err; |
| } |
| |
| static int eswifi_off_put(struct net_context *context) |
| { |
| struct eswifi_off_socket *socket = context->offload_context; |
| struct eswifi_dev *eswifi = eswifi_by_iface_idx(context->iface); |
| int err; |
| |
| LOG_DBG(""); |
| |
| eswifi_lock(eswifi); |
| |
| if (socket->state != ESWIFI_SOCKET_STATE_CONNECTED) { |
| eswifi_unlock(eswifi); |
| return -ENOTCONN; |
| } |
| |
| __select_socket(eswifi, socket->index); |
| |
| k_delayed_work_cancel(&socket->read_work); |
| |
| socket->context = NULL; |
| socket->state = ESWIFI_SOCKET_STATE_NONE; |
| |
| /* Stop TCP client */ |
| snprintf(eswifi->buf, sizeof(eswifi->buf), "P6=0\r"); |
| err = eswifi_at_cmd(eswifi, eswifi->buf); |
| if (err < 0) { |
| err = -EIO; |
| LOG_ERR("Unable to disconnect"); |
| } |
| |
| eswifi_unlock(eswifi); |
| |
| return err; |
| } |
| |
| static int eswifi_off_get(sa_family_t family, |
| enum net_sock_type type, |
| enum net_ip_protocol ip_proto, |
| struct net_context **context) |
| { |
| struct eswifi_dev *eswifi = eswifi_by_iface_idx((*context)->iface); |
| struct eswifi_off_socket *socket = NULL; |
| int err, i; |
| |
| LOG_DBG(""); |
| |
| if (family != AF_INET) { |
| LOG_ERR("Only AF_INET is supported!"); |
| return -EPFNOSUPPORT; |
| } |
| |
| if (ip_proto != IPPROTO_TCP) { |
| /* TODO: add UDP */ |
| LOG_ERR("Only TCP supported"); |
| return -EPROTONOSUPPORT; |
| } |
| |
| eswifi_lock(eswifi); |
| |
| /* pickup available socket */ |
| for (i = 0; i < ESWIFI_OFFLOAD_MAX_SOCKETS; i++) { |
| if (!eswifi->socket[i].context) { |
| socket = &eswifi->socket[i]; |
| socket->index = i; |
| socket->context = *context; |
| (*context)->offload_context = socket; |
| break; |
| } |
| } |
| |
| if (!socket) { |
| LOG_ERR("No socket resource available"); |
| eswifi_unlock(eswifi); |
| return -ENOMEM; |
| } |
| |
| k_work_init(&socket->connect_work, eswifi_off_connect_work); |
| k_work_init(&socket->send_work, eswifi_off_send_work); |
| k_delayed_work_init(&socket->read_work, eswifi_off_read_work); |
| k_sem_init(&socket->read_sem, 1, 1); |
| |
| err = __select_socket(eswifi, socket->index); |
| if (err < 0) { |
| LOG_ERR("Unable to select socket %u", socket->index); |
| eswifi_unlock(eswifi); |
| return -EIO; |
| } |
| |
| /* Set Transport Protocol */ |
| snprintf(eswifi->buf, sizeof(eswifi->buf), "P1=%d\r", |
| ESWIFI_TRANSPORT_TCP); |
| err = eswifi_at_cmd(eswifi, eswifi->buf); |
| if (err < 0) { |
| LOG_ERR("Unable to set transport protocol"); |
| eswifi_unlock(eswifi); |
| return -EIO; |
| } |
| |
| k_delayed_work_submit_to_queue(&eswifi->work_q, &socket->read_work, |
| 500); |
| |
| eswifi_unlock(eswifi); |
| |
| return 0; |
| } |
| |
| static struct net_offload eswifi_offload = { |
| .get = eswifi_off_get, |
| .bind = eswifi_off_bind, |
| .listen = eswifi_off_listen, |
| .connect = eswifi_off_connect, |
| .accept = eswifi_off_accept, |
| .send = eswifi_off_send, |
| .sendto = eswifi_off_sendto, |
| .recv = eswifi_off_recv, |
| .put = eswifi_off_put, |
| }; |
| |
| static int eswifi_off_enable_dhcp(struct eswifi_dev *eswifi) |
| { |
| char cmd[] = "C4=1\r"; |
| int err; |
| |
| LOG_DBG(""); |
| |
| eswifi_lock(eswifi); |
| |
| err = eswifi_at_cmd(eswifi, cmd); |
| |
| eswifi_unlock(eswifi); |
| |
| return 0; |
| } |
| |
| static int eswifi_off_disable_bypass(struct eswifi_dev *eswifi) |
| { |
| char cmd[] = "PR=0\r"; |
| int err; |
| |
| LOG_DBG(""); |
| |
| eswifi_lock(eswifi); |
| |
| err = eswifi_at_cmd(eswifi, cmd); |
| |
| eswifi_unlock(eswifi); |
| |
| return err; |
| } |
| |
| int eswifi_offload_init(struct eswifi_dev *eswifi) |
| { |
| eswifi->iface->if_dev->offload = &eswifi_offload; |
| int err; |
| |
| err = eswifi_off_enable_dhcp(eswifi); |
| if (err < 0) { |
| LOG_ERR("Unable to configure dhcp"); |
| return err; |
| } |
| |
| err = eswifi_off_disable_bypass(eswifi); |
| if (err < 0) { |
| LOG_ERR("Unable to disable bypass mode"); |
| return err; |
| } |
| |
| return 0; |
| } |