blob: 0c9b5b77ed0e1e2d89a94e0ba44466ac273d0aab [file] [log] [blame]
/*
* 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;
}