[PATCH 1/2] cmd: bcm: logsetup: Add Broadcom error log setup command

Simon Glass sjg at chromium.org
Sat Dec 28 03:26:49 CET 2019


Hi Vladimir,

On Mon, 18 Nov 2019 at 17:59, Vladimir Olovyannikov
<vladimir.olovyannikov at broadcom.com> wrote:
>
> Some Broadcom platforms have ability to record event logs
> by SCP.
> Add a logsetup command which is used to perform initial
> configuration of this log and move the command to
> bcm/ directory to be used for Broadcom-specific
> U-boot commands.
>
> Signed-off-by: Vladimir Olovyannikov <vladimir.olovyannikov at broadcom.com>
> ---
>  cmd/Kconfig        |   2 +
>  cmd/Makefile       |   2 +
>  cmd/bcm/Kconfig    |  12 ++
>  cmd/bcm/Makefile   |   4 +
>  cmd/bcm/elog.h     |  64 +++++++
>  cmd/bcm/logsetup.c | 433 +++++++++++++++++++++++++++++++++++++++++++++
>  6 files changed, 517 insertions(+)
>  create mode 100644 cmd/bcm/Kconfig
>  create mode 100644 cmd/bcm/Makefile
>  create mode 100644 cmd/bcm/elog.h
>  create mode 100644 cmd/bcm/logsetup.c

Reviewed-by: Simon Glass <sjg at chromium.org>

Please see comments below.

>
> diff --git a/cmd/Kconfig b/cmd/Kconfig
> index cf982ff65e..c13998887f 100644
> --- a/cmd/Kconfig
> +++ b/cmd/Kconfig
> @@ -2119,4 +2119,6 @@ config CMD_UBIFS
>         help
>           UBIFS is a file system for flash devices which works on top of UBI.
>
> +source cmd/bcm/Kconfig
> +
>  endmenu
> diff --git a/cmd/Makefile b/cmd/Makefile
> index 2d723ea0f0..ae38bea901 100644
> --- a/cmd/Makefile
> +++ b/cmd/Makefile
> @@ -183,6 +183,8 @@ endif # !CONFIG_SPL_BUILD
>  # core command
>  obj-y += nvedit.o
>
> +obj-$(CONFIG_CMD_BCM_EXT_UTILS) += bcm/
> +
>  obj-$(CONFIG_TI_COMMON_CMD_OPTIONS) += ti/
>
>  filechk_data_gz = (echo "static const char data_gz[] ="; cat $< | scripts/bin2c; echo ";")
> diff --git a/cmd/bcm/Kconfig b/cmd/bcm/Kconfig
> new file mode 100644
> index 0000000000..189a45004e
> --- /dev/null
> +++ b/cmd/bcm/Kconfig
> @@ -0,0 +1,12 @@
> +menu "Broadcom Extended Utilities"
> +
> +config CMD_BCM_LOGSETUP
> +       bool "Command to setup logging on Broadcom boards"
> +       depends on TARGET_BCMNS3

Better to depend on an ARCH if you can. Options shouldn't depend on
specific targets.

> +       default n
> +       help
> +          Support specific log setup on Broadcom SoCs. This command
> +          allows checking if logging support is present, and update
> +          log sections.

Is there documentation on the log format? Where is it stored? Could
use a few more details here.

