[U-Boot] [PATCH 06/13] log: Add an implemention of logging
Bin Meng
bmeng.cn at gmail.com
Mon Sep 18 03:45:00 UTC 2017
Hi Simon,
On Sun, Sep 17, 2017 at 5:23 AM, Simon Glass <sjg at chromium.org> wrote:
> Add the logging header file and implementation with some configuration
> options to control it.
>
> Signed-off-by: Simon Glass <sjg at 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 <sjg at 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 <monstr at 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 <sjg at 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
More information about the U-Boot
mailing list