summaryrefslogtreecommitdiffstats
path: root/plat/brcm/board/common/bcm_elog.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--plat/brcm/board/common/bcm_elog.c268
1 files changed, 268 insertions, 0 deletions
diff --git a/plat/brcm/board/common/bcm_elog.c b/plat/brcm/board/common/bcm_elog.c
new file mode 100644
index 0000000..093157e
--- /dev/null
+++ b/plat/brcm/board/common/bcm_elog.c
@@ -0,0 +1,268 @@
+/*
+ * Copyright (c) 2018 - 2020, Broadcom
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+#include <stdarg.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <arch_helpers.h>
+#include <common/debug.h>
+#include <plat/common/platform.h>
+
+#include <bcm_elog.h>
+
+/* error logging signature */
+#define BCM_ELOG_SIG_OFFSET 0x0000
+#define BCM_ELOG_SIG_VAL 0x75767971
+
+/* current logging offset that points to where new logs should be added */
+#define BCM_ELOG_OFF_OFFSET 0x0004
+
+/* current logging length (excluding header) */
+#define BCM_ELOG_LEN_OFFSET 0x0008
+
+#define BCM_ELOG_HEADER_LEN 12
+
+/*
+ * @base: base address of memory where log is saved
+ * @max_size: max size of memory reserved for logging
+ * @is_active: indicates logging is currently active
+ * @level: current logging level
+ */
+struct bcm_elog {
+ uintptr_t base;
+ uint32_t max_size;
+ unsigned int is_active;
+ unsigned int level;
+};
+
+static struct bcm_elog global_elog;
+
+extern void memcpy16(void *dst, const void *src, unsigned int len);
+
+/*
+ * Log one character
+ */
+static void elog_putchar(struct bcm_elog *elog, unsigned char c)
+{
+ uint32_t offset, len;
+
+ offset = mmio_read_32(elog->base + BCM_ELOG_OFF_OFFSET);
+ len = mmio_read_32(elog->base + BCM_ELOG_LEN_OFFSET);
+ mmio_write_8(elog->base + offset, c);
+ offset++;
+
+ /* log buffer is now full and need to wrap around */
+ if (offset >= elog->max_size)
+ offset = BCM_ELOG_HEADER_LEN;
+
+ /* only increment length when log buffer is not full */
+ if (len < elog->max_size - BCM_ELOG_HEADER_LEN)
+ len++;
+
+ mmio_write_32(elog->base + BCM_ELOG_OFF_OFFSET, offset);
+ mmio_write_32(elog->base + BCM_ELOG_LEN_OFFSET, len);
+}
+
+static void elog_unsigned_num(struct bcm_elog *elog, unsigned long unum,
+ unsigned int radix)
+{
+ /* Just need enough space to store 64 bit decimal integer */
+ unsigned char num_buf[20];
+ int i = 0, rem;
+
+ do {
+ rem = unum % radix;
+ if (rem < 0xa)
+ num_buf[i++] = '0' + rem;
+ else
+ num_buf[i++] = 'a' + (rem - 0xa);
+ } while (unum /= radix);
+
+ while (--i >= 0)
+ elog_putchar(elog, num_buf[i]);
+}
+
+static void elog_string(struct bcm_elog *elog, const char *str)
+{
+ while (*str)
+ elog_putchar(elog, *str++);
+}
+
+/*
+ * Routine to initialize error logging
+ */
+int bcm_elog_init(void *base, uint32_t size, unsigned int level)
+{
+ struct bcm_elog *elog = &global_elog;
+ uint32_t val;
+
+ elog->base = (uintptr_t)base;
+ elog->max_size = size;
+ elog->is_active = 1;
+ elog->level = level / 10;
+
+ /*
+ * If a valid signature can be found, it means logs have been copied
+ * into designated memory by another software. In this case, we should
+ * not re-initialize the entry header in the designated memory
+ */
+ val = mmio_read_32(elog->base + BCM_ELOG_SIG_OFFSET);
+ if (val != BCM_ELOG_SIG_VAL) {
+ mmio_write_32(elog->base + BCM_ELOG_SIG_OFFSET,
+ BCM_ELOG_SIG_VAL);
+ mmio_write_32(elog->base + BCM_ELOG_OFF_OFFSET,
+ BCM_ELOG_HEADER_LEN);
+ mmio_write_32(elog->base + BCM_ELOG_LEN_OFFSET, 0);
+ }
+
+ return 0;
+}
+
+/*
+ * Routine to disable error logging
+ */
+void bcm_elog_exit(void)
+{
+ struct bcm_elog *elog = &global_elog;
+
+ if (!elog->is_active)
+ return;
+
+ elog->is_active = 0;
+
+ flush_dcache_range(elog->base, elog->max_size);
+}
+
+/*
+ * Routine to copy error logs from current memory to 'dst' memory and continue
+ * logging from the new 'dst' memory.
+ * dst and base addresses must be 16-bytes aligned.
+ */
+int bcm_elog_copy_log(void *dst, uint32_t max_size)
+{
+ struct bcm_elog *elog = &global_elog;
+ uint32_t offset, len;
+
+ if (!elog->is_active || ((uintptr_t)dst == elog->base))
+ return -1;
+
+ /* flush cache before copying logs */
+ flush_dcache_range(elog->base, max_size);
+
+ /*
+ * If current offset exceeds the new max size, then that is considered
+ * as a buffer overflow situation. In this case, we reset the offset
+ * back to the beginning
+ */
+ offset = mmio_read_32(elog->base + BCM_ELOG_OFF_OFFSET);
+ if (offset >= max_size) {
+ offset = BCM_ELOG_HEADER_LEN;
+ mmio_write_32(elog->base + BCM_ELOG_OFF_OFFSET, offset);
+ }
+
+ /* note payload length does not include header */
+ len = mmio_read_32(elog->base + BCM_ELOG_LEN_OFFSET);
+ if (len > max_size - BCM_ELOG_HEADER_LEN) {
+ len = max_size - BCM_ELOG_HEADER_LEN;
+ mmio_write_32(elog->base + BCM_ELOG_LEN_OFFSET, len);
+ }
+
+ /* Need to copy everything including the header. */
+ memcpy16(dst, (const void *)elog->base, len + BCM_ELOG_HEADER_LEN);
+ elog->base = (uintptr_t)dst;
+ elog->max_size = max_size;
+
+ return 0;
+}
+
+/*
+ * Main routine to save logs into memory
+ */
+void bcm_elog(const char *fmt, ...)
+{
+ va_list args;
+ const char *prefix_str;
+ int bit64;
+ int64_t num;
+ uint64_t unum;
+ char *str;
+ struct bcm_elog *elog = &global_elog;
+
+ /* We expect the LOG_MARKER_* macro as the first character */
+ unsigned int level = fmt[0];
+
+ if (!elog->is_active || level > elog->level)
+ return;
+
+ prefix_str = plat_log_get_prefix(level);
+
+ while (*prefix_str != '\0') {
+ elog_putchar(elog, *prefix_str);
+ prefix_str++;
+ }
+
+ va_start(args, fmt);
+ fmt++;
+ while (*fmt) {
+ bit64 = 0;
+
+ if (*fmt == '%') {
+ fmt++;
+ /* Check the format specifier */
+loop:
+ switch (*fmt) {
+ case 'i': /* Fall through to next one */
+ case 'd':
+ if (bit64)
+ num = va_arg(args, int64_t);
+ else
+ num = va_arg(args, int32_t);
+
+ if (num < 0) {
+ elog_putchar(elog, '-');
+ unum = (unsigned long)-num;
+ } else
+ unum = (unsigned long)num;
+
+ elog_unsigned_num(elog, unum, 10);
+ break;
+ case 's':
+ str = va_arg(args, char *);
+ elog_string(elog, str);
+ break;
+ case 'x':
+ if (bit64)
+ unum = va_arg(args, uint64_t);
+ else
+ unum = va_arg(args, uint32_t);
+
+ elog_unsigned_num(elog, unum, 16);
+ break;
+ case 'l':
+ bit64 = 1;
+ fmt++;
+ goto loop;
+ case 'u':
+ if (bit64)
+ unum = va_arg(args, uint64_t);
+ else
+ unum = va_arg(args, uint32_t);
+
+ elog_unsigned_num(elog, unum, 10);
+ break;
+ default:
+ /* Exit on any other format specifier */
+ goto exit;
+ }
+ fmt++;
+ continue;
+ }
+ elog_putchar(elog, *fmt++);
+ }
+exit:
+ va_end(args);
+}