> +
> +endmenu
> diff --git a/cmd/bcm/Makefile b/cmd/bcm/Makefile
> new file mode 100644
> index 0000000000..c5ae924ea0
> --- /dev/null
> +++ b/cmd/bcm/Makefile
> @@ -0,0 +1,4 @@
> +# SPDX-License-Identifier: GPL-2.0+
> +# Copyright 2019 Broadcom
> +
> +obj-$(CONFIG_CMD_BCM_LOGSETUP) += logsetup.o
> diff --git a/cmd/bcm/elog.h b/cmd/bcm/elog.h
> new file mode 100644
> index 0000000000..51317ac578
> --- /dev/null
> +++ b/cmd/bcm/elog.h
> @@ -0,0 +1,64 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright 2019 Broadcom
> + */
> +
> +#ifndef __ELOG_H__
> +#define __ELOG_H__
> +
> +#define GLOBAL_META_HDR_SIG    0x45524c47
> +#define MAX_REC_COUNT          13
> +#define MAX_REC_FORMAT         1
> +#define MAX_SRC_TYPE           3
> +#define MAX_NVM_TYPE           3
> +/* A special type. Use defaults specified in CRMU config */
> +#define NVM_DEFAULT            0xff
> +
> +/* Max. number of cmd parameters per elog spec */
> +#define PARAM_COUNT            3
> +
> +#define REC_DESC_LENGTH                8
> +
> +enum {
> +       LOG_SETUP_CMD_VALIDATE_META,
> +       LOG_SETUP_CMD_WRITE_META,
> +       LOG_SETUP_CMD_ERASE,
> +       LOG_SETUP_CMD_READ,
> +       LOG_SETUP_CMD_CHECK
> +};
> +
> +#pragma pack(push, 1)
> +
> +struct meta_record {
> +       u8 record_type;
> +       u8 record_format;
> +       u8 src_mem_type;
> +       u8 alt_src_mem_type;
> +       u8 nvm_type;
> +       char rec_desc[REC_DESC_LENGTH];
> +       u64 src_mem_addr;
> +       u64 alt_src_mem_addr;
> +       u64 rec_addr;
> +       u32 rec_size;
> +       u32 sector_size;
> +       u8 padding[3];
> +};

Pease comment the struct and all members.

> +
> +struct global_header {
> +       u32 signature;
> +       u32 sector_size;
> +       u8 revision;
> +       u8 rec_count;
> +       u16 padding;
> +};
> +
> +struct log_setup {
> +       u32 cmd;
> +       u32 params[PARAM_COUNT];
> +       u32 result;
> +       u32 ret_code;
> +};
> +
> +#pragma pack(pop)

Use __packed on each struct instead.

> +
> +#endif
> diff --git a/cmd/bcm/logsetup.c b/cmd/bcm/logsetup.c
> new file mode 100644
> index 0000000000..220518f884
> --- /dev/null
> +++ b/cmd/bcm/logsetup.c
> @@ -0,0 +1,433 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2019 Broadcom
> + */
> +
> +/*
> + * Create a binary file ready to be flashed
> + * as a global meta for logging, from a source file.
> + */
> +
> +#include <common.h>
> +#include <malloc.h>
> +#include <string.h>
> +#include <asm/io.h>
> +#include <asm/system.h>
> +#include <linux/ctype.h>
> +#include <linux/delay.h>
> +
> +#include "elog.h"
> +
> +#define FILE_LINE_BUF_SIZE     1024
> +#define GLOBAL_PARAM_COUNT     3
> +#define REC_PARAM_COUNT                11
> +
> +#define GLOBAL_NAME            "GLOBAL"
> +#define RECORD_NAME            "RECORD"
> +
> +#define MCU_IPC_MCU_CMD_ELOG_SETUP 0x84
> +/* SPI write operations can be slow */
> +#define DEFAULT_TIMEOUT_MS     10000
> +
> +#define MCU_IPC_CMD_DONE_MASK  0x80000000
> +#define MCU_IPC_CMD_REPLY_MASK 0x3fff0000
> +#define MCU_IPC_CMD_REPLY_SHIFT        16
> +
> +#define MIN_ARGS_COUNT         3
> +#define MAX_ARGS_COUNT         5
> +#define SEP_STR                        ","
> +#define SUPPORTED_CMD          "-s"
> +#define CHK_HDR_CMD            "-c"
> +
> +enum {
> +       PARAM_GLOBAL,
> +       PARAM_REC
> +};
> +
> +static uintptr_t crmu_mbox0_address;
> +
> +/*
> + * Send a logging command to MCU patch for execution.
> + *
> + * Parameters:
> + *   cmd:         an IPC command code
> + *   param:       a pointer to parameters structure for IPC
> + *   is_real_cmd: whether command produces a response in the structure.
> + *                Only "support" command does not.

@return

> + */
> +static int send_crmu_log_cmd(u32 cmd, u32 param, int is_real_cmd)
> +{
> +       u32 timeout;
> +       u32 val;
> +       int ret = CMD_RET_FAILURE;
> +       struct log_setup *setup;
> +
> +       setup = (struct log_setup *)(uintptr_t)param;
> +       timeout = DEFAULT_TIMEOUT_MS;
> +
> +       writel(cmd, crmu_mbox0_address);
> +       writel(param, crmu_mbox0_address + sizeof(u32));
> +
> +       do {
> +               mdelay(1);
> +               val = readl(is_real_cmd ? (uintptr_t)&setup->ret_code :
> +                                               crmu_mbox0_address);
> +               if (val & MCU_IPC_CMD_DONE_MASK) {
> +                       ret = CMD_RET_SUCCESS;
> +                       break;
> +               }
> +
> +       } while (timeout--);
> +
> +       if (ret == CMD_RET_FAILURE) {
> +               pr_err("CRMU cmd timeout!\n");
> +               return ret;
> +       }
> +
> +       /* Obtain status */
> +       val = (val & MCU_IPC_CMD_REPLY_MASK) >> MCU_IPC_CMD_REPLY_SHIFT;
> +       if (val)
> +               ret = CMD_RET_FAILURE;
> +
> +       return ret;
> +}
> +
> +static char *get_next_param(char **str, char *delimiter)

Functions should have comments.

> +{
> +       char *ret = strsep(str, delimiter);
> +
> +       if (!ret)
> +               return NULL;
> +
> +       return strim(ret);
> +}
> +
> +static int count_chars(char *str, char delimiter)
> +{
> +       int count = 0;
> +
> +       if (!str || (!strlen(str)))
> +               return 0;
> +
> +       do {
> +               if (*str == delimiter)
> +                       count++;
> +
> +               str++;
> +       } while (*str);
> +
> +       /* Need to consider leftovers (if any) after the last delimiter */
> +       count++;
> +
> +       return count;
> +}
> +
> +static int do_setup(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
> +{
> +       struct global_header global;
> +       struct log_setup setup = {0};
> +       u8 *temp;
> +       u8 *ptr = NULL;
> +       char *buf;
> +       u32 line_no = 0;
> +       u32 expected_meta_size;
> +       u32 data_size;
> +       u32 log_offset = 0;
> +       u32 num_recs = 0;
> +       u32 addr;
> +       int global_ready = 0;
> +       int ret = CMD_RET_FAILURE;
> +       int force = 0;
> +
> +       if (argc < MIN_ARGS_COUNT || argc > MAX_ARGS_COUNT)
> +               return CMD_RET_USAGE;
> +
> +       /* Usage: addr or (-s/-c) mbox0_addr [data_size] [force] */
> +       crmu_mbox0_address = simple_strtoul(argv[2], NULL, 16);
> +       if (!crmu_mbox0_address) {
> +               pr_err("Invalid CRMU mbox0 address\n");
> +               return ret;
> +       }
> +
> +       if (argc == MIN_ARGS_COUNT) {
> +               if (strncmp(argv[1], SUPPORTED_CMD, 2) == 0)
> +                       return send_crmu_log_cmd(MCU_IPC_MCU_CMD_ELOG_SETUP,
> +                                                LOG_SETUP_CMD_CHECK,
> +                                                0
> +                                               );
> +
> +               if (strncmp(argv[1], CHK_HDR_CMD, 2) == 0)
> +                       return send_crmu_log_cmd(MCU_IPC_MCU_CMD_ELOG_SETUP,
> +                                                (uintptr_t)&setup,
> +                                                1
> +                                               );
> +       }
> +
> +       /* Expect 1st parameter as a pointer to metadata */
> +       addr = simple_strtoul(argv[1], NULL, 16);
> +       if (!addr) {
> +               pr_err("Address 0x0 is not allowed\n");
> +               return ret;
> +       }
> +
> +       data_size = simple_strtoul(argv[3], NULL, 16);
> +       if (!data_size) {
> +               pr_err("Invalid source size\n");
> +               return ret;
> +       }
> +
> +       if (argc == MAX_ARGS_COUNT)
> +               force = simple_strtoul(argv[MAX_ARGS_COUNT - 1], NULL, 10);
> +
> +       memset(&global, 0, sizeof(struct global_header));
> +       expected_meta_size = sizeof(struct global_header) +
> +                               MAX_REC_COUNT * sizeof(struct meta_record);
> +
> +       temp = (u8 *)malloc(expected_meta_size);
> +       if (!temp)
> +               return ret;
> +
> +       memset(temp, 0, expected_meta_size);
> +
> +       ptr = temp;
> +       buf = (char *)(uintptr_t)addr;
> +       /* End of file mark */
> +       buf[data_size] = '\0';
> +
> +       do {
> +               char  *type_name;
> +               u32 entry_type;
> +               char *line;
> +               char *value;
> +
> +               line_no++;
> +               line = get_next_param(&buf, "\n");
> +               if (!line)
> +                       break;
> +
> +               if ((!strlen(line)) || line[0] == '#')
> +                       continue;
> +
> +               value = get_next_param(&line, "=");
> +
> +               if (!value || (!strlen(value))) {
> +                       pr_err("Invalid format of line %d\n", line_no);
> +                       goto free_params;
> +               }
> +
> +               type_name = GLOBAL_NAME;
> +
> +               if (!strncasecmp(value, type_name, strlen(GLOBAL_NAME))) {
> +                       entry_type = PARAM_GLOBAL;
> +               } else {
> +                       type_name = RECORD_NAME;
> +                       if (!strncasecmp(value,
> +                                        type_name,
> +                                        strlen(RECORD_NAME)
> +                                       )
> +                          ) {
> +                               entry_type = PARAM_REC;
> +                       } else {
> +                               pr_err("Unknown type %s, line %d\n",
> +                                      value,
> +                                      line_no
> +                                     );
> +                               goto free_params;
> +                       }
> +               }
> +
> +               if (global_ready && entry_type == PARAM_GLOBAL) {
> +                       /* Multiple globals are not allowed */
> +                       pr_err("Multiple GLOBAL records, line %d\n", line_no);
> +                       goto free_params;
> +               }
> +
> +               if (entry_type == PARAM_GLOBAL) {
> +                       /* Parse Global and create global record in outfile */
> +                       if (count_chars(line, ',') != GLOBAL_PARAM_COUNT) {
> +                               pr_err("Invalid GLOBAL format, line %d\n",
> +                                      line_no
> +                                     );
> +                               goto free_params;
> +                       }
> +
> +                       global.sector_size =
> +                               simple_strtoul
> +                                       (get_next_param(&line, SEP_STR),
> +                                        NULL,
> +                                        0
> +                                       );
> +                       global.revision =
> +                               (u8)simple_strtoul
> +                                       (get_next_param(&line, SEP_STR),
> +                                        NULL,
> +                                        0
> +                                       );
> +                       log_offset = simple_strtoul
> +                                       (get_next_param(&line, SEP_STR),
> +                                        NULL,
> +                                        0
> +                                       );
> +
> +                       if (!global.sector_size) {
> +                               pr_err("Invalid GLOBAL, line %d\n", line_no);
> +                               goto free_params;
> +                       }
> +
> +                       global.signature = GLOBAL_META_HDR_SIG;
> +                       global_ready = 1;
> +
> +                       /* Shift to the first RECORD header */
> +                       log_offset += 2 * global.sector_size;
> +                       ptr += sizeof(struct global_header);
> +               } else {
> +                       struct meta_record rec = {0};
> +                       char *desc;
> +                       char *rec_addr_str;
> +                       int auto_addr = 0;
> +
> +                       if (count_chars(line, ',') != REC_PARAM_COUNT) {
> +                               pr_err("Invalid RECORD, line %d\n", line_no);
> +                               goto free_params;
> +                       }
> +
> +                       rec.record_type = (u8)simple_strtoul
> +                                       (get_next_param(&line, SEP_STR),
> +                                        NULL,
> +                                        0
> +                                       );
> +                       rec.record_format = (u8)simple_strtoul
> +                                       (get_next_param(&line, SEP_STR),
> +                                        NULL,
> +                                        0
> +                                       );
> +                       rec.src_mem_type = (u8)simple_strtoul
> +                                       (get_next_param(&line, SEP_STR),
> +                                        NULL,
> +                                        0
> +                                       );
> +                       rec.alt_src_mem_type = (u8)simple_strtoul
> +                                       (get_next_param(&line, SEP_STR),
> +                                        NULL,
> +                                        0
> +                                       );
> +                       rec.nvm_type = (u8)simple_strtoul
> +                                       (get_next_param(&line, SEP_STR),
> +                                        NULL,
> +                                        0
> +                                       );
> +                       desc = get_next_param(&line, SEP_STR);
> +                       if (desc)
> +                               desc = strim(desc);
> +
> +                       rec.src_mem_addr = (u64)simple_strtoul
> +                                       (get_next_param(&line, SEP_STR),
> +                                        NULL,
> +                                        0
> +                                       );
> +                       rec.alt_src_mem_addr = (u64)simple_strtoul
> +                                       (get_next_param(&line, SEP_STR),
> +                                        NULL,
> +                                        0
> +                                       );
> +                       rec_addr_str = get_next_param(&line, SEP_STR);
> +                       if (rec_addr_str)
> +                               rec_addr_str = strim(rec_addr_str);
> +
> +                       if (!strcmp(rec_addr_str, "auto"))
> +                               auto_addr = 1;
> +                       else
> +                               rec.rec_addr = (u64)simple_strtoul
> +                                       (rec_addr_str,
> +                                        NULL,
> +                                        0
> +                                       );
> +
> +                       rec.rec_size = simple_strtoul
> +                                       (get_next_param(&line, SEP_STR),
> +                                        NULL,
> +                                        0
> +                                       );
> +                       rec.sector_size = simple_strtoul
> +                                       (get_next_param(&line, SEP_STR),
> +                                        NULL,
> +                                        0
> +                                       );
> +                       if (auto_addr) {
> +                               rec.rec_addr = (u64)log_offset;
> +                               log_offset += rec.rec_size;
> +                       }
> +
> +                       /* Sanity checks */
> +                       if (rec.record_type > MAX_REC_COUNT ||
> +                           rec.record_format > MAX_REC_FORMAT ||
> +                           (rec.nvm_type > MAX_NVM_TYPE &&
> +                            rec.nvm_type != NVM_DEFAULT) ||
> +                           !rec.rec_size ||
> +                           !rec.sector_size ||
> +                           (!desc || !strlen(desc) ||
> +                            (strlen(desc) > sizeof(rec.rec_desc) + 1)
> +                           )
> +                          ) {
> +                               pr_err("Invalid RECORD %s, line %d\n",
> +                                      desc ? desc : " (no desc)", line_no
> +                                     );
> +                               goto free_params;
> +                       }
> +
> +                       memset(rec.rec_desc, ' ', sizeof(rec.rec_desc));
> +                       memcpy(rec.rec_desc, desc, strlen(desc));
> +                       memcpy(ptr, &rec, sizeof(struct meta_record));
> +                       ptr += sizeof(struct meta_record);
> +                       num_recs++;
> +               }

This function is far too long. Could you separate out the guts of it
into another function, and parse the args first?

> +
> +       } while (buf && ((uintptr_t)buf < (addr + data_size)));
> +
> +       if (!num_recs) {
> +               pr_err("No RECORD entry!\n");
> +               goto free_params;
> +       }
> +
> +       if (!global_ready) {
> +               pr_err("Global entry was not found!\n");
> +               goto free_params;
> +       }
> +
> +       if (num_recs > MAX_REC_COUNT) {
> +               pr_err("Too many records (max %d)\n", MAX_REC_COUNT);
> +               goto free_params;
> +       }
> +
> +       /* Recalculate new expected size */
> +       if (num_recs != MAX_REC_COUNT) {
> +               expected_meta_size = sizeof(struct global_header) +
> +                       num_recs * sizeof(struct meta_record);
> +       }
> +
> +       global.rec_count = num_recs;
> +       memcpy(temp, &global, sizeof(struct global_header));
> +
> +       setup.params[0] = (uintptr_t)temp;
> +       setup.params[1] = expected_meta_size;
> +
> +       /* Apply the command via CRMU mailboxes and read the result. */
> +       setup.cmd = (force) ? LOG_SETUP_CMD_WRITE_META :
> +                       LOG_SETUP_CMD_VALIDATE_META;
> +
> +       /* Finally, request the MCU patch to perform the command */
> +       ret = send_crmu_log_cmd(MCU_IPC_MCU_CMD_ELOG_SETUP,
> +                               (uintptr_t)(&setup),
> +                               1
> +                              );
> +
> +free_params:
> +       free(temp);
> +
> +       return ret;
> +}
> +
> +U_BOOT_CMD(logsetup, 5, 1, do_setup, "setup Broadcom MCU logging subsystem",
> +          "\taddr mbox0_addr [data_size] [force]\nor\n\t -s(or -c) mbox0_addr"

Please add full help explaining the arguments.

> +         );
> +
> --
> 2.17.1
>

Regards,
Simon


More information about the U-Boot mailing list