blob: d23dd52eb556295c2217ae351e6d1b57b6e296fe [file] [log] [blame]
/*
* Copyright (c) 2018 Linaro Limited
* Copyright (c) 2024 Tenstorrent AI ULC
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <zephyr/kernel.h>
#include <zephyr/internal/syscall_handler.h>
#include <zephyr/sys/math_extras.h>
#include <zephyr/net/socket.h>
/* Get size, in elements, of an array within a struct. */
#define STRUCT_MEMBER_ARRAY_SIZE(type, field) ARRAY_SIZE(((type *)0)->field)
/* Returns results in word_idx and bit_mask "output" params */
#define FD_SET_CALC_OFFSETS(set, word_idx, bit_mask) { \
unsigned int b_idx = fd % (sizeof(set->bitset[0]) * 8); \
word_idx = fd / (sizeof(set->bitset[0]) * 8); \
bit_mask = 1 << b_idx; \
}
int zvfs_poll_internal(struct zvfs_pollfd *fds, int nfds, k_timeout_t timeout);
void ZVFS_FD_ZERO(struct zvfs_fd_set *set)
{
int i;
for (i = 0; i < ARRAY_SIZE(set->bitset); i++) {
set->bitset[i] = 0U;
}
}
int ZVFS_FD_ISSET(int fd, struct zvfs_fd_set *set)
{
uint32_t word_idx, bit_mask;
if (fd < 0 || fd >= ZVFS_FD_SETSIZE) {
return 0;
}
FD_SET_CALC_OFFSETS(set, word_idx, bit_mask);
return (set->bitset[word_idx] & bit_mask) != 0U;
}
void ZVFS_FD_CLR(int fd, struct zvfs_fd_set *set)
{
uint32_t word_idx, bit_mask;
if (fd < 0 || fd >= ZVFS_FD_SETSIZE) {
return;
}
FD_SET_CALC_OFFSETS(set, word_idx, bit_mask);
set->bitset[word_idx] &= ~bit_mask;
}
void ZVFS_FD_SET(int fd, struct zvfs_fd_set *set)
{
uint32_t word_idx, bit_mask;
if (fd < 0 || fd >= ZVFS_FD_SETSIZE) {
return;
}
FD_SET_CALC_OFFSETS(set, word_idx, bit_mask);
set->bitset[word_idx] |= bit_mask;
}
int z_impl_zvfs_select(int nfds, struct zvfs_fd_set *ZRESTRICT readfds,
struct zvfs_fd_set *ZRESTRICT writefds,
struct zvfs_fd_set *ZRESTRICT exceptfds,
const struct timespec *ZRESTRICT timeout, const void *ZRESTRICT sigmask)
{
struct zvfs_pollfd pfds[CONFIG_ZVFS_POLL_MAX];
k_timeout_t poll_timeout;
int i, res;
int num_pfds = 0;
int num_selects = 0;
int fd_no = 0;
for (i = 0; i < STRUCT_MEMBER_ARRAY_SIZE(struct zvfs_fd_set, bitset); i++) {
uint32_t bit_mask = 1U;
uint32_t read_mask = 0U, write_mask = 0U, except_mask = 0U;
uint32_t ored_mask;
if (readfds != NULL) {
read_mask = readfds->bitset[i];
}
if (writefds != NULL) {
write_mask = writefds->bitset[i];
}
if (exceptfds != NULL) {
except_mask = exceptfds->bitset[i];
}
ored_mask = read_mask | write_mask | except_mask;
if (ored_mask == 0U) {
fd_no += sizeof(ored_mask) * 8;
continue;
}
do {
if (ored_mask & bit_mask) {
int events = 0;
if (num_pfds >= ARRAY_SIZE(pfds)) {
errno = ENOMEM;
return -1;
}
if (read_mask & bit_mask) {
events |= ZVFS_POLLIN;
}
if (write_mask & bit_mask) {
events |= ZVFS_POLLOUT;
}
if (except_mask & bit_mask) {
events |= ZVFS_POLLPRI;
}
pfds[num_pfds].fd = fd_no;
pfds[num_pfds++].events = events;
}
bit_mask <<= 1;
fd_no++;
} while (bit_mask != 0U);
}
if (timeout == NULL) {
poll_timeout = K_FOREVER;
} else {
poll_timeout =
K_USEC(timeout->tv_sec * USEC_PER_SEC + timeout->tv_nsec / NSEC_PER_USEC);
}
res = zvfs_poll_internal(pfds, num_pfds, poll_timeout);
if (res == -1) {
return -1;
}
if (readfds != NULL) {
ZVFS_FD_ZERO(readfds);
}
if (writefds != NULL) {
ZVFS_FD_ZERO(writefds);
}
if (exceptfds != NULL) {
ZVFS_FD_ZERO(exceptfds);
}
for (i = 0; i < num_pfds && res > 0; i++) {
short revents = pfds[i].revents;
int fd = pfds[i].fd;
if (revents == 0) {
continue;
}
/* POSIX: "EBADF: One or more of the file descriptor sets
* specified a file descriptor that is not a valid open
* file descriptor."
* So, unlike poll(), a single invalid fd aborts the entire
* select().
*/
if (revents & ZVFS_POLLNVAL) {
errno = EBADF;
return -1;
}
if (revents & ZVFS_POLLIN) {
if (readfds != NULL) {
ZVFS_FD_SET(fd, readfds);
num_selects++;
}
}
if (revents & ZVFS_POLLOUT) {
if (writefds != NULL) {
ZVFS_FD_SET(fd, writefds);
num_selects++;
}
}
/* It's unclear if HUP/ERR belong here. At least not ignore
* them. Zephyr doesn't use HUP and barely use ERR so far.
*/
if (revents & (ZVFS_POLLPRI | ZVFS_POLLHUP | ZVFS_POLLERR)) {
if (exceptfds != NULL) {
ZVFS_FD_SET(fd, exceptfds);
num_selects++;
}
if (writefds != NULL) {
ZVFS_FD_SET(fd, writefds);
num_selects++;
}
}
res--;
}
return num_selects;
}
#ifdef CONFIG_USERSPACE
static int z_vrfy_zvfs_select(int nfds, struct zvfs_fd_set *ZRESTRICT readfds,
struct zvfs_fd_set *ZRESTRICT writefds,
struct zvfs_fd_set *ZRESTRICT exceptfds,
const struct timespec *ZRESTRICT timeout,
const void *ZRESTRICT sigmask)
{
struct zvfs_fd_set *readfds_copy = NULL, *writefds_copy = NULL, *exceptfds_copy = NULL;
struct timespec *to = NULL;
int ret = -1;
if (readfds) {
readfds_copy =
k_usermode_alloc_from_copy((void *)readfds, sizeof(struct zvfs_fd_set));
if (!readfds_copy) {
errno = ENOMEM;
goto out;
}
}
if (writefds) {
writefds_copy =
k_usermode_alloc_from_copy((void *)writefds, sizeof(struct zvfs_fd_set));
if (!writefds_copy) {
errno = ENOMEM;
goto out;
}
}
if (exceptfds) {
exceptfds_copy =
k_usermode_alloc_from_copy((void *)exceptfds, sizeof(struct zvfs_fd_set));
if (!exceptfds_copy) {
errno = ENOMEM;
goto out;
}
}
if (timeout) {
to = k_usermode_alloc_from_copy((void *)timeout, sizeof(*to));
if (!to) {
errno = ENOMEM;
goto out;
}
}
ret = z_impl_zvfs_select(nfds, readfds_copy, writefds_copy, exceptfds_copy, to, sigmask);
if (ret >= 0) {
if (readfds_copy) {
k_usermode_to_copy((void *)readfds, readfds_copy,
sizeof(struct zvfs_fd_set));
}
if (writefds_copy) {
k_usermode_to_copy((void *)writefds, writefds_copy,
sizeof(struct zvfs_fd_set));
}
if (exceptfds_copy) {
k_usermode_to_copy((void *)exceptfds, exceptfds_copy,
sizeof(struct zvfs_fd_set));
}
}
out:
k_free(to);
k_free(readfds_copy);
k_free(writefds_copy);
k_free(exceptfds_copy);
return ret;
}
#include <zephyr/syscalls/zvfs_select_mrsh.c>
#endif