/*
* Copyright (c) 2017 Richard Braun.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LOG_BUFFER_SIZE 16384
#if !ISP2(LOG_BUFFER_SIZE)
#error "log buffer size must be a power-of-two"
#endif
#define LOG_MSG_SIZE 128
#define LOG_MARKER 0x1
static struct thread *log_thread;
static struct cbuf log_cbuf;
static char log_buffer[LOG_BUFFER_SIZE];
static unsigned long log_nr_overruns;
static unsigned int log_print_level;
/*
* Global lock.
*
* Interrupts must be disabled when holding this lock.
*/
static struct spinlock log_lock;
/*
* A record starts with a marker byte and ends with a null terminating byte.
*
* There must be no null byte inside the header, and the buffer is expected
* to be a standard null-terminated string.
*/
struct log_record {
uint8_t mark;
uint8_t level;
char buffer[LOG_MSG_SIZE];
};
static void
log_push(char c)
{
if (cbuf_size(&log_cbuf) == cbuf_capacity(&log_cbuf)) {
log_nr_overruns++;
}
cbuf_push(&log_cbuf, c);
}
static const char *
log_level2str(unsigned int level)
{
switch (level) {
case LOG_EMERG:
return "emerg";
case LOG_ALERT:
return "alert";
case LOG_CRIT:
return "crit";
case LOG_ERR:
return "error";
case LOG_WARNING:
return "warning";
case LOG_NOTICE:
return "notice";
case LOG_INFO:
return "info";
case LOG_DEBUG:
return "debug";
default:
return NULL;
}
}
static char
log_level2char(unsigned int level)
{
assert(level < LOG_NR_LEVELS);
return '0' + level;
}
static uint8_t
log_char2level(char c)
{
uint8_t level;
level = c - '0';
assert(level < LOG_NR_LEVELS);
return level;
}
static void
log_record_init_produce(struct log_record *record, unsigned int level)
{
record->mark = LOG_MARKER;
record->level = log_level2char(level);
}
static void
log_record_consume(struct log_record *record, char c, size_t *sizep)
{
char *ptr;
assert(*sizep < sizeof(*record));
ptr = (char *)record;
ptr[*sizep] = c;
(*sizep)++;
}
static int
log_record_init_consume(struct log_record *record, unsigned long *indexp)
{
bool marker_found;
size_t size;
int error;
char c;
marker_found = false;
size = 0;
for (;;) {
if (*indexp == cbuf_end(&log_cbuf)) {
if (!marker_found) {
return ERROR_INVAL;
}
break;
}
error = cbuf_read(&log_cbuf, *indexp, &c);
if (error) {
*indexp = cbuf_start(&log_cbuf);
continue;
}
(*indexp)++;
if (!marker_found) {
if (c != LOG_MARKER) {
continue;
}
marker_found = true;
log_record_consume(record, c, &size);
continue;
} else if (size == offsetof(struct log_record, level)) {
record->level = log_char2level(c);
size++;
continue;
}
log_record_consume(record, c, &size);
if (c == '\0') {
break;
}
}
return 0;
}
static void
log_record_print(const struct log_record *record, unsigned int level)
{
if (record->level > level) {
return;
}
if (record->level <= LOG_WARNING) {
printf("%7s %s\n", log_level2str(record->level), record->buffer);
} else {
printf("%s\n", record->buffer);
}
}
static void
log_record_push(struct log_record *record)
{
const char *ptr;
ptr = (const char *)record;
while (*ptr != '\0') {
log_push(*ptr);
ptr++;
}
log_push('\0');
}
static void
log_run(void *arg)
{
unsigned long flags, index, nr_overruns;
struct log_record record;
bool start_shell;
int error;
(void)arg;
nr_overruns = 0;
start_shell = true;
spinlock_lock_intr_save(&log_lock, &flags);
index = cbuf_start(&log_cbuf);
for (;;) {
while (index == cbuf_end(&log_cbuf)) {
/*
* Starting the shell after the log thread sleeps for the first
* time cleanly serializes log messages and shell prompt, making
* a clean ordered output.
*/
if (start_shell) {
shell_start();
start_shell = false;
}
thread_sleep(&log_lock, &log_cbuf, "log_cbuf");
}
error = log_record_init_consume(&record, &index);
/* Drain the log buffer before reporting overruns */
if (cbuf_size(&log_cbuf) == 0) {
nr_overruns = log_nr_overruns;
log_nr_overruns = 0;
}
spinlock_unlock_intr_restore(&log_lock, flags);
if (!error) {
log_record_print(&record, log_print_level);
}
if (nr_overruns != 0) {
log_msg(LOG_ERR, "log: buffer overruns, %lu bytes dropped",
nr_overruns);
nr_overruns = 0;
}
spinlock_lock_intr_save(&log_lock, &flags);
}
}
#ifdef X15_SHELL
static void
log_dump(unsigned int level)
{
unsigned long index, flags;
struct log_record record;
int error;
spinlock_lock_intr_save(&log_lock, &flags);
index = cbuf_start(&log_cbuf);
for (;;) {
error = log_record_init_consume(&record, &index);
if (error) {
break;
}
spinlock_unlock_intr_restore(&log_lock, flags);
log_record_print(&record, level);
spinlock_lock_intr_save(&log_lock, &flags);
}
spinlock_unlock_intr_restore(&log_lock, flags);
}
static void
log_shell_dump(int argc, char **argv)
{
unsigned int level;
int ret;
if (argc != 2) {
level = log_print_level;
} else {
ret = sscanf(argv[1], "%u", &level);
if ((ret != 1) || (level >= LOG_NR_LEVELS)) {
printf("log: dump: invalid arguments\n");
return;
}
}
log_dump(level);
}
static struct shell_cmd log_shell_cmds[] = {
SHELL_CMD_INITIALIZER2("log_dump", log_shell_dump,
"log_dump []",
"dump the log buffer",
"Only records of level less than or equal to the given level"
" are printed. Level may be one of :\n"
" 0: emergency\n"
" 1: alert\n"
" 2: critical\n"
" 3: error\n"
" 4: warning\n"
" 5: notice\n"
" 6: info\n"
" 7: debug"),
};
#endif /* X15_SHELL */
void __init
log_setup(void)
{
cbuf_init(&log_cbuf, log_buffer, sizeof(log_buffer));
spinlock_init(&log_lock);
log_print_level = LOG_INFO;
}
void __init
log_start(void)
{
struct thread_attr attr;
int error;
thread_attr_init(&attr, THREAD_KERNEL_PREFIX "log_run");
thread_attr_set_detached(&attr);
error = thread_create(&log_thread, &attr, log_run, NULL);
if (error) {
panic("log: unable to create thread");
}
SHELL_REGISTER_CMDS(log_shell_cmds);
}
int
log_msg(unsigned int level, const char *format, ...)
{
va_list ap;
int ret;
va_start(ap, format);
ret = log_vmsg(level, format, ap);
va_end(ap);
return ret;
}
int
log_vmsg(unsigned int level, const char *format, va_list ap)
{
struct log_record record;
unsigned long flags;
int nr_chars;
log_record_init_produce(&record, level);
nr_chars = vsnprintf(record.buffer, sizeof(record.buffer), format, ap);
if ((unsigned int)nr_chars >= sizeof(record.buffer)) {
log_msg(LOG_ERR, "log: message too large");
goto out;
}
spinlock_lock_intr_save(&log_lock, &flags);
log_record_push(&record);
thread_wakeup(log_thread);
spinlock_unlock_intr_restore(&log_lock, flags);
out:
return nr_chars;
}
int
log_emerg(const char *format, ...)
{
va_list ap;
int ret;
va_start(ap, format);
ret = log_vemerg(format, ap);
va_end(ap);
return ret;
}
int
log_alert(const char *format, ...)
{
va_list ap;
int ret;
va_start(ap, format);
ret = log_valert(format, ap);
va_end(ap);
return ret;
}
int
log_crit(const char *format, ...)
{
va_list ap;
int ret;
va_start(ap, format);
ret = log_vcrit(format, ap);
va_end(ap);
return ret;
}
int
log_err(const char *format, ...)
{
va_list ap;
int ret;
va_start(ap, format);
ret = log_verr(format, ap);
va_end(ap);
return ret;
}
int
log_warning(const char *format, ...)
{
va_list ap;
int ret;
va_start(ap, format);
ret = log_vwarning(format, ap);
va_end(ap);
return ret;
}
int
log_notice(const char *format, ...)
{
va_list ap;
int ret;
va_start(ap, format);
ret = log_vnotice(format, ap);
va_end(ap);
return ret;
}
int
log_info(const char *format, ...)
{
va_list ap;
int ret;
va_start(ap, format);
ret = log_vinfo(format, ap);
va_end(ap);
return ret;
}
int
log_debug(const char *format, ...)
{
va_list ap;
int ret;
va_start(ap, format);
ret = log_vdebug(format, ap);
va_end(ap);
return ret;
}
int
log_vemerg(const char *format, va_list ap)
{
return log_vmsg(LOG_EMERG, format, ap);
}
int
log_valert(const char *format, va_list ap)
{
return log_vmsg(LOG_ALERT, format, ap);
}
int
log_vcrit(const char *format, va_list ap)
{
return log_vmsg(LOG_CRIT, format, ap);
}
int
log_verr(const char *format, va_list ap)
{
return log_vmsg(LOG_ERR, format, ap);
}
int
log_vwarning(const char *format, va_list ap)
{
return log_vmsg(LOG_WARNING, format, ap);
}
int
log_vnotice(const char *format, va_list ap)
{
return log_vmsg(LOG_NOTICE, format, ap);
}
int
log_vinfo(const char *format, va_list ap)
{
return log_vmsg(LOG_INFO, format, ap);
}
int
log_vdebug(const char *format, va_list ap)
{
return log_vmsg(LOG_DEBUG, format, ap);
}