/**************************************************************************** * drivers/syslog/ramlog.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 #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_RAMLOG /**************************************************************************** * Private Types ****************************************************************************/ struct ramlog_dev_s { volatile size_t rl_head; /* The head index (where data is added) */ volatile size_t rl_tail; /* The tail index (where data is removed) */ mutex_t rl_lock; /* Enforces mutually exclusive access */ #ifndef CONFIG_RAMLOG_NONBLOCKING sem_t rl_waitsem; /* Used to wait for data */ #endif 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'. */ FAR struct pollfd *rl_fds[CONFIG_RAMLOG_NPOLLWAITERS]; }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ /* Helper functions */ #ifndef CONFIG_RAMLOG_NONBLOCKING static int ramlog_readnotify(FAR struct ramlog_dev_s *priv); #endif static void ramlog_pollnotify(FAR struct ramlog_dev_s *priv, pollevent_t eventset); static int ramlog_addchar(FAR struct ramlog_dev_s *priv, char ch); /* Character driver methods */ static ssize_t ramlog_file_read(FAR struct file *filep, FAR char *buffer, size_t buflen); static ssize_t ramlog_file_write(FAR struct file *filep, FAR const char *buffer, size_t buflen); static int ramlog_file_ioctl(FAR struct file *filep, int cmd, unsigned long arg); static int ramlog_file_poll(FAR struct file *filep, FAR struct pollfd *fds, bool setup); /**************************************************************************** * Private Data ****************************************************************************/ static const struct file_operations g_ramlogfops = { NULL, /* open */ NULL, /* close */ ramlog_file_read, /* read */ ramlog_file_write, /* write */ NULL, /* seek */ ramlog_file_ioctl, /* ioctl */ NULL, /* mmap */ NULL, /* truncate */ ramlog_file_poll /* poll */ }; /* This is the pre-allocated buffer used for the console RAM log and/or * for the syslogging function. */ #ifdef CONFIG_RAMLOG_SYSLOG # ifdef RAMLOG_BUFFER_SECTION static char g_sysbuffer[CONFIG_RAMLOG_BUFSIZE] locate_data(RAMLOG_BUFFER_SECTION); # else static char g_sysbuffer[CONFIG_RAMLOG_BUFSIZE]; # endif /* This is the device structure for the console or syslogging function. It * must be statically initialized because the RAMLOG ramlog_putc function * could be called before the driver initialization logic executes. */ static struct ramlog_dev_s g_sysdev = { CONFIG_RAMLOG_BUFSIZE, /* rl_head */ CONFIG_RAMLOG_BUFSIZE, /* rl_tail */ NXMUTEX_INITIALIZER, /* rl_lock */ # ifndef CONFIG_RAMLOG_NONBLOCKING SEM_INITIALIZER(0), /* rl_waitsem */ # endif CONFIG_RAMLOG_BUFSIZE, /* rl_bufsize */ g_sysbuffer /* rl_buffer */ }; #endif /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: ramlog_bufferused ****************************************************************************/ static size_t ramlog_bufferused(FAR struct ramlog_dev_s *priv) { return (priv->rl_bufsize + priv->rl_head - priv->rl_tail) % priv->rl_bufsize; } /**************************************************************************** * Name: ramlog_readnotify ****************************************************************************/ #ifndef CONFIG_RAMLOG_NONBLOCKING static int ramlog_readnotify(FAR struct ramlog_dev_s *priv) { irqstate_t flags; int i; /* Notify all waiting readers that they can read from the FIFO */ flags = enter_critical_section(); for (i = 0; ; i++) { int semcount = 0; nxsem_get_value(&priv->rl_waitsem, &semcount); if (semcount >= 0) { break; } nxsem_post(&priv->rl_waitsem); } leave_critical_section(flags); /* Return number of notified readers. */ return i; } #endif /**************************************************************************** * Name: ramlog_pollnotify ****************************************************************************/ static void ramlog_pollnotify(FAR struct ramlog_dev_s *priv, pollevent_t eventset) { irqstate_t flags; /* This function may be called from an interrupt handler */ flags = enter_critical_section(); poll_notify(priv->rl_fds, CONFIG_RAMLOG_NPOLLWAITERS, eventset); leave_critical_section(flags); } /**************************************************************************** * Name: ramlog_initbuf * * Description: * Initialize g_sysdev based on the current system ramlog buffer. * ****************************************************************************/ #ifdef CONFIG_RAMLOG_SYSLOG static void ramlog_initbuf(void) { FAR struct ramlog_dev_s *priv = &g_sysdev; #ifdef CONFIG_BOARDCTL_RESET_CAUSE struct boardioc_reset_cause_s cause; int ret; #endif bool is_empty = true; char prev; char cur; size_t i; if (priv->rl_head != CONFIG_RAMLOG_BUFSIZE || priv->rl_tail != CONFIG_RAMLOG_BUFSIZE) { return; } #ifdef CONFIG_BOARDCTL_RESET_CAUSE memset(&cause, 0, sizeof(cause)); ret = boardctl(BOARDIOC_RESET_CAUSE, (uintptr_t)&cause); if (ret >= 0 && cause.cause == BOARDIOC_RESETCAUSE_SYS_CHIPPOR) { memset(priv->rl_buffer, 0, priv->rl_bufsize); priv->rl_head = priv->rl_tail = 0; return; } #endif prev = priv->rl_buffer[priv->rl_bufsize - 1]; for (i = 0; i < priv->rl_bufsize; i++) { cur = priv->rl_buffer[i]; if (!isprint(cur) && !isspace(cur) && cur != '\0') { memset(priv->rl_buffer, 0, priv->rl_bufsize); is_empty = true; break; } else if (prev && !cur) { priv->rl_head = i; is_empty = false; } else if (!prev && cur) { priv->rl_tail = i; } prev = cur; } if (is_empty) { priv->rl_head = priv->rl_tail = 0; } } #endif /**************************************************************************** * Name: ramlog_addchar ****************************************************************************/ static int ramlog_addchar(FAR struct ramlog_dev_s *priv, char ch) { irqstate_t flags; size_t nexthead; #ifdef CONFIG_RAMLOG_SYSLOG if (priv == &g_sysdev) { ramlog_initbuf(); } #endif /* Disable interrupts (in case we are NOT called from interrupt handler) */ flags = enter_critical_section(); #ifdef CONFIG_RAMLOG_CRLF /* Ignore carriage returns */ if (ch == '\r') { leave_critical_section(flags); return OK; } /* Pre-pend a carriage before a linefeed */ if (ch == '\n') { ch = '\r'; } again: #endif /* 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) { #ifdef CONFIG_RAMLOG_OVERWRITE /* Yes... Overwrite with the latest log in the circular buffer */ priv->rl_buffer[priv->rl_tail] = '\0'; priv->rl_tail += 1; if (priv->rl_tail >= priv->rl_bufsize) { priv->rl_tail = 0; } #else /* Yes... Return an indication that nothing was saved in the buffer. */ leave_critical_section(flags); return -EBUSY; #endif } /* No... copy the byte and re-enable interrupts */ priv->rl_buffer[priv->rl_head] = ch; priv->rl_head = nexthead; #ifdef CONFIG_RAMLOG_CRLF if (ch == '\r') { ch = '\n'; goto again; } #endif leave_critical_section(flags); return OK; } /**************************************************************************** * Name: ramlog_addbuf ****************************************************************************/ static ssize_t ramlog_addbuf(FAR struct ramlog_dev_s *priv, FAR const char *buffer, size_t len) { int readers_waken; ssize_t nwritten; char ch; int ret; ret = nxmutex_lock(&priv->rl_lock); if (ret < 0) { return ret; } for (nwritten = 0; (size_t)nwritten < len; nwritten++) { /* Get the next character to output */ ch = buffer[nwritten]; /* Then output the character */ ret = ramlog_addchar(priv, ch); if (ret < 0) { /* The buffer is full and nothing was saved. The remaining * data to be written is dropped on the floor. */ break; } } /* Was anything written? */ if (nwritten > 0) { readers_waken = 0; #ifndef CONFIG_RAMLOG_NONBLOCKING /* Are there threads waiting for read data? */ readers_waken = ramlog_readnotify(priv); #endif /* If there are multiple readers, some of them might block despite * POLLIN because first reader might read all data. Favor readers * and notify poll waiters only if no reader was awakened, even if * the latter may starve. * * This also implies we do not have to make these two notify * operations a critical section. */ if (readers_waken == 0 && ramlog_bufferused(priv) >= CONFIG_RAMLOG_POLLTHRESHOLD) { /* Notify all poll/select waiters that they can read from the * FIFO. */ ramlog_pollnotify(priv, POLLIN); } } /* We always have to return the number of bytes requested and NOT the * number of bytes that were actually written. Otherwise, callers * probably retry, causing same error condition again. */ nxmutex_unlock(&priv->rl_lock); return len; } /**************************************************************************** * Name: ramlog_read ****************************************************************************/ static ssize_t ramlog_file_read(FAR struct file *filep, FAR char *buffer, size_t len) { FAR struct inode *inode = filep->f_inode; FAR struct ramlog_dev_s *priv; ssize_t nread; char ch; int ret; /* Some sanity checking */ DEBUGASSERT(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 = nxmutex_lock(&priv->rl_lock); if (ret < 0) { return ret; } /* Loop until something is read */ for (nread = 0; (size_t)nread < len; ) { /* Get the next byte from the buffer */ if (priv->rl_head == priv->rl_tail) { /* The circular buffer is empty. */ #ifdef CONFIG_RAMLOG_NONBLOCKING /* Return what we have (with zero mean the end-of-file) */ break; #else /* Did we read anything? */ if (nread > 0) { /* Yes.. break out to return what we have. */ break; } /* If the driver was opened with O_NONBLOCK option, then don't * wait. */ 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_file_write() will note that it needs to post the * semaphore to wake us up. */ nxmutex_unlock(&priv->rl_lock); /* 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 = nxsem_wait(&priv->rl_waitsem); /* Did we successfully get the rl_waitsem? */ if (ret >= 0) { /* Yes... then retake the mutual exclusion mutex */ ret = nxmutex_lock(&priv->rl_lock); } /* Was the mutex wait successful? Did we successful re-take the * mutual exclusion mutex? */ if (ret < 0) { /* No.. One of the two nxsem_wait's failed. */ /* Return the error. We did handle the case where we read * anything already before waiting. */ nread = ret; /* Break out to return what we have. Note, we can't exactly * "break" out because whichever error occurred, we do not hold * the exclusion mutex. */ goto errout_without_lock; } #endif /* CONFIG_RAMLOG_NONBLOCKING */ } else { /* The circular buffer is not empty, get the next byte from the * tail index. */ ch = priv->rl_buffer[priv->rl_tail]; priv->rl_buffer[priv->rl_tail] = '\0'; /* Increment the tail index. */ 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 mutex */ nxmutex_unlock(&priv->rl_lock); /* Notify all poll/select waiters that they can write to the FIFO */ #ifndef CONFIG_RAMLOG_NONBLOCKING errout_without_lock: #endif if (nread > 0) { ramlog_pollnotify(priv, POLLOUT); } /* Return the number of characters actually read */ return nread; } /**************************************************************************** * Name: ramlog_file_write ****************************************************************************/ static ssize_t ramlog_file_write(FAR struct file *filep, FAR const char *buffer, size_t len) { FAR struct inode *inode = filep->f_inode; FAR struct ramlog_dev_s *priv; /* Some sanity checking */ DEBUGASSERT(inode->i_private); priv = inode->i_private; return ramlog_addbuf(priv, buffer, len); } /**************************************************************************** * Name: ramlog_file_ioctl ****************************************************************************/ static int ramlog_file_ioctl(FAR struct file *filep, int cmd, unsigned long arg) { FAR struct inode *inode = filep->f_inode; FAR struct ramlog_dev_s *priv; int ret; DEBUGASSERT(inode->i_private); priv = inode->i_private; ret = nxmutex_lock(&priv->rl_lock); if (ret < 0) { return ret; } switch (cmd) { case FIONREAD: *(FAR int *)((uintptr_t)arg) = ramlog_bufferused(priv); break; default: ret = -ENOTTY; break; } nxmutex_unlock(&priv->rl_lock); return ret; } /**************************************************************************** * Name: ramlog_file_poll ****************************************************************************/ static int ramlog_file_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; irqstate_t flags; size_t next_head; int ret; int i; /* Some sanity checking */ DEBUGASSERT(inode->i_private); priv = inode->i_private; /* Get exclusive access to the poll structures */ ret = nxmutex_lock(&priv->rl_lock); if (ret < 0) { return ret; } /* 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? */ eventset = 0; flags = enter_critical_section(); next_head = priv->rl_head + 1; if (next_head >= priv->rl_bufsize) { next_head = 0; } /* First, check if the receive buffer is not full. */ if (next_head != priv->rl_tail) { eventset |= POLLOUT; } /* Check if the receive buffer is not empty. */ if (ramlog_bufferused(priv) >= CONFIG_RAMLOG_POLLTHRESHOLD) { eventset |= POLLIN; } leave_critical_section(flags); poll_notify(&fds, 1, eventset); } else if (fds->priv) { /* This is a request to tear down the poll. */ struct pollfd **slot = (struct pollfd **)fds->priv; #ifdef CONFIG_DEBUG_FEATURES if (!slot) { ret = -EIO; goto errout; } #endif /* Remove all memory of the poll setup */ *slot = NULL; fds->priv = NULL; } errout: nxmutex_unlock(&priv->rl_lock); return ret; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: ramlog_register * * Description: * Create the RAM logging device and register it at the specified path. * ****************************************************************************/ 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 = kmm_zalloc(sizeof(struct ramlog_dev_s)); if (priv != NULL) { /* Initialize the non-zero values in the RAM logging device structure */ nxmutex_init(&priv->rl_lock); #ifndef CONFIG_RAMLOG_NONBLOCKING nxsem_init(&priv->rl_waitsem, 0, 0); #endif priv->rl_bufsize = buflen; priv->rl_buffer = buffer; /* Register the character driver */ ret = register_driver(devpath, &g_ramlogfops, 0666, priv); if (ret < 0) { nxmutex_destroy(&priv->rl_lock); #ifndef CONFIG_RAMLOG_NONBLOCKING nxsem_destroy(&priv->rl_waitsem); #endif kmm_free(priv); } } return ret; } /**************************************************************************** * Name: ramlog_syslog_register * * Description: * Use a pre-allocated RAM logging device and register it at the path * specified by CONFIG_RAMLOG_SYSLOG * ****************************************************************************/ #ifdef CONFIG_RAMLOG_SYSLOG void ramlog_syslog_register(void) { /* Register the syslog character driver */ register_driver(CONFIG_SYSLOG_DEVPATH, &g_ramlogfops, 0666, &g_sysdev); } #endif /**************************************************************************** * Name: ramlog_putc * * Description: * This is the low-level system logging interface. * ****************************************************************************/ #ifdef CONFIG_RAMLOG_SYSLOG int ramlog_putc(FAR struct syslog_channel_s *channel, int ch) { FAR struct ramlog_dev_s *priv = &g_sysdev; int readers_waken = 0; int ret; UNUSED(channel); /* Add the character to the RAMLOG */ ret = ramlog_addchar(priv, ch); if (ret < 0) { /* The buffer is full and 'ch' was not saved. */ return ret; } #ifndef CONFIG_RAMLOG_NONBLOCKING /* Are there threads waiting for read data? */ readers_waken = ramlog_readnotify(priv); #endif /* If there are multiple readers, some of them might block despite * POLLIN because first reader might read all data. Favor readers * and notify poll waiters only if no reader was awakened, even if * the latter may starve. * * This also implies we do not have to make these two notify * operations a critical section. */ if (readers_waken == 0 && ramlog_bufferused(priv) >= CONFIG_RAMLOG_POLLTHRESHOLD) { /* Notify all poll/select waiters that they can read from the FIFO */ ramlog_pollnotify(priv, POLLIN); } /* Return the character added on success */ return ch; } ssize_t ramlog_write(FAR struct syslog_channel_s *channel, FAR const char *buffer, size_t buflen) { FAR struct ramlog_dev_s *priv = &g_sysdev; return ramlog_addbuf(priv, buffer, buflen); } #endif #endif /* CONFIG_RAMLOG */