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