[U-Boot,1/2] cmd: bcm: logsetup: Add Broadcom error log setup command
diff mbox series

Message ID 20191119005842.16010-2-vladimir.olovyannikov@broadcom.com
State New
Delegated to: Tom Rini
Headers show
Series
  • Add error logging support for Broadcom boards
Related show

Commit Message

Vladimir Olovyannikov Nov. 19, 2019, 12:58 a.m. UTC
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@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

Patch
diff mbox series

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
+	default n
+	help
+	   Support specific log setup on Broadcom SoCs. This command
+	   allows checking if logging support is present, and update
+	   log sections.
+
+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];
+};
+
+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)
+
+#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.
+ */
+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)
+{
+	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++;
+		}
+
+	} 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"
+	  );
+