diff --git a/drivers/modem/CMakeLists.txt b/drivers/modem/CMakeLists.txt index a82175b756a..072dd72d1c8 100644 --- a/drivers/modem/CMakeLists.txt +++ b/drivers/modem/CMakeLists.txt @@ -10,6 +10,7 @@ zephyr_sources_ifdef(CONFIG_MODEM_CONTEXT zephyr_sources_ifdef(CONFIG_MODEM_IFACE_UART modem_iface_uart.c) zephyr_sources_ifdef(CONFIG_MODEM_CMD_HANDLER modem_cmd_handler.c) +zephyr_sources_ifdef(CONFIG_MODEM_SOCKET modem_socket.c) if(CONFIG_MODEM_UBLOX_SARA_R4) zephyr_library_include_directories(${ZEPHYR_BASE}/subsys/net/ip) diff --git a/drivers/modem/Kconfig b/drivers/modem/Kconfig index f07e3bab794..1acd1f8c525 100644 --- a/drivers/modem/Kconfig +++ b/drivers/modem/Kconfig @@ -98,6 +98,25 @@ config MODEM_CMD_HANDLER_MAX_PARAM_COUNT of the match_buf (match_buf_len) field as it needs to be large enough to hold a single line of data (ending with /r). +config MODEM_SOCKET + bool "Generic modem socket support layer" + help + This layer provides much of the groundwork for keeping track of + modem "sockets" throughout their lifecycle (from the initial offload + API calls through the command handler call back layers). + To configure this layer for use, create a modem_socket_config + object with your socket data and pass it's reference to + modem_socket_init(). + +config MODEM_SOCKET_PACKET_COUNT + int "Maximum number of stored packet sizes per socket" + depends on MODEM_SOCKET + default 6 + help + As the modem indicates more data is available to be received, + these values are organized into "packets". This setting limits + the maximum number of packet sizes the socket can keep track of. + endif # MODEM_CONTEXT config MODEM_SHELL diff --git a/drivers/modem/modem_socket.c b/drivers/modem/modem_socket.c new file mode 100644 index 00000000000..d25f6d9becb --- /dev/null +++ b/drivers/modem/modem_socket.c @@ -0,0 +1,302 @@ +/** @file + * @brief Modem socket / packet size handler + * + * Generic modem socket and packet size implementation for modem context + */ + +/* + * Copyright (c) 2019 Foundries.io + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include "modem_socket.h" + +/* + * Packet Size Support Functions + */ + +static u16_t modem_socket_packet_get_total(struct modem_socket *sock) +{ + int i; + u16_t total = 0U; + + if (!sock || !sock->packet_count) { + return 0U; + } + + for (i = 0; i < sock->packet_count; i++) { + total += sock->packet_sizes[i]; + } + + return total; +} + +static int modem_socket_packet_drop_first(struct modem_socket *sock) +{ + int i; + + if (!sock || !sock->packet_count) { + return -EINVAL; + } + + sock->packet_count--; + for (i = 0; i < sock->packet_count; i++) { + sock->packet_sizes[i] = + sock->packet_sizes[i + 1]; + } + + sock->packet_sizes[sock->packet_count] = 0U; + return 0; +} + +int modem_socket_packet_size_update(struct modem_socket_config *cfg, + struct modem_socket *sock, int new_total) +{ + u16_t old_total = 0U; + + if (!sock) { + return -EINVAL; + } + + if (new_total < 0) { + new_total += modem_socket_packet_get_total(sock); + } + + if (new_total <= 0) { + /* reset outstanding value here */ + sock->packet_count = 0U; + sock->packet_sizes[0] = 0U; + return 0; + } + + old_total = modem_socket_packet_get_total(sock); + if (new_total == old_total) { + goto data_ready; + } + + /* remove sent packets */ + if (new_total < old_total) { + /* remove packets that are not included in new_size */ + while (old_total > new_total && sock->packet_count > 0) { + old_total -= sock->packet_sizes[0]; + modem_socket_packet_drop_first(sock); + } + + goto data_ready; + } + + /* new packet to add */ + if (sock->packet_count >= CONFIG_MODEM_SOCKET_PACKET_COUNT) { + return -ENOMEM; + } + + if (new_total - old_total > 0) { + sock->packet_sizes[sock->packet_count] = new_total - old_total; + sock->packet_count++; + } else { + return -EINVAL; + } + +data_ready: + return new_total; +} + +/* + * VTable OPS + */ + +static ssize_t modem_socket_read_op(void *obj, void *buf, size_t sz) +{ + /* TODO: NOT IMPLEMENTED */ + return -ENOTSUP; +} + +static ssize_t modem_socket_write_op(void *obj, const void *buf, size_t sz) +{ + /* TODO: NOT IMPLEMENTED */ + return -ENOTSUP; +} + +static int modem_socket_ioctl_op(void *obj, unsigned int request, va_list args) +{ + /* TODO: NOT IMPLEMENTED */ + return -ENOTSUP; +} + +static const struct fd_op_vtable modem_sock_fd_vtable = { + .read = modem_socket_read_op, + .write = modem_socket_write_op, + .ioctl = modem_socket_ioctl_op, +}; + +/* + * Socket Support Functions + */ + +int modem_socket_get(struct modem_socket_config *cfg, + int family, int type, int proto) +{ + int i; + + for (i = 0; i < cfg->sockets_len; i++) { + if (cfg->sockets[i].id < cfg->base_socket_num) { + break; + } + } + + if (i >= cfg->sockets_len) { + return -ENOMEM; + } + + /* FIXME: 4 fds max now due to POSIX_OS conflict */ + cfg->sockets[i].sock_fd = z_reserve_fd(); + if (cfg->sockets[i].sock_fd < 0) { + return -errno; + } + + cfg->sockets[i].family = family; + cfg->sockets[i].type = type; + cfg->sockets[i].ip_proto = proto; + /* socket # needs assigning */ + cfg->sockets[i].id = cfg->sockets_len + 1; + z_finalize_fd(cfg->sockets[i].sock_fd, cfg, &modem_sock_fd_vtable); + + return cfg->sockets[i].sock_fd; +} + +struct modem_socket *modem_socket_from_fd(struct modem_socket_config *cfg, + int sock_fd) +{ + int i; + + for (i = 0; i < cfg->sockets_len; i++) { + if (cfg->sockets[i].sock_fd == sock_fd) { + return &cfg->sockets[i]; + } + } + + return NULL; +} + +struct modem_socket *modem_socket_from_id(struct modem_socket_config *cfg, + int id) +{ + int i; + + if (id < cfg->base_socket_num) { + return NULL; + } + + for (i = 0; i < cfg->sockets_len; i++) { + if (cfg->sockets[i].id == id) { + return &cfg->sockets[i]; + } + } + + return NULL; +} + +struct modem_socket *modem_socket_from_newid(struct modem_socket_config *cfg) +{ + return modem_socket_from_id(cfg, cfg->sockets_len + 1); +} + +void modem_socket_put(struct modem_socket_config *cfg, int sock_fd) +{ + if (sock_fd < 0 || sock_fd >= cfg->sockets_len) { + return; + } + + z_free_fd(sock_fd); + cfg->sockets[sock_fd].id = cfg->base_socket_num - 1; + cfg->sockets[sock_fd].sock_fd = -1; + (void)memset(&cfg->sockets[sock_fd].src, 0, sizeof(struct sockaddr)); + (void)memset(&cfg->sockets[sock_fd].dst, 0, sizeof(struct sockaddr)); +} + +/* + * Generic Poll Function + */ + +/* + * FIXME: The design here makes the poll function non-reentrant. If two + * different threads poll on two different sockets we'll end up with unexpected + * behavior - the higher priority thread will be unblocked, regardless on which + * socket it polled. I think we could live with such limitation though in the + * initial implementation, but this should be improved in the future. + */ +int modem_socket_poll(struct modem_socket_config *cfg, + struct pollfd *fds, int nfds, int msecs) +{ + struct modem_socket *sock; + int ret, i; + u8_t found_count = 0; + + if (!cfg) { + return -EINVAL; + } + + for (i = 0; i < nfds; i++) { + sock = modem_socket_from_fd(cfg, fds[i].fd); + if (sock) { + /* + * Handle user check for POLLOUT events: + * we consider the socket to always be writeable. + */ + if (fds[i].events & POLLOUT) { + fds[i].revents |= POLLOUT; + found_count++; + } else if (fds[i].events & POLLIN) { + sock->is_polled = true; + } + } + } + + /* exit early if we've found rdy sockets */ + if (found_count) { + errno = 0; + return found_count; + } + + ret = k_sem_take(&cfg->sem_poll, msecs); + for (i = 0; i < nfds; i++) { + sock = modem_socket_from_fd(cfg, fds[i].fd); + if (!sock) { + continue; + } + + if (fds[i].events & POLLIN && sock->packet_sizes[0] > 0U) { + fds[i].revents |= POLLIN; + found_count++; + } + + sock->is_polled = false; + } + + /* EBUSY, EAGAIN and ETIMEDOUT aren't true errors */ + if (ret < 0 && ret != -EBUSY && ret != -EAGAIN && ret != -ETIMEDOUT) { + errno = ret; + return -1; + } + + errno = 0; + return found_count; +} + +int modem_socket_init(struct modem_socket_config *cfg) +{ + int i; + + k_sem_init(&cfg->sem_poll, 0, 1); + for (i = 0; i < cfg->sockets_len; i++) { + k_sem_init(&cfg->sockets[i].sem_data_ready, 0, 1); + cfg->sockets[i].id = cfg->base_socket_num - 1; + } + + return 0; +} diff --git a/drivers/modem/modem_socket.h b/drivers/modem/modem_socket.h new file mode 100644 index 00000000000..50803673081 --- /dev/null +++ b/drivers/modem/modem_socket.h @@ -0,0 +1,74 @@ +/** @file + * @brief Modem socket header file. + * + * Generic modem socket and packet size implementation for modem context + */ + +/* + * Copyright (c) 2019 Foundries.io + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_MODEM_MODEM_SOCKET_H_ +#define ZEPHYR_INCLUDE_DRIVERS_MODEM_MODEM_SOCKET_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct modem_socket { + sa_family_t family; + enum net_sock_type type; + enum net_ip_protocol ip_proto; + struct sockaddr src; + struct sockaddr dst; + int id; + int sock_fd; + + /** packet data */ + u16_t packet_sizes[CONFIG_MODEM_SOCKET_PACKET_COUNT]; + u16_t packet_count; + + /** data ready semaphore */ + struct k_sem sem_data_ready; + + /** socket state */ + bool is_polled; + + /** temporary socket data */ + void *data; +}; + +struct modem_socket_config { + struct modem_socket *sockets; + size_t sockets_len; + + /* beginning socket id (modems can set this to 0 or 1 as needed) */ + int base_socket_num; + struct k_sem sem_poll; +}; + +/* return total size of remaining packets */ +int modem_socket_packet_size_update(struct modem_socket_config *cfg, + struct modem_socket *sock, int new_total); +int modem_socket_get(struct modem_socket_config *cfg, int family, int type, + int proto); +struct modem_socket *modem_socket_from_fd(struct modem_socket_config *cfg, + int sock_fd); +struct modem_socket *modem_socket_from_id(struct modem_socket_config *cfg, + int id); +struct modem_socket *modem_socket_from_newid(struct modem_socket_config *cfg); +void modem_socket_put(struct modem_socket_config *cfg, int sock_fd); +int modem_socket_poll(struct modem_socket_config *cfg, + struct pollfd *fds, int nfds, int msecs); +int modem_socket_init(struct modem_socket_config *cfg); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_MODEM_MODEM_SOCKET_H_ */