Subject: a "new" %b format
To: tech-kern@netbsd.org, jkh@freebsd.org, Sean Eric Fagan <sef@kithrup.com>
From: Chris Torek <torek@BSDI.COM>
List: tech-kern
Date: 08/11/1998 17:35:06
I finally added this to the BSD/OS kernel.  In the hope that
maybe we will not have unnecessary driver-level divergence between
the various BSDs, I thought I would hand you guys the code too. :-)

You can use it or throw it away or whatever; I am just hoping that
driver-writers will be able to use the same %b-encoding everwhere.

What is this good for?  It:

 - Handles quad_t's (via %qb or %llb -- you will have to write this
   part yourself though).

 - Can decode bits >= bit-32.  The decode string directive for a bit
   is 'b' followed by the bit number.  (Old %b decodes stop at bit 31,
   because higher values are valid ASCII characters.)  The bit-number
   for new %b is zero origin.  (I always used to get totally confused
   writing %b-strings, because of the 1-origin aspect.)

 - Can extract bit fields in addition to single bits.  Codes are 'f'
   and 'F', followed by bit index and field size.  The 'f' directive
   extracts and prints NAME=val; 'F' just extracts a field.

 - Can do symbolic decoding of field values.  Codes are '=' and ':',
   followed by value to compare against last extracted field.  If
   equal, prints NAME, preceded by an '=' for directive '='.

For example, to decode a floppy status register, you might use:
	printf("status=0x%b\n", reg,
	   "\177\020"
	   "F\6\2" ":\3pollfail\0" ":\2invalid\0" ":\1command failed\0"
	   "b\5seek end\0"
	   "b\4equipment check\0"
	   "b\3not ready\0"
	   "b\2head 1\0"
	   "f\0\2drive\0");
If reg is 0x62, this would print:
	status=0x62<command failed,seek end,drive=2>
whereas for 0x10 you would get:
	status=0x10<seek end,drive=0>

To decode the AFSR on an Ultrasparc (using %qb and bits > 31):

#define	AFSR_BITS	"\177\20" \
	"b\40ME\0"	"b\37PRIV\0"	"b\36ISAP\0"	"b\35ETP\0" \
	"b\34IVUE\0"	"b\33TO\0"	"b\32BERR\0"	"b\31LDP\0" \
	"b\30CP\0"	"b\27WP\0"	"b\26EDP\0"	"b\25UE\0" \
	"b\24CE\0"	"f\20\4ETS\0"	"f\0\20P_SYND\0"

or the TTE data bits:

#define	TTE_DATA_BITS 	"\177\20" \
	"b\77V\0" \
	"f\75\2SIZE\0" \
		"=\0008K\0" "=\00164K\0" "=\002512K\0" "=\0034M\0" \
	"b\74NFO\0"	"b\73IE\0"	"f\62\10SOFT2\0" \
	"f\51\10DIAG\0"	"f\15\33PA<40:13>\0" "f\7\5SOFT\0" \
	"b\6L\0"	"b\5CP\0"	"b\4CV\0" \
	"b\3E\0"	"b\2P\0"	"b\1W\0"	"b\0G\0"


You can run the string literals together; I just find that using
ANSI-style concatenation makes it a lot easier to read.

The new format has its warts, but the code is not that big and it
is definitely nice to be able to extract fields (as well, of course,
as to handle 64-bit registers :-) ).  The reason I used \177 (rather
than a new letter like %B or something) is so that people can
replace (or #ifdef) XXX_BITS defines piecemeal in header files,
without affecting drivers: the old %b format will still work (for
registers <= 32 bits), just not decode as much stuff.

Chris

(Note, putstr() just returns the pointer to the character past the
'\0', and I whacked ksprintn to build the at the end of the buffer
and working backward, so that it prints correctly going forward.)

/*
 * Handle the %b formats.  "New" %b is marked by a string that begins
 * with \177 (an impossible base).
 */
static void
bfmt(val, p, flags, vp)
	u_quad_t val;
	u_char *p;
	int flags;
	void *vp;
{
	int base, ch, bit, len, sep;
	u_quad_t field;

	ch = *p++;
	base = ch != '\177' ? ch : *p++;
	putstr(ksprintn(val, base, NULL), flags, vp);
	sep = '<';
	if (ch != '\177') {
		/* old (standard) %b format. */
		while ((bit = *p++) != '\0') {
			bit--;	/* 1-origin */
			if ((u_int)(val >> bit) & 1) {
				putchar(sep, flags, vp);
				for (; (ch = *p) > ' '; p++)
					putchar(ch, flags, vp);
				sep = ',';
			} else
				while (*p > ' ')
					p++;
		}
	} else {
		/* new quad-capable %b format; also does fields. */
		field = val;
		while ((ch = *p++) != '\0') {
			bit = *p++;	/* now 0-origin */
			switch (ch) {
			case 'b':
				if (((u_int)(val >> bit) & 1) == 0)
					goto skip;
				putchar(sep, flags, vp);
				p = putstr(p, flags, vp);
				sep = ',';
				break;
			case 'f':
			case 'F':
				len = *p++;	/* field length */
				field = (val >> bit) & ((1ULL << len) - 1);
				if (ch == 'F')	/* just extract */
					break;
				putchar(sep, flags, vp);
				sep = ',';
				p = putstr(p, flags, vp);
				putchar('=', flags, vp);
				putstr(ksprintn(field, base, NULL), flags, vp);
				break;
			case '=':
			case ':':
				/*
				 * Here "bit" is actually a value instead,
				 * to be compared against the last field.
				 * This only works for values in [0..255],
				 * of course.
				 */
				if ((int)field != bit)
					goto skip;
				if (ch == '=')
					putchar('=', flags, vp);
				p = putstr(p, flags, vp);
				break;
			default:
			skip:
				while (*p++ != '\0')
					continue;
				break;
			}
		}
	}
	if (sep != '<')
		putchar('>', flags, vp);
}