/**************************************************************************** * fs/vfs/fs_poll.c * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. The * ASF licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the * License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "inode/inode.h" #include "fs_heap.h" /**************************************************************************** * Private Types ****************************************************************************/ struct pollfd_s { FAR struct pollfd *fds; nfds_t nfds; }; /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: poll_teardown * * Description: * Teardown the poll operation for each descriptor in the list and return * the count of non-zero poll events. * ****************************************************************************/ static inline void poll_teardown(FAR struct pollfd *fds, nfds_t nfds, FAR int *count) { unsigned int i; /* Process each descriptor in the list */ *count = 0; for (i = 0; i < nfds; i++) { if (fds[i].fd >= 0) { int status = poll_fdsetup(fds[i].fd, &fds[i], false); if (status < 0) { fds[i].revents |= POLLERR; } } /* Check if any events were posted */ if (fds[i].revents != 0) { (*count)++; } /* Un-initialize the poll structure */ fds[i].arg = NULL; fds[i].cb = NULL; } } /**************************************************************************** * Name: poll_setup * * Description: * Setup the poll operation for each descriptor in the list. * ****************************************************************************/ static inline int poll_setup(FAR struct pollfd *fds, nfds_t nfds, FAR sem_t *sem) { unsigned int i; int ret = OK; int count = 0; /* Process each descriptor in the list */ for (i = 0; i < nfds; i++) { /* Setup the poll descriptor * * REVISIT: In a multi-threaded environment, one use case might be to * share a single, array of struct pollfd in poll() calls on different * threads. That use case is not supportable here because the non- * standard internal fields get reset here on each call to poll() * on each thread. */ fds[i].arg = sem; fds[i].cb = poll_default_cb; fds[i].revents = 0; fds[i].priv = NULL; /* Check for invalid descriptors. "If the value of fd is less than 0, * events shall be ignored, and revents shall be set to 0 in that entry * on return from poll()." * * NOTE: There is a potential problem here. If there is only one fd * and if it is negative, then poll will hang. From my reading of the * spec, that appears to be the correct behavior. */ if (fds[i].fd >= 0) { ret = poll_fdsetup(fds[i].fd, &fds[i], true); if (ret < 0) { poll_teardown(fds, i, &count); fds[i].revents |= POLLERR; fds[i].arg = NULL; fds[i].cb = NULL; return count + 1; } else if (fds[i].revents != 0) { count++; } } } if (count > 0) { /* If there are already events available in poll_setup, * we execute teardown and return immediately. */ poll_teardown(fds, i, &count); } return count; } /**************************************************************************** * Name: poll_cleanup * * Description: * Cleanup the poll operation. * ****************************************************************************/ static void poll_cleanup(FAR void *arg) { FAR struct pollfd_s *fdsinfo = (FAR struct pollfd_s *)arg; int count; poll_teardown(fdsinfo->fds, fdsinfo->nfds, &count); } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: poll_fdsetup * * Description: * Configure (or unconfigure) one file/socket descriptor for the poll * operation. If fds and sem are non-null, then the poll is being setup. * if fds and sem are NULL, then the poll is being torn down. * ****************************************************************************/ int poll_fdsetup(int fd, FAR struct pollfd *fds, bool setup) { FAR struct file *filep; int ret; /* Get the file pointer corresponding to this file descriptor */ ret = fs_getfilep(fd, &filep); if (ret < 0) { return ret; } DEBUGASSERT(filep != NULL); /* Let file_poll() do the rest */ ret = file_poll(filep, fds, setup); fs_putfilep(filep); return ret; } /**************************************************************************** * Name: poll_default_cb * * Description: * The default poll callback function, this function do the final step of * poll notification. * * Input Parameters: * fds - The fds * * Returned Value: * None * ****************************************************************************/ void poll_default_cb(FAR struct pollfd *fds) { int semcount = 0; FAR sem_t *pollsem; if (fds->arg != NULL) { pollsem = (FAR sem_t *)fds->arg; nxsem_get_value(pollsem, &semcount); if (semcount < 1) { nxsem_post(pollsem); } } } /**************************************************************************** * Name: poll_notify * * Description: * Notify the poll, this function should be called by drivers to notify * the caller the poll is ready. * * Input Parameters: * afds - The fds array * nfds - Number of fds array * eventset - List of events to check for activity * * Returned Value: * None * ****************************************************************************/ void poll_notify(FAR struct pollfd **afds, int nfds, pollevent_t eventset) { int i; FAR struct pollfd *fds; DEBUGASSERT(afds != NULL && nfds >= 1); for (i = 0; i < nfds && eventset; i++) { fds = afds[i]; if (fds != NULL) { /* The error event must be set in fds->revents */ fds->revents |= eventset & (fds->events | POLLERR | POLLHUP); if ((fds->revents & (POLLERR | POLLHUP)) != 0) { /* Error or Hung up, clear POLLOUT event */ fds->revents &= ~POLLOUT; } if ((fds->revents != 0 || (fds->events & POLLALWAYS) != 0) && fds->cb != NULL) { finfo("Report events: %08" PRIx32 "\n", fds->revents); fds->cb(fds); } } } } /**************************************************************************** * Name: file_poll * * Description: * Low-level poll operation based on struct file. This is used both to (1) * support detached file, and also (2) by poll_fdsetup() to perform all * normal operations on file descriptors. * * Input Parameters: * file File structure instance * fds - The structure describing the events to be monitored, OR NULL if * this is a request to stop monitoring events. * setup - true: Setup up the poll; false: Teardown the poll * * Returned Value: * 0: Success; Negated errno on failure * ****************************************************************************/ int file_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup) { FAR struct inode *inode; int ret = -ENOSYS; DEBUGASSERT(filep != NULL); inode = filep->f_inode; if (inode != NULL) { /* Is a driver registered? Does it support the poll method? * If not, return -ENOSYS */ if ((INODE_IS_DRIVER(inode) || INODE_IS_MQUEUE(inode) || INODE_IS_SOCKET(inode) || INODE_IS_PIPE(inode)) && inode->u.i_ops != NULL && inode->u.i_ops->poll != NULL) { /* Yes, it does... Setup the poll */ ret = inode->u.i_ops->poll(filep, fds, setup); if (ret < 0) { ferr("poll failed:%p, setup:%d, ret:%d\n", inode->u.i_ops->poll, setup, ret); } } #ifndef CONFIG_DISABLE_MOUNTPOINT else if (INODE_IS_MOUNTPT(inode) && inode->u.i_mops != NULL && inode->u.i_mops->poll != NULL) { ret = inode->u.i_mops->poll(filep, fds, setup); if (ret < 0) { ferr("poll failed:%p, setup:%d, ret:%d\n", inode->u.i_ops->poll, setup, ret); } } #endif /* Regular files (and block devices) are always readable and * writable. Open Group: "Regular files shall always poll TRUE for * reading and writing." */ else if (INODE_IS_MOUNTPT(inode) || INODE_IS_BLOCK(inode) || INODE_IS_MTD(inode)) { if (setup) { poll_notify(&fds, 1, POLLIN | POLLOUT); } ret = OK; } } else { poll_notify(&fds, 1, POLLERR | POLLHUP); ret = OK; } return ret; } /**************************************************************************** * Name: poll * * Description: * poll() waits for one of a set of file descriptors to become ready to * perform I/O. If none of the events requested (and no error) has * occurred for any of the file descriptors, then poll() blocks until * one of the events occurs. * * Input Parameters: * fds - List of structures describing file descriptors to be monitored * nfds - The number of entries in the list * timeout - Specifies an upper limit on the time for which poll() will * block in milliseconds. A negative value of timeout means an infinite * timeout. * * Returned Value: * On success, the number of structures that have non-zero revents fields. * A value of 0 indicates that the call timed out and no file descriptors * were ready. On error, -1 is returned, and errno is set appropriately: * * EBADF - An invalid file descriptor was given in one of the sets. * EFAULT - The fds address is invalid * EINTR - A signal occurred before any requested event. * EINVAL - The nfds value exceeds a system limit. * ENOMEM - There was no space to allocate internal data structures. * ENOSYS - One or more of the drivers supporting the file descriptor * does not support the poll method. * ****************************************************************************/ int poll(FAR struct pollfd *fds, nfds_t nfds, int timeout) { FAR struct pollfd *kfds; sem_t sem; int count = 0; int ret = OK; DEBUGASSERT(nfds == 0 || fds != NULL); /* poll() is a cancellation point */ enter_cancellation_point(); #ifdef CONFIG_BUILD_KERNEL /* Allocate kernel memory for the fds */ kfds = fs_heap_malloc(nfds * sizeof(struct pollfd)); if (!kfds) { /* Out of memory */ ret = -ENOMEM; goto out_with_cancelpt; } /* Copy the user fds to neutral kernel memory */ memcpy(kfds, fds, nfds * sizeof(struct pollfd)); #else /* Can use the user fds directly */ kfds = fds; #endif /* Set up the poll structure */ nxsem_init(&sem, 0, 0); /* If there are already events available in poll_setup, * we return immediately */ count = poll_setup(kfds, nfds, &sem); if (count == 0) { struct pollfd_s fdsinfo; /* Push a cancellation point onto the stack. This will be called if * the thread is canceled. */ fdsinfo.fds = kfds; fdsinfo.nfds = nfds; tls_cleanup_push(tls_get_info(), poll_cleanup, &fdsinfo); if (timeout > 0) { /* "Implementations may place limitations on the granularity of * timeout intervals. If the requested timeout interval requires * a finer granularity than the implementation supports, the * actual timeout interval will be rounded up to the next * supported value." -- opengroup.org * * Round timeout up to next full tick. */ /* Either wait for either a poll event(s), for a signal to occur, * or for the specified timeout to elapse with no event. * * NOTE: If a poll event is pending (i.e., the semaphore has * already been incremented), nxsem_tickwait() will not wait, but * will return immediately. */ ret = nxsem_tickwait(&sem, MSEC2TICK((clock_t)timeout)); if (ret < 0) { if (ret == -ETIMEDOUT) { /* Return zero (OK) in the event of a timeout */ ret = OK; } /* EINTR is the only other error expected in normal operation */ } } else if (timeout < 0) { /* Wait for the poll event or signal with no timeout */ ret = nxsem_wait(&sem); } /* Teardown the poll operation and get the count of events. Zero will * be returned in the case of a timeout. * * Preserve ret, if negative, since it holds the result of the wait. */ poll_teardown(kfds, nfds, &count); /* Pop the cancellation point */ tls_cleanup_pop(tls_get_info(), 0); } nxsem_destroy(&sem); #ifdef CONFIG_BUILD_KERNEL /* Copy the events back to user */ if (ret == OK) { int i; for (i = 0; i < nfds; i++) { fds[i].revents = kfds[i].revents; } } /* Free the temporary buffer */ fs_heap_free(kfds); out_with_cancelpt: #endif leave_cancellation_point(); if (ret < 0) { set_errno(-ret); return ERROR; } else { return count; } }