| /**************************************************************************** |
| * drivers/ipcc/ipcc_read.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * 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 <nuttx/config.h> |
| #include <nuttx/ipcc.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/semaphore.h> |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <sys/types.h> |
| |
| #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 */ |
| |
| if (nxsem_get_value(&priv->rxsem, &semval) >= 0) |
| { |
| while (semval++ <= 0) |
| { |
| nxsem_post(&priv->rxsem); |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * 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 */ |
| |
| 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); |
| } |