/**************************************************************************** * drivers/ipcc/ipcc_read.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 "ipcc_priv.h" /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: ipcc_rxfree_notify * * Description: * Notifies all blocked threads or those waiting in poll/select that * there is data on buffer to perform reading. * * Input Parameters: * ipcc - pointer to driver instance * * Returned Value: * None * * Assumptions/Limitations: * This function can be called from interrupt handler from lower half. * ****************************************************************************/ void ipcc_rxfree_notify(FAR struct ipcc_driver_s *priv) { int semval; if (priv == NULL) { /* priv can be NULL when ipcc lower half is initialized but * upper half has not yet been initialized, and rx interrupt * has been received. In such case we don't wake any reader, * because since ipcc is not yet initialized there cannot be * any readers yet. We can safely return here, first read() * to this ipcc channel will immediately read data. */ return; } if ((nxsem_get_value(&priv->rxsem, &semval) == 0) && semval > 0) { /* Notify all poll/select waiters that they can read from the driver. * Do it only when there are no already blocked readers to avoid * situation where thread that is polling gets notified only to * be blocked in read() because another thread have read from * buffer before polling thread could. */ ipcc_pollnotify(priv, POLLIN); return; } /* Notify all blocked readers that data is available to read */ do { nxsem_post(&priv->rxsem); } while (nxsem_get_value(&priv->rxsem, &semval) == 0 && semval <= 0); } /**************************************************************************** * Name: ipcc_read * * Description: * Reads data from the IPCC lower driver. Will block if no data is * available, unless O_NONBLOCK flag is set. * * Input Parameters: * filep - file on vfs associated with the driver * buffer - buffer where read data should be copied to * buflen - size of the buffer... buffer * * Returned Value: * Will return number of bytes read or negated errno * * Assumptions/Limitations: * ****************************************************************************/ ssize_t ipcc_read(FAR struct file *filep, FAR char *buffer, size_t buflen) { FAR struct ipcc_driver_s *priv; ssize_t nread; int ret; int flags; /* Get our private data structure */ DEBUGASSERT(filep != NULL && filep->f_inode != NULL); priv = filep->f_inode->i_private; /* Get exclusive access to driver */ if ((ret = nxmutex_lock(&priv->lock))) { /* nxsem_wait() will return on signal, we did not start * any transfer yet, so we can safely return with error */ return ret; } /* Disable interrupts, or we might get in situation when we: * - read 0 bytes from circbuf * - interrupt comes in * - it copies data to buffer and notifies blocked readers * (in this case sem count is 0, so no sem_post() is called) * - interrupt ends * - since we are in blocking mode, and we read 0 from buffer * we call sem_wait() and we hang in there cause rx interrupt * will not be triggered again. */ flags = enter_critical_section(); for (; ; ) { #ifdef CONFIG_IPCC_BUFFERED /* Data is buffered in interrupt handler, so we simply * have to return buffers content to the user */ if (circbuf_used(&priv->ipcc->rxbuf)) { /* There is some data on buffer, we are sure we won't block * so immediately leave critical section to not block other * more important drivers */ leave_critical_section(flags); } if ((nread = circbuf_read(&priv->ipcc->rxbuf, buffer, buflen)) > 0) { /* got some data */ if (priv->ipcc->overflow) { /* We tried to buffer data in previous interrupt, but * it failed due to rxbuf being full. Now that we took * some data from buffer, we can try to buffer data again */ priv->ipcc->ops.buffer_data(priv->ipcc, &priv->ipcc->rxbuf); if ((size_t)nread < buflen) { /* There is still some space left on buffer, and * we just added new data to buffer, get more data * for the user */ nread += circbuf_read(&priv->ipcc->rxbuf, buffer + nread, buflen - nread); } } /* return number of bytes read to the caller */ nxmutex_unlock(&priv->lock); return nread; } #else /* CONFIG_IPCC_BUFFERED */ /* Unbuffered read, read data directly from lower driver */ if ((nread = priv->ipcc->ops.read(priv->ipcc, buffer, buflen)) != 0) { /* Got some data, return number of bytes read to the caller * --or-- * read() returned error in which case return errno value */ leave_critical_section(flags); nxmutex_unlock(&priv->lock); return nread; } #endif /* CONFIG_IPCC_BUFFERED */ /* no data on the buffer, should we block? */ if (filep->f_oflags & O_NONBLOCK) { /* No, we should not block, so inform caller that * no data could be read */ leave_critical_section(flags); nxmutex_unlock(&priv->lock); return -EAGAIN; } /* We are in blocking mode, so we have to wait for data to arrive. */ nxmutex_unlock(&priv->lock); if ((ret = nxsem_wait(&priv->rxsem))) { leave_critical_section(flags); /* We were interrupted by signal, but we have not written * any data to caller's buffer, so we return with error */ return ret; } /* Data should now be available, but it's possible that * another thread will read all data from buffer before * we can do it, so we will stay in the loop until we * manage to read something - or interrupt signal occurs * * We have released exclusive lock to driver when we were * waiting for data, so now let's retake it. */ nxmutex_lock(&priv->lock); } leave_critical_section(flags); }