[U-Boot] [PATCH v3 07/14] log: Add an implemention of logging

Lukasz Majewski lukma at denx.de
Tue Nov 21 09:55:52 UTC 2017


On Mon, 20 Nov 2017 15:33:28 -0700
Simon Glass <sjg at chromium.org> wrote:

> Add the logging header file and implementation with some configuration
> options to control it.

Despite one question -

Reviewed-by: Lukasz Majewski <lukma at denx.de>

> 
> Signed-off-by: Simon Glass <sjg at chromium.org>
> ---
> 
> Changes in v3: None
> Changes in v2:
> - Add a comment as to why CONFIG_LOG_MAX_LEVEL is not defined
> - Drop MAINTAINERS entries for files not added by this patch
> - Drop the use of 'continue' in the macro
> - Fix LOG_SPL_MAX_LEVEL typo (should be SPL_LOG_MAX_LEVEL)
> - Fix up bad use of #if CONFIG_VAL() - use #ifdef instead
> - Line up log levels with Linux
> 
>  MAINTAINERS                       |   7 ++
>  common/Kconfig                    |  56 +++++++++
>  common/Makefile                   |   1 +
>  common/log.c                      | 244
> ++++++++++++++++++++++++++++++++++++++
> include/asm-generic/global_data.h |   5 +
> include/log.h                     | 235
> ++++++++++++++++++++++++++++++++++++ 6 files changed, 548
> insertions(+) create mode 100644 common/log.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index b167b028ecf..6814c2fc566 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -291,6 +291,13 @@ 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
> +
>  MICROBLAZE
>  M:	Michal Simek <monstr at monstr.eu>
>  S:	Maintained
> diff --git a/common/Kconfig b/common/Kconfig
> index c50d6ebb2ad..9747443feb2 100644
> --- a/common/Kconfig
> +++ b/common/Kconfig
> @@ -420,6 +420,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 SPL_LOG_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 DEFAULT_FDT_FILE
>  	string "Default fdt file"
>  	help
> diff --git a/common/Makefile b/common/Makefile
> index cec506fe3e1..f4b632761fa 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 00000000000..a7d9a548f2a
> --- /dev/null
> +++ b/common/log.c
> @@ -0,0 +1,244 @@
> +/*
> + * 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++) {
			  ^^^^ - I suppose that this is "true"
				 Does it comply with any standard (is
				 it the undefined behaviour?)

> +			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++;
> +	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->filter_head);
> +		ldev->drv = drv;
> +		list_add_tail(&ldev->sibling_node,
> +			      (struct list_head *)&gd->log_head);
> +		drv++;
> +	}
> +	gd->default_log_level = LOGL_INFO;
> +
> +	return 0;
> +}
> diff --git a/include/asm-generic/global_data.h
> b/include/asm-generic/global_data.h index 79197acfa42..77755dbb068
> 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 08ad44cf497..5dced16880b 100644
> --- a/include/log.h
> +++ b/include/log.h
> @@ -10,6 +10,87 @@
>  #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_EMERG = 0,		/*U-Boot is unstable */
> +	LOGL_ALERT,		/* Action must be taken
> immediately */
> +	LOGL_CRIT,		/* Critical conditions */
> +	LOGL_ERR,		/* Error that prevents something
> from working */
> +	LOGL_WARNING,		/* Warning may prevent optimial
> operation */
> +	LOGL_NOTICE,		/* Normal but significant
> condition, printf() */
> +	LOGL_INFO,		/* General information message */
> +	LOGL_DEBUG,		/* Basic debug-level message */
> +	LOGL_DEBUG_CONTENT,	/* Debug message showing full
> message content */
> +	LOGL_DEBUG_IO,		/* Debug message showing
> hardware I/O access */ +
> +	LOGL_COUNT,
> +	LOGL_FIRST = LOGL_EMERG,
> +	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
> +
> +/*
> + * This header may be including when CONFIG_LOG is disabled, in
> which case
> + * CONFIG_LOG_MAX_LEVEL is not defined. Add a check for this.
> + */
> +#if CONFIG_IS_ENABLED(LOG)
> +#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) \
> +		_log(_cat, _l, __FILE__, __LINE__, __func__, \
> +		     pr_fmt(_fmt), ##_args); \
> +	})
> +
>  #ifdef DEBUG
>  #define _DEBUG	1
>  #else
> @@ -22,6 +103,16 @@
>  #define _SPL_BUILD	0
>  #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)
> +
> +#else /* _DEBUG */
> +
>  /*
>   * Output a debug text when condition "cond" is met. The "cond"
> should be
>   * computed by a preprocessor in the best case, allowing for the best
> @@ -33,6 +124,8 @@
>  			printf(pr_fmt(fmt), ##args);	\
>  	} while (0)
>  
> +#endif /* _DEBUG */
> +
>  /* Show a message if DEBUG is defined in a file */
>  #define debug(fmt, args...)			\
>  	debug_cond(_DEBUG, fmt, ##args)
> @@ -56,4 +149,146 @@ void __assert_fail(const char *assertion, const
> char *file, unsigned int line, ({ if (!(x) && _DEBUG) \
>  		__assert_fail(#x, __FILE__, __LINE__, __func__); })
>  
> +/**
> + * 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);
> +};
> +
> +/**
> + * 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



Best regards,

Lukasz Majewski

--

DENX Software Engineering GmbH,      Managing Director: Wolfgang Denk
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
Phone: (+49)-8142-66989-10 Fax: (+49)-8142-66989-80 Email: wd at denx.de
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 488 bytes
Desc: OpenPGP digital signature
URL: <http://lists.denx.de/pipermail/u-boot/attachments/20171121/c54521b2/attachment.sig>


More information about the U-Boot mailing list