/**************************************************************************** * drivers/ramlog.c * * Copyright (C) 2012 Gregory Nutt. All rights reserved. * Author: Gregory Nutt * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_RAMLOG /**************************************************************************** * Private Types ****************************************************************************/ /**************************************************************************** * Private Types ****************************************************************************/ struct ramlog_dev_s { volatile uint8_t rl_nwaiters; /* Number of threads waiting for data */ volatile uint16_t rl_head; /* The head index (where data is added) */ volatile uint16_t rl_tail; /* The tail index (where data is removed) */ sem_t rl_exclsem; /* Enforces mutually exclusive access */ sem_t rl_waitsem; /* Used to wait for data */ size_t rl_bufsize; /* Size of the RAM buffer */ FAR char *rl_buffer; /* Circular RAM buffer */ /* The following is a list if poll structures of threads waiting for * driver events. The 'struct pollfd' reference for each open is also * retained in the f_priv field of the 'struct file'. */ #ifndef CONFIG_DISABLE_POLL struct pollfd *rl_fds[CONFIG_RAMLOG_NPOLLWAITERS]; #endif }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static ssize_t ramlog_read(FAR struct file *, FAR char *, size_t); static ssize_t ramlog_write(FAR struct file *, FAR const char *, size_t); #ifndef CONFIG_DISABLE_POLL static int ramlog_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup); #endif /**************************************************************************** * Private Data ****************************************************************************/ static const struct file_operations g_ramlogfops = { 0, /* open */ 0, /* close */ ramlog_read, /* read */ ramlog_write, /* write */ 0, /* seek */ 0 /* ioctl */ #ifndef CONFIG_DISABLE_POLL , ramlog_poll /* poll */ #endif }; /* This is the pre-allocated buffer used for the console RAM log */ #ifdef CONFIG_RAMLOG_CONSOLE static char g_consoleramlog[CONFIG_RAMLOG_CONSOLE_BUFSIZE]; #endif /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: ramlog_pollnotify ****************************************************************************/ #ifndef CONFIG_DISABLE_POLL static void ramlog_pollnotify(FAR struct ramlog_dev_s *priv, pollevent_t eventset) { FAR struct pollfd *fds; irqstate_t flags; int i; /* This function may be called from an interrupt handler */ for (i = 0; i < CONFIG_RAMLOG_NPOLLWAITERS; i++) { flags = irqsave(); fds = priv->rl_fds[i]; if (fds) { fds->revents |= (fds->events & eventset); if (fds->revents != 0) { sem_post(fds->sem); } } irqrestore(flags); } } #else # define ramlog_pollnotify(priv,event) #endif /**************************************************************************** * Name: ramlog_read ****************************************************************************/ static ssize_t ramlog_read(FAR struct file *filep, FAR char *buffer, size_t len) { struct inode *inode = filep->f_inode; struct ramlog_dev_s *priv; ssize_t nread; char ch; int ret; /* Some sanity checking */ DEBUGASSERT(inode && inode->i_private); priv = inode->i_private; /* If the circular buffer is empty, then wait for something to be written * to it. This function may NOT be called from an interrupt handler. */ DEBUGASSERT(!up_interrupt_context()); /* Get exclusive access to the rl_tail index */ ret = sem_wait(&priv->rl_exclsem); if (ret < 0) { return ret; } /* Loop until something is read */ for (nread = 0; nread < len; ) { /* Get the next byte from the buffer */ if (priv->rl_head == priv->rl_tail) { /* The circular buffer is empty. Did we read anything? */ if (nread > 0) { /* Yes.. re-enable interrupts and the break out to return what * we have. */ break; } /* If the driver was opened with O_NONBLOCK option, then don't wait. * Re-enable interrupts and return EGAIN. */ if (filep->f_oflags & O_NONBLOCK) { nread = -EAGAIN; break; } /* Otherwise, wait for something to be written to the circular * buffer. Increment the number of waiters so that the ramlog_write() * will not that it needs to post the semaphore to wake us up. */ sched_lock(); priv->rl_nwaiters++; sem_post(&priv->rl_exclsem); /* We may now be pre-empted! But that should be okay because we * have already incremented nwaiters. Pre-emptions is disabled * but will be re-enabled while we are waiting. */ ret = sem_wait(&priv->rl_waitsem); /* Interrupts will be disabled when we return. So the decrementing * rl_nwaiters here is safe. */ priv->rl_nwaiters--; sched_unlock(); /* Did we successfully get the rl_waitsem? */ if (ret >= 0) { /* Yes... then retake the mutual exclusion semaphore */ ret = sem_wait(&priv->rl_exclsem); } /* Was the semaphore wait successful? Did we successful re-take the * mutual exclusion semaphore? */ if (ret < 0) { /* No.. One of the two sem_wait's failed. */ int errval = errno; /* Were we awakened by a signal? Did we read anything before * we received the signal? */ if (errval != EINTR || nread >= 0) { /* Yes.. return the error. */ nread = -errval; } /* Break out to return what we have. Note, we can't exactly * "break" out because whichever error occurred, we do not hold * the exclusion semaphore. */ goto errout_without_sem; } } else { /* The circular buffer is not empty, get the next byte from the * tail index. */ ch = priv->rl_buffer[priv->rl_tail]; /* Increment the tail index and re-enable interrupts */ if (++priv->rl_tail >= priv->rl_bufsize) { priv->rl_tail = 0; } /* Add the character to the user buffer */ buffer[nread] = ch; nread++; } } /* Relinquish the mutual exclusion semaphore */ sem_post(&priv->rl_exclsem); /* Notify all poll/select waiters that they can write to the FIFO */ errout_without_sem: if (nread > 0) { ramlog_pollnotify(priv, POLLOUT); } return nread; } /**************************************************************************** * Name: ramlog_write ****************************************************************************/ static ssize_t ramlog_write(FAR struct file *filep, FAR const char *buffer, size_t len) { struct inode *inode = filep->f_inode; struct ramlog_dev_s *priv; irqstate_t flags; ssize_t nwritten; int nexthead; int i; /* Some sanity checking */ DEBUGASSERT(inode && inode->i_private); priv = inode->i_private; /* Loop until all of the bytes have been written. This function may be * called from an interrupt handler! Semaphores cannot be used! * * The write logic only needs to modify the rl_head index. Therefore, * there is a difference in the way that rl_head and rl_tail are protected: * rl_tail is protected with a semaphore; rl_tail is protected by disabling * interrupts. */ for (nwritten = 0; nwritten < len; nwritten++) { /* Disable interrupts (in case we are NOT called from interrupt handler) */ flags = irqsave(); /* Calculate the write index AFTER the next byte is written */ nexthead = priv->rl_head + 1; if (nexthead >= priv->rl_bufsize) { nexthead = 0; } /* Would the next write overflow the circular buffer? */ if (nexthead == priv->rl_tail) { /* Yes... then break out of the loop to return the number of bytes * written. The data to be written is dropped on the floor. */ return nwritten; } /* No... copy the byte and re-enable interrupts */ priv->rl_buffer[priv->rl_head] = buffer[nwritten]; priv->rl_head = nexthead; irqrestore(flags); } /* Was anything written? */ if (nwritten > 0) { /* Are there threads waiting for read data? */ flags = irqsave(); for (i = 0; i < priv->rl_nwaiters; i++) { /* Yes.. Notify all of the waiting readers that more data is available */ sem_post(&priv->rl_waitsem); } /* Notify all poll/select waiters that they can write to the FIFO */ ramlog_pollnotify(priv, POLLIN); } /* Return the number of bytes written */ return nwritten; } /**************************************************************************** * Name: ramlog_poll ****************************************************************************/ #ifndef CONFIG_DISABLE_POLL int ramlog_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup) { FAR struct inode *inode = filep->f_inode; FAR struct ramlog_dev_s *priv; pollevent_t eventset; int ndx; int ret; int i; /* Some sanity checking */ DEBUGASSERT(inode && inode->i_private); priv = inode->i_private; /* Get exclusive access to the poll structures */ ret = sem_wait(&priv->rl_exclsem); if (ret < 0) { int errval = errno; return -errval; } /* Are we setting up the poll? Or tearing it down? */ if (setup) { /* This is a request to set up the poll. Find an available * slot for the poll structure reference */ for (i = 0; i < CONFIG_RAMLOG_NPOLLWAITERS; i++) { /* Find an available slot */ if (!priv->rl_fds[i]) { /* Bind the poll structure and this slot */ priv->rl_fds[i] = fds; fds->priv = &priv->rl_fds[i]; break; } } if (i >= CONFIG_RAMLOG_NPOLLWAITERS) { fds->priv = NULL; ret = -EBUSY; goto errout; } /* Should immediately notify on any of the requested events? * First, check if the xmit buffer is full. */ eventset = 0; ndx = priv->rl_head + 1; if (ndx >= priv->rl_bufsize) { ndx = 0; } if (ndx != priv->rl_tail) { eventset |= POLLOUT; } /* Check if the receive buffer is empty */ if (priv->rl_head != priv->rl_tail) { eventset |= POLLIN; } if (eventset) { ramlog_pollnotify(priv, eventset); } } else if (fds->priv) { /* This is a request to tear down the poll. */ struct pollfd **slot = (struct pollfd **)fds->priv; #ifdef CONFIG_DEBUG if (!slot) { ret = -EIO; goto errout; } #endif /* Remove all memory of the poll setup */ *slot = NULL; fds->priv = NULL; } errout: sem_post(&priv->rl_exclsem); return ret; } #endif /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: ramlog_register * * Description: * Create the RAM logging device and register it at the specified path. * Mostly likely this path will be /dev/console * ****************************************************************************/ int ramlog_register(FAR const char *devpath, FAR char *buffer, size_t buflen) { FAR struct ramlog_dev_s *priv; int ret = -ENOMEM; /* Sanity checking */ DEBUGASSERT(devpath && buffer && buflen > 1); /* Allocate a RAM logging device structure */ priv = (struct ramlog_dev_s *)kzalloc(sizeof(struct ramlog_dev_s)); if (priv) { /* Initialize the non-zero values in the RAM logging device structure */ sem_init(&priv->rl_exclsem, 0, 1); sem_init(&priv->rl_waitsem, 0, 0); priv->rl_bufsize = buflen; priv->rl_buffer = buffer; /* Register the character driver */ ret = register_driver(devpath, &g_ramlogfops, 0666, priv); if (ret < 0) { kfree(priv); } } return ret; } /**************************************************************************** * Name: ramlog_consoleinit * * Description: * Create the RAM logging device and register it at the specified path. * Mostly likely this path will be /dev/console * ****************************************************************************/ #ifdef CONFIG_RAMLOG_CONSOLE int ramlog_consoleinit(void) { /* Register a RAM log as the console device */ return ramlog_register("/dev/console", g_consoleramlog, CONFIG_RAMLOG_CONSOLE_BUFSIZE); } #endif /**************************************************************************** * Name: ramlog * * Description: * This is the low-level system logging interface. The debugging/syslogging * interfaces are lib_rawprintf() and lib_lowprinf(). The difference is * the lib_rawprintf() writes to fd=1 (stdout) and lib_lowprintf() uses * a lower level interface that works from interrupt handlers. This * function is a a low-level interface used to implement lib_lowprintf() * when CONFIG_RAMLOG_SYSLOG=y and CONFIG_SYSLOG=ramlog * ****************************************************************************/ #ifdef CONFIG_RAMLOG_SYSLOG # warning "Missing logic" #endif #endif /* CONFIG_RAMLOG */