/* * Copyright (c) 2017-2019 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 #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_PRINT_LEVEL LOG_INFO static struct thread *log_thread; static struct mbuf log_mbuf; static char log_buffer[LOG_BUFFER_SIZE]; static unsigned int log_nr_overruns; static struct bulletin log_bulletin; /* * Global lock. * * Interrupts must be disabled when holding this lock. */ static struct spinlock log_lock; struct log_record { uint8_t level; char msg[LOG_MSG_SIZE]; }; struct log_consumer { struct mbuf *mbuf; size_t index; }; static void log_consumer_init(struct log_consumer *ctx, struct mbuf *mbuf) { ctx->mbuf = mbuf; ctx->index = mbuf_start(mbuf); } static int log_consumer_pop(struct log_consumer *ctx, struct log_record *record) { size_t size; int error; for (;;) { size = sizeof(*record); error = mbuf_read(ctx->mbuf, &ctx->index, record, &size); if (error != EINVAL) { break; } else { ctx->index = mbuf_start(ctx->mbuf); } } return error; } 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 void log_print_record(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->msg); } else { printf("%s\n", record->msg); } } static void log_run(void *arg) { struct log_consumer ctx; unsigned long flags; bool published; (void)arg; published = false; spinlock_lock_intr_save(&log_lock, &flags); log_consumer_init(&ctx, &log_mbuf); for (;;) { struct log_record record; for (;;) { int error; error = log_consumer_pop(&ctx, &record); if (!error) { break; } else if (log_nr_overruns != 0) { record.level = LOG_ERR; snprintf(record.msg, sizeof(record.msg), "log: buffer overruns, %u messages dropped", log_nr_overruns); log_nr_overruns = 0; break; } if (!published) { spinlock_unlock_intr_restore(&log_lock, flags); bulletin_publish(&log_bulletin, 0); spinlock_lock_intr_save(&log_lock, &flags); published = true; } thread_sleep(&log_lock, &log_mbuf, "log_mbuf"); } spinlock_unlock_intr_restore(&log_lock, flags); log_print_record(&record, LOG_PRINT_LEVEL); spinlock_lock_intr_save(&log_lock, &flags); } } #ifdef CONFIG_SHELL static void log_dump(unsigned int level) { struct log_consumer ctx; struct log_record record; unsigned long flags; int error; spinlock_lock_intr_save(&log_lock, &flags); log_consumer_init(&ctx, &log_mbuf); for (;;) { error = log_consumer_pop(&ctx, &record); if (error) { break; } spinlock_unlock_intr_restore(&log_lock, flags); log_print_record(&record, level); spinlock_lock_intr_save(&log_lock, &flags); } spinlock_unlock_intr_restore(&log_lock, flags); } static void log_shell_dump(struct shell *shell, int argc, char **argv) { unsigned int level; int ret; (void)shell; 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"), }; static int __init log_setup_shell(void) { SHELL_REGISTER_CMDS(log_shell_cmds, shell_get_main_cmd_set()); return 0; } INIT_OP_DEFINE(log_setup_shell, INIT_OP_DEP(log_setup, true), INIT_OP_DEP(shell_setup, true)); #endif /* CONFIG_SHELL */ static int __init log_setup(void) { mbuf_init(&log_mbuf, log_buffer, sizeof(log_buffer), sizeof(struct log_record)); spinlock_init(&log_lock); bulletin_init(&log_bulletin); boot_log_info(); arg_log_info(); cpu_log_info(cpu_current()); return 0; } INIT_OP_DEFINE(log_setup, INIT_OP_DEP(arg_setup, true), INIT_OP_DEP(cpu_setup, true), INIT_OP_DEP(spinlock_setup, true)); static int __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"); } return 0; } INIT_OP_DEFINE(log_start, INIT_OP_DEP(log_setup, true), INIT_OP_DEP(thread_setup, true)); 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 error, nr_chars; size_t size; char *ptr; assert(level < LOG_NR_LEVELS); record.level = level; nr_chars = vsnprintf(record.msg, sizeof(record.msg), format, ap); if ((unsigned int)nr_chars >= sizeof(record.msg)) { log_msg(LOG_ERR, "log: message too large"); goto out; } ptr = strchr(record.msg, '\n'); if (ptr != NULL) { *ptr = '\0'; nr_chars = ptr - record.msg; } assert(nr_chars >= 0); size = offsetof(struct log_record, msg) + nr_chars + 1; spinlock_lock_intr_save(&log_lock, &flags); error = mbuf_push(&log_mbuf, &record, size, true); if (error) { log_nr_overruns++; } 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); } struct bulletin * log_get_bulletin(void) { return &log_bulletin; }