diff options
author | Richard Braun <rbraun@sceen.net> | 2017-06-10 16:12:19 +0200 |
---|---|---|
committer | Richard Braun <rbraun@sceen.net> | 2017-06-10 16:12:19 +0200 |
commit | ec7c306bda9a7a4a8b656417e505041e97779a5c (patch) | |
tree | 46fb818620eee358ec91460219ac997c2107b853 /kern | |
parent | 88a92b367618409cf7c6cd50112e3b81f93d5272 (diff) |
kern/log: new module
Diffstat (limited to 'kern')
-rw-r--r-- | kern/kernel.c | 2 | ||||
-rw-r--r-- | kern/log.c | 457 | ||||
-rw-r--r-- | kern/log.h | 91 |
3 files changed, 550 insertions, 0 deletions
diff --git a/kern/kernel.c b/kern/kernel.c index 1d61b0ca..30dff08e 100644 --- a/kern/kernel.c +++ b/kern/kernel.c @@ -19,6 +19,7 @@ #include <kern/init.h> #include <kern/kernel.h> #include <kern/llsync.h> +#include <kern/log.h> #include <kern/percpu.h> #include <kern/shell.h> #include <kern/sleepq.h> @@ -52,6 +53,7 @@ kernel_main(void) sref_setup(); vm_page_info(); shell_start(); + log_start(); #ifdef X15_RUN_TEST_MODULE test_setup(); diff --git a/kern/log.c b/kern/log.c new file mode 100644 index 00000000..1320d3b8 --- /dev/null +++ b/kern/log.c @@ -0,0 +1,457 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <limits.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdint.h> + +#include <kern/cbuf.h> +#include <kern/init.h> +#include <kern/log.h> +#include <kern/macros.h> +#include <kern/panic.h> +#include <kern/spinlock.h> +#include <kern/thread.h> + +#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; + +/* + * 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) +{ + bool marker_found; + size_t size; + int error; + char c; + + marker_found = false; + size = 0; + + for (;;) { + error = cbuf_pop(&log_cbuf, &c); + + if (error) { + if (!marker_found) { + return ERROR_INVAL; + } + + break; + } + + 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) +{ + printf("%7s %s\n", log_level2str(record->level), 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, nr_overruns; + struct log_record record; + int error; + + (void)arg; + + nr_overruns = 0; + + for (;;) { + spinlock_lock_intr_save(&log_lock, &flags); + + while (cbuf_size(&log_cbuf) == 0) { + thread_sleep(&log_lock, &log_cbuf, "log_cbuf"); + } + + error = log_record_init_consume(&record); + + /* 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); + } + + if (nr_overruns != 0) { + log_msg(LOG_ERR, "log: buffer overruns, %lu bytes dropped", + nr_overruns); + nr_overruns = 0; + } + } +} + +void __init +log_setup(void) +{ + cbuf_init(&log_cbuf, log_buffer, sizeof(log_buffer)); + spinlock_init(&log_lock); +} + +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"); + } +} + +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); +} diff --git a/kern/log.h b/kern/log.h new file mode 100644 index 00000000..a306ba36 --- /dev/null +++ b/kern/log.h @@ -0,0 +1,91 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + * + * + * System logging. + */ + +#ifndef _KERN_LOG_H +#define _KERN_LOG_H + +#include <stdarg.h> + +enum { + LOG_EMERG, + LOG_ALERT, + LOG_CRIT, + LOG_ERR, + LOG_WARNING, + LOG_NOTICE, + LOG_INFO, + LOG_DEBUG, + LOG_NR_LEVELS, +}; + +/* + * Initialize the log module. + */ +void log_setup(void); + +/* + * Start the log thread. + */ +void log_start(void); + +/* + * Generate a message and send it to the log thread. + * + * Except for level, the arguments and return value are similar to printf(). + * + * This function may safely be called in interrupt context. + */ +int log_msg(unsigned int level, const char *format, ...) + __attribute__((format(printf, 2, 3))); + +int log_vmsg(unsigned int level, const char *format, va_list ap) + __attribute__((format(printf, 2, 0))); + +/* + * Convenience wrappers. + */ + +int log_emerg(const char *format, ...) __attribute__((format(printf, 1, 2))); +int log_alert(const char *format, ...) __attribute__((format(printf, 1, 2))); +int log_crit(const char *format, ...) __attribute__((format(printf, 1, 2))); +int log_err(const char *format, ...) __attribute__((format(printf, 1, 2))); +int log_warning(const char *format, ...) __attribute__((format(printf, 1, 2))); +int log_notice(const char *format, ...) __attribute__((format(printf, 1, 2))); +int log_info(const char *format, ...) __attribute__((format(printf, 1, 2))); +int log_debug(const char *format, ...) __attribute__((format(printf, 1, 2))); + +int log_vemerg(const char *format, va_list ap) + __attribute__((format(printf, 1, 0))); +int log_valert(const char *format, va_list ap) + __attribute__((format(printf, 1, 0))); +int log_vcrit(const char *format, va_list ap) + __attribute__((format(printf, 1, 0))); +int log_verr(const char *format, va_list ap) + __attribute__((format(printf, 1, 0))); +int log_vwarning(const char *format, va_list ap) + __attribute__((format(printf, 1, 0))); +int log_vnotice(const char *format, va_list ap) + __attribute__((format(printf, 1, 0))); +int log_vinfo(const char *format, va_list ap) + __attribute__((format(printf, 1, 0))); +int log_vdebug(const char *format, va_list ap) + __attribute__((format(printf, 1, 0))); + +#endif /* _KERN_LOG_H */ |