Hi Simon, On Sun, Sep 17, 2017 at 5:23 AM, Simon Glass <s...@chromium.org> wrote: > Add the logging header file and implementation with some configuration > options to control it. > > Signed-off-by: Simon Glass <s...@chromium.org> > --- > > MAINTAINERS | 9 ++ > common/Kconfig | 56 +++++++++ > common/Makefile | 1 + > common/log.c | 246 +++++++++++++++++++++++++++++++++++++ > include/asm-generic/global_data.h | 5 + > include/log.h | 247 > ++++++++++++++++++++++++++++++++++++-- > 6 files changed, 555 insertions(+), 9 deletions(-) > create mode 100644 common/log.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 04acf2b89d..eb420afa8d 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -290,6 +290,15 @@ S: Maintained > T: git git://git.denx.de/u-boot-i2c.git > F: drivers/i2c/ > > +LOGGING > +M: Simon Glass <s...@chromium.org> > +S: Maintained > +T: git git://git.denx.de/u-boot.git > +F: common/log.c > +F: cmd/log.c > +F: test/log/log_test.c > +F: test/py/tests/test_log.py
test/log/log_test.c and test/py/tests/test_log.py have not been introduced at this point. > + > MICROBLAZE > M: Michal Simek <mon...@monstr.eu> > S: Maintained > diff --git a/common/Kconfig b/common/Kconfig > index 4d8cae9610..cbccc8ae26 100644 > --- a/common/Kconfig > +++ b/common/Kconfig > @@ -384,6 +384,62 @@ config SYS_STDIO_DEREGISTER > > endmenu > > +menu "Logging" > + > +config LOG > + bool "Enable logging support" > + help > + This enables support for logging of status and debug messages. These > + can be displayed on the console, recorded in a memory buffer, or > + discarded if not needed. Logging supports various categories and > + levels of severity. > + > +config SPL_LOG > + bool "Enable logging support in SPL" > + help > + This enables support for logging of status and debug messages. These > + can be displayed on the console, recorded in a memory buffer, or > + discarded if not needed. Logging supports various categories and > + levels of severity. > + > +config LOG_MAX_LEVEL > + int "Maximum log level to record" > + depends on LOG > + default 5 > + help > + This selects the maximum log level that will be recorded. Any value > + higher than this will be ignored. If possible log statements below > + this level will be discarded at build time. Levels: > + > + 0 - panic > + 1 - critical > + 2 - error > + 3 - warning > + 4 - note > + 5 - info > + 6 - detail > + 7 - debug > + > +config LOG_SPL_MAX_LEVEL > + int "Maximum log level to record in SPL" > + depends on SPL_LOG > + default 3 > + help > + This selects the maximum log level that will be recorded. Any value > + higher than this will be ignored. If possible log statements below > + this level will be discarded at build time. Levels: > + > + 0 - panic > + 1 - critical > + 2 - error > + 3 - warning > + 4 - note > + 5 - info > + 6 - detail > + 7 - debug > + > +endmenu > + > config DTB_RESELECT > bool "Support swapping dtbs at a later point in boot" > depends on FIT_EMBED > diff --git a/common/Makefile b/common/Makefile > index 1b56cf9a70..d37c8d5636 100644 > --- a/common/Makefile > +++ b/common/Makefile > @@ -128,5 +128,6 @@ obj-y += cli.o > obj-$(CONFIG_FSL_DDR_INTERACTIVE) += cli_simple.o cli_readline.o > obj-$(CONFIG_CMD_DFU) += dfu.o > obj-y += command.o > +obj-$(CONFIG_$(SPL_)LOG) += log.o > obj-y += s_record.o > obj-y += xyzModem.o > diff --git a/common/log.c b/common/log.c > new file mode 100644 > index 0000000000..6bf2219d38 > --- /dev/null > +++ b/common/log.c > @@ -0,0 +1,246 @@ > +/* > + * Logging support > + * > + * Copyright (c) 2017 Google, Inc > + * Written by Simon Glass <s...@chromium.org> > + * > + * SPDX-License-Identifier: GPL-2.0+ > + */ > + > +#include <common.h> > +#include <log.h> > +#include <malloc.h> > + > +DECLARE_GLOBAL_DATA_PTR; > + > +static struct log_device *log_device_find_by_name(const char *drv_name) > +{ > + struct log_device *ldev; > + > + list_for_each_entry(ldev, &gd->log_head, sibling_node) { > + if (!strcmp(drv_name, ldev->drv->name)) > + return ldev; > + } > + > + return NULL; > +} > + > +/** > + * log_has_cat() - check if a log category exists within a list > + * > + * @cat_list: List of categories to check, at most LOGF_MAX_CATEGORIES > entries > + * long, terminated by LC_END if fewer > + * @cat: Category to search for > + * @return true if @cat is in @cat_list, else false > + */ > +static bool log_has_cat(enum log_category_t cat_list[], enum log_category_t > cat) > +{ > + int i; > + > + for (i = 0; i < LOGF_MAX_CATEGORIES && cat_list[i] != LOGC_END; i++) { > + if (cat_list[i] == cat) > + return true; > + } > + > + return false; > +} > + > +/** > + * log_has_file() - check if a file is with a list > + * > + * @file_list: List of files to check, separated by comma > + * @file: File to check for. This string is matched against the end of each > + * file in the list, i.e. ignoring any preceeding path. The list is > + * intended to consist of relative pathnames, e.g. > common/main.c,cmd/log.c > + * @return true if @file is in @file_list, else false > + */ > +static bool log_has_file(const char *file_list, const char *file) > +{ > + int file_len = strlen(file); > + const char *s, *p; > + int substr_len; > + > + for (s = file_list; *s; s = p + (*p != '\0')) { > + p = strchrnul(s, ','); > + substr_len = p - s; > + if (file_len >= substr_len && > + !strncmp(file + file_len - substr_len, s, substr_len)) > + return true; > + } > + > + return false; > +} > + > +/** > + * log_passes_filters() - check if a log record passes the filters for a > device > + * > + * @ldev: Log device to check > + * @rec: Log record to check > + * @return true if @rec is not blocked by the filters in @ldev, false if it > is > + */ > +static bool log_passes_filters(struct log_device *ldev, struct log_rec *rec) > +{ > + struct log_filter *filt; > + > + /* If there are no filters, filter on the default log level */ > + if (list_empty(&ldev->filter_head)) { > + if (rec->level > gd->default_log_level) > + return false; > + return true; > + } > + > + list_for_each_entry(filt, &ldev->filter_head, sibling_node) { > + if (rec->level > filt->max_level) > + continue; > + if ((filt->flags & LOGFF_HAS_CAT) && > + !log_has_cat(filt->cat_list, rec->cat)) > + continue; > + if (filt->file_list && > + !log_has_file(filt->file_list, rec->file)) > + continue; > + return true; > + } > + > + return false; > +} > + > +/** > + * log_dispatch() - Send a log record to all log devices for processing > + * > + * The log record is sent to each log device in turn, skipping those which > have > + * filters which block the record > + * > + * @rec: Log record to dispatch > + * @return 0 (meaning success) > + */ > +static int log_dispatch(struct log_rec *rec) > +{ > + struct log_device *ldev; > + > + list_for_each_entry(ldev, &gd->log_head, sibling_node) { > + if (log_passes_filters(ldev, rec)) > + ldev->drv->emit(ldev, rec); > + } > + > + return 0; > +} > + > +int _log(enum log_category_t cat, enum log_level_t level, const char *file, > + int line, const char *func, const char *fmt, ...) > +{ > + char buf[CONFIG_SYS_CBSIZE]; > + struct log_rec rec; > + va_list args; > + > + rec.cat = cat; > + rec.level = level; > + rec.file = file; > + rec.line = line; > + rec.func = func; > + va_start(args, fmt); > + vsnprintf(buf, sizeof(buf), fmt, args); > + va_end(args); > + rec.msg = buf; > + if (!gd || !(gd->flags & GD_FLG_LOG_READY)) { > + if (gd) > + gd->log_drop_count++; > + return -ENOSYS; > + } > + log_dispatch(&rec); > + > + return 0; > +} > + > +int log_add_filter(const char *drv_name, enum log_category_t cat_list[], > + enum log_level_t max_level, const char *file_list) > +{ > + struct log_filter *filt; > + struct log_device *ldev; > + int i; > + > + ldev = log_device_find_by_name(drv_name); > + if (!ldev) > + return -ENOENT; > + filt = (struct log_filter *)calloc(1, sizeof(*filt)); > + if (!filt) > + return -ENOMEM; > + > + if (cat_list) { > + filt->flags |= LOGFF_HAS_CAT; > + for (i = 0; ; i++) { > + if (i == ARRAY_SIZE(filt->cat_list)) > + return -ENOSPC; > + filt->cat_list[i] = cat_list[i]; > + if (cat_list[i] == LOGC_END) > + break; > + } > + } > + filt->max_level = max_level; > + if (file_list) { > + filt->file_list = strdup(file_list); > + if (!filt->file_list) > + goto nomem; > + } > + filt->filter_num = ldev->next_filter_num++; > + INIT_LIST_HEAD(&filt->sibling_node); > + list_add_tail(&filt->sibling_node, &ldev->filter_head); > + > + return filt->filter_num; > + > +nomem: > + free(filt); > + return -ENOMEM; > +} > + > +int log_remove_filter(const char *drv_name, int filter_num) > +{ > + struct log_filter *filt; > + struct log_device *ldev; > + > + ldev = log_device_find_by_name(drv_name); > + if (!ldev) > + return -ENOENT; > + > + list_for_each_entry(filt, &ldev->filter_head, sibling_node) { > + if (filt->filter_num == filter_num) { > + list_del(&filt->sibling_node); > + free(filt); > + > + return 0; > + } > + } > + > + return -ENOENT; > +} > + > +int log_init(void) > +{ > + struct log_driver *drv = ll_entry_start(struct log_driver, > log_driver); > + const int count = ll_entry_count(struct log_driver, log_driver); > + struct log_driver *end = drv + count; > + > + /* > + * We cannot add runtime data to the driver since it is likely stored > + * in rodata. Instead, set up a 'device' corresponding to each driver. > + * We only support having a single device. > + */ > + INIT_LIST_HEAD((struct list_head *)&gd->log_head); > + while (drv < end) { > + struct log_device *ldev; > + > + ldev = calloc(1, sizeof(*ldev)); > + if (!ldev) { > + debug("%s: Cannot allocate memory\n", __func__); > + return -ENOMEM; > + } > + INIT_LIST_HEAD(&ldev->sibling_node); > + INIT_LIST_HEAD(&ldev->filter_head); > + ldev->drv = drv; > + list_add_tail(&ldev->sibling_node, > + (struct list_head *)&gd->log_head); > + drv++; > + } > + gd->default_log_level = LOGL_INFO; Shouldn't this be the Kconfig option CONFIG_LOG_MAX_LEVEL? > + > + return 0; > +} > diff --git a/include/asm-generic/global_data.h > b/include/asm-generic/global_data.h > index 79197acfa4..77755dbb06 100644 > --- a/include/asm-generic/global_data.h > +++ b/include/asm-generic/global_data.h > @@ -114,6 +114,11 @@ typedef struct global_data { > struct bootstage_data *bootstage; /* Bootstage information */ > struct bootstage_data *new_bootstage; /* Relocated bootstage info */ > #endif > +#ifdef CONFIG_LOG > + int log_drop_count; /* Number of dropped log messages */ > + int default_log_level; /* For devices with no filters */ > + struct list_head log_head; /* List of struct log_device */ > +#endif > } gd_t; > #endif > > diff --git a/include/log.h b/include/log.h > index 4101a74161..fb6a196202 100644 > --- a/include/log.h > +++ b/include/log.h > @@ -10,6 +10,82 @@ > #ifndef __LOG_H > #define __LOG_H > > +#include <dm/uclass-id.h> > +#include <linux/list.h> > + > +/** Log levels supported, ranging from most to least important */ > +enum log_level_t { > + LOGL_PANIC = 0, > + LOGL_CRIT, > + LOGL_ERR, > + LOGL_WARN, > + LOGL_NOTE, > + LOGL_INFO, > + LOGL_DETAIL, > + LOGL_DEBUG, > + > + LOGL_COUNT, > + LOGL_FIRST = LOGL_PANIC, > + LOGL_MAX = LOGL_DEBUG, > +}; > + > +/** > + * Log categories supported. Most of these correspond to uclasses (i.e. > + * enum uclass_id) but there are also some more generic categories > + */ > +enum log_category_t { > + LOGC_FIRST = 0, /* First part mirrors UCLASS_... */ > + > + LOGC_NONE = UCLASS_COUNT, > + LOGC_ARCH, > + LOGC_BOARD, > + LOGC_CORE, > + LOGC_DT, > + > + LOGC_COUNT, > + LOGC_END, > +}; > + > +/** > + * _log() - Internal function to emit a new log record > + * > + * @cat: Category of log record (indicating which subsystem generated it) > + * @level: Level of log record (indicating its severity) > + * @file: File name of file where log record was generated > + * @line: Line number in file where log record was generated > + * @func: Function where log record was generated > + * @fmt: printf() format string for log record > + * @...: Optional parameters, according to the format string @fmt > + * @return 0 if log record was emitted, -ve on error > + */ > +int _log(enum log_category_t cat, enum log_level_t level, const char *file, > + int line, const char *func, const char *fmt, ...); > + > +/* Define this at the top of a file to add a prefix to debug messages */ > +#ifndef pr_fmt > +#define pr_fmt(fmt) fmt > +#endif > + > +/* Use a default category if this file does not supply one */ > +#ifndef LOG_CATEGORY > +#define LOG_CATEGORY LOGC_NONE > +#endif > + > +#if CONFIG_VAL(LOG_MAX_LEVEL) > +#define _LOG_MAX_LEVEL CONFIG_VAL(LOG_MAX_LEVEL) > +#else > +#define _LOG_MAX_LEVEL LOGL_INFO > +#endif > + > +/* Emit a log record if the level is less that the maximum */ > +#define log(_cat, _level, _fmt, _args...) ({ \ > + int _l = _level; \ > + if (_l > _LOG_MAX_LEVEL) \ > + continue; \ > + _log(_cat, _l, __FILE__, __LINE__, __func__, \ > + pr_fmt(_fmt), ##_args); \ > + }) > + > #ifdef DEBUG > #define _DEBUG 1 > #else > @@ -22,10 +98,19 @@ > #define _SPL_BUILD 0 > #endif > > -/* Define this at the top of a file to add a prefix to debug messages */ > -#ifndef pr_fmt > -#define pr_fmt(fmt) fmt > -#endif > +#if !_DEBUG && CONFIG_IS_ENABLED(LOG) > + > +#define debug_cond(cond, fmt, args...) \ > + do { \ > + if (1) \ > + log(LOG_CATEGORY, LOGL_DEBUG, fmt, ##args); \ > + } while (0) > + > +#define error(fmt, args...) do { \ > + log(LOG_CATEGORY, LOGL_ERR, fmt, ##args); \ > +} while (0) > + > +#else /* _DEBUG */ > > /* > * Output a debug text when condition "cond" is met. The "cond" should be > @@ -38,6 +123,13 @@ > printf(pr_fmt(fmt), ##args); \ > } while (0) > > +#define error(fmt, args...) do { \ > + printf("ERROR: " pr_fmt(fmt) "\nat %s:%d/%s()\n", \ > + ##args, __FILE__, __LINE__, __func__); \ > +} while (0) > + > +#endif /* _DEBUG */ > + > /* Show a message if DEBUG is defined in a file */ > #define debug(fmt, args...) \ > debug_cond(_DEBUG, fmt, ##args) > @@ -61,11 +153,6 @@ void __assert_fail(const char *assertion, const char > *file, unsigned int line, > ({ if (!(x) && _DEBUG) \ > __assert_fail(#x, __FILE__, __LINE__, __func__); }) > > -#define error(fmt, args...) do { \ > - printf("ERROR: " pr_fmt(fmt) "\nat %s:%d/%s()\n", \ > - ##args, __FILE__, __LINE__, __func__); \ > -} while (0) > - > #ifndef BUG > #define BUG() do { \ > printf("BUG: failure at %s:%d/%s()!\n", __FILE__, __LINE__, \ > @@ -76,4 +163,146 @@ void __assert_fail(const char *assertion, const char > *file, unsigned int line, > while (0) > #endif /* BUG */ > > +/** > + * struct log_rec - a single log record > + * > + * Holds information about a single record in the log > + * > + * Members marked as 'not allocated' are stored as pointers and the caller is > + * responsible for making sure that the data pointed to is not overwritten. > + * Memebers marked as 'allocated' are allocated (e.g. via strdup()) by the > log > + * system. > + * > + * @cat: Category, representing a uclass or part of U-Boot > + * @level: Severity level, less severe is higher > + * @file: Name of file where the log record was generated (not allocated) > + * @line: Line number where the log record was generated > + * @func: Function where the log record was generated (not allocated) > + * @msg: Log message (allocated) > + */ > +struct log_rec { > + enum log_category_t cat; > + enum log_level_t level; > + const char *file; > + int line; > + const char *func; > + const char *msg; > +}; > + > +struct log_device; > + > +/** > + * struct log_driver - a driver which accepts and processes log records > + * > + * @name: Name of driver > + */ > +struct log_driver { > + const char *name; > + /** > + * emit() - emit a log record > + * > + * Called by the log system to pass a log record to a particular > driver > + * for processing. The filter is checked before calling this function. > + */ > + int (*emit)(struct log_device *ldev, struct log_rec *rec); > +}; > + So we are creating a new type of non-DM driver which is log-specific? How about we add this emit to the existing uclass driver that can be used as the log driver? (eg: blk devices with file system?) > +/** > + * struct log_device - an instance of a log driver > + * > + * Since drivers are set up at build-time we need to have a separate device > for > + * the run-time aspects of drivers (currently just a list of filters to apply > + * to records send to this device). > + * > + * @next_filter_num: Seqence number of next filter filter added (0=no filters > + * yet). This increments with each new filter on the device, but never > + * decrements > + * @drv: Pointer to driver for this device > + * @filter_head: List of filters for this device > + * @sibling_node: Next device in the list of all devices > + */ > +struct log_device { > + int next_filter_num; > + struct log_driver *drv; > + struct list_head filter_head; > + struct list_head sibling_node; > +}; > + > +enum { > + LOGF_MAX_CATEGORIES = 5, /* maximum categories per filter */ > +}; > + > +enum log_filter_flags { > + LOGFF_HAS_CAT = 1 << 0, /* Filter has a category list > */ > +}; > + > +/** > + * struct log_filter - criterial to filter out log messages > + * > + * @filter_num: Sequence number of this filter. This is returned when > adding a > + * new filter, and must be provided when removing a previously added > + * filter. > + * @flags: Flags for this filter (LOGFF_...) > + * @cat_list: List of categories to allow (terminated by LOGC_none). If empty > + * then all categories are permitted. Up to LOGF_MAX_CATEGORIES entries > + * can be provided > + * @max_level: Maximum log level to allow > + * @file_list: List of files to allow, separated by comma. If NULL then all > + * files are permitted > + * @sibling_node: Next filter in the list of filters for this log device > + */ > +struct log_filter { > + int filter_num; > + int flags; > + enum log_category_t cat_list[LOGF_MAX_CATEGORIES]; > + enum log_level_t max_level; > + const char *file_list; > + struct list_head sibling_node; > +}; > + > +#define LOG_DRIVER(_name) \ > + ll_entry_declare(struct log_driver, _name, log_driver) > + > +/** > + * log_add_filter() - Add a new filter to a log device > + * > + * @drv_name: Driver name to add the filter to (since each driver only has a > + * single device) > + * @cat_list: List of categories to allow (terminated by LOGC_none). If empty > + * then all categories are permitted. Up to LOGF_MAX_CATEGORIES entries > + * can be provided > + * @max_level: Maximum log level to allow > + * @file_list: List of files to allow, separated by comma. If NULL then all > + * files are permitted > + * @return the sequence number of the new filter (>=0) if the filter was > added, > + * or a -ve value on error > + */ > +int log_add_filter(const char *drv_name, enum log_category_t cat_list[], > + enum log_level_t max_level, const char *file_list); > + > +/** > + * log_remove_filter() - Remove a filter from a log device > + * > + * @drv_name: Driver name to remove the filter from (since each driver only > has > + * a single device) > + * @filter_num: Filter number to remove (as returned by log_add_filter()) > + * @return 0 if the filter was removed, -ENOENT if either the driver or the > + * filter number was not found > + */ > +int log_remove_filter(const char *drv_name, int filter_num); > + > +#if CONFIG_IS_ENABLED(LOG) > +/** > + * log_init() - Set up the log system ready for use > + * > + * @return 0 if OK, -ENOMEM if out of memory > + */ > +int log_init(void); > +#else > +static inline int log_init(void) > +{ > + return 0; > +} > +#endif > + > #endif > -- Regards, Bin _______________________________________________ U-Boot mailing list U-Boot@lists.denx.de https://lists.denx.de/listinfo/u-boot