nuttx/drivers/syslog/ramlog.c
dongjiuzhu1 bfb0095ec7 driver/syslog: reset syslog buffer when syslog buffer include invalid data
When the machine is cold started, the psram area where the syslog
buffer is located contains some random values, so it will cause
loss of valid log.

Signed-off-by: dongjiuzhu1 <dongjiuzhu1@xiaomi.com>
2023-09-07 11:42:24 +03:00

912 lines
23 KiB
C

/****************************************************************************
* 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 <nuttx/config.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <poll.h>
#include <errno.h>
#include <assert.h>
#include <debug.h>
#include <ctype.h>
#include <sys/boardctl.h>
#include <nuttx/arch.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mutex.h>
#include <nuttx/semaphore.h>
#include <nuttx/fs/fs.h>
#include <nuttx/fs/ioctl.h>
#include <nuttx/syslog/ramlog.h>
#include <nuttx/compiler.h>
#include <nuttx/irq.h>
#ifdef CONFIG_RAMLOG
/****************************************************************************
* Private Types
****************************************************************************/
struct ramlog_dev_s
{
#ifndef CONFIG_RAMLOG_NONBLOCKING
volatile uint8_t rl_nwaiters; /* Number of threads waiting for data */
#endif
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 =
{
# ifndef CONFIG_RAMLOG_NONBLOCKING
0, /* rl_nwaiters */
# endif
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 < priv->rl_nwaiters; i++)
{
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;
int i;
/* This function may be called from an interrupt handler */
for (i = 0; i < CONFIG_RAMLOG_NPOLLWAITERS; i++)
{
flags = enter_critical_section();
poll_notify(&priv->rl_fds[i], 1, 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.
*/
sched_lock();
priv->rl_nwaiters++;
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);
/* 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 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);
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_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 */