+static char *symbol_string(char *buf, char *end, void *ptr,
+ struct printf_spec spec, char ext)
+{
+ unsigned long value = (unsigned long) ptr;
+#ifdef CONFIG_KALLSYMS
+ char sym[KSYM_SYMBOL_LEN];
+ if (ext != 'f' && ext != 's')
+ sprint_symbol(sym, value);
+ else
+ kallsyms_lookup(value, NULL, NULL, NULL, sym);
+ return string(buf, end, sym, spec);
+#else
+ spec.field_width = 2*sizeof(void *);
+ spec.flags |= SPECIAL | SMALL | ZEROPAD;
+ spec.base = 16;
+ return number(buf, end, value, spec);
+#endif
+}
+
+static char *resource_string(char *buf, char *end, struct resource *res,
+ struct printf_spec spec)
+{
+#ifndef IO_RSRC_PRINTK_SIZE
+#define IO_RSRC_PRINTK_SIZE 4
+#endif
+
+#ifndef MEM_RSRC_PRINTK_SIZE
+#define MEM_RSRC_PRINTK_SIZE 8
+#endif
+ struct printf_spec num_spec = {
+ .base = 16,
+ .precision = -1,
+ .flags = SPECIAL | SMALL | ZEROPAD,
+ };
+ /* room for the actual numbers, the two "0x", -, [, ] and the final zero */
+ char sym[4*sizeof(resource_size_t) + 8];
+ char *p = sym, *pend = sym + sizeof(sym);
+ int size = -1;
+
+ if (res->flags & IORESOURCE_IO)
+ size = IO_RSRC_PRINTK_SIZE;
+ else if (res->flags & IORESOURCE_MEM)
+ size = MEM_RSRC_PRINTK_SIZE;
+
+ *p++ = '[';
+ num_spec.field_width = size;
+ p = number(p, pend, res->start, num_spec);
+ *p++ = '-';
+ p = number(p, pend, res->end, num_spec);
+ *p++ = ']';
+ *p = 0;
+
+ return string(buf, end, sym, spec);
+}
+
+static char *mac_address_string(char *buf, char *end, u8 *addr,
+ struct printf_spec spec, const char *fmt)
+{
+ char mac_addr[sizeof("xx:xx:xx:xx:xx:xx")];
+ char *p = mac_addr;
+ int i;
+
+ for (i = 0; i < 6; i++) {
+ p = pack_hex_byte(p, addr[i]);
+ if (fmt[0] == 'M' && i != 5)
+ *p++ = ':';
+ }
+ *p = '\0';
+
+ return string(buf, end, mac_addr, spec);
+}
+
+static char *ip4_string(char *p, const u8 *addr, bool leading_zeros)
+{
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ char temp[3]; /* hold each IP quad in reverse order */
+ int digits = put_dec_trunc(temp, addr[i]) - temp;
+ if (leading_zeros) {
+ if (digits < 3)
+ *p++ = '0';
+ if (digits < 2)
+ *p++ = '0';
+ }
+ /* reverse the digits in the quad */
+ while (digits--)
+ *p++ = temp[digits];
+ if (i < 3)
+ *p++ = '.';
+ }
+
+ *p = '\0';
+ return p;
+}
+
+static char *ip6_compressed_string(char *p, const char *addr)
+{
+ int i;
+ int j;
+ int range;
+ unsigned char zerolength[8];
+ int longest = 1;
+ int colonpos = -1;
+ u16 word;
+ u8 hi;
+ u8 lo;
+ bool needcolon = false;
+ bool useIPv4;
+ struct in6_addr in6;
+
+ memcpy(&in6, addr, sizeof(struct in6_addr));
+
+ useIPv4 = ipv6_addr_v4mapped(&in6) || ipv6_addr_is_isatap(&in6);
+
+ memset(zerolength, 0, sizeof(zerolength));
+
+ if (useIPv4)
+ range = 6;
+ else
+ range = 8;
+
+ /* find position of longest 0 run */
+ for (i = 0; i < range; i++) {
+ for (j = i; j < range; j++) {
+ if (in6.s6_addr16[j] != 0)
+ break;
+ zerolength[i]++;
+ }
+ }
+ for (i = 0; i < range; i++) {
+ if (zerolength[i] > longest) {
+ longest = zerolength[i];
+ colonpos = i;
+ }
+ }
+
+ /* emit address */
+ for (i = 0; i < range; i++) {
+ if (i == colonpos) {
+ if (needcolon || i == 0)
+ *p++ = ':';
+ *p++ = ':';
+ needcolon = false;
+ i += longest - 1;
+ continue;
+ }
+ if (needcolon) {
+ *p++ = ':';
+ needcolon = false;
+ }
+ /* hex u16 without leading 0s */
+ word = ntohs(in6.s6_addr16[i]);
+ hi = word >> 8;
+ lo = word & 0xff;
+ if (hi) {
+ if (hi > 0x0f)
+ p = pack_hex_byte(p, hi);
+ else
+ *p++ = hex_asc_lo(hi);
+ }
+ if (hi || lo > 0x0f)
+ p = pack_hex_byte(p, lo);
+ else
+ *p++ = hex_asc_lo(lo);
+ needcolon = true;
+ }
+
+ if (useIPv4) {
+ if (needcolon)
+ *p++ = ':';
+ p = ip4_string(p, &in6.s6_addr[12], false);
+ }
+
+ *p = '\0';
+ return p;
+}
+
+static char *ip6_string(char *p, const char *addr, const char *fmt)
+{
+ int i;
+ for (i = 0; i < 8; i++) {
+ p = pack_hex_byte(p, *addr++);
+ p = pack_hex_byte(p, *addr++);
+ if (fmt[0] == 'I' && i != 7)
+ *p++ = ':';
+ }
+
+ *p = '\0';
+ return p;
+}
+
+static char *ip6_addr_string(char *buf, char *end, const u8 *addr,
+ struct printf_spec spec, const char *fmt)
+{
+ char ip6_addr[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255")];
+
+ if (fmt[0] == 'I' && fmt[2] == 'c')
+ ip6_compressed_string(ip6_addr, addr);
+ else
+ ip6_string(ip6_addr, addr, fmt);
+
+ return string(buf, end, ip6_addr, spec);
+}
+
+static char *ip4_addr_string(char *buf, char *end, const u8 *addr,
+ struct printf_spec spec, const char *fmt)
+{
+ char ip4_addr[sizeof("255.255.255.255")];
+
+ ip4_string(ip4_addr, addr, fmt[0] == 'i');
+
+ return string(buf, end, ip4_addr, spec);
+}
+
+/*
+ * 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:
+ *
+ * - 'F' For symbolic function descriptor pointers with offset
+ * - 'f' For simple symbolic function names without offset
+ * - 'S' For symbolic direct pointers with offset
+ * - 's' For symbolic direct pointers without offset
+ * - 'R' For a struct resource pointer, it prints the range of
+ * addresses (not the name nor the flags)
+ * - 'M' For a 6-byte MAC address, it prints the address in the
+ * usual colon-separated hex notation
+ * - 'm' For a 6-byte MAC address, it prints the hex address without colons
+ * - 'I' [46] for IPv4/IPv6 addresses printed in the usual way
+ * IPv4 uses dot-separated decimal without leading 0's (1.2.3.4)
+ * IPv6 uses colon separated network-order 16 bit hex with leading 0's
+ * - 'i' [46] for 'raw' IPv4/IPv6 addresses
+ * IPv6 omits the colons (01020304...0f)
+ * IPv4 uses dot-separated decimal with leading 0's (010.123.045.006)
+ * - 'I6c' for IPv6 addresses printed as specified by
+ * http://www.ietf.org/id/draft-kawamura-ipv6-text-representation-03.txt
+ * 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, char *end, void *ptr,
+ struct printf_spec spec)
+{
+ if (!ptr)
+ return string(buf, end, "(null)", spec);
+
+ switch (*fmt) {
+ case 'F':
+ case 'f':
+ ptr = dereference_function_descriptor(ptr);
+ case 's':
+ /* Fallthrough */
+ case 'S':
+ return symbol_string(buf, end, ptr, spec, *fmt);
+ case 'R':
+ return resource_string(buf, end, ptr, spec);
+ case 'M': /* Colon separated: 00:01:02:03:04:05 */
+ case 'm': /* Contiguous: 000102030405 */
+ return mac_address_string(buf, end, ptr, spec, fmt);
+ case 'I': /* Formatted IP supported
+ * 4: 1.2.3.4
+ * 6: 0001:0203:...:0708
+ * 6c: 1::708 or 1::1.2.3.4
+ */
+ case 'i': /* Contiguous:
+ * 4: 001.002.003.004
+ * 6: 000102...0f
+ */
+ switch (fmt[1]) {
+ case '6':
+ return ip6_addr_string(buf, end, ptr, spec, fmt);
+ case '4':
+ return ip4_addr_string(buf, end, ptr, spec, fmt);
+ }
+ break;
+ }
+ spec.flags |= SMALL;
+ if (spec.field_width == -1) {
+ spec.field_width = 2*sizeof(void *);
+ spec.flags |= ZEROPAD;
+ }
+ spec.base = 16;
+
+ return number(buf, end, (unsigned long) ptr, spec);
+}
+
+/*
+ * Helper function to decode printf style format.
+ * Each call decode a token from the format and return the
+ * number of characters read (or likely the delta where it wants
+ * to go on the next call).
+ * The decoded token is returned through the parameters
+ *
+ * '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
+ *
+ * @fmt: the format string
+ * @type of the token returned
+ * @flags: various flags such as +, -, # tokens..
+ * @field_width: overwritten width
+ * @base: base of the number (octal, hex, ...)
+ * @precision: precision of a number
+ * @qualifier: qualifier of a number (long, size_t, ...)
+ */
+static int format_decode(const char *fmt, struct printf_spec *spec)
+{
+ const char *start = fmt;
+
+ /* we finished early by reading the field width */
+ if (spec->type == FORMAT_TYPE_WIDTH) {
+ if (spec->field_width < 0) {
+ spec->field_width = -spec->field_width;
+ spec->flags |= LEFT;
+ }
+ spec->type = FORMAT_TYPE_NONE;
+ goto precision;
+ }
+
+ /* we finished early by reading the precision */
+ if (spec->type == FORMAT_TYPE_PRECISION) {
+ if (spec->precision < 0)
+ spec->precision = 0;
+
+ spec->type = FORMAT_TYPE_NONE;
+ goto qualifier;
+ }
+
+ /* By default */
+ spec->type = FORMAT_TYPE_NONE;
+
+ for (; *fmt ; ++fmt) {
+ if (*fmt == '%')
+ break;
+ }
+
+ /* Return the current non-format string */
+ if (fmt != start || !*fmt)
+ return fmt - start;
+
+ /* Process flags */
+ spec->flags = 0;
+
+ while (1) { /* this also skips first '%' */
+ bool found = true;
+
+ ++fmt;
+
+ switch (*fmt) {
+ case '-': spec->flags |= LEFT; break;
+ case '+': spec->flags |= PLUS; break;
+ case ' ': spec->flags |= SPACE; break;
+ case '#': spec->flags |= SPECIAL; break;
+ case '0': spec->flags |= ZEROPAD; break;
+ default: found = false;
+ }
+
+ if (!found)
+ break;
+ }
+
+ /* get field width */
+ spec->field_width = -1;
+
+ if (isdigit(*fmt))
+ spec->field_width = skip_atoi(&fmt);
+ else if (*fmt == '*') {
+ /* it's the next argument */
+ spec->type = FORMAT_TYPE_WIDTH;
+ return ++fmt - start;
+ }
+
+precision:
+ /* get the precision */
+ spec->precision = -1;
+ if (*fmt == '.') {
+ ++fmt;
+ if (isdigit(*fmt)) {
+ spec->precision = skip_atoi(&fmt);
+ if (spec->precision < 0)
+ spec->precision = 0;
+ } else if (*fmt == '*') {
+ /* it's the next argument */
+ spec->type = FORMAT_TYPE_PRECISION;
+ return ++fmt - start;
+ }
+ }
+
+qualifier:
+ /* get the conversion qualifier */
+ spec->qualifier = -1;
+ if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L' ||
+ *fmt == 'Z' || *fmt == 'z' || *fmt == 't') {
+ spec->qualifier = *fmt++;
+ if (unlikely(spec->qualifier == *fmt)) {
+ if (spec->qualifier == 'l') {
+ spec->qualifier = 'L';
+ ++fmt;
+ } else if (spec->qualifier == 'h') {
+ spec->qualifier = 'H';
+ ++fmt;
+ }
+ }
+ }
+
+ /* default base */
+ spec->base = 10;
+ switch (*fmt) {
+ case 'c':
+ spec->type = FORMAT_TYPE_CHAR;
+ return ++fmt - start;
+
+ case 's':
+ spec->type = FORMAT_TYPE_STR;
+ return ++fmt - start;
+
+ case 'p':
+ spec->type = FORMAT_TYPE_PTR;
+ return fmt - start;
+ /* skip alnum */
+
+ case 'n':
+ spec->type = FORMAT_TYPE_NRCHARS;
+ return ++fmt - start;
+
+ case '%':
+ spec->type = FORMAT_TYPE_PERCENT_CHAR;
+ return ++fmt - start;
+
+ /* integer number formats - set up the flags and "break" */
+ case 'o':
+ spec->base = 8;
+ break;
+
+ case 'x':
+ spec->flags |= SMALL;
+
+ case 'X':
+ spec->base = 16;
+ break;
+
+ case 'd':
+ case 'i':
+ spec->flags |= SIGN;
+ case 'u':
+ break;
+
+ default:
+ spec->type = FORMAT_TYPE_INVALID;
+ return fmt - start;
+ }
+
+ if (spec->qualifier == 'L')
+ spec->type = FORMAT_TYPE_LONG_LONG;
+ else if (spec->qualifier == 'l') {
+ if (spec->flags & SIGN)
+ spec->type = FORMAT_TYPE_LONG;
+ else
+ spec->type = FORMAT_TYPE_ULONG;
+ } else if (spec->qualifier == 'Z' || spec->qualifier == 'z') {
+ spec->type = FORMAT_TYPE_SIZE_T;
+ } else if (spec->qualifier == 't') {
+ spec->type = FORMAT_TYPE_PTRDIFF;
+ } else if (spec->qualifier == 'H') {
+ if (spec->flags & SIGN)
+ spec->type = FORMAT_TYPE_BYTE;
+ else
+ spec->type = FORMAT_TYPE_UBYTE;
+ } else if (spec->qualifier == 'h') {
+ if (spec->flags & SIGN)
+ spec->type = FORMAT_TYPE_SHORT;
+ else
+ spec->type = FORMAT_TYPE_USHORT;
+ } else {
+ if (spec->flags & SIGN)
+ spec->type = FORMAT_TYPE_INT;
+ else
+ spec->type = FORMAT_TYPE_UINT;
+ }
+
+ return ++fmt - start;
+}
+