diff options
author | Mike Frysinger <vapier@gentoo.org> | 2009-02-16 23:21:36 -0500 |
---|---|---|
committer | Wolfgang Denk <wd@denx.de> | 2009-03-20 22:39:09 +0100 |
commit | 6c6166f52983dac775e3852f9d0f49d033f27108 (patch) | |
tree | 38a782d4c9d928c5fef02b40dd0675e215596ea2 /lib_generic/vsprintf.c | |
parent | a43ea5cc6d612471fbc74f0a26b2bea5864aa1d6 (diff) | |
download | u-boot-imx-6c6166f52983dac775e3852f9d0f49d033f27108.zip u-boot-imx-6c6166f52983dac775e3852f9d0f49d033f27108.tar.gz u-boot-imx-6c6166f52983dac775e3852f9d0f49d033f27108.tar.bz2 |
vsprintf: pull updates from Linux kernel
This brings in support for the %p modifier which allows us to easily print
out things like ip addresses, mac addresses, and pointers.
It also converts the rarely used 'q' length modifier to the common 'L'
modifier when dealing with quad types.
While this new code is a bit larger (~1k .text), most of it should be made
up by converting the existing ip/mac address code to use format modifiers.
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Diffstat (limited to 'lib_generic/vsprintf.c')
-rw-r--r-- | lib_generic/vsprintf.c | 479 |
1 files changed, 373 insertions, 106 deletions
diff --git a/lib_generic/vsprintf.c b/lib_generic/vsprintf.c index 767dde1..3ab1f5c 100644 --- a/lib_generic/vsprintf.c +++ b/lib_generic/vsprintf.c @@ -21,6 +21,31 @@ extern int do_reset (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]); #endif +#ifdef CONFIG_SYS_64BIT_VSPRINTF +# define NUM_TYPE long long +#else +# define NUM_TYPE long +#endif +#define noinline __attribute__((noinline)) + +#define do_div(n, base) ({ \ + unsigned int __res; \ + __res = ((unsigned NUM_TYPE) n) % base; \ + n = ((unsigned NUM_TYPE) n) / base; \ + __res; \ +}) + +const char hex_asc[] = "0123456789abcdef"; +#define hex_asc_lo(x) hex_asc[((x) & 0x0f)] +#define hex_asc_hi(x) hex_asc[((x) & 0xf0) >> 4] + +static inline char *pack_hex_byte(char *buf, u8 byte) +{ + *buf++ = hex_asc_hi(byte); + *buf++ = hex_asc_lo(byte); + return buf; +} + unsigned long simple_strtoul(const char *cp,char **endp,unsigned int base) { unsigned long result = 0,value; @@ -120,52 +145,132 @@ static int skip_atoi(const char **s) return i; } +/* Decimal conversion is by far the most typical, and is used + * for /proc and /sys data. This directly impacts e.g. top performance + * with many processes running. We optimize it for speed + * using code from + * http://www.cs.uiowa.edu/~jones/bcd/decimal.html + * (with permission from the author, Douglas W. Jones). */ + +/* Formats correctly any integer in [0,99999]. + * Outputs from one to five digits depending on input. + * On i386 gcc 4.1.2 -O2: ~250 bytes of code. */ +static char* put_dec_trunc(char *buf, unsigned q) +{ + unsigned d3, d2, d1, d0; + d1 = (q>>4) & 0xf; + d2 = (q>>8) & 0xf; + d3 = (q>>12); + + d0 = 6*(d3 + d2 + d1) + (q & 0xf); + q = (d0 * 0xcd) >> 11; + d0 = d0 - 10*q; + *buf++ = d0 + '0'; /* least significant digit */ + d1 = q + 9*d3 + 5*d2 + d1; + if (d1 != 0) { + q = (d1 * 0xcd) >> 11; + d1 = d1 - 10*q; + *buf++ = d1 + '0'; /* next digit */ + + d2 = q + 2*d2; + if ((d2 != 0) || (d3 != 0)) { + q = (d2 * 0xd) >> 7; + d2 = d2 - 10*q; + *buf++ = d2 + '0'; /* next digit */ + + d3 = q + 4*d3; + if (d3 != 0) { + q = (d3 * 0xcd) >> 11; + d3 = d3 - 10*q; + *buf++ = d3 + '0'; /* next digit */ + if (q != 0) + *buf++ = q + '0'; /* most sign. digit */ + } + } + } + return buf; +} +/* Same with if's removed. Always emits five digits */ +static char* put_dec_full(char *buf, unsigned q) +{ + /* BTW, if q is in [0,9999], 8-bit ints will be enough, */ + /* but anyway, gcc produces better code with full-sized ints */ + unsigned d3, d2, d1, d0; + d1 = (q>>4) & 0xf; + d2 = (q>>8) & 0xf; + d3 = (q>>12); + + /* Possible ways to approx. divide by 10 */ + /* gcc -O2 replaces multiply with shifts and adds */ + // (x * 0xcd) >> 11: 11001101 - shorter code than * 0x67 (on i386) + // (x * 0x67) >> 10: 1100111 + // (x * 0x34) >> 9: 110100 - same + // (x * 0x1a) >> 8: 11010 - same + // (x * 0x0d) >> 7: 1101 - same, shortest code (on i386) + + d0 = 6*(d3 + d2 + d1) + (q & 0xf); + q = (d0 * 0xcd) >> 11; + d0 = d0 - 10*q; + *buf++ = d0 + '0'; + d1 = q + 9*d3 + 5*d2 + d1; + q = (d1 * 0xcd) >> 11; + d1 = d1 - 10*q; + *buf++ = d1 + '0'; + + d2 = q + 2*d2; + q = (d2 * 0xd) >> 7; + d2 = d2 - 10*q; + *buf++ = d2 + '0'; + + d3 = q + 4*d3; + q = (d3 * 0xcd) >> 11; /* - shorter code */ + /* q = (d3 * 0x67) >> 10; - would also work */ + d3 = d3 - 10*q; + *buf++ = d3 + '0'; + *buf++ = q + '0'; + return buf; +} +/* No inlining helps gcc to use registers better */ +static noinline char* put_dec(char *buf, unsigned NUM_TYPE num) +{ + while (1) { + unsigned rem; + if (num < 100000) + return put_dec_trunc(buf, num); + rem = do_div(num, 100000); + buf = put_dec_full(buf, rem); + } +} + #define ZEROPAD 1 /* pad with zero */ #define SIGN 2 /* unsigned/signed long */ #define PLUS 4 /* show plus */ #define SPACE 8 /* space if plus */ #define LEFT 16 /* left justified */ -#define SPECIAL 32 /* 0x */ -#define LARGE 64 /* use 'ABCDEF' instead of 'abcdef' */ +#define SMALL 32 /* Must be 32 == 0x20 */ +#define SPECIAL 64 /* 0x */ -#ifdef CONFIG_SYS_64BIT_VSPRINTF -#define do_div(n,base) ({ \ - unsigned int __res; \ - __res = ((unsigned long long) n) % base; \ - n = ((unsigned long long) n) / base; \ - __res; \ -}) -#else -#define do_div(n,base) ({ \ - int __res; \ - __res = ((unsigned long) n) % base; \ - n = ((unsigned long) n) / base; \ - __res; \ -}) -#endif - -#ifdef CONFIG_SYS_64BIT_VSPRINTF -static char * number(char * str, long long num, unsigned int base, int size, int precision ,int type) -#else -static char * number(char * str, long num, unsigned int base, int size, int precision ,int type) -#endif +static char *number(char *buf, unsigned NUM_TYPE num, int base, int size, int precision, int type) { - char c,sign,tmp[66]; - const char *digits="0123456789abcdefghijklmnopqrstuvwxyz"; + /* we are called with base 8, 10 or 16, only, thus don't need "G..." */ + static const char digits[16] = "0123456789ABCDEF"; /* "GHIJKLMNOPQRSTUVWXYZ"; */ + + char tmp[66]; + char sign; + char locase; + int need_pfx = ((type & SPECIAL) && base != 10); int i; - if (type & LARGE) - digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + /* locase = 0 or 0x20. ORing digits or letters with 'locase' + * produces same digits or (maybe lowercased) letters */ + locase = (type & SMALL); if (type & LEFT) type &= ~ZEROPAD; - if (base < 2 || base > 36) - return 0; - c = (type & ZEROPAD) ? '0' : ' '; sign = 0; if (type & SIGN) { - if (num < 0) { + if ((signed NUM_TYPE) num < 0) { sign = '-'; - num = -num; + num = - (signed NUM_TYPE) num; size--; } else if (type & PLUS) { sign = '+'; @@ -175,68 +280,231 @@ static char * number(char * str, long num, unsigned int base, int size, int prec size--; } } - if (type & SPECIAL) { + if (need_pfx) { + size--; if (base == 16) - size -= 2; - else if (base == 8) size--; } + + /* generate full string in tmp[], in reverse order */ i = 0; if (num == 0) - tmp[i++]='0'; - else while (num != 0) - tmp[i++] = digits[do_div(num,base)]; + tmp[i++] = '0'; + /* Generic code, for any base: + else do { + tmp[i++] = (digits[do_div(num,base)] | locase); + } while (num != 0); + */ + else if (base != 10) { /* 8 or 16 */ + int mask = base - 1; + int shift = 3; + if (base == 16) shift = 4; + do { + tmp[i++] = (digits[((unsigned char)num) & mask] | locase); + num >>= shift; + } while (num); + } else { /* base 10 */ + i = put_dec(tmp, num) - tmp; + } + + /* printing 100 using %2d gives "100", not "00" */ if (i > precision) precision = i; + /* leading space padding */ size -= precision; - if (!(type&(ZEROPAD+LEFT))) - while(size-->0) - *str++ = ' '; + if (!(type & (ZEROPAD+LEFT))) + while(--size >= 0) + *buf++ = ' '; + /* sign */ if (sign) - *str++ = sign; - if (type & SPECIAL) { - if (base==8) - *str++ = '0'; - else if (base==16) { - *str++ = '0'; - *str++ = digits[33]; - } + *buf++ = sign; + /* "0x" / "0" prefix */ + if (need_pfx) { + *buf++ = '0'; + if (base == 16) + *buf++ = ('X' | locase); + } + /* zero or space padding */ + if (!(type & LEFT)) { + char c = (type & ZEROPAD) ? '0' : ' '; + while (--size >= 0) + *buf++ = c; } - if (!(type & LEFT)) - while (size-- > 0) - *str++ = c; - while (i < precision--) - *str++ = '0'; - while (i-- > 0) - *str++ = tmp[i]; - while (size-- > 0) - *str++ = ' '; - return str; + /* hmm even more zero padding? */ + while (i <= --precision) + *buf++ = '0'; + /* actual digits of result */ + while (--i >= 0) + *buf++ = tmp[i]; + /* trailing space padding */ + while (--size >= 0) + *buf++ = ' '; + return buf; } -/* Forward decl. needed for IP address printing stuff... */ -int sprintf(char * buf, const char *fmt, ...); +static char *string(char *buf, char *s, int field_width, int precision, int flags) +{ + int len, i; -int vsprintf(char *buf, const char *fmt, va_list args) + if (s == 0) + s = "<NULL>"; + + len = strnlen(s, precision); + + if (!(flags & LEFT)) + while (len < field_width--) + *buf++ = ' '; + for (i = 0; i < len; ++i) + *buf++ = *s++; + while (len < field_width--) + *buf++ = ' '; + return buf; +} + +#ifdef CONFIG_CMD_NET +static char *mac_address_string(char *buf, u8 *addr, int field_width, + int precision, int flags) { - int len; -#ifdef CONFIG_SYS_64BIT_VSPRINTF - unsigned long long num; -#else - unsigned long num; + char mac_addr[6 * 3]; /* (6 * 2 hex digits), 5 colons and trailing zero */ + char *p = mac_addr; + int i; + + for (i = 0; i < 6; i++) { + p = pack_hex_byte(p, addr[i]); + if (!(flags & SPECIAL) && i != 5) + *p++ = ':'; + } + *p = '\0'; + + return string(buf, mac_addr, field_width, precision, flags & ~SPECIAL); +} + +static char *ip6_addr_string(char *buf, u8 *addr, int field_width, + int precision, int flags) +{ + char ip6_addr[8 * 5]; /* (8 * 4 hex digits), 7 colons and trailing zero */ + char *p = ip6_addr; + int i; + + for (i = 0; i < 8; i++) { + p = pack_hex_byte(p, addr[2 * i]); + p = pack_hex_byte(p, addr[2 * i + 1]); + if (!(flags & SPECIAL) && i != 7) + *p++ = ':'; + } + *p = '\0'; + + return string(buf, ip6_addr, field_width, precision, flags & ~SPECIAL); +} + +static char *ip4_addr_string(char *buf, u8 *addr, int field_width, + int precision, int flags) +{ + char ip4_addr[4 * 4]; /* (4 * 3 decimal digits), 3 dots and trailing zero */ + char temp[3]; /* hold each IP quad in reverse order */ + char *p = ip4_addr; + int i, digits; + + for (i = 0; i < 4; i++) { + digits = put_dec_trunc(temp, addr[i]) - temp; + /* reverse the digits in the quad */ + while (digits--) + *p++ = temp[digits]; + if (i != 3) + *p++ = '.'; + } + *p = '\0'; + + return string(buf, ip4_addr, field_width, precision, flags & ~SPECIAL); +} #endif - int i, base; - char * str; - const char *s; + +/* + * Show a '%p' thing. A kernel extension is that the '%p' is followed + * by an extra set of alphanumeric characters that are extended format + * specifiers. + * + * Right now we handle: + * + * - 'M' For a 6-byte MAC address, it prints the address in the + * usual colon-separated hex notation + * - 'I' [46] for IPv4/IPv6 addresses printed in the usual way (dot-separated + * decimal for v4 and colon separated network-order 16 bit hex for v6) + * - 'i' [46] for 'raw' IPv4/IPv6 addresses, IPv6 omits the colons, IPv4 is + * currently the same + * + * Note: The difference between 'S' and 'F' is that on ia64 and ppc64 + * function pointers are really function descriptors, which contain a + * pointer to the real address. + */ +static char *pointer(const char *fmt, char *buf, void *ptr, int field_width, int precision, int flags) +{ + if (!ptr) + return string(buf, "(null)", field_width, precision, flags); + +#ifdef CONFIG_CMD_NET + switch (*fmt) { + case 'm': + flags |= SPECIAL; + /* Fallthrough */ + case 'M': + return mac_address_string(buf, ptr, field_width, precision, flags); + case 'i': + flags |= SPECIAL; + /* Fallthrough */ + case 'I': + if (fmt[1] == '6') + return ip6_addr_string(buf, ptr, field_width, precision, flags); + if (fmt[1] == '4') + return ip4_addr_string(buf, ptr, field_width, precision, flags); + flags &= ~SPECIAL; + break; + } +#endif + flags |= SMALL; + if (field_width == -1) { + field_width = 2*sizeof(void *); + flags |= ZEROPAD; + } + return number(buf, (unsigned long) ptr, 16, field_width, precision, flags); +} + +/** + * vsprintf - Format a string and place it in a buffer + * @buf: The buffer to place the result into + * @fmt: The format string to use + * @args: Arguments for the format string + * + * This function follows C99 vsprintf, but has some extensions: + * %pS output the name of a text symbol + * %pF output the name of a function pointer + * %pR output the address range in a struct resource + * + * The function returns the number of characters written + * into @buf. + * + * Call this function if you are already dealing with a va_list. + * You probably want sprintf() instead. + */ +int vsprintf(char *buf, const char *fmt, va_list args) +{ + unsigned NUM_TYPE num; + int base; + char *str; int flags; /* flags to number() */ int field_width; /* width of output field */ int precision; /* min. # of digits for integers; max number of chars for from string */ - int qualifier; /* 'h', 'l', or 'q' for integer fields */ + int qualifier; /* 'h', 'l', or 'L' for integer fields */ + /* 'z' support added 23/7/1999 S.H. */ + /* 'z' changed to 'Z' --davidm 1/25/99 */ + /* 't' added for ptrdiff_t */ + + str = buf; - for (str=buf ; *fmt ; ++fmt) { + for (; *fmt ; ++fmt) { if (*fmt != '%') { *str++ = *fmt; continue; @@ -252,7 +520,7 @@ int vsprintf(char *buf, const char *fmt, va_list args) case ' ': flags |= SPACE; goto repeat; case '#': flags |= SPECIAL; goto repeat; case '0': flags |= ZEROPAD; goto repeat; - } + } /* get field width */ field_width = -1; @@ -286,14 +554,13 @@ int vsprintf(char *buf, const char *fmt, va_list args) /* get the conversion qualifier */ qualifier = -1; if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L' || - *fmt == 'Z' || *fmt == 'z' || *fmt == 't' || - *fmt == 'q' ) { + *fmt == 'Z' || *fmt == 'z' || *fmt == 't') { qualifier = *fmt; - if (qualifier == 'l' && *(fmt+1) == 'l') { - qualifier = 'q'; + ++fmt; + if (qualifier == 'l' && *fmt == 'l') { + qualifier = 'L'; ++fmt; } - ++fmt; } /* default base */ @@ -310,32 +577,18 @@ int vsprintf(char *buf, const char *fmt, va_list args) continue; case 's': - s = va_arg(args, char *); - if (!s) - s = "<NULL>"; - - len = strnlen(s, precision); - - if (!(flags & LEFT)) - while (len < field_width--) - *str++ = ' '; - for (i = 0; i < len; ++i) - *str++ = *s++; - while (len < field_width--) - *str++ = ' '; + str = string(str, va_arg(args, char *), field_width, precision, flags); continue; case 'p': - if (field_width == -1) { - field_width = 2*sizeof(void *); - flags |= ZEROPAD; - } - str = number(str, - (unsigned long) va_arg(args, void *), 16, - field_width, precision, flags); + str = pointer(fmt+1, str, + va_arg(args, void *), + field_width, precision, flags); + /* Skip all alphanumeric pointer suffixes */ + while (isalnum(fmt[1])) + fmt++; continue; - case 'n': if (qualifier == 'l') { long * ip = va_arg(args, long *); @@ -355,9 +608,9 @@ int vsprintf(char *buf, const char *fmt, va_list args) base = 8; break; - case 'X': - flags |= LARGE; case 'x': + flags |= SMALL; + case 'X': base = 16; break; @@ -376,12 +629,14 @@ int vsprintf(char *buf, const char *fmt, va_list args) continue; } #ifdef CONFIG_SYS_64BIT_VSPRINTF - if (qualifier == 'q') /* "quad" for 64 bit variables */ + if (qualifier == 'L') /* "quad" for 64 bit variables */ num = va_arg(args, unsigned long long); else #endif if (qualifier == 'l') { num = va_arg(args, unsigned long); + if (flags & SIGN) + num = (signed long) num; } else if (qualifier == 'Z' || qualifier == 'z') { num = va_arg(args, size_t); } else if (qualifier == 't') { @@ -389,17 +644,29 @@ int vsprintf(char *buf, const char *fmt, va_list args) } else if (qualifier == 'h') { num = (unsigned short) va_arg(args, int); if (flags & SIGN) - num = (short) num; - } else if (flags & SIGN) - num = va_arg(args, int); - else + num = (signed short) num; + } else { num = va_arg(args, unsigned int); + if (flags & SIGN) + num = (signed int) num; + } str = number(str, num, base, field_width, precision, flags); } *str = '\0'; return str-buf; } +/** + * sprintf - Format a string and place it in a buffer + * @buf: The buffer to place the result into + * @fmt: The format string to use + * @...: Arguments for the format string + * + * The function returns the number of characters written + * into @buf. + * + * See the vsprintf() documentation for format string extensions over C99. + */ int sprintf(char * buf, const char *fmt, ...) { va_list args; |