/** * 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 #include "atomic.h" #include "printf.h" #include "syscalls.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___ 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 corelock_t lock = CORELOCK_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); if(sys_putchar) sys_putchar(c); } int printk(const char *format, ...) { va_list ap; va_start(ap, format); /* Begin protected code */ corelock_lock(&lock); tfp_format(stdout_putp, uart_putf, format, ap); /* End protected code */ corelock_unlock(&lock); va_end(ap); return 0; }