kendryte-standalone-sdk/lib/bsp/printf.c

669 lines
17 KiB
C

/* Copyright 2018 Canaan Inc.
*
* 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
*
* http://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.
*/
/**
* File: printf.c
*
* Copyright (c) 2004,2012 Kustaa Nyholm / SpareTimeLabs
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Kustaa Nyholm or SpareTimeLabs nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stddef.h>
#include "printf.h"
#include "atomic.h"
#include "uarths.h"
/**
* Configuration
*/
/* Enable long int support */
#define PRINTF_LONG_SUPPORT
/* Enable long long int support (implies long int support) */
#define PRINTF_LONG_LONG_SUPPORT
/* Enable %z (size_t) support */
#define PRINTF_SIZE_T_SUPPORT
/**
* Configuration adjustments
*/
#if defined(PRINTF_LONG_LONG_SUPPORT)
#define PRINTF_LONG_SUPPORT
#endif
/* __SIZEOF_<type>__ defined at least by gcc */
#if defined(__SIZEOF_POINTER__)
#define SIZEOF_POINTER __SIZEOF_POINTER__
#endif
#if defined(__SIZEOF_LONG_LONG__)
#define SIZEOF_LONG_LONG __SIZEOF_LONG_LONG__
#endif
#if defined(__SIZEOF_LONG__)
#define SIZEOF_LONG __SIZEOF_LONG__
#endif
#if defined(__SIZEOF_INT__)
#define SIZEOF_INT __SIZEOF_INT__
#endif
#if defined(__GNUC__)
#define _TFP_GCC_NO_INLINE_ __attribute__((noinline))
#else
#define _TFP_GCC_NO_INLINE_
#endif
#if defined(PRINTF_LONG_SUPPORT)
#define BF_MAX 20 /* long = 64b on some architectures */
#else
#define BF_MAX 10 /* int = 32b on some architectures */
#endif
#define IS_DIGIT(x) ((x) >= '0' && (x) <= '9')
/* Clear unused warnings for actually unused variables */
#define UNUSED(x) (void)(x)
/**
* Implementation
*/
struct param
{
char lz : 1; /* Leading zeros */
char alt : 1; /* alternate form */
char uc : 1; /* Upper case (for base16 only) */
char align_left : 1; /* 0 == align right (default), 1 == align left */
int width; /* field width */
int prec; /* precision */
char sign; /* The sign to display (if any) */
unsigned int base; /* number base (e.g.: 8, 10, 16) */
char *bf; /* Buffer to output */
size_t bf_len; /* Buffer length */
};
static hartlock_t lock = HARTLOCK_INIT;
#if defined(PRINTF_LONG_LONG_SUPPORT)
static void _TFP_GCC_NO_INLINE_ ulli2a(unsigned long long int num, struct param *p)
{
unsigned long long int d = 1;
char *bf = p->bf;
if ((p->prec == 0) && (num == 0))
return;
while (num / d >= p->base)
{
d *= p->base;
}
while (d != 0)
{
int dgt = num / d;
num %= d;
d /= p->base;
*bf++ = dgt + (dgt < 10 ? '0' : (p->uc ? 'A' : 'a') - 10);
}
p->bf_len = bf - p->bf;
}
static void lli2a(long long int num, struct param *p)
{
if (num < 0)
{
num = -num;
p->sign = '-';
}
ulli2a(num, p);
}
#endif
#if defined(PRINTF_LONG_SUPPORT)
static void uli2a(unsigned long int num, struct param *p)
{
unsigned long int d = 1;
char *bf = p->bf;
if ((p->prec == 0) && (num == 0))
return;
while (num / d >= p->base)
{
d *= p->base;
}
while (d != 0)
{
int dgt = num / d;
num %= d;
d /= p->base;
*bf++ = dgt + (dgt < 10 ? '0' : (p->uc ? 'A' : 'a') - 10);
}
p->bf_len = bf - p->bf;
}
static void li2a(long num, struct param *p)
{
if (num < 0)
{
num = -num;
p->sign = '-';
}
uli2a(num, p);
}
#endif
static void ui2a(unsigned int num, struct param *p)
{
unsigned int d = 1;
char *bf = p->bf;
if ((p->prec == 0) && (num == 0))
return;
while (num / d >= p->base)
{
d *= p->base;
}
while (d != 0)
{
int dgt = num / d;
num %= d;
d /= p->base;
*bf++ = dgt + (dgt < 10 ? '0' : (p->uc ? 'A' : 'a') - 10);
}
p->bf_len = bf - p->bf;
}
static void i2a(int num, struct param *p)
{
if (num < 0)
{
num = -num;
p->sign = '-';
}
ui2a(num, p);
}
static int a2d(char ch)
{
if (IS_DIGIT(ch))
return ch - '0';
else if (ch >= 'a' && ch <= 'f')
return ch - 'a' + 10;
else if (ch >= 'A' && ch <= 'F')
return ch - 'A' + 10;
else
return -1;
}
static char a2u(char ch, const char **src, int base, unsigned int *nump)
{
const char *p = *src;
unsigned int num = 0;
int digit;
while ((digit = a2d(ch)) >= 0)
{
if (digit > base)
break;
num = num * base + digit;
ch = *p++;
}
*src = p;
*nump = num;
return ch;
}
static void putchw(void *putp, putcf putf, struct param *p)
{
char ch;
int width = p->width;
int prec = p->prec;
char *bf = p->bf;
size_t bf_len = p->bf_len;
/* Number of filling characters */
width -= bf_len;
prec -= bf_len;
if (p->sign)
width--;
if (p->alt && p->base == 16)
width -= 2;
else if (p->alt && p->base == 8)
width--;
if (prec > 0)
width -= prec;
/* Fill with space to align to the right, before alternate or sign */
if (!p->lz && !p->align_left)
{
while (width-- > 0)
putf(putp, ' ');
}
/* print sign */
if (p->sign)
putf(putp, p->sign);
/* Alternate */
if (p->alt && p->base == 16)
{
putf(putp, '0');
putf(putp, (p->uc ? 'X' : 'x'));
}
else if (p->alt && p->base == 8)
{
putf(putp, '0');
}
/* Fill with zeros, after alternate or sign */
while (prec-- > 0)
putf(putp, '0');
if (p->lz)
{
while (width-- > 0)
putf(putp, '0');
}
/* Put actual buffer */
while ((bf_len-- > 0) && (ch = *bf++))
putf(putp, ch);
/* Fill with space to align to the left, after string */
if (!p->lz && p->align_left)
{
while (width-- > 0)
putf(putp, ' ');
}
}
void tfp_format(void *putp, putcf putf, const char *fmt, va_list va)
{
struct param p;
char bf[BF_MAX];
char ch;
while ((ch = *(fmt++)))
{
if (ch != '%')
{
putf(putp, ch);
}
else
{
#if defined(PRINTF_LONG_SUPPORT)
char lng = 0; /* 1 for long, 2 for long long */
#endif
/* Init parameter struct */
p.lz = 0;
p.alt = 0;
p.uc = 0;
p.align_left = 0;
p.width = 0;
p.prec = -1;
p.sign = 0;
p.bf = bf;
p.bf_len = 0;
/* Flags */
while ((ch = *(fmt++)))
{
switch (ch)
{
case '-':
p.align_left = 1;
continue;
case '0':
p.lz = 1;
continue;
case '#':
p.alt = 1;
continue;
default:
break;
}
break;
}
if (p.align_left)
p.lz = 0;
/* Width */
if (ch == '*')
{
ch = *(fmt++);
p.width = va_arg(va, int);
if (p.width < 0)
{
p.align_left = 1;
p.width = -p.width;
}
}
else if (IS_DIGIT(ch))
{
unsigned int width;
ch = a2u(ch, &fmt, 10, &(width));
p.width = width;
}
/* Precision */
if (ch == '.')
{
ch = *(fmt++);
if (ch == '*')
{
int prec;
ch = *(fmt++);
prec = va_arg(va, int);
if (prec < 0)
/* act as if precision was
* omitted */
p.prec = -1;
else
p.prec = prec;
}
else if (IS_DIGIT(ch))
{
unsigned int prec;
ch = a2u(ch, &fmt, 10, &(prec));
p.prec = prec;
}
else
{
p.prec = 0;
}
}
if (p.prec >= 0)
/* precision causes zero pad to be ignored */
p.lz = 0;
#if defined(PRINTF_SIZE_T_SUPPORT)
#if defined(PRINTF_LONG_SUPPORT)
if (ch == 'z')
{
ch = *(fmt++);
if (sizeof(size_t) == sizeof(unsigned long int))
lng = 1;
#if defined(PRINTF_LONG_LONG_SUPPORT)
else if (sizeof(size_t) == sizeof(unsigned long long int))
lng = 2;
#endif
}
else
#endif
#endif
#if defined(PRINTF_LONG_SUPPORT)
if (ch == 'l')
{
ch = *(fmt++);
lng = 1;
#if defined(PRINTF_LONG_LONG_SUPPORT)
if (ch == 'l')
{
ch = *(fmt++);
lng = 2;
}
#endif
}
#endif
switch (ch)
{
case 0:
goto abort;
case 'u':
p.base = 10;
if (p.prec < 0)
p.prec = 1;
#if defined(PRINTF_LONG_SUPPORT)
#if defined(PRINTF_LONG_LONG_SUPPORT)
if (2 == lng)
ulli2a(va_arg(va, unsigned long long int), &p);
else
#endif
if (1 == lng)
uli2a(va_arg(va, unsigned long int), &p);
else
#endif
ui2a(va_arg(va, unsigned int), &p);
putchw(putp, putf, &p);
break;
case 'd': /* No break */
case 'i':
p.base = 10;
if (p.prec < 0)
p.prec = 1;
#if defined(PRINTF_LONG_SUPPORT)
#if defined(PRINTF_LONG_LONG_SUPPORT)
if (2 == lng)
lli2a(va_arg(va, long long int), &p);
else
#endif
if (1 == lng)
li2a(va_arg(va, long int), &p);
else
#endif
i2a(va_arg(va, int), &p);
putchw(putp, putf, &p);
break;
#if defined(SIZEOF_POINTER)
case 'p':
p.alt = 1;
#if defined(SIZEOF_INT) && SIZEOF_POINTER <= SIZEOF_INT
lng = 0;
#elif defined(SIZEOF_LONG) && SIZEOF_POINTER <= SIZEOF_LONG
lng = 1;
#elif defined(SIZEOF_LONG_LONG) && SIZEOF_POINTER <= SIZEOF_LONG_LONG
lng = 2;
#endif
#endif
/* No break */
case 'x': /* No break */
case 'X':
p.base = 16;
p.uc = (ch == 'X') ? 1 : 0;
if (p.prec < 0)
p.prec = 1;
#if defined(PRINTF_LONG_SUPPORT)
#if defined(PRINTF_LONG_LONG_SUPPORT)
if (2 == lng)
ulli2a(va_arg(va, unsigned long long int), &p);
else
#endif
if (1 == lng)
uli2a(va_arg(va, unsigned long int), &p);
else
#endif
ui2a(va_arg(va, unsigned int), &p);
putchw(putp, putf, &p);
break;
case 'o':
p.base = 8;
if (p.prec < 0)
p.prec = 1;
ui2a(va_arg(va, unsigned int), &p);
putchw(putp, putf, &p);
break;
case 'c':
putf(putp, (char)(va_arg(va, int)));
break;
case 's':
{
unsigned int prec = p.prec;
char *b;
p.bf = va_arg(va, char*);
b = p.bf;
while ((prec-- != 0) && *b++)
{
p.bf_len++;
}
p.prec = -1;
putchw(putp, putf, &p);
}
break;
case '%':
putf(putp, ch);
break;
default:
break;
}
}
}
abort:;
}
#if defined(TINYPRINTF_DEFINE_TFP_PRINTF)
static putcf stdout_putf;
static void *stdout_putp;
void init_printf(void *putp, putcf putf)
{
stdout_putf = putf;
stdout_putp = putp;
}
void tfp_printf(char *fmt, ...)
{
va_list va;
va_start(va, fmt);
tfp_format(stdout_putp, stdout_putf, fmt, va);
va_end(va);
}
#endif
#if defined(TINYPRINTF_DEFINE_TFP_SPRINTF)
struct _vsnprintf_putcf_data
{
size_t dest_capacity;
char *dest;
size_t num_chars;
};
static void _vsnprintf_putcf(void *p, char c)
{
struct _vsnprintf_putcf_data *data = (struct _vsnprintf_putcf_data*)p;
if (data->num_chars < data->dest_capacity)
data->dest[data->num_chars] = c;
data->num_chars++;
}
int tfp_vsnprintf(char *str, size_t size, const char *format, va_list ap)
{
struct _vsnprintf_putcf_data data;
if (size < 1)
return 0;
data.dest = str;
data.dest_capacity = size - 1;
data.num_chars = 0;
tfp_format(&data, _vsnprintf_putcf, format, ap);
if (data.num_chars < data.dest_capacity)
data.dest[data.num_chars] = '\0';
else
data.dest[data.dest_capacity] = '\0';
return data.num_chars;
}
int tfp_snprintf(char *str, size_t size, const char *format, ...)
{
va_list ap;
int retval;
va_start(ap, format);
retval = tfp_vsnprintf(str, size, format, ap);
va_end(ap);
return retval;
}
struct _vsprintf_putcf_data
{
char *dest;
size_t num_chars;
};
static void _vsprintf_putcf(void *p, char c)
{
struct _vsprintf_putcf_data *data = (struct _vsprintf_putcf_data*)p;
data->dest[data->num_chars++] = c;
}
int tfp_vsprintf(char *str, const char *format, va_list ap)
{
struct _vsprintf_putcf_data data;
data.dest = str;
data.num_chars = 0;
tfp_format(&data, _vsprintf_putcf, format, ap);
data.dest[data.num_chars] = '\0';
return data.num_chars;
}
int tfp_sprintf(char *str, const char *format, ...)
{
va_list ap;
int retval;
va_start(ap, format);
retval = tfp_vsprintf(str, format, ap);
va_end(ap);
return retval;
}
#endif
static void uart_putf(void *unused, char c)
{
UNUSED(unused);
uart_putchar(c);
}
int printk(const char *format, ...)
{
va_list ap;
va_start(ap, format);
/* Begin protected code */
hartlock_lock(&lock);
tfp_format(stdout_putp, uart_putf, format, ap);
/* End protected code */
hartlock_unlock(&lock);
va_end(ap);
return 0;
}