diff options
Diffstat (limited to 'src/lib/ppc405lib/printf.c')
-rw-r--r-- | src/lib/ppc405lib/printf.c | 703 |
1 files changed, 703 insertions, 0 deletions
diff --git a/src/lib/ppc405lib/printf.c b/src/lib/ppc405lib/printf.c new file mode 100644 index 0000000..d75cdf3 --- /dev/null +++ b/src/lib/ppc405lib/printf.c @@ -0,0 +1,703 @@ +/* IBM_PROLOG_BEGIN_TAG */ +/* This is an automatically generated prolog. */ +/* */ +/* $Source: src/lib/ppc405lib/printf.c $ */ +/* */ +/* OpenPOWER OnChipController Project */ +/* */ +/* Contributors Listed Below - COPYRIGHT 2015 */ +/* [+] International Business Machines Corp. */ +/* */ +/* */ +/* 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. */ +/* */ +/* IBM_PROLOG_END_TAG */ +// $Id: printf.c,v 1.1.1.1 2013/12/11 20:49:20 bcbrock Exp $ +// $Source: /afs/awd/projects/eclipz/KnowledgeBase/.cvsroot/eclipz/chips/p8/working/procedures/lib/printf.c,v $ +//----------------------------------------------------------------------------- +// *! (C) Copyright International Business Machines Corp. 2013 +// *! All Rights Reserved -- Property of IBM +// *! *** IBM Confidential *** +//----------------------------------------------------------------------------- + +/// \file printf.c +/// \brief Clean-room implementation of printf() functions for SSX I/O +/// +/// For licensing reasons we are required to create our own version of the +/// printf() family of functions. This implementation was created without +/// reference to or inclusion of any licensed or copyrighted code. +/// +/// The functions defined in this file have prototypes, behavior and return +/// values as defined by C language standards. In the event of an error a +/// negative value is returned, generally corresponding to a standard Unix +/// 'errno' code. Note that SSX does not support either an application- or +/// per-thread 'errno', so the only record of any error is the \a error field +/// of the stream. Also note that SSX may be configured to cause a panic if an +/// error is detected rather than returning an error code. +/// +/// This implementation defines a limited but useful subset of the C standard +/// for format control. This implementation includes the following: +/// +/// - \b c, \b d, \b i, \b n, \b p, \b s, \b u, \b x, and \b X conversion +/// specifiers, as well as '%%' to output a single '%' +/// +/// - \b #, \b 0, \b ' ' and \b + flag characters +/// +/// - Decimal field width specifiers including * (but indirect field widths +/// must be positive as left-justification is not supported) +/// +/// - Decimal precision specifiers (currently only apply to %s formats, may be +/// indirect using *) +/// +/// - \b l, \b ll and \b z length modifiers +/// +/// \b Notes: +/// +/// \a If a \c p conversion specifier is used without any flags (\c '%p'), the +/// \c p conversion is interptered as if it were \c 0x%08lx for 32-bit address +/// machines and \c 0x%016llx for 64-bit address machines. The GCC builtin +/// format checker gives warnings about '0' flag characters for \c p +/// conversion specifiers, so there is otherwise no 'un-warned' way to get +/// this preferred (by some) format of pointer values. If you do include +/// explicit flags (e.g., \c %30p) they will be processed as expected. +/// +/// Similar to how printf() behaves on an X86-Linux machine, a null pointer +/// will print as "(null)" with the %s format (unless the precision specifier +/// precludes it) and "(nil)" with the %p format. +/// +/// Note that calling formatted I/O functions on non-blocking streams may fail +/// with the -EAGAIN error, and there is no clean way to restart these +/// calls. Calling formatted (or any) I/O functions on blocking streams from +/// interrupt contexts in SSX is also likely to fail intermittently since +/// interrupt contexts can not block in SSX. +/// +/// \todo I'd really like to implement the '-' flag for +/// left-justification. Implementing the precision specifer for integers +/// should be done for completeness. + +#include "ssx.h" +#include "ssx_io.h" + +// Formatting options + +#define OPTION_ALTERNATE 0x0001 +#define OPTION_PAD_ZERO 0x0002 +#define OPTION_PLUS_SIGN 0x0004 +#define OPTION_FIELD_WIDTH 0x0008 +#define OPTION_PRECISION 0x0010 +#define OPTION_LONG 0x0020 +#define OPTION_LONG_LONG 0x0040 +#define OPTION_SIZE_T 0x0080 +#define OPTION_UPPERCASE 0x0100 +#define OPTION_HEX 0x0200 +#define OPTION_SPACE 0x0400 + + +// Generate padding if required, returning the total number of pad characters +// output or a negative error code. The 'nchars' argument is the number of +// non-pad characters to be output by the caller. + +#define PAD_SIZE 8 +static const char zeros[PAD_SIZE] = {'0', '0', '0', '0', '0', '0', '0', '0'}; +static const char blanks[PAD_SIZE] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}; + +static ssize_t +pad(FILE *stream, size_t nchars, int options, size_t width) +{ + const char *padchars; + size_t chars, written; + int rc; + + if (!(options & OPTION_FIELD_WIDTH) || (nchars >= width)) { + return 0; + } + chars = width - nchars; + if (options & OPTION_PAD_ZERO) { + padchars = zeros; + } else { + padchars = blanks; + } + while (chars) { + rc = swrite(stream, (void *)padchars, MIN(chars, PAD_SIZE), &written); + if (rc < 0) return rc; + chars -= written; + } + return width - nchars; +} + + +// Format a character + +static ssize_t +format_char(FILE *stream, unsigned char c, int options, size_t width) +{ + ssize_t padchars, nchars; + int rc; + + padchars = pad(stream, 1, options, width); + if (padchars < 0) return padchars; + nchars = padchars + 1; + rc = swrite(stream, (void *)(&c), 1, 0); + if (rc < 0) return rc; + return nchars; +} + + +// Format a string +// +// If the string is the NULL pointer then normally "(null)" is printed +// unless the precision is < 6, in which case the empty string is printed. +// The specification leaves it as undefined what happens if a string requests +// 0 padding; Here we always pad with blanks (although GCC/PowerPC catches +// this as an error). + +static ssize_t +format_string(FILE *stream, const char *s, int options, + size_t width, size_t precision) +{ + size_t len; + ssize_t padchars, nchars; + int rc; + + if (s == 0) { + if ((options & OPTION_PRECISION) && (precision < 6)) { + s = ""; + } else { + s = "(null)"; + } + } + + len = strlen(s); + if (options & OPTION_PRECISION) { + len = MIN(len, precision); + } + + options &= ~OPTION_PAD_ZERO; + padchars = pad(stream, len, options, width); + if (padchars < 0) return padchars; + nchars = padchars + len; + rc = swrite(stream, (void *)s, len, 0); + if (rc < 0) return rc; + return nchars; +} + + +// Format an integer - signed and unsigned. A 64-bit integer (assumed to be +// the longest we'll see) has 20 decimal digits. An extra space is reserved +// for the sign. If zero-padding is specified, the sign will be output +// separately. + +static const char lower[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + +static const char upper[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + +static ssize_t +format_int(FILE *stream, long long int lli, int options, size_t width) +{ + char digits[21]; + int rc, k, ndigits, negative, positive, i; + ssize_t output; + + negative = (lli < 0); + positive = (lli > 0); + + // Unpack the integer to characters. The code is optimized for 32-bit + // machines where 64-bit division is not built in. The first part of the + // loop handles integers requiring a 64-bit divide, the second loop + // handles 32-bit integers. + + if (lli == 0) { + digits[20] = '0'; + k = 20; + } else if (negative) { + for (k = 21; + lli != (int)lli; + digits[--k] = lower[-(lli % 10)], lli = lli / 10); + for (i = (int)lli; + i != 0; + digits[--k] = lower[-(i % 10)], i = i / 10); + } else { + for (k = 21; + lli != (int)lli; + digits[--k] = lower[lli % 10], lli = lli / 10); + for (i = (int)lli; + i != 0; + digits[--k] = lower[i % 10], i = i / 10); + } + + ndigits = 21 - k; + + // Handle other options and output + + output = 0; + if (options & OPTION_PAD_ZERO) { + + if (negative) { + rc = swrite(stream, "-", 1, 0); + if (rc < 0) return rc; + output++; + } else if (positive) { + if (options & OPTION_PLUS_SIGN) { + rc = swrite(stream, "+", 1, 0); + if (rc < 0) return rc; + output++; + } else if (options & OPTION_SPACE) { + rc = swrite(stream, " ", 1, 0); + if (rc < 0) return rc; + output++; + } + } + rc = pad(stream, ndigits + output, options, width); + if (rc < 0) return rc; + output += rc; + rc = swrite(stream, &(digits[k]), ndigits, 0); + if (rc < 0) return rc; + output += ndigits; + + } else { + + if (negative) { + digits[--k] = '-'; + ndigits++; + } else if (positive) { + if (options & OPTION_PLUS_SIGN) { + digits[--k] = '+'; + ndigits++; + } else if (options & OPTION_SPACE) { + digits[--k] = ' '; + ndigits++; + } + } + rc = pad(stream, ndigits, options, width); + if (rc < 0) return rc; + output += rc; + rc = swrite(stream, &(digits[k]), ndigits, 0); + if (rc < 0) return rc; + output += ndigits; + } + + return output; +} + + +static ssize_t +format_unsigned(FILE *stream, unsigned long long ull, int options, size_t width) +{ + char digits[21], *alternate; + const char *xchars; + int rc, k, ndigits, zero; + unsigned u; + ssize_t output; + + zero = (ull == 0); + + // Determine hex case and alternate string + + alternate = 0; + if (options & OPTION_HEX) { + if (options & OPTION_UPPERCASE) { + xchars = upper; + if (options & OPTION_ALTERNATE) { + alternate = "0X"; + } + } else { + xchars = lower; + if (options & OPTION_ALTERNATE) { + alternate = "0x"; + } + } + } else { + xchars = lower; + } + + // Unpack the unsigned integer to characters. The Hex conversions are + // easier since they can be done with shift and mask rather than + // divison. The code is optimized for a 32-bit machine where 64-bit + // division is not built-in. + + if (zero) { + digits[20] = '0'; + k = 20; + } else if (options & OPTION_HEX) { + for (k = 21; + ull != (unsigned)ull; + digits[--k] = xchars[ull & 0xf], ull = ull >> 4); + for (u = (unsigned)ull; + u != 0; + digits[--k] = xchars[u & 0xf], u = u >> 4); + } else { + for (k = 21; + ull != (unsigned)ull; + digits[--k] = xchars[ull % 10], ull = ull / 10); + for (u = (unsigned)ull; + u != 0; + digits[--k] = xchars[u % 10], u = u / 10); + } + + ndigits = 21 - k; + + // Handle other options and output + + output = 0; + if (options & OPTION_PAD_ZERO) { + + if (!zero && alternate) { + rc = swrite(stream, (void *)alternate, 2, 0); + if (rc < 0) return rc; + output += 2; + } + rc = pad(stream, ndigits + output, options, width); + if (rc < 0) return rc; + output += rc; + rc = swrite(stream, &(digits[k]), ndigits, 0); + if (rc < 0) return rc; + output += ndigits; + + } else { + + if (!zero && alternate) { + output += 2; + } + rc = pad(stream, ndigits + output, options, width); + if (rc < 0) return rc; + output += rc; + if (!zero && alternate) { + rc = swrite(stream, alternate, 2, 0); + if (rc < 0) return rc; + output += 2; + } + rc = swrite(stream, &(digits[k]), ndigits, 0); + if (rc < 0) return rc; + output += ndigits; + } + + return output; +} + + +int +vfprintf(FILE *stream, const char *format, va_list argp) +{ + const char *fmt, *scan; + int rc, total_chars, options, done; + size_t width, precision; + + int arg_i, *arg_pi; + long int arg_li; + long long int arg_lli; + ssize_t arg_zi; + unsigned arg_u; + unsigned long arg_lu; + unsigned long long arg_llu; + size_t arg_zu; + char *arg_s; + + total_chars = 0; + + fmt = format; + while (*fmt) { + + // Scan until '%' or the end of the format, then output the text. + + scan = fmt; + while (*scan && (*scan != '%')) { + scan++; + } + if (scan != fmt) { + rc = swrite(stream, fmt, scan - fmt, 0); + if (rc < 0) return rc; + total_chars += scan - fmt; + } + fmt = scan; + if (!*fmt) { + return total_chars; + } + fmt++; + + // We got a '%'. Check for %% and %n. + + switch (*fmt) { + case '\0': + SSX_IO_ERROR(stream, EINVAL); + break; + case '%': + rc = swrite(stream, "%", 1, 0); + if (rc < 0) return rc; + total_chars++; + fmt++; + continue; + case 'n': + arg_pi = va_arg(argp, int *); + *arg_pi = total_chars; + fmt++; + continue; + } + + // Collect padding options, if any. Left justification is not + // implemeted. + + options = 0; + done = 0; + do { + switch (*fmt) { + case '\0': + SSX_IO_ERROR(stream, EINVAL); + break; + case '#': + options |= OPTION_ALTERNATE; + break; + case '0': + options |= OPTION_PAD_ZERO; + break; + case '+': + options |= OPTION_PLUS_SIGN; + break; + case ' ': + options |= OPTION_SPACE; + break; + case '-': + SSX_IO_ERROR(stream, EINVAL); // Left just. not impl. + break; + default: + done = 1; + break; + } + if (!done) { + fmt++; + } + } while (!done); + + // Collect the field width, if specified. A negative precision + // specified as an argument indicates left justification (not + // implemented). + + width = 0; + if (isdigit(*fmt)) { + options |= OPTION_FIELD_WIDTH; + for (; isdigit(*fmt); fmt++) { + width = (width * 10) + (*fmt - '0'); + } + } else if (*fmt == '*') { + fmt++; + options |= OPTION_FIELD_WIDTH; + arg_i = va_arg(argp, int); + if (arg_i < 0) { + SSX_IO_ERROR(stream, EINVAL); // Left just. not impl. + } + width = arg_i; + } + + // Collect the precision, if specified. By standard specification an + // empty or negative precision is interpreted as 0. + + precision = 0; + if (*fmt == '.') { + fmt++; + options |= OPTION_PRECISION; + if (isdigit(*fmt)) { + for(; isdigit(*fmt); fmt++) { + precision = (precision * 10) + (*fmt - '0'); + } + } else if (*fmt == '*') { + fmt++; + arg_i = va_arg(argp, int); + if (arg_i < 0) { + arg_i = 0; + } + precision = arg_i; + } + } + + // Collect length modifiers. + + done = 0; + do { + switch (*fmt) { + case '\0': + SSX_IO_ERROR(stream, EINVAL); + break; + case 'l': + if (options & OPTION_LONG) { + options &= ~OPTION_LONG; + options |= OPTION_LONG_LONG; + } else if (options & OPTION_LONG_LONG) { + SSX_IO_ERROR(stream, EINVAL); + } else { + options |= OPTION_LONG; + } + if (options & OPTION_SIZE_T) { + SSX_IO_ERROR(stream, EINVAL); + } + break; + case 'z': + if ((options & OPTION_LONG) || (options & OPTION_LONG_LONG)) { + SSX_IO_ERROR(stream, EINVAL); + } + options |= OPTION_SIZE_T; + break; + default: + done = 1; + break; + } + if (!done) { + fmt++; + } + } while (!done); + + // Use the conversion specifier to format the next argument + + switch (*fmt) { + + case 'c': + arg_i = va_arg(argp, int); + rc = format_char(stream, (unsigned char)arg_i, options, width); + if (rc < 0) return rc; + total_chars++; + break; + + case 'd': + case 'i': + if (options & OPTION_LONG) { + arg_li = va_arg(argp, long int); + rc = format_int(stream, (long long int)arg_li, options, + width); + } else if (options & OPTION_LONG_LONG) { + arg_lli = va_arg(argp, long long int); + rc = format_int(stream, (long long int)arg_lli, options, + width); + } else if (options & OPTION_SIZE_T) { + arg_zi = va_arg(argp, ssize_t); + rc = format_int(stream, (long long int)arg_zi, options, + width); + } else { + arg_i = va_arg(argp, int); + rc = format_int(stream, (long long int)arg_i, options, + width); + } + if (rc < 0) return rc; + total_chars += rc; + break; + + case 'p': + arg_lu = va_arg(argp, unsigned long); + options |= (OPTION_ALTERNATE | OPTION_HEX); + if (!(options & OPTION_PAD_ZERO) && + !(options & OPTION_FIELD_WIDTH)) { + options |= (OPTION_PAD_ZERO | OPTION_FIELD_WIDTH); + width = (2 * sizeof(unsigned long)) + 2; /* 0x........ */ + } + if (arg_lu == 0) { + options &= ~OPTION_PRECISION; + rc = format_string(stream, "(nil)", options, width, precision); + } else { + rc = format_unsigned(stream, (unsigned long long)arg_lu, + options, width); + } + if (rc < 0) return rc; + total_chars += rc; + break; + + case 's': + arg_s = va_arg(argp, char *); + rc = format_string(stream, arg_s, options, width, precision); + if (rc < 0) return rc; + total_chars += rc; + break; + + case 'X': + options |= OPTION_UPPERCASE; + case 'x': + options |= OPTION_HEX; + case 'u': + if (options & OPTION_LONG) { + arg_lu = va_arg(argp, unsigned long); + rc = format_unsigned(stream, (unsigned long long)arg_lu, + options, width); + } else if (options & OPTION_LONG_LONG) { + arg_llu = va_arg(argp, unsigned long long); + rc = format_unsigned(stream, (unsigned long long)arg_llu, + options, width); + } else if (options & OPTION_SIZE_T) { + arg_zu = va_arg(argp, size_t); + rc = format_unsigned(stream, (unsigned long long)arg_zu, + options, width); + } else { + arg_u = va_arg(argp, unsigned); + rc = format_unsigned(stream, (unsigned long long )arg_u, + options, width); + } + if (rc < 0) return rc; + total_chars += rc; + break; + + default: + SSX_IO_ERROR(stream, EINVAL); + break; + } + + fmt++; + } + + return total_chars; +} + + +int +vprintf(const char *format, va_list argp) +{ + return vfprintf(stdout, format, argp); +} + + +int +fprintf(FILE *stream, const char *format, ...) +{ + va_list argp; + int rc; + + va_start(argp, format); + rc = vfprintf(stream, format, argp); + va_end(argp); + return rc; +} + + +int +printf(const char *format, ...) +{ + va_list argp; + int rc; + + va_start(argp, format); + rc = vfprintf(stdout, format, argp); + va_end(argp); + return rc; +} + + +int +printk(const char *format, ...) +{ + va_list argp; + int rc; + + va_start(argp, format); + rc = vfprintf(ssxout, format, argp); + va_end(argp); + return rc; +} + |