1 /*
2 * Copyright (c) 2018 - 2020, Broadcom
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7 #include <stdarg.h>
8 #include <stdint.h>
9 #include <string.h>
10
11 #include <arch_helpers.h>
12 #include <common/debug.h>
13 #include <plat/common/platform.h>
14
15 #include <bcm_elog.h>
16
17 /* error logging signature */
18 #define BCM_ELOG_SIG_OFFSET 0x0000
19 #define BCM_ELOG_SIG_VAL 0x75767971
20
21 /* current logging offset that points to where new logs should be added */
22 #define BCM_ELOG_OFF_OFFSET 0x0004
23
24 /* current logging length (excluding header) */
25 #define BCM_ELOG_LEN_OFFSET 0x0008
26
27 #define BCM_ELOG_HEADER_LEN 12
28
29 /*
30 * @base: base address of memory where log is saved
31 * @max_size: max size of memory reserved for logging
32 * @is_active: indicates logging is currently active
33 * @level: current logging level
34 */
35 struct bcm_elog {
36 uintptr_t base;
37 uint32_t max_size;
38 unsigned int is_active;
39 unsigned int level;
40 };
41
42 static struct bcm_elog global_elog;
43
44 extern void memcpy16(void *dst, const void *src, unsigned int len);
45
46 /*
47 * Log one character
48 */
elog_putchar(struct bcm_elog * elog,unsigned char c)49 static void elog_putchar(struct bcm_elog *elog, unsigned char c)
50 {
51 uint32_t offset, len;
52
53 offset = mmio_read_32(elog->base + BCM_ELOG_OFF_OFFSET);
54 len = mmio_read_32(elog->base + BCM_ELOG_LEN_OFFSET);
55 mmio_write_8(elog->base + offset, c);
56 offset++;
57
58 /* log buffer is now full and need to wrap around */
59 if (offset >= elog->max_size)
60 offset = BCM_ELOG_HEADER_LEN;
61
62 /* only increment length when log buffer is not full */
63 if (len < elog->max_size - BCM_ELOG_HEADER_LEN)
64 len++;
65
66 mmio_write_32(elog->base + BCM_ELOG_OFF_OFFSET, offset);
67 mmio_write_32(elog->base + BCM_ELOG_LEN_OFFSET, len);
68 }
69
elog_unsigned_num(struct bcm_elog * elog,unsigned long unum,unsigned int radix)70 static void elog_unsigned_num(struct bcm_elog *elog, unsigned long unum,
71 unsigned int radix)
72 {
73 /* Just need enough space to store 64 bit decimal integer */
74 unsigned char num_buf[20];
75 int i = 0, rem;
76
77 do {
78 rem = unum % radix;
79 if (rem < 0xa)
80 num_buf[i++] = '0' + rem;
81 else
82 num_buf[i++] = 'a' + (rem - 0xa);
83 } while (unum /= radix);
84
85 while (--i >= 0)
86 elog_putchar(elog, num_buf[i]);
87 }
88
elog_string(struct bcm_elog * elog,const char * str)89 static void elog_string(struct bcm_elog *elog, const char *str)
90 {
91 while (*str)
92 elog_putchar(elog, *str++);
93 }
94
95 /*
96 * Routine to initialize error logging
97 */
bcm_elog_init(void * base,uint32_t size,unsigned int level)98 int bcm_elog_init(void *base, uint32_t size, unsigned int level)
99 {
100 struct bcm_elog *elog = &global_elog;
101 uint32_t val;
102
103 elog->base = (uintptr_t)base;
104 elog->max_size = size;
105 elog->is_active = 1;
106 elog->level = level / 10;
107
108 /*
109 * If a valid signature can be found, it means logs have been copied
110 * into designated memory by another software. In this case, we should
111 * not re-initialize the entry header in the designated memory
112 */
113 val = mmio_read_32(elog->base + BCM_ELOG_SIG_OFFSET);
114 if (val != BCM_ELOG_SIG_VAL) {
115 mmio_write_32(elog->base + BCM_ELOG_SIG_OFFSET,
116 BCM_ELOG_SIG_VAL);
117 mmio_write_32(elog->base + BCM_ELOG_OFF_OFFSET,
118 BCM_ELOG_HEADER_LEN);
119 mmio_write_32(elog->base + BCM_ELOG_LEN_OFFSET, 0);
120 }
121
122 return 0;
123 }
124
125 /*
126 * Routine to disable error logging
127 */
bcm_elog_exit(void)128 void bcm_elog_exit(void)
129 {
130 struct bcm_elog *elog = &global_elog;
131
132 if (!elog->is_active)
133 return;
134
135 elog->is_active = 0;
136
137 flush_dcache_range(elog->base, elog->max_size);
138 }
139
140 /*
141 * Routine to copy error logs from current memory to 'dst' memory and continue
142 * logging from the new 'dst' memory.
143 * dst and base addresses must be 16-bytes aligned.
144 */
bcm_elog_copy_log(void * dst,uint32_t max_size)145 int bcm_elog_copy_log(void *dst, uint32_t max_size)
146 {
147 struct bcm_elog *elog = &global_elog;
148 uint32_t offset, len;
149
150 if (!elog->is_active || ((uintptr_t)dst == elog->base))
151 return -1;
152
153 /* flush cache before copying logs */
154 flush_dcache_range(elog->base, max_size);
155
156 /*
157 * If current offset exceeds the new max size, then that is considered
158 * as a buffer overflow situation. In this case, we reset the offset
159 * back to the beginning
160 */
161 offset = mmio_read_32(elog->base + BCM_ELOG_OFF_OFFSET);
162 if (offset >= max_size) {
163 offset = BCM_ELOG_HEADER_LEN;
164 mmio_write_32(elog->base + BCM_ELOG_OFF_OFFSET, offset);
165 }
166
167 /* note payload length does not include header */
168 len = mmio_read_32(elog->base + BCM_ELOG_LEN_OFFSET);
169 if (len > max_size - BCM_ELOG_HEADER_LEN) {
170 len = max_size - BCM_ELOG_HEADER_LEN;
171 mmio_write_32(elog->base + BCM_ELOG_LEN_OFFSET, len);
172 }
173
174 /* Need to copy everything including the header. */
175 memcpy16(dst, (const void *)elog->base, len + BCM_ELOG_HEADER_LEN);
176 elog->base = (uintptr_t)dst;
177 elog->max_size = max_size;
178
179 return 0;
180 }
181
182 /*
183 * Main routine to save logs into memory
184 */
bcm_elog(const char * fmt,...)185 void bcm_elog(const char *fmt, ...)
186 {
187 va_list args;
188 const char *prefix_str;
189 int bit64;
190 int64_t num;
191 uint64_t unum;
192 char *str;
193 struct bcm_elog *elog = &global_elog;
194
195 /* We expect the LOG_MARKER_* macro as the first character */
196 unsigned int level = fmt[0];
197
198 if (!elog->is_active || level > elog->level)
199 return;
200
201 prefix_str = plat_log_get_prefix(level);
202
203 while (*prefix_str != '\0') {
204 elog_putchar(elog, *prefix_str);
205 prefix_str++;
206 }
207
208 va_start(args, fmt);
209 fmt++;
210 while (*fmt) {
211 bit64 = 0;
212
213 if (*fmt == '%') {
214 fmt++;
215 /* Check the format specifier */
216 loop:
217 switch (*fmt) {
218 case 'i': /* Fall through to next one */
219 case 'd':
220 if (bit64)
221 num = va_arg(args, int64_t);
222 else
223 num = va_arg(args, int32_t);
224
225 if (num < 0) {
226 elog_putchar(elog, '-');
227 unum = (unsigned long)-num;
228 } else
229 unum = (unsigned long)num;
230
231 elog_unsigned_num(elog, unum, 10);
232 break;
233 case 's':
234 str = va_arg(args, char *);
235 elog_string(elog, str);
236 break;
237 case 'x':
238 if (bit64)
239 unum = va_arg(args, uint64_t);
240 else
241 unum = va_arg(args, uint32_t);
242
243 elog_unsigned_num(elog, unum, 16);
244 break;
245 case 'l':
246 bit64 = 1;
247 fmt++;
248 goto loop;
249 case 'u':
250 if (bit64)
251 unum = va_arg(args, uint64_t);
252 else
253 unum = va_arg(args, uint32_t);
254
255 elog_unsigned_num(elog, unum, 10);
256 break;
257 default:
258 /* Exit on any other format specifier */
259 goto exit;
260 }
261 fmt++;
262 continue;
263 }
264 elog_putchar(elog, *fmt++);
265 }
266 exit:
267 va_end(args);
268 }
269