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 */ 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 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 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 */ 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 */ 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 */ 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 */ 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