You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
269 lines
5.9 KiB
269 lines
5.9 KiB
4 months ago
|
/*
|
||
|
* 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);
|
||
|
}
|