1 /*
2 * Copyright 2018 The Hafnium Authors.
3 *
4 * Use of this source code is governed by a BSD-style
5 * license that can be found in the LICENSE file or at
6 * https://opensource.org/licenses/BSD-3-Clause.
7 */
8
9 #include "hf/dlog.h"
10
11 #include <stddef.h>
12
13 #include "hf/spinlock.h"
14 #include "hf/std.h"
15 #include "hf/stdout.h"
16
17 enum { DLOG_MAX_STRING_LENGTH = 64 };
18
19 /* Keep fields aligned */
20 /* clang-format off */
21 struct format_flags {
22 bool minus : 1;
23 bool plus : 1;
24 bool space : 1;
25 bool alt : 1;
26 bool zero : 1;
27 bool upper : 1;
28 bool neg : 1;
29 };
30 /* clang-format on */
31
32 enum format_base {
33 base2 = 2,
34 base8 = 8,
35 base10 = 10,
36 base16 = 16,
37 };
38
39 enum format_length {
40 length8 = 8,
41 length16 = 16,
42 length32 = 32,
43 length64 = 64,
44 };
45
46 static_assert(sizeof(char) == sizeof(uint8_t),
47 "dlog expects char to be 8 bits wide");
48 static_assert(sizeof(short) == sizeof(uint16_t),
49 "dlog expects short to be 16 bits wide");
50 static_assert(sizeof(int) == sizeof(uint32_t),
51 "dlog expects int to be 32 bits wide");
52 static_assert(sizeof(long) == sizeof(uint64_t),
53 "dlog expects long to be 64 bits wide");
54 static_assert(sizeof(long long) == sizeof(uint64_t),
55 "dlog expects long long to be 64 bits wide");
56 static_assert(sizeof(intmax_t) == sizeof(uint64_t),
57 "dlog expects intmax_t to be 64 bits wide");
58 static_assert(sizeof(size_t) == sizeof(uint64_t),
59 "dlog expects size_t to be 64 bits wide");
60 static_assert(sizeof(ptrdiff_t) == sizeof(uint64_t),
61 "dlog expects ptrdiff_t to be 64 bits wide");
62
63 static bool dlog_lock_enabled = false;
64 static struct spinlock sl = SPINLOCK_INIT;
65
66 /*
67 * These global variables for the log buffer are not static because a test needs
68 * to access them directly.
69 */
70 size_t dlog_buffer_offset;
71 char dlog_buffer[DLOG_BUFFER_SIZE];
72
73 /**
74 * Takes the lock, if it is enabled.
75 */
lock(void)76 static void lock(void)
77 {
78 if (dlog_lock_enabled) {
79 sl_lock(&sl);
80 }
81 }
82
83 /**
84 * Releases the lock, if it is enabled.
85 */
unlock(void)86 static void unlock(void)
87 {
88 if (dlog_lock_enabled) {
89 sl_unlock(&sl);
90 }
91 }
92
93 /**
94 * Enables the lock protecting the serial device.
95 */
dlog_enable_lock(void)96 void dlog_enable_lock(void)
97 {
98 dlog_lock_enabled = true;
99 }
100
dlog_putchar(char c)101 static void dlog_putchar(char c)
102 {
103 dlog_buffer[dlog_buffer_offset] = c;
104 dlog_buffer_offset = (dlog_buffer_offset + 1) % DLOG_BUFFER_SIZE;
105 stdout_putchar(c);
106 }
107
108 /**
109 * Prints a literal string (i.e. '%' is not interpreted specially) to the debug
110 * log.
111 *
112 * Returns number of characters written.
113 */
print_raw_string(const char * str)114 static size_t print_raw_string(const char *str)
115 {
116 const char *c = str;
117
118 for (; *c != '\0'; c++) {
119 dlog_putchar(*c);
120 }
121
122 return c - str;
123 }
124
125 /**
126 * Prints a formatted string to the debug log. The format includes a minimum
127 * width, the fill character, and flags (whether to align to left or right).
128 *
129 * str is the full string, while suffix is a pointer within str that indicates
130 * where the suffix begins. This is used when printing right-aligned numbers
131 * with a zero fill; for example, -10 with width 4 should be padded to -010,
132 * so suffix would point to index one of the "-10" string .
133 *
134 * Returns number of characters written.
135 */
print_string(const char * str,const char * suffix,size_t min_width,struct format_flags flags,char fill)136 static size_t print_string(const char *str, const char *suffix,
137 size_t min_width, struct format_flags flags,
138 char fill)
139 {
140 size_t chars_written = 0;
141 size_t len = suffix - str;
142
143 /* Print the string up to the beginning of the suffix. */
144 while (str != suffix) {
145 chars_written++;
146 dlog_putchar(*str++);
147 }
148
149 if (flags.minus) {
150 /* Left-aligned. Print suffix, then print padding if needed. */
151 len += print_raw_string(suffix);
152 while (len < min_width) {
153 chars_written++;
154 dlog_putchar(' ');
155 len++;
156 }
157 return chars_written;
158 }
159
160 /* Fill until we reach the desired length. */
161 len += strnlen_s(suffix, DLOG_MAX_STRING_LENGTH);
162 while (len < min_width) {
163 chars_written++;
164 dlog_putchar(fill);
165 len++;
166 }
167
168 /* Now print the rest of the string. */
169 chars_written += print_raw_string(suffix);
170 return chars_written;
171 }
172
173 /**
174 * Prints an integer to the debug log. The caller specifies the base, its
175 * minimum width and printf-style flags.
176 *
177 * Returns number of characters written.
178 */
print_int(size_t value,enum format_base base,size_t min_width,struct format_flags flags)179 static size_t print_int(size_t value, enum format_base base, size_t min_width,
180 struct format_flags flags)
181 {
182 static const char *digits_lower = "0123456789abcdefxb";
183 static const char *digits_upper = "0123456789ABCDEFXB";
184 const char *digits = flags.upper ? digits_upper : digits_lower;
185 char buf[DLOG_MAX_STRING_LENGTH];
186 char *ptr = &buf[sizeof(buf) - 1];
187 char *num;
188 *ptr = '\0';
189 do {
190 --ptr;
191 *ptr = digits[value % base];
192 value /= base;
193 } while (value);
194
195 /* Num stores where the actual number begins. */
196 num = ptr;
197
198 /* Add prefix if requested. */
199 if (flags.alt) {
200 switch (base) {
201 case base16:
202 ptr -= 2;
203 ptr[0] = '0';
204 ptr[1] = digits[16];
205 break;
206
207 case base2:
208 ptr -= 2;
209 ptr[0] = '0';
210 ptr[1] = digits[17];
211 break;
212
213 case base8:
214 ptr--;
215 *ptr = '0';
216 break;
217
218 case base10:
219 /* do nothing */
220 break;
221 }
222 }
223
224 /* Add sign if requested. */
225 if (flags.neg) {
226 *--ptr = '-';
227 } else if (flags.plus) {
228 *--ptr = '+';
229 } else if (flags.space) {
230 *--ptr = ' ';
231 }
232 return print_string(ptr, num, min_width, flags, flags.zero ? '0' : ' ');
233 }
234
235 /**
236 * Parses the optional flags field of a printf-style format. Returns a pointer
237 * to the first non-flag character in the string.
238 */
parse_flags(const char * fmt,struct format_flags * flags)239 static const char *parse_flags(const char *fmt, struct format_flags *flags)
240 {
241 for (;; fmt++) {
242 switch (*fmt) {
243 case '-':
244 flags->minus = true;
245 break;
246
247 case '+':
248 flags->plus = true;
249 break;
250
251 case ' ':
252 flags->space = true;
253 break;
254
255 case '#':
256 flags->alt = true;
257 break;
258
259 case '0':
260 flags->zero = true;
261 break;
262
263 default:
264 return fmt;
265 }
266 }
267 }
268
269 /**
270 * Parses the optional length modifier field of a printf-style format.
271 *
272 * Returns a pointer to the first non-length modifier character in the string.
273 */
parse_length_modifier(const char * fmt,enum format_length * length)274 static const char *parse_length_modifier(const char *fmt,
275 enum format_length *length)
276 {
277 switch (*fmt) {
278 case 'h':
279 fmt++;
280 if (*fmt == 'h') {
281 fmt++;
282 *length = length8;
283 } else {
284 *length = length16;
285 }
286 break;
287 case 'l':
288 fmt++;
289 if (*fmt == 'l') {
290 fmt++;
291 *length = length64;
292 } else {
293 *length = length64;
294 }
295 break;
296
297 case 'j':
298 case 'z':
299 case 't':
300 fmt++;
301 *length = length64;
302 break;
303
304 default:
305 *length = length32;
306 break;
307 }
308
309 return fmt;
310 }
311
312 /**
313 * Parses the optional minimum width field of a printf-style format.
314 * If the width is negative, `flags.minus` is set.
315 *
316 * Returns a pointer to the first non-digit character in the string.
317 */
parse_min_width(const char * fmt,struct va_list_wrapper * args,struct format_flags * flags,int * min_width)318 static const char *parse_min_width(const char *fmt,
319 struct va_list_wrapper *args,
320 struct format_flags *flags, int *min_width)
321 {
322 int width = 0;
323
324 /* Read minimum width from arguments. */
325 if (*fmt == '*') {
326 fmt++;
327 width = va_arg(args->va, int);
328 if (width < 0) {
329 width = -width;
330 flags->minus = true;
331 }
332 } else {
333 for (; *fmt >= '0' && *fmt <= '9'; fmt++) {
334 width = (width * 10) + (*fmt - '0');
335 }
336 }
337
338 *min_width = width;
339
340 return fmt;
341 }
342
343 /**
344 * Reinterpret an unsigned 64-bit integer as a potentially shorter unsigned
345 * integer according to the length modifier.
346 * Returns an unsigned integer suitable for passing to `print_int`.
347 */
reinterpret_unsigned_int(enum format_length length,uint64_t value)348 uint64_t reinterpret_unsigned_int(enum format_length length, uint64_t value)
349 {
350 switch (length) {
351 case length8:
352 return (uint8_t)value;
353 case length16:
354 return (uint16_t)value;
355 case length32:
356 return (uint32_t)value;
357 case length64:
358 return value;
359 }
360 }
361
362 /**
363 * Reinterpret an unsigned 64-bit integer as a potentially shorter signed
364 * integer according to the length modifier.
365 *
366 * Returns an *unsigned* integer suitable for passing to `print_int`. If the
367 * reinterpreted value is negative, `flags.neg` is set and the absolute value is
368 * returned.
369 */
reinterpret_signed_int(enum format_length length,uint64_t value,struct format_flags * flags)370 uint64_t reinterpret_signed_int(enum format_length length, uint64_t value,
371 struct format_flags *flags)
372 {
373 int64_t signed_value = (int64_t)reinterpret_unsigned_int(length, value);
374
375 switch (length) {
376 case length8:
377 if ((int8_t)signed_value < 0) {
378 flags->neg = true;
379 signed_value = (-signed_value) & 0xFF;
380 }
381 break;
382 case length16:
383 if ((int16_t)signed_value < 0) {
384 flags->neg = true;
385 signed_value = (-signed_value) & 0xFFFF;
386 }
387 break;
388 case length32:
389 if ((int32_t)signed_value < 0) {
390 flags->neg = true;
391 signed_value = (-signed_value) & 0xFFFFFFFF;
392 }
393 break;
394 case length64:
395 if (signed_value < 0) {
396 flags->neg = true;
397 signed_value = -signed_value;
398 }
399 break;
400 }
401
402 return signed_value;
403 }
404
405 /**
406 * Same as "dlog", except that arguments are passed as a va_list
407 *
408 * Returns number of characters written, or `-1` if format string is invalid.
409 */
vdlog(const char * fmt,struct va_list_wrapper * args)410 size_t vdlog(const char *fmt, struct va_list_wrapper *args)
411 {
412 size_t chars_written = 0;
413
414 lock();
415
416 while (*fmt != '\0') {
417 switch (*fmt) {
418 default:
419 chars_written++;
420 dlog_putchar(*fmt);
421 fmt++;
422 break;
423
424 case '%': {
425 struct format_flags flags = {0};
426 int min_width = 0;
427 enum format_length length = length32;
428 uint64_t value;
429
430 fmt++;
431 fmt = parse_flags(fmt, &flags);
432 fmt = parse_min_width(fmt, args, &flags, &min_width);
433 fmt = parse_length_modifier(fmt, &length);
434
435 /* Handle the format specifier. */
436 switch (*fmt) {
437 case '%':
438 fmt++;
439 chars_written++;
440 dlog_putchar('%');
441 break;
442
443 case 'c': {
444 char str[2] = {va_arg(args->va, int), '\0'};
445
446 fmt++;
447 chars_written += print_string(
448 str, str, min_width, flags, ' ');
449 break;
450 }
451
452 case 's': {
453 char *str = va_arg(args->va, char *);
454
455 fmt++;
456 chars_written += print_string(
457 str, str, min_width, flags, ' ');
458 break;
459 }
460
461 case 'd':
462 case 'i': {
463 fmt++;
464 value = va_arg(args->va, uint64_t);
465 value = reinterpret_signed_int(length, value,
466 &flags);
467
468 chars_written += print_int(value, base10,
469 min_width, flags);
470 break;
471 }
472
473 case 'b':
474 fmt++;
475 value = va_arg(args->va, uint64_t);
476 value = reinterpret_unsigned_int(length, value);
477
478 chars_written += print_int(value, base2,
479 min_width, flags);
480 break;
481
482 case 'B':
483 fmt++;
484 flags.upper = true;
485 value = va_arg(args->va, uint64_t);
486 value = reinterpret_unsigned_int(length, value);
487
488 chars_written += print_int(value, base2,
489 min_width, flags);
490 break;
491
492 case 'o':
493 fmt++;
494 value = va_arg(args->va, uint64_t);
495 value = reinterpret_unsigned_int(length, value);
496
497 chars_written += print_int(value, base8,
498 min_width, flags);
499 break;
500
501 case 'x':
502 fmt++;
503 value = va_arg(args->va, uint64_t);
504 value = reinterpret_unsigned_int(length, value);
505
506 chars_written += print_int(value, base16,
507 min_width, flags);
508 break;
509
510 case 'X':
511 fmt++;
512 flags.upper = true;
513 value = va_arg(args->va, uint64_t);
514 value = reinterpret_unsigned_int(length, value);
515
516 chars_written += print_int(value, base16,
517 min_width, flags);
518 break;
519
520 case 'u':
521 fmt++;
522 value = va_arg(args->va, uint64_t);
523 value = reinterpret_unsigned_int(length, value);
524
525 chars_written += print_int(value, base10,
526 min_width, flags);
527 break;
528
529 case 'p':
530 fmt++;
531 value = va_arg(args->va, uint64_t);
532 min_width = sizeof(size_t) * 2 + 2;
533 flags.zero = true;
534 flags.alt = true;
535
536 chars_written += print_int(value, base16,
537 min_width, flags);
538 break;
539
540 default:
541 chars_written = -1;
542 goto out;
543 }
544 }
545 }
546 }
547
548 out:
549 stdout_flush();
550 unlock();
551 return chars_written;
552 }
553
554 /**
555 * Prints the given format string to the debug log.
556 *
557 * The format string supported is the same as described in
558 * https://en.cppreference.com/w/c/io/fprintf, with the following exceptions:
559 * - Floating-point formatters (`%f`, `%F`, `%e`, `%E`, `%a`, `%A`, `%g`, `%G`,
560 * `%L`) are not supported because floats are not used in Hafnium and
561 * formatting them is too complicated.
562 * - `%n` is not supported because it is rarely used and potentially dangerous.
563 * - Precision modifiers (`%.*` and `%.` followed by an integer) are not
564 * supported.
565 *
566 * Returns number of characters written, or `-1` if format string is invalid.
567 */
dlog(const char * fmt,...)568 size_t dlog(const char *fmt, ...)
569 {
570 size_t chars_written = 0;
571 struct va_list_wrapper args;
572
573 va_start(args.va, fmt);
574 chars_written = vdlog(fmt, &args);
575 va_end(args.va);
576 return chars_written;
577 }
578