readcis.c revision 61804
1/*
2 * Copyright (c) 1995 Andrew McRae.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 * 3. The name of the author may not be used to endorse or promote products
13 *    derived from this software without specific prior written permission.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#ifndef lint
28static const char rcsid[] =
29  "$FreeBSD: head/usr.sbin/pccard/pccardd/readcis.c 61804 2000-06-18 20:22:11Z roberto $";
30#endif /* not lint */
31
32/*
33 * Code cleanup, bug-fix and extension
34 * by Tatsumi Hosokawa <hosokawa@mt.cs.keio.ac.jp>
35 */
36
37#include <err.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41#include <unistd.h>
42#include <sys/ioctl.h>
43
44#include <pccard/cardinfo.h>
45#include <pccard/cis.h>
46
47#include "readcis.h"
48
49#ifdef RATOCLAN
50static int	rex5588 = 0;
51#endif
52
53static int read_attr(int, char *, int);
54static int ck_linktarget(int, off_t, int);
55static void cis_info(struct cis *, unsigned char *, int);
56static void device_desc(unsigned char *, int, struct dev_mem *);
57static void config_map(struct cis *, unsigned char *, int);
58static void cis_config(struct cis *, unsigned char *, int);
59static void cis_func_id(struct cis *, unsigned char *, int);
60static void cis_network_ext(struct cis *, unsigned char *, int);
61static struct tuple_list *read_one_tuplelist(int, int, off_t);
62static struct tuple_list *read_tuples(int);
63static struct tuple *find_tuple_in_list(struct tuple_list *, unsigned char);
64static struct tuple_info *get_tuple_info(unsigned char);
65
66static struct tuple_info tuple_info[] = {
67	{"Null tuple", 0x00, 0},
68	{"Common memory descriptor", 0x01, 255},
69	{"Long link to next chain for CardBus", 0x02, 255},
70	{"Indirect access", 0x03, 255},
71	{"Configuration map for CardBus", 0x04, 255},
72	{"Configuration entry for CardBus", 0x05, 255},
73	{"Long link to next chain for MFC", 0x06, 255},
74	{"Base address register for CardBus", 0x07, 6},
75	{"Checksum", 0x10, 5},
76	{"Long link to attribute memory", 0x11, 4},
77	{"Long link to common memory", 0x12, 4},
78	{"Link target", 0x13, 3},
79	{"No link", 0x14, 0},
80	{"Version 1 info", 0x15, 255},
81	{"Alternate language string", 0x16, 255},
82	{"Attribute memory descriptor", 0x17, 255},
83	{"JEDEC descr for common memory", 0x18, 255},
84	{"JEDEC descr for attribute memory", 0x19, 255},
85	{"Configuration map", 0x1A, 255},
86	{"Configuration entry", 0x1B, 255},
87	{"Other conditions for common memory", 0x1C, 255},
88	{"Other conditions for attribute memory", 0x1D, 255},
89	{"Geometry info for common memory", 0x1E, 255},
90	{"Geometry info for attribute memory", 0x1F, 255},
91	{"Manufacturer ID", 0x20, 4},
92	{"Functional ID", 0x21, 2},
93	{"Functional EXT", 0x22, 255},
94	{"Software interleave", 0x23, 2},
95	{"Version 2 Info", 0x40, 255},
96	{"Data format", 0x41, 255},
97	{"Geometry", 0x42, 4},
98	{"Byte order", 0x43, 2},
99	{"Card init date", 0x44, 4},
100	{"Battery replacement", 0x45, 4},
101	{"Organization", 0x46, 255},
102	{"Terminator", 0xFF, 0},
103	{0, 0, 0}
104};
105
106/*
107 *	After reading the tuples, decode the relevant ones.
108 */
109struct cis *
110readcis(int fd)
111{
112	struct tuple_list *tl;
113	struct tuple *tp;
114	struct cis *cp;
115
116	cp = xmalloc(sizeof(*cp));
117	cp->tlist = read_tuples(fd);
118	if (cp->tlist == 0)
119		return (NULL);
120
121	for (tl = cp->tlist; tl; tl = tl->next)
122		for (tp = tl->tuples; tp; tp = tp->next) {
123#if 0
124			printf("tuple code = 0x%02x, data is\n", tp->code);
125			dump(tp->data, tp->length);
126#endif
127			switch (tp->code) {
128			case CIS_MEM_COMMON:	/* 0x01 */
129				device_desc(tp->data, tp->length, &cp->common_mem);
130				break;
131			case CIS_INFO_V1:	/* 0x15 */
132				cis_info(cp, tp->data, tp->length);
133				break;
134			case CIS_MEM_ATTR:	/* 0x17 */
135				device_desc(tp->data, tp->length, &cp->attr_mem);
136				break;
137			case CIS_CONF_MAP:	/* 0x1A */
138				config_map(cp, tp->data, tp->length);
139				break;
140			case CIS_CONFIG:	/* 0x1B */
141				cis_config(cp, tp->data, tp->length);
142				break;
143			case CIS_FUNC_ID:	/* 0x21 */
144				cis_func_id(cp, tp->data, tp->length);
145				break;
146			case CIS_FUNC_EXT:	/* 0x22 */
147				if (cp->func_id1 == 6)	/* LAN adaptor */
148					cis_network_ext(cp, tp->data, tp->length);
149				break;
150			}
151		}
152	return (cp);
153}
154
155/*
156 *	free_cis - delete cis entry.
157 */
158void
159freecis(struct cis *cp)
160{
161	struct cis_ioblk *io;
162	struct cis_memblk *mem;
163	struct cis_config *conf;
164	struct tuple *tp;
165	struct tuple_list *tl;
166
167	while ((tl = cp->tlist) != 0) {
168		cp->tlist = tl->next;
169		while ((tp = tl->tuples) != 0) {
170			tl->tuples = tp->next;
171			if (tp->data)
172				free(tp->data);
173		}
174	}
175
176	while ((conf = cp->conf) != 0) {
177		cp->conf = conf->next;
178		while ((io = conf->io) != 0) {
179			conf->io = io->next;
180			free(io);
181		}
182		while ((mem = conf->mem) != 0) {
183			conf->mem = mem->next;
184			free(mem);
185		}
186		free(conf);
187	}
188	free(cp);
189}
190
191/*
192 *	Fills in CIS version data.
193 */
194static void
195cis_info(struct cis *cp, unsigned char *p, int len)
196{
197	cp->maj_v = *p++;
198	cp->min_v = *p++;
199	len -= 2;
200	if (cp->manuf) {
201		free(cp->manuf);
202		cp->manuf = NULL;
203	}
204	if (len > 1 && *p != 0xff) {
205		cp->manuf = strdup(p);
206		while (*p++ && --len > 0);
207	}
208	if (cp->vers) {
209		free(cp->vers);
210		cp->vers = NULL;
211	}
212	if (len > 1 && *p != 0xff) {
213		cp->vers = strdup(p);
214		while (*p++ && --len > 0);
215	} else {
216		cp->vers = strdup("?");
217	}
218	if (cp->add_info1) {
219		free(cp->add_info1);
220		cp->add_info1 = NULL;
221	}
222	if (len > 1 && *p != 0xff) {
223		cp->add_info1 = strdup(p);
224		while (*p++ && --len > 0);
225	}
226	if (cp->add_info2) {
227		free(cp->add_info2);
228		cp->add_info2 = NULL;
229	}
230	if (len > 1 && *p != 0xff)
231		cp->add_info2 = strdup(p);
232}
233
234/*
235 *	Fills in CIS function ID.
236 */
237static void
238cis_func_id(struct cis *cp, unsigned char *p, int len)
239{
240	cp->func_id1 = *p++;
241	cp->func_id2 = *p++;
242}
243
244static void
245cis_network_ext(struct cis *cp, unsigned char *p, int len)
246{
247	int i;
248
249	switch (p[0]) {
250	case 4:		/* Node ID */
251		if (len <= 2 || len < p[1] + 2)
252			return;
253
254		if (cp->lan_nid)
255			free(cp->lan_nid);
256		cp->lan_nid = xmalloc(p[1]);
257
258		for (i = 0; i <= p[1]; i++)
259			cp->lan_nid[i] = p[i + 1];
260		break;
261	}
262}
263
264/*
265 *	"FUJITSU LAN Card (FMV-J182)" has broken CIS
266 */
267static int
268fmvj182_check(unsigned char *p)
269{
270	char    manuf[BUFSIZ], vers[BUFSIZ];
271
272	p++;			/* major version */
273	p++;			/* minor version */
274	strncpy(manuf, p, sizeof(manuf) - 1);
275	while (*p++);
276	strncpy(vers, p, sizeof(vers) - 1);
277	if (!strcmp(manuf, "FUJITSU") && !strcmp(vers, "LAN Card(FMV-J182)"))
278		return 1;
279	else
280		return 0;
281}
282
283#ifdef RATOCLAN
284/*
285 *	"RATOC LAN Card (REX-5588)" has broken CIS
286 */
287static int
288rex5588_check(unsigned char *p)
289{
290	char    manuf[BUFSIZ], vers[BUFSIZ];
291
292	p++;			/* major version */
293	p++;			/* minor version */
294	strncpy(manuf, p, sizeof(manuf) - 1);
295	while (*p++);
296	strncpy(vers, p, sizeof(manuf) - 1);
297	if (!strcmp(manuf, "PCMCIA LAN MBH10304  ES"))
298		return 1;
299	else
300		return 0;
301}
302#endif
303
304#ifdef HSSYNTH
305/*
306 *	Broken CIS for "HITACHI MICROCOMPUTER SYSTEM LTD." "MSSHVPC02"
307 */
308static int
309hss_check(unsigned char *p)
310{
311	char    manuf[BUFSIZ], vers[BUFSIZ];
312
313	p++;			/* major version */
314	p++;			/* minor version */
315	strncpy(manuf, p, sizeof(manuf) - 1);
316	while (*p++);
317	strncpy(vers, p, sizeof(vers) - 1);
318	if (!strcmp(manuf, "HITACHI MICROCOMPUTER SYSTEMS LTD.")
319	 && !strcmp(vers, "MSSHVPC02"))
320		return 1;
321	else
322		return 0;
323}
324#endif	/* HSSYNTH */
325
326/*
327 *	device_desc - decode device descriptor.
328 */
329static void
330device_desc(unsigned char *p, int len, struct dev_mem *dp)
331{
332	while (len > 0 && *p != 0xFF) {
333		dp->valid = 1;
334		dp->type = (*p & 0xF0) >> 4;
335		dp->wps = !!(*p & 0x8);
336		dp->speed = *p & 7;
337		p++;
338		if (*p != 0xFF) {
339			dp->addr = (*p >> 3) & 0xF;
340			dp->units = *p & 7;
341		}
342		p++;
343		len -= 2;
344	}
345}
346
347/*
348 *	configuration map of card control register.
349 */
350static void
351config_map(struct cis *cp, unsigned char *p, int len)
352{
353	unsigned char *p1;
354	int rlen = (*p & 3) + 1;
355
356	p1 = p + 1;
357	cp->last_config = *p1++ & 0x3F;
358	cp->reg_addr = parse_num(rlen | 0x10, p1, &p1, 0);
359	cp->ccrs = *p1;
360}
361
362/*
363 *	Parse variable length value.
364 */
365u_int
366parse_num(int sz, u_char *p, u_char **q, int ofs)
367{
368	u_int num = 0;
369
370	switch (sz) {
371	case 0:
372	case 0x10:
373		break;
374	case 1:
375	case 0x11:
376		num = (*p++) + ofs;
377		break;
378	case 2:
379	case 0x12:
380		num = tpl16(p) + ofs;
381		p += 2;
382		break;
383	case 0x13:
384		num = tpl24(p) + ofs;
385		p += 3;
386		break;
387	case 3:
388	case 0x14:
389		num = tpl32(p) + ofs;
390		p += 4;
391		break;
392	}
393	if (q)
394		*q = p;
395	return num;
396}
397
398/*
399 *	CIS config entry - Decode and build configuration entry.
400 */
401static void
402cis_config(struct cis *cp, unsigned char *p, int len)
403{
404	int     x;
405	int     i, j;
406	struct cis_config *conf, *last;
407	unsigned char feat;
408
409	conf = xmalloc(sizeof(*conf));
410	if ((last = cp->conf) != 0) {
411		while (last->next)
412			last = last->next;
413		last->next = conf;
414	} else
415		cp->conf = conf;
416 	conf->id = *p & 0x3F;	/* Config index */
417#ifdef RATOCLAN
418	if (rex5588 && conf->id >= 0x08 && conf->id <= 0x1d)
419		conf->id |= 0x20;
420#endif
421 	if (*p & 0x40)		/* Default flag */
422		cp->def_config = conf;
423	if (*p++ & 0x80)
424 		p++;		/* Interface byte skip */
425 	feat = *p++;		/* Features byte */
426	for (i = 0; i < CIS_FEAT_POWER(feat); i++) {
427		unsigned char parms = *p++;
428
429		conf->pwr = 1;
430		for (j = 0; j < 8; j++)
431			if (parms & (1 << j))
432				while (*p++ & 0x80);
433	}
434	if (feat & CIS_FEAT_TIMING) {
435		conf->timing = 1;
436		i = *p++;
437		if (CIS_WAIT_SCALE(i) != 3)
438			p++;
439		if (CIS_READY_SCALE(i) != 7)
440			p++;
441		if (CIS_RESERVED_SCALE(i) != 7)
442			p++;
443	}
444	if (feat & CIS_FEAT_I_O) {
445		conf->iospace = 1;
446		if (CIS_IO_RANGE & *p)
447			conf->io_blks = CIS_IO_BLKS(p[1]) + 1;
448		conf->io_addr = CIS_IO_ADDR(*p);
449		conf->io_bus = (*p >> 5) & 3; /* CIS_IO_8BIT | CIS_IO_16BIT */
450		if (*p++ & CIS_IO_RANGE) {
451			struct cis_ioblk *io;
452			struct cis_ioblk *last_io = NULL;
453
454			i = CIS_IO_ADSZ(*p);
455			j = CIS_IO_BLKSZ(*p++);
456			for (x = 0; x < conf->io_blks; x++) {
457				io = xmalloc(sizeof(*io));
458				if (last_io)
459					last_io->next = io;
460				else
461					conf->io = io;
462				last_io = io;
463				io->addr = parse_num(i, p, &p, 0);
464				io->size = parse_num(j, p, &p, 1);
465			}
466		}
467	}
468	if (feat & CIS_FEAT_IRQ) {
469		conf->irq = 1;
470		conf->irqlevel = *p & 0xF;
471		conf->irq_flags = *p & 0xF0;
472		if (*p++ & CIS_IRQ_MASK) {
473			conf->irq_mask = tpl16(p);
474			p += 2;
475		}
476	}
477	switch (CIS_FEAT_MEMORY(feat)) {
478	case 0:
479		break;
480	case 1:
481		conf->memspace = 1;
482		conf->mem = xmalloc(sizeof(*conf->mem));
483		conf->mem->length = tpl16(p) << 8;
484		break;
485	case 2:
486		conf->memspace = 1;
487		conf->mem = xmalloc(sizeof(*conf->mem));
488		conf->mem->length = tpl16(p) << 8;
489		conf->mem->address = tpl16(p + 2) << 8;
490		break;
491	case 3: {
492		struct cis_memblk *mem;
493		struct cis_memblk *last_mem = NULL;
494
495		conf->memspace = 1;
496		x = *p++;
497		conf->memwins = CIS_MEM_WINS(x);
498		for (i = 0; i < conf->memwins; i++) {
499			mem = xmalloc(sizeof(*mem));
500			if (last_mem)
501				last_mem->next = mem;
502			else
503				conf->mem = mem;
504			last_mem = mem;
505			mem->length = parse_num(CIS_MEM_LENSZ(x) | 0x10, p, &p, 0) << 8;
506			mem->address = parse_num(CIS_MEM_ADDRSZ(x) | 0x10, p, &p, 0) << 8;
507			if (x & CIS_MEM_HOST) {
508				mem->host_address = parse_num(CIS_MEM_ADDRSZ(x) | 0x10,
509							      p, &p, 0) << 8;
510			}
511		}
512		break;
513	    }
514	}
515	if (feat & CIS_FEAT_MISC) {
516		conf->misc_valid = 1;
517		conf->misc = *p++;
518	}
519}
520
521/*
522 *	Read the tuples from the card.
523 *	The processing of tuples is as follows:
524 *		- Read tuples at attribute memory, offset 0.
525 *		- If a CIS_END is the first tuple, look for
526 *		  a tuple list at common memory offset 0; this list
527 *		  must start with a LINKTARGET.
528 *		- If a long link tuple was encountered, execute the long
529 *		  link.
530 *		- If a no-link tuple was seen, terminate processing.
531 *		- If no no-link tuple exists, and no long link tuple
532 *		  exists while processing the primary tuple list,
533 *		  then look for a LINKTARGET tuple in common memory.
534 *		- If a long link tuple is found in any list, then process
535 *		  it. Only one link is allowed per list.
536 */
537static struct tuple_list *tlist;
538
539static struct tuple_list *
540read_tuples(int fd)
541{
542	struct tuple_list *tl = 0, *last_tl;
543	struct tuple *tp;
544	int     flag;
545	off_t   offs;
546
547	tlist = 0;
548	last_tl = tlist = read_one_tuplelist(fd, MDF_ATTR, (off_t) 0);
549
550	/* Now start processing the links (if any). */
551	do {
552		flag = MDF_ATTR;
553		tp = find_tuple_in_list(last_tl, CIS_LONGLINK_A);
554		if (tp == 0) {
555			flag = 0;
556			tp = find_tuple_in_list(last_tl, CIS_LONGLINK_C);
557		}
558		if (tp && tp->length == 4) {
559			offs = tpl32(tp->data);
560#ifdef	DEBUG
561			printf("Checking long link at %qd (%s memory)\n",
562			    offs, flag ? "Attribute" : "Common");
563#endif
564			/* If a link was found, read the tuple list from it. */
565			if (ck_linktarget(fd, offs, flag)) {
566				tl = read_one_tuplelist(fd, flag, offs);
567				last_tl->next = tl;
568				last_tl = tl;
569			}
570		} else
571			tl = 0;
572	} while (tl);
573
574	/*
575	 * If the primary list had no NOLINK tuple, and no LINKTARGET,
576	 * then try to read a tuple list at common memory (offset 0).
577	 */
578	if (find_tuple_in_list(tlist, CIS_NOLINK) == 0 && tlist->next == 0 &&
579	    ck_linktarget(fd, (off_t) 0, 0)) {
580#ifdef	DEBUG
581		printf("Reading long link at %qd (%s memory)\n",
582		    offs, flag ? "Attribute" : "Common");
583#endif
584		tlist->next = read_one_tuplelist(fd, 0, (off_t) 0);
585	}
586	return (tlist);
587}
588
589/*
590 *	Read one tuple list from the card.
591 */
592static struct tuple_list *
593read_one_tuplelist(int fd, int flags, off_t offs)
594{
595	struct tuple *tp, *last_tp = 0;
596	struct tuple_list *tl;
597	struct tuple_info *tinfo;
598	int     total = 0;
599	unsigned char code, length;
600	int     fmvj182 = 0;
601#ifdef HSSYNTH
602	int     hss = 0;
603#endif	/* HSSYNTH */
604
605	/* Check to see if this memory has already been scanned. */
606	for (tl = tlist; tl; tl = tl->next)
607		if (tl->offs == offs && tl->flags == (flags & MDF_ATTR))
608			return (0);
609	tl = xmalloc(sizeof(*tl));
610	tl->offs = offs;
611	tl->flags = flags & MDF_ATTR;
612	ioctl(fd, PIOCRWFLAG, &flags);
613	lseek(fd, offs, SEEK_SET);
614	do {
615		if (read_attr(fd, &code, 1) != 1) {
616			warn("CIS code read");
617			break;
618		}
619		total++;
620		if (code == CIS_NULL)
621			continue;
622		tp = xmalloc(sizeof(*tp));
623		tp->code = code;
624		if (code == CIS_END)
625			length = 0;
626		else {
627			if (read_attr(fd, &length, 1) != 1) {
628				warn("CIS len read");
629				break;
630			}
631			total++;
632			if (fmvj182 && (code == 0x1b) && (length == 25))
633				length = 31;
634		}
635		tp->length = length;
636#ifdef	DEBUG
637		printf("Tuple code = 0x%x, len = %d\n", code, length);
638#endif
639		if (length == 0xFF) {
640			length = tp->length = 0;
641			code = CIS_END;
642		}
643		if (length != 0) {
644			total += length;
645			tp->data = xmalloc(length);
646			if (read_attr(fd, tp->data, length) != length) {
647				warn("CIS read");
648				break;
649			}
650		}
651
652		/*
653		 * Check the tuple, and ignore it if it isn't in the table
654		 * or the length is illegal.
655		 */
656		tinfo = get_tuple_info(code);
657		if (code == CIS_INFO_V1) {
658			/* Hack for broken CIS of FMV-J182 Ethernet card */
659			fmvj182 = fmvj182_check(tp->data);
660#ifdef RATOCLAN
661			/* Hack for RATOC LAN card */
662			rex5588 = rex5588_check(tp->data);
663#endif /* RATOCLAN */
664#ifdef	HSSYNTH
665			/* Hack for Hitachi Speech Synthesis card */
666			hss = hss_check(tp->data);
667#endif	/* HSSYNTH */
668		}
669		if (tinfo == NULL || (tinfo->length != 255 && tinfo->length > length)) {
670			printf("code %s ignored\n", tuple_name(code));
671			tp->code = CIS_NULL;
672		}
673		if (tl->tuples == NULL)
674			tl->tuples = tp;
675		else
676			last_tp->next = tp;
677		last_tp = tp;
678	} while (code != CIS_END && total < 1024);
679	return (tl);
680}
681
682/*
683 *	return true if the offset points to a LINKTARGET tuple.
684 */
685static int
686ck_linktarget(int fd, off_t offs, int flag)
687{
688	char    blk[5];
689
690	ioctl(fd, PIOCRWFLAG, &flag);
691	lseek(fd, offs, SEEK_SET);
692	if (read_attr(fd, blk, 5) != 5)
693		return (0);
694	if (blk[0] == 0x13 &&
695	    blk[1] == 0x3 &&
696	    blk[2] == 'C' &&
697	    blk[3] == 'I' &&
698	    blk[4] == 'S')
699		return (1);
700	return (0);
701}
702
703/*
704 *	find_tuple_in_list - find a tuple within a
705 *	single tuple list.
706 */
707static struct tuple *
708find_tuple_in_list(struct tuple_list *tl, unsigned char code)
709{
710	struct tuple *tp;
711
712	for (tp = tl->tuples; tp; tp = tp->next)
713		if (tp->code == code)
714			break;
715	return (tp);
716}
717
718static int
719read_attr(int fd, char *bp, int len)
720{
721	char    blk[1024], *p = blk;
722	int     i, l;
723
724	if (len > sizeof(blk) / 2)
725		len = sizeof(blk) / 2;
726	l = i = read(fd, blk, len * 2);
727	if (i <= 0) {
728		printf("Read return %d bytes (expected %d)\n", i, len * 2);
729		return (i);
730	}
731	while (i > 0) {
732		*bp++ = *p++;
733		p++;
734		i -= 2;
735	}
736	return (l / 2);
737}
738
739/*
740 *	return table entry for code.
741 */
742static struct tuple_info *
743get_tuple_info(unsigned char code)
744{
745	struct tuple_info *tp;
746
747	for (tp = tuple_info; tp->name; tp++)
748		if (tp->code == code)
749			return (tp);
750	printf("Code %d not found\n", code);
751	return (0);
752}
753
754char *
755tuple_name(unsigned char code)
756{
757	struct tuple_info *tp;
758
759	tp = get_tuple_info(code);
760	if (tp)
761		return (tp->name);
762	return ("Unknown");
763}
764