summaryrefslogtreecommitdiffstats
path: root/plat/brcm/board/common/bcm_elog.c
blob: 093157ef0010e04a386d52be1170e1330b530edb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
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);
}