491 lines
9.7 KiB
C
491 lines
9.7 KiB
C
/*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*
|
|
* Copyright (c) 2019 Western Digital Corporation or its affiliates.
|
|
*
|
|
* Authors:
|
|
* Anup Patel <anup.patel@wdc.com>
|
|
*/
|
|
|
|
#include <sbi/riscv_locks.h>
|
|
#include <sbi/sbi_console.h>
|
|
#include <sbi/sbi_hart.h>
|
|
#include <sbi/sbi_platform.h>
|
|
#include <sbi/sbi_scratch.h>
|
|
#include <sbi/sbi_string.h>
|
|
|
|
#define CONSOLE_TBUF_MAX 256
|
|
|
|
static const struct sbi_console_device *console_dev = NULL;
|
|
static char console_tbuf[CONSOLE_TBUF_MAX];
|
|
static u32 console_tbuf_len;
|
|
static spinlock_t console_out_lock = SPIN_LOCK_INITIALIZER;
|
|
|
|
bool sbi_isprintable(char c)
|
|
{
|
|
if (((31 < c) && (c < 127)) || (c == '\f') || (c == '\r') ||
|
|
(c == '\n') || (c == '\t')) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int sbi_getc(void)
|
|
{
|
|
if (console_dev && console_dev->console_getc)
|
|
return console_dev->console_getc();
|
|
return -1;
|
|
}
|
|
|
|
static unsigned long nputs(const char *str, unsigned long len)
|
|
{
|
|
unsigned long i;
|
|
|
|
if (console_dev) {
|
|
if (console_dev->console_puts)
|
|
return console_dev->console_puts(str, len);
|
|
else if (console_dev->console_putc) {
|
|
for (i = 0; i < len; i++) {
|
|
if (str[i] == '\n')
|
|
console_dev->console_putc('\r');
|
|
console_dev->console_putc(str[i]);
|
|
}
|
|
}
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static void nputs_all(const char *str, unsigned long len)
|
|
{
|
|
unsigned long p = 0;
|
|
|
|
while (p < len)
|
|
p += nputs(&str[p], len - p);
|
|
}
|
|
|
|
void sbi_putc(char ch)
|
|
{
|
|
nputs_all(&ch, 1);
|
|
}
|
|
|
|
void sbi_puts(const char *str)
|
|
{
|
|
unsigned long len = sbi_strlen(str);
|
|
|
|
spin_lock(&console_out_lock);
|
|
nputs_all(str, len);
|
|
spin_unlock(&console_out_lock);
|
|
}
|
|
|
|
unsigned long sbi_nputs(const char *str, unsigned long len)
|
|
{
|
|
unsigned long ret;
|
|
|
|
spin_lock(&console_out_lock);
|
|
ret = nputs(str, len);
|
|
spin_unlock(&console_out_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void sbi_gets(char *s, int maxwidth, char endchar)
|
|
{
|
|
int ch;
|
|
char *retval = s;
|
|
|
|
while ((ch = sbi_getc()) != endchar && ch >= 0 && maxwidth > 1) {
|
|
*retval = (char)ch;
|
|
retval++;
|
|
maxwidth--;
|
|
}
|
|
*retval = '\0';
|
|
}
|
|
|
|
unsigned long sbi_ngets(char *str, unsigned long len)
|
|
{
|
|
int ch;
|
|
unsigned long i;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
ch = sbi_getc();
|
|
if (ch < 0)
|
|
break;
|
|
str[i] = ch;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
#define PAD_RIGHT 1
|
|
#define PAD_ZERO 2
|
|
#define PAD_ALTERNATE 4
|
|
#define PAD_SIGN 8
|
|
#define USE_TBUF 16
|
|
#define PRINT_BUF_LEN 64
|
|
|
|
#define va_start(v, l) __builtin_va_start((v), l)
|
|
#define va_end __builtin_va_end
|
|
#define va_arg __builtin_va_arg
|
|
typedef __builtin_va_list va_list;
|
|
|
|
static void printc(char **out, u32 *out_len, char ch, int flags)
|
|
{
|
|
if (!out) {
|
|
sbi_putc(ch);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* The *printf entry point functions have enforced that (*out) can
|
|
* only be null when out_len is non-null and its value is zero.
|
|
*/
|
|
if (!out_len || *out_len > 1) {
|
|
*(*out)++ = ch;
|
|
**out = '\0';
|
|
if (out_len) {
|
|
--(*out_len);
|
|
if ((flags & USE_TBUF) && *out_len == 1) {
|
|
nputs_all(console_tbuf, CONSOLE_TBUF_MAX - *out_len);
|
|
*out = console_tbuf;
|
|
*out_len = CONSOLE_TBUF_MAX;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int prints(char **out, u32 *out_len, const char *string, int width,
|
|
int flags)
|
|
{
|
|
int pc = 0;
|
|
width -= sbi_strlen(string);
|
|
if (!(flags & PAD_RIGHT)) {
|
|
for (; width > 0; --width) {
|
|
printc(out, out_len, flags & PAD_ZERO ? '0' : ' ', flags);
|
|
++pc;
|
|
}
|
|
}
|
|
for (; *string; ++string) {
|
|
printc(out, out_len, *string, flags);
|
|
++pc;
|
|
}
|
|
for (; width > 0; --width) {
|
|
printc(out, out_len, ' ', flags);
|
|
++pc;
|
|
}
|
|
|
|
return pc;
|
|
}
|
|
|
|
static int printi(char **out, u32 *out_len, long long i,
|
|
int width, int flags, int type)
|
|
{
|
|
int pc = 0;
|
|
char *s, sign = 0, letbase, print_buf[PRINT_BUF_LEN];
|
|
unsigned long long u, b, t;
|
|
|
|
b = 10;
|
|
letbase = 'a';
|
|
if (type == 'o')
|
|
b = 8;
|
|
else if (type == 'x' || type == 'X' || type == 'p' || type == 'P') {
|
|
b = 16;
|
|
letbase &= ~0x20;
|
|
letbase |= type & 0x20;
|
|
}
|
|
|
|
u = i;
|
|
sign = 0;
|
|
if (type == 'i' || type == 'd') {
|
|
if ((flags & PAD_SIGN) && i > 0)
|
|
sign = '+';
|
|
if (i < 0) {
|
|
sign = '-';
|
|
u = -i;
|
|
}
|
|
}
|
|
|
|
s = print_buf + PRINT_BUF_LEN - 1;
|
|
*s = '\0';
|
|
|
|
if (!u) {
|
|
*--s = '0';
|
|
} else {
|
|
while (u) {
|
|
t = u % b;
|
|
u = u / b;
|
|
if (t >= 10)
|
|
t += letbase - '0' - 10;
|
|
*--s = t + '0';
|
|
}
|
|
}
|
|
|
|
if (flags & PAD_ZERO) {
|
|
if (sign) {
|
|
printc(out, out_len, sign, flags);
|
|
++pc;
|
|
--width;
|
|
}
|
|
if (i && (flags & PAD_ALTERNATE)) {
|
|
if (b == 16 || b == 8) {
|
|
printc(out, out_len, '0', flags);
|
|
++pc;
|
|
--width;
|
|
}
|
|
if (b == 16) {
|
|
printc(out, out_len, 'x' - 'a' + letbase, flags);
|
|
++pc;
|
|
--width;
|
|
}
|
|
}
|
|
} else {
|
|
if (i && (flags & PAD_ALTERNATE)) {
|
|
if (b == 16)
|
|
*--s = 'x' - 'a' + letbase;
|
|
if (b == 16 || b == 8)
|
|
*--s = '0';
|
|
}
|
|
if (sign)
|
|
*--s = sign;
|
|
}
|
|
|
|
return pc + prints(out, out_len, s, width, flags);
|
|
}
|
|
|
|
static int print(char **out, u32 *out_len, const char *format, va_list args)
|
|
{
|
|
bool flags_done;
|
|
int width, flags, pc = 0;
|
|
char type, scr[2], *tout;
|
|
bool use_tbuf = (!out) ? true : false;
|
|
|
|
/*
|
|
* The console_tbuf is protected by console_out_lock and
|
|
* print() is always called with console_out_lock held
|
|
* when out == NULL.
|
|
*/
|
|
if (use_tbuf) {
|
|
console_tbuf_len = CONSOLE_TBUF_MAX;
|
|
tout = console_tbuf;
|
|
out = &tout;
|
|
out_len = &console_tbuf_len;
|
|
}
|
|
|
|
/* handle special case: *out_len == 1*/
|
|
if (out) {
|
|
if(!out_len || *out_len)
|
|
**out = '\0';
|
|
}
|
|
|
|
for (; *format != 0; ++format) {
|
|
width = flags = 0;
|
|
if (use_tbuf)
|
|
flags |= USE_TBUF;
|
|
if (*format == '%') {
|
|
++format;
|
|
if (*format == '\0')
|
|
break;
|
|
if (*format == '%')
|
|
goto literal;
|
|
/* Get flags */
|
|
flags_done = false;
|
|
while (!flags_done) {
|
|
switch (*format) {
|
|
case '-':
|
|
flags |= PAD_RIGHT;
|
|
break;
|
|
case '+':
|
|
flags |= PAD_SIGN;
|
|
break;
|
|
case '#':
|
|
flags |= PAD_ALTERNATE;
|
|
break;
|
|
case '0':
|
|
flags |= PAD_ZERO;
|
|
break;
|
|
case ' ':
|
|
case '\'':
|
|
/* Ignored flags, do nothing */
|
|
break;
|
|
default:
|
|
flags_done = true;
|
|
break;
|
|
}
|
|
if (!flags_done)
|
|
++format;
|
|
}
|
|
if (flags & PAD_RIGHT)
|
|
flags &= ~PAD_ZERO;
|
|
/* Get width */
|
|
for (; *format >= '0' && *format <= '9'; ++format) {
|
|
width *= 10;
|
|
width += *format - '0';
|
|
}
|
|
if (*format == 's') {
|
|
char *s = va_arg(args, char *);
|
|
pc += prints(out, out_len, s ? s : "(null)",
|
|
width, flags);
|
|
continue;
|
|
}
|
|
if ((*format == 'd') || (*format == 'i')) {
|
|
pc += printi(out, out_len, va_arg(args, int),
|
|
width, flags, *format);
|
|
continue;
|
|
}
|
|
if ((*format == 'u') || (*format == 'o')
|
|
|| (*format == 'x') || (*format == 'X')) {
|
|
pc += printi(out, out_len, va_arg(args, unsigned int),
|
|
width, flags, *format);
|
|
continue;
|
|
}
|
|
if ((*format == 'p') || (*format == 'P')) {
|
|
pc += printi(out, out_len, (uintptr_t)va_arg(args, void*),
|
|
width, flags, *format);
|
|
continue;
|
|
}
|
|
if (*format == 'l') {
|
|
type = 'i';
|
|
if (format[1] == 'l') {
|
|
++format;
|
|
if ((format[1] == 'u') || (format[1] == 'o')
|
|
|| (format[1] == 'd') || (format[1] == 'i')
|
|
|| (format[1] == 'x') || (format[1] == 'X')) {
|
|
++format;
|
|
type = *format;
|
|
}
|
|
pc += printi(out, out_len, va_arg(args, long long),
|
|
width, flags, type);
|
|
continue;
|
|
}
|
|
if ((format[1] == 'u') || (format[1] == 'o')
|
|
|| (format[1] == 'd') || (format[1] == 'i')
|
|
|| (format[1] == 'x') || (format[1] == 'X')) {
|
|
++format;
|
|
type = *format;
|
|
}
|
|
if ((type == 'd') || (type == 'i'))
|
|
pc += printi(out, out_len, va_arg(args, long),
|
|
width, flags, type);
|
|
else
|
|
pc += printi(out, out_len, va_arg(args, unsigned long),
|
|
width, flags, type);
|
|
continue;
|
|
}
|
|
if (*format == 'c') {
|
|
/* char are converted to int then pushed on the stack */
|
|
scr[0] = va_arg(args, int);
|
|
scr[1] = '\0';
|
|
pc += prints(out, out_len, scr, width, flags);
|
|
continue;
|
|
}
|
|
} else {
|
|
literal:
|
|
printc(out, out_len, *format, flags);
|
|
++pc;
|
|
}
|
|
}
|
|
|
|
if (use_tbuf && console_tbuf_len < CONSOLE_TBUF_MAX)
|
|
nputs_all(console_tbuf, CONSOLE_TBUF_MAX - console_tbuf_len);
|
|
|
|
return pc;
|
|
}
|
|
|
|
int sbi_sprintf(char *out, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
int retval;
|
|
|
|
if (unlikely(!out))
|
|
sbi_panic("sbi_sprintf called with NULL output string\n");
|
|
|
|
va_start(args, format);
|
|
retval = print(&out, NULL, format, args);
|
|
va_end(args);
|
|
|
|
return retval;
|
|
}
|
|
|
|
int sbi_snprintf(char *out, u32 out_sz, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
int retval;
|
|
|
|
if (unlikely(!out && out_sz != 0))
|
|
sbi_panic("sbi_snprintf called with NULL output string and "
|
|
"output size is not zero\n");
|
|
|
|
va_start(args, format);
|
|
retval = print(&out, &out_sz, format, args);
|
|
va_end(args);
|
|
|
|
return retval;
|
|
}
|
|
|
|
int sbi_printf(const char *format, ...)
|
|
{
|
|
va_list args;
|
|
int retval;
|
|
|
|
spin_lock(&console_out_lock);
|
|
va_start(args, format);
|
|
retval = print(NULL, NULL, format, args);
|
|
va_end(args);
|
|
spin_unlock(&console_out_lock);
|
|
|
|
return retval;
|
|
}
|
|
|
|
int sbi_dprintf(const char *format, ...)
|
|
{
|
|
va_list args;
|
|
int retval = 0;
|
|
struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
|
|
|
|
va_start(args, format);
|
|
if (scratch->options & SBI_SCRATCH_DEBUG_PRINTS) {
|
|
spin_lock(&console_out_lock);
|
|
retval = print(NULL, NULL, format, args);
|
|
spin_unlock(&console_out_lock);
|
|
}
|
|
va_end(args);
|
|
|
|
return retval;
|
|
}
|
|
|
|
void sbi_panic(const char *format, ...)
|
|
{
|
|
va_list args;
|
|
|
|
spin_lock(&console_out_lock);
|
|
va_start(args, format);
|
|
print(NULL, NULL, format, args);
|
|
va_end(args);
|
|
spin_unlock(&console_out_lock);
|
|
|
|
sbi_hart_hang();
|
|
}
|
|
|
|
const struct sbi_console_device *sbi_console_get_device(void)
|
|
{
|
|
return console_dev;
|
|
}
|
|
|
|
void sbi_console_set_device(const struct sbi_console_device *dev)
|
|
{
|
|
if (!dev)
|
|
return;
|
|
|
|
console_dev = dev;
|
|
}
|
|
|
|
int sbi_console_init(struct sbi_scratch *scratch)
|
|
{
|
|
int rc = sbi_platform_console_init(sbi_platform_ptr(scratch));
|
|
|
|
/* console is not a necessary device */
|
|
if (rc == SBI_ENODEV)
|
|
return 0;
|
|
|
|
return rc;
|
|
}
|