blob: 1a4bf67030e37b78b29eee4283de6a68df8571e3 [file] [log] [blame]
/*
* Copyright 2018 The Hafnium Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "hf/dlog.h"
#include <stdbool.h>
#include <stddef.h>
#include "hf/spci.h"
#include "hf/spinlock.h"
#include "hf/std.h"
#include "hf/stdout.h"
/* Keep macro alignment */
/* clang-format off */
#define FLAG_SPACE 0x01
#define FLAG_ZERO 0x02
#define FLAG_MINUS 0x04
#define FLAG_PLUS 0x08
#define FLAG_ALT 0x10
#define FLAG_UPPER 0x20
#define FLAG_NEG 0x40
#define DLOG_MAX_STRING_LENGTH 64
/* clang-format on */
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 raw string to the debug log and returns its length.
*/
static size_t print_raw_string(const char *str)
{
const char *c = str;
while (*c != '\0') {
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 .
*/
static void print_string(const char *str, const char *suffix, size_t width,
int flags, char fill)
{
size_t len = suffix - str;
/* Print the string up to the beginning of the suffix. */
while (str != suffix) {
dlog_putchar(*str++);
}
if (flags & FLAG_MINUS) {
/* Left-aligned. Print suffix, then print padding if needed. */
len += print_raw_string(suffix);
while (len < width) {
dlog_putchar(' ');
len++;
}
return;
}
/* Fill until we reach the desired length. */
len += strnlen_s(suffix, DLOG_MAX_STRING_LENGTH);
while (len < width) {
dlog_putchar(fill);
len++;
}
/* Now print the rest of the string. */
print_raw_string(suffix);
}
/**
* Prints a number to the debug log. The caller specifies the base, its minimum
* width and printf-style flags.
*/
static void print_num(size_t v, size_t base, size_t width, int flags)
{
static const char *digits_lower = "0123456789abcdefx";
static const char *digits_upper = "0123456789ABCDEFX";
const char *d = (flags & FLAG_UPPER) ? digits_upper : digits_lower;
char buf[DLOG_MAX_STRING_LENGTH];
char *ptr = &buf[sizeof(buf) - 1];
char *num;
*ptr = '\0';
do {
--ptr;
*ptr = d[v % base];
v /= base;
} while (v);
/* Num stores where the actual number begins. */
num = ptr;
/* Add prefix if requested. */
if (flags & FLAG_ALT) {
switch (base) {
case 16:
ptr -= 2;
ptr[0] = '0';
ptr[1] = d[16];
break;
case 8:
ptr--;
*ptr = '0';
break;
}
}
/* Add sign if requested. */
if (flags & FLAG_NEG) {
*--ptr = '-';
} else if (flags & FLAG_PLUS) {
*--ptr = '+';
} else if (flags & FLAG_SPACE) {
*--ptr = ' ';
}
if (flags & FLAG_ZERO) {
print_string(ptr, num, width, flags, '0');
} else {
print_string(ptr, ptr, width, flags, ' ');
}
}
/**
* Parses the optional flags field of a printf-style format. It returns the spot
* on the string where a non-flag character was found.
*/
static const char *parse_flags(const char *p, int *flags)
{
for (;;) {
switch (*p) {
case ' ':
*flags |= FLAG_SPACE;
break;
case '0':
*flags |= FLAG_ZERO;
break;
case '-':
*flags |= FLAG_MINUS;
break;
case '+':
*flags |= FLAG_PLUS;
case '#':
*flags |= FLAG_ALT;
break;
default:
return p;
}
p++;
}
}
/**
* Send the contents of the given VM's log buffer to the log, preceded by the VM
* ID and followed by a newline.
*/
void dlog_flush_vm_buffer(spci_vm_id_t id, char buffer[], size_t length)
{
lock();
print_raw_string("VM ");
print_num(id, 10, 0, 0);
print_raw_string(": ");
for (size_t i = 0; i < length; ++i) {
dlog_putchar(buffer[i]);
buffer[i] = '\0';
}
dlog_putchar('\n');
unlock();
}
/**
* Same as "dlog", except that arguments are passed as a va_list
*/
void vdlog(const char *fmt, va_list args)
{
const char *p;
size_t w;
int flags;
char buf[2];
lock();
for (p = fmt; *p; p++) {
switch (*p) {
default:
dlog_putchar(*p);
break;
case '%':
/* Read optional flags. */
flags = 0;
p = parse_flags(p + 1, &flags) - 1;
/* Read the minimum width, if one is specified. */
w = 0;
while (p[1] >= '0' && p[1] <= '9') {
w = (w * 10) + (p[1] - '0');
p++;
}
/* Read minimum width from arguments. */
if (w == 0 && p[1] == '*') {
int v = va_arg(args, int);
if (v >= 0) {
w = v;
} else {
w = -v;
flags |= FLAG_MINUS;
}
p++;
}
/* Handle the format specifier. */
switch (p[1]) {
case 's': {
char *str = va_arg(args, char *);
print_string(str, str, w, flags, ' ');
p++;
} break;
case 'd':
case 'i': {
int v = va_arg(args, int);
if (v < 0) {
flags |= FLAG_NEG;
v = -v;
}
print_num((size_t)v, 10, w, flags);
p++;
} break;
case 'X':
flags |= FLAG_UPPER;
print_num(va_arg(args, size_t), 16, w, flags);
p++;
break;
case 'p':
print_num(va_arg(args, size_t), 16,
sizeof(size_t) * 2, FLAG_ZERO);
p++;
break;
case 'x':
print_num(va_arg(args, size_t), 16, w, flags);
p++;
break;
case 'u':
print_num(va_arg(args, size_t), 10, w, flags);
p++;
break;
case 'o':
print_num(va_arg(args, size_t), 8, w, flags);
p++;
break;
case 'c':
buf[1] = 0;
buf[0] = va_arg(args, int);
print_string(buf, buf, w, flags, ' ');
p++;
break;
case '%':
break;
default:
dlog_putchar('%');
}
break;
}
}
unlock();
}
/**
* Prints the given format string to the debug log.
*/
void dlog(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vdlog(fmt, args);
va_end(args);
}