| /* |
| * Copyright 2018 The Hafnium Authors. |
| * |
| * Use of this source code is governed by a BSD-style |
| * license that can be found in the LICENSE file or at |
| * https://opensource.org/licenses/BSD-3-Clause. |
| */ |
| |
| #include "hf/dlog.h" |
| |
| #include <stddef.h> |
| |
| #include "hf/spinlock.h" |
| #include "hf/std.h" |
| #include "hf/stdout.h" |
| |
| enum { DLOG_MAX_STRING_LENGTH = 64 }; |
| |
| /* Keep fields aligned */ |
| /* clang-format off */ |
| struct format_flags { |
| bool minus : 1; |
| bool plus : 1; |
| bool space : 1; |
| bool alt : 1; |
| bool zero : 1; |
| bool upper : 1; |
| bool neg : 1; |
| }; |
| /* clang-format on */ |
| |
| enum format_base { |
| base2 = 2, |
| base8 = 8, |
| base10 = 10, |
| base16 = 16, |
| }; |
| |
| enum format_length { |
| length8 = 8, |
| length16 = 16, |
| length32 = 32, |
| length64 = 64, |
| }; |
| |
| static_assert(sizeof(char) == sizeof(uint8_t), |
| "dlog expects char to be 8 bits wide"); |
| static_assert(sizeof(short) == sizeof(uint16_t), |
| "dlog expects short to be 16 bits wide"); |
| static_assert(sizeof(int) == sizeof(uint32_t), |
| "dlog expects int to be 32 bits wide"); |
| static_assert(sizeof(long) == sizeof(uint64_t), |
| "dlog expects long to be 64 bits wide"); |
| static_assert(sizeof(long long) == sizeof(uint64_t), |
| "dlog expects long long to be 64 bits wide"); |
| static_assert(sizeof(intmax_t) == sizeof(uint64_t), |
| "dlog expects intmax_t to be 64 bits wide"); |
| static_assert(sizeof(size_t) == sizeof(uint64_t), |
| "dlog expects size_t to be 64 bits wide"); |
| static_assert(sizeof(ptrdiff_t) == sizeof(uint64_t), |
| "dlog expects ptrdiff_t to be 64 bits wide"); |
| |
| static bool dlog_lock_enabled = false; |
| static struct spinlock sl = SPINLOCK_INIT; |
| |
| /* |
| * These global variables for the log buffer are not static because a test needs |
| * to access them directly. |
| */ |
| size_t dlog_buffer_offset; |
| char dlog_buffer[DLOG_BUFFER_SIZE]; |
| |
| /** |
| * Takes the lock, if it is enabled. |
| */ |
| static void lock(void) |
| { |
| if (dlog_lock_enabled) { |
| sl_lock(&sl); |
| } |
| } |
| |
| /** |
| * Releases the lock, if it is enabled. |
| */ |
| static void unlock(void) |
| { |
| if (dlog_lock_enabled) { |
| sl_unlock(&sl); |
| } |
| } |
| |
| /** |
| * Enables the lock protecting the serial device. |
| */ |
| void dlog_enable_lock(void) |
| { |
| dlog_lock_enabled = true; |
| } |
| |
| static void dlog_putchar(char c) |
| { |
| dlog_buffer[dlog_buffer_offset] = c; |
| dlog_buffer_offset = (dlog_buffer_offset + 1) % DLOG_BUFFER_SIZE; |
| stdout_putchar(c); |
| } |
| |
| /** |
| * Prints a literal string (i.e. '%' is not interpreted specially) to the debug |
| * log. |
| * |
| * Returns number of characters written. |
| */ |
| static size_t print_raw_string(const char *str) |
| { |
| const char *c = str; |
| |
| for (; *c != '\0'; c++) { |
| dlog_putchar(*c); |
| } |
| |
| return c - str; |
| } |
| |
| /** |
| * Prints a formatted string to the debug log. The format includes a minimum |
| * width, the fill character, and flags (whether to align to left or right). |
| * |
| * str is the full string, while suffix is a pointer within str that indicates |
| * where the suffix begins. This is used when printing right-aligned numbers |
| * with a zero fill; for example, -10 with width 4 should be padded to -010, |
| * so suffix would point to index one of the "-10" string . |
| * |
| * Returns number of characters written. |
| */ |
| static size_t print_string(const char *str, const char *suffix, |
| size_t min_width, struct format_flags flags, |
| char fill) |
| { |
| size_t chars_written = 0; |
| size_t len = suffix - str; |
| |
| /* Print the string up to the beginning of the suffix. */ |
| while (str != suffix) { |
| chars_written++; |
| dlog_putchar(*str++); |
| } |
| |
| if (flags.minus) { |
| /* Left-aligned. Print suffix, then print padding if needed. */ |
| len += print_raw_string(suffix); |
| while (len < min_width) { |
| chars_written++; |
| dlog_putchar(' '); |
| len++; |
| } |
| return chars_written; |
| } |
| |
| /* Fill until we reach the desired length. */ |
| len += strnlen_s(suffix, DLOG_MAX_STRING_LENGTH); |
| while (len < min_width) { |
| chars_written++; |
| dlog_putchar(fill); |
| len++; |
| } |
| |
| /* Now print the rest of the string. */ |
| chars_written += print_raw_string(suffix); |
| return chars_written; |
| } |
| |
| /** |
| * Prints an integer to the debug log. The caller specifies the base, its |
| * minimum width and printf-style flags. |
| * |
| * Returns number of characters written. |
| */ |
| static size_t print_int(size_t value, enum format_base base, size_t min_width, |
| struct format_flags flags) |
| { |
| static const char *digits_lower = "0123456789abcdefxb"; |
| static const char *digits_upper = "0123456789ABCDEFXB"; |
| const char *digits = flags.upper ? digits_upper : digits_lower; |
| char buf[DLOG_MAX_STRING_LENGTH]; |
| char *ptr = &buf[sizeof(buf) - 1]; |
| char *num; |
| *ptr = '\0'; |
| do { |
| --ptr; |
| *ptr = digits[value % base]; |
| value /= base; |
| } while (value); |
| |
| /* Num stores where the actual number begins. */ |
| num = ptr; |
| |
| /* Add prefix if requested. */ |
| if (flags.alt) { |
| switch (base) { |
| case base16: |
| ptr -= 2; |
| ptr[0] = '0'; |
| ptr[1] = digits[16]; |
| break; |
| |
| case base2: |
| ptr -= 2; |
| ptr[0] = '0'; |
| ptr[1] = digits[17]; |
| break; |
| |
| case base8: |
| ptr--; |
| *ptr = '0'; |
| break; |
| |
| case base10: |
| /* do nothing */ |
| break; |
| } |
| } |
| |
| /* Add sign if requested. */ |
| if (flags.neg) { |
| *--ptr = '-'; |
| } else if (flags.plus) { |
| *--ptr = '+'; |
| } else if (flags.space) { |
| *--ptr = ' '; |
| } |
| return print_string(ptr, num, min_width, flags, flags.zero ? '0' : ' '); |
| } |
| |
| /** |
| * Parses the optional flags field of a printf-style format. Returns a pointer |
| * to the first non-flag character in the string. |
| */ |
| static const char *parse_flags(const char *fmt, struct format_flags *flags) |
| { |
| for (;; fmt++) { |
| switch (*fmt) { |
| case '-': |
| flags->minus = true; |
| break; |
| |
| case '+': |
| flags->plus = true; |
| break; |
| |
| case ' ': |
| flags->space = true; |
| break; |
| |
| case '#': |
| flags->alt = true; |
| break; |
| |
| case '0': |
| flags->zero = true; |
| break; |
| |
| default: |
| return fmt; |
| } |
| } |
| } |
| |
| /** |
| * Parses the optional length modifier field of a printf-style format. |
| * |
| * Returns a pointer to the first non-length modifier character in the string. |
| */ |
| static const char *parse_length_modifier(const char *fmt, |
| enum format_length *length) |
| { |
| switch (*fmt) { |
| case 'h': |
| fmt++; |
| if (*fmt == 'h') { |
| fmt++; |
| *length = length8; |
| } else { |
| *length = length16; |
| } |
| break; |
| case 'l': |
| fmt++; |
| if (*fmt == 'l') { |
| fmt++; |
| *length = length64; |
| } else { |
| *length = length64; |
| } |
| break; |
| |
| case 'j': |
| case 'z': |
| case 't': |
| fmt++; |
| *length = length64; |
| break; |
| |
| default: |
| *length = length32; |
| break; |
| } |
| |
| return fmt; |
| } |
| |
| /** |
| * Parses the optional minimum width field of a printf-style format. |
| * If the width is negative, `flags.minus` is set. |
| * |
| * Returns a pointer to the first non-digit character in the string. |
| */ |
| static const char *parse_min_width(const char *fmt, |
| struct va_list_wrapper *args, |
| struct format_flags *flags, int *min_width) |
| { |
| int width = 0; |
| |
| /* Read minimum width from arguments. */ |
| if (*fmt == '*') { |
| fmt++; |
| width = va_arg(args->va, int); |
| if (width < 0) { |
| width = -width; |
| flags->minus = true; |
| } |
| } else { |
| for (; *fmt >= '0' && *fmt <= '9'; fmt++) { |
| width = (width * 10) + (*fmt - '0'); |
| } |
| } |
| |
| *min_width = width; |
| |
| return fmt; |
| } |
| |
| /** |
| * Reinterpret an unsigned 64-bit integer as a potentially shorter unsigned |
| * integer according to the length modifier. |
| * Returns an unsigned integer suitable for passing to `print_int`. |
| */ |
| uint64_t reinterpret_unsigned_int(enum format_length length, uint64_t value) |
| { |
| switch (length) { |
| case length8: |
| return (uint8_t)value; |
| case length16: |
| return (uint16_t)value; |
| case length32: |
| return (uint32_t)value; |
| case length64: |
| return value; |
| } |
| } |
| |
| /** |
| * Reinterpret an unsigned 64-bit integer as a potentially shorter signed |
| * integer according to the length modifier. |
| * |
| * Returns an *unsigned* integer suitable for passing to `print_int`. If the |
| * reinterpreted value is negative, `flags.neg` is set and the absolute value is |
| * returned. |
| */ |
| uint64_t reinterpret_signed_int(enum format_length length, uint64_t value, |
| struct format_flags *flags) |
| { |
| int64_t signed_value = (int64_t)reinterpret_unsigned_int(length, value); |
| |
| switch (length) { |
| case length8: |
| if ((int8_t)signed_value < 0) { |
| flags->neg = true; |
| signed_value = (-signed_value) & 0xFF; |
| } |
| break; |
| case length16: |
| if ((int16_t)signed_value < 0) { |
| flags->neg = true; |
| signed_value = (-signed_value) & 0xFFFF; |
| } |
| break; |
| case length32: |
| if ((int32_t)signed_value < 0) { |
| flags->neg = true; |
| signed_value = (-signed_value) & 0xFFFFFFFF; |
| } |
| break; |
| case length64: |
| if (signed_value < 0) { |
| flags->neg = true; |
| signed_value = -signed_value; |
| } |
| break; |
| } |
| |
| return signed_value; |
| } |
| |
| /** |
| * Same as "dlog", except that arguments are passed as a va_list |
| * |
| * Returns number of characters written, or `-1` if format string is invalid. |
| */ |
| size_t vdlog(const char *fmt, struct va_list_wrapper *args) |
| { |
| size_t chars_written = 0; |
| |
| lock(); |
| |
| while (*fmt != '\0') { |
| switch (*fmt) { |
| default: |
| chars_written++; |
| dlog_putchar(*fmt); |
| fmt++; |
| break; |
| |
| case '%': { |
| struct format_flags flags = {0}; |
| int min_width = 0; |
| enum format_length length = length32; |
| uint64_t value; |
| |
| fmt++; |
| fmt = parse_flags(fmt, &flags); |
| fmt = parse_min_width(fmt, args, &flags, &min_width); |
| fmt = parse_length_modifier(fmt, &length); |
| |
| /* Handle the format specifier. */ |
| switch (*fmt) { |
| case '%': |
| fmt++; |
| chars_written++; |
| dlog_putchar('%'); |
| break; |
| |
| case 'c': { |
| char str[2] = {va_arg(args->va, int), '\0'}; |
| |
| fmt++; |
| chars_written += print_string( |
| str, str, min_width, flags, ' '); |
| break; |
| } |
| |
| case 's': { |
| char *str = va_arg(args->va, char *); |
| |
| fmt++; |
| chars_written += print_string( |
| str, str, min_width, flags, ' '); |
| break; |
| } |
| |
| case 'd': |
| case 'i': { |
| fmt++; |
| value = va_arg(args->va, uint64_t); |
| value = reinterpret_signed_int(length, value, |
| &flags); |
| |
| chars_written += print_int(value, base10, |
| min_width, flags); |
| break; |
| } |
| |
| case 'b': |
| fmt++; |
| value = va_arg(args->va, uint64_t); |
| value = reinterpret_unsigned_int(length, value); |
| |
| chars_written += print_int(value, base2, |
| min_width, flags); |
| break; |
| |
| case 'B': |
| fmt++; |
| flags.upper = true; |
| value = va_arg(args->va, uint64_t); |
| value = reinterpret_unsigned_int(length, value); |
| |
| chars_written += print_int(value, base2, |
| min_width, flags); |
| break; |
| |
| case 'o': |
| fmt++; |
| value = va_arg(args->va, uint64_t); |
| value = reinterpret_unsigned_int(length, value); |
| |
| chars_written += print_int(value, base8, |
| min_width, flags); |
| break; |
| |
| case 'x': |
| fmt++; |
| value = va_arg(args->va, uint64_t); |
| value = reinterpret_unsigned_int(length, value); |
| |
| chars_written += print_int(value, base16, |
| min_width, flags); |
| break; |
| |
| case 'X': |
| fmt++; |
| flags.upper = true; |
| value = va_arg(args->va, uint64_t); |
| value = reinterpret_unsigned_int(length, value); |
| |
| chars_written += print_int(value, base16, |
| min_width, flags); |
| break; |
| |
| case 'u': |
| fmt++; |
| value = va_arg(args->va, uint64_t); |
| value = reinterpret_unsigned_int(length, value); |
| |
| chars_written += print_int(value, base10, |
| min_width, flags); |
| break; |
| |
| case 'p': |
| fmt++; |
| value = va_arg(args->va, uint64_t); |
| min_width = sizeof(size_t) * 2 + 2; |
| flags.zero = true; |
| flags.alt = true; |
| |
| chars_written += print_int(value, base16, |
| min_width, flags); |
| break; |
| |
| default: |
| chars_written = -1; |
| goto out; |
| } |
| } |
| } |
| } |
| |
| out: |
| stdout_flush(); |
| unlock(); |
| return chars_written; |
| } |
| |
| /** |
| * Prints the given format string to the debug log. |
| * |
| * The format string supported is the same as described in |
| * https://en.cppreference.com/w/c/io/fprintf, with the following exceptions: |
| * - Floating-point formatters (`%f`, `%F`, `%e`, `%E`, `%a`, `%A`, `%g`, `%G`, |
| * `%L`) are not supported because floats are not used in Hafnium and |
| * formatting them is too complicated. |
| * - `%n` is not supported because it is rarely used and potentially dangerous. |
| * - Precision modifiers (`%.*` and `%.` followed by an integer) are not |
| * supported. |
| * |
| * Returns number of characters written, or `-1` if format string is invalid. |
| */ |
| size_t dlog(const char *fmt, ...) |
| { |
| size_t chars_written = 0; |
| struct va_list_wrapper args; |
| |
| va_start(args.va, fmt); |
| chars_written = vdlog(fmt, &args); |
| va_end(args.va); |
| return chars_written; |
| } |