NetBSD-5.0.2/sys/dev/pcmcia/pcmcia_cis.c

Compare this file to the similar file:
Show the results in this format:

/*	$NetBSD: pcmcia_cis.c,v 1.52 2008/07/03 19:07:43 drochner Exp $	*/

/*
 * Copyright (c) 1997 Marc Horowitz.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Marc Horowitz.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: pcmcia_cis.c,v 1.52 2008/07/03 19:07:43 drochner Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/malloc.h>

#include <dev/pcmcia/pcmciareg.h>
#include <dev/pcmcia/pcmciachip.h>
#include <dev/pcmcia/pcmciavar.h>

#ifdef PCMCIACISDEBUG
int	pcmciacis_debug = 0;
#define	DPRINTF(arg) if (pcmciacis_debug) printf arg
#else
#define	DPRINTF(arg)
#endif

#define	PCMCIA_CIS_SIZE		1024

struct cis_state {
	int	count;
	int	gotmfc;
	struct pcmcia_config_entry temp_cfe;
	struct pcmcia_config_entry *default_cfe;
	struct pcmcia_card *card;
	struct pcmcia_function *pf;
};

int	pcmcia_parse_cis_tuple(struct pcmcia_tuple *, void *);
static void create_pf(struct cis_state *);

static void decode_end(struct pcmcia_tuple *, struct cis_state *);
static void decode_longlink_mfc(struct pcmcia_tuple *, struct cis_state *);
static void decode_device(struct pcmcia_tuple *, struct cis_state *);
static void decode_vers_1(struct pcmcia_tuple *, struct cis_state *);
static void decode_manfid(struct pcmcia_tuple *, struct cis_state *);
static void decode_funcid(struct pcmcia_tuple *, struct cis_state *);
static void decode_funce(struct pcmcia_tuple *, struct cis_state *);
static void decode_config(struct pcmcia_tuple *, struct cis_state *);
static void decode_cftable_entry(struct pcmcia_tuple *, struct cis_state *);


static void
create_pf(struct cis_state *state)
{
	state->pf = malloc(sizeof(*state->pf), M_DEVBUF, M_NOWAIT|M_ZERO);
	state->pf->number = state->count++;
	state->pf->last_config_index = -1;
	SIMPLEQ_INIT(&state->pf->cfe_head);
	SIMPLEQ_INSERT_TAIL(&state->card->pf_head, state->pf, pf_list);
}

void
pcmcia_free_pf(struct pcmcia_function_head *pfhead)
{
	struct pcmcia_function *pf, *npf;
	struct pcmcia_config_entry *cfe, *ncfe;

	for (pf = SIMPLEQ_FIRST(pfhead); pf != NULL; pf = npf) {
		npf = SIMPLEQ_NEXT(pf, pf_list);
		for (cfe = SIMPLEQ_FIRST(&pf->cfe_head); cfe != NULL;
		    cfe = ncfe) {
			ncfe = SIMPLEQ_NEXT(cfe, cfe_list);
			free(cfe, M_DEVBUF);
		}
		free(pf, M_DEVBUF);
	}

	SIMPLEQ_INIT(pfhead);
}

void
pcmcia_read_cis(sc)
	struct pcmcia_softc *sc;
{
	struct cis_state state;

	memset(&state, 0, sizeof state);

	state.card = &sc->card;

	state.card->error = 0;
	state.card->cis1_major = -1;
	state.card->cis1_minor = -1;
	state.card->cis1_info[0] = NULL;
	state.card->cis1_info[1] = NULL;
	state.card->cis1_info[2] = NULL;
	state.card->cis1_info[3] = NULL;
	state.card->manufacturer = PCMCIA_VENDOR_INVALID;
	state.card->product = PCMCIA_PRODUCT_INVALID;
	SIMPLEQ_INIT(&state.card->pf_head);

	state.pf = NULL;

	if (pcmcia_scan_cis(sc->dev, pcmcia_parse_cis_tuple,
	    &state) == -1)
		state.card->error++;
}

int
pcmcia_scan_cis(dev, fct, arg)
	struct device *dev;
	int (*fct)(struct pcmcia_tuple *, void *);
	void *arg;
{
	struct pcmcia_softc *sc = device_private(dev);
	pcmcia_chipset_tag_t pct;
	pcmcia_chipset_handle_t pch;
	int window;
	struct pcmcia_mem_handle pcmh;
	struct pcmcia_tuple tuple;
	int longlink_present;
	int longlink_common;
	u_long longlink_addr;
	int mfc_count;
	int mfc_index;
	struct {
		int	common;
		u_long	addr;
	} mfc[256 / 5];
	int ret;

	ret = 0;

	pct = sc->pct;
	pch = sc->pch;

	/* allocate some memory */

	if (pcmcia_chip_mem_alloc(pct, pch, PCMCIA_CIS_SIZE, &pcmh)) {
#ifdef DIAGNOSTIC
		aprint_error_dev(sc->dev,
				 "can't alloc memory to read attributes\n");
#endif
		return -1;
	}
	/* initialize state for the primary tuple chain */
	if (pcmcia_chip_mem_map(pct, pch, PCMCIA_MEM_ATTR, 0,
	    PCMCIA_CIS_SIZE, &pcmh, &tuple.ptr, &window)) {
		pcmcia_chip_mem_free(pct, pch, &pcmh);
#ifdef DIAGNOSTIC
		aprint_error_dev(sc->dev,
				 "can't map memory to read attributes\n");
#endif
		return -1;
	}
	tuple.memt = pcmh.memt;
	tuple.memh = pcmh.memh;

	DPRINTF(("cis mem map %x\n", (unsigned int) tuple.memh));

	tuple.mult = 2;

	longlink_present = 1;
	longlink_common = 1;
	longlink_addr = 0;

	mfc_count = 0;
	mfc_index = 0;

	DPRINTF(("%s: CIS tuple chain:\n", device_xname(sc->dev)));

	while (1) {
		DELAY(1000);

		while (1) {
			/*
			 * Perform boundary check for insane cards.
			 * If CIS is too long, simulate CIS end.
			 * (This check may not be sufficient for
			 * malicious cards.)
			 */
			if (tuple.mult * tuple.ptr >= PCMCIA_CIS_SIZE - 1
			    - 32 /* ad hoc value */ ) {
				DPRINTF(("CISTPL_END (too long CIS)\n"));
				tuple.code = PCMCIA_CISTPL_END;
				goto cis_end;
			}

			/* get the tuple code */

			tuple.code = pcmcia_cis_read_1(&tuple, tuple.ptr);

			/* two special-case tuples */

			if (tuple.code == PCMCIA_CISTPL_NULL) {
				DPRINTF((" 00\nCISTPL_NONE\n"));
				tuple.ptr++;
				continue;
			} else if (tuple.code == PCMCIA_CISTPL_END) {
				DPRINTF((" ff\nCISTPL_END\n"));
			cis_end:
				/* Call the function for the END tuple, since
				   the CIS semantics depend on it */
				if ((*fct) (&tuple, arg)) {
					pcmcia_chip_mem_unmap(pct, pch,
							      window);
					ret = 1;
					goto done;
				}
				tuple.ptr++;
				break;
			}

			/* now all the normal tuples */

			tuple.length = pcmcia_cis_read_1(&tuple, tuple.ptr + 1);
#ifdef PCMCIACISDEBUG
			/* print the tuple */
			{
				int i;

				DPRINTF((" %02x %02x", tuple.code,
				    tuple.length));

				for (i = 0; i < tuple.length; i++) {
					DPRINTF((" %02x",
					    pcmcia_tuple_read_1(&tuple, i)));
					if ((i % 16) == 13)
						DPRINTF(("\n"));
				}
				if ((i % 16) != 14)
					DPRINTF(("\n"));
			}
#endif
			switch (tuple.code) {
			case PCMCIA_CISTPL_LONGLINK_A:
			case PCMCIA_CISTPL_LONGLINK_C:
				if (tuple.length < 4) {
					DPRINTF(("CISTPL_LONGLINK_%s too "
					    "short %d\n",
					    longlink_common ? "C" : "A",
					    tuple.length));
					break;
				}
				longlink_present = 1;
				longlink_common = (tuple.code ==
				    PCMCIA_CISTPL_LONGLINK_C) ? 1 : 0;
				longlink_addr = pcmcia_tuple_read_4(&tuple, 0);
				DPRINTF(("CISTPL_LONGLINK_%s %lx\n",
				    longlink_common ? "C" : "A",
				    longlink_addr));
				break;
			case PCMCIA_CISTPL_NO_LINK:
				longlink_present = 0;
				DPRINTF(("CISTPL_NO_LINK\n"));
				break;
			case PCMCIA_CISTPL_CHECKSUM:
				if (tuple.length < 5) {
					DPRINTF(("CISTPL_CHECKSUM too "
					    "short %d\n", tuple.length));
					break;
				} {
					int16_t offset;
					u_long addr, length;
					u_int cksum, sum;
					int i;

					*((u_int16_t *) & offset) =
					    pcmcia_tuple_read_2(&tuple, 0);
					length = pcmcia_tuple_read_2(&tuple, 2);
					cksum = pcmcia_tuple_read_1(&tuple, 4);

					addr = tuple.ptr + offset;

					DPRINTF(("CISTPL_CHECKSUM addr=%lx "
					    "len=%lx cksum=%x",
					    addr, length, cksum));

					/*
					 * XXX do more work to deal with
					 * distant regions
					 */
					if ((addr >= PCMCIA_CIS_SIZE) ||
#if 0
					    ((addr + length) < 0) ||
#endif
					    ((addr + length) >=
					      PCMCIA_CIS_SIZE)) {
						DPRINTF((" skipped, "
						    "too distant\n"));
						break;
					}
					sum = 0;
					for (i = 0; i < length; i++)
						sum +=
						    bus_space_read_1(tuple.memt,
						    tuple.memh,
						    addr + tuple.mult * i);
					if (cksum != (sum & 0xff)) {
						DPRINTF((" failed sum=%x\n",
						    sum));
						aprint_error_dev(sc->dev,
						 "CIS checksum failed\n");
#if 0
						/*
						 * XXX Some working cards have
						 * XXX bad checksums!!
						 */
						ret = -1;
#endif
					} else {
						DPRINTF((" ok\n"));
					}
				}
				break;
			case PCMCIA_CISTPL_LONGLINK_MFC:
				if (tuple.length < 1) {
					DPRINTF(("CISTPL_LONGLINK_MFC too "
					    "short %d\n", tuple.length));
					break;
				}
				if (((tuple.length - 1) % 5) != 0) {
					DPRINTF(("CISTPL_LONGLINK_MFC bogus "
					    "length %d\n", tuple.length));
					break;
				}
				/*
				 * this is kind of ad hoc, as I don't have
				 * any real documentation
				 */
				{
					int i, tmp_count;

					/*
					 * put count into tmp var so that
					 * if we have to bail (because it's
					 * a bogus count) it won't be
					 * remembered for later use.
					 */
					tmp_count =
					    pcmcia_tuple_read_1(&tuple, 0);
					DPRINTF(("CISTPL_LONGLINK_MFC %d",
					    tmp_count));

					/*
					 * make _sure_ it's the right size;
					 * if too short, it may be a weird
					 * (unknown/undefined) format
					 */
					if (tuple.length != (tmp_count*5 + 1)) {
						DPRINTF((" bogus length %d\n",
						    tuple.length));
						break;
					}

#ifdef PCMCIACISDEBUG	/* maybe enable all the time? */
					/*
					 * sanity check for a programming
					 * error which is difficult to find
					 * when debugging.
					 */
					if (tmp_count >
					    howmany(sizeof mfc, sizeof mfc[0]))
						panic("CISTPL_LONGLINK_MFC mfc "
						    "count would blow stack");
#endif

					mfc_count = tmp_count;
					for (i = 0; i < mfc_count; i++) {
						mfc[i].common =
						    (pcmcia_tuple_read_1(&tuple,
						    1 + 5 * i) ==
						    PCMCIA_MFC_MEM_COMMON) ?
						    1 : 0;
						mfc[i].addr =
						    pcmcia_tuple_read_4(&tuple,
						    1 + 5 * i + 1);
						DPRINTF((" %s:%lx",
						    mfc[i].common ? "common" :
						    "attr", mfc[i].addr));
					}
					DPRINTF(("\n"));
				}
				/*
				 * for LONGLINK_MFC, fall through to the
				 * function.  This tuple has structural and
				 * semantic content.
				 */
			default:
				{
					if ((*fct) (&tuple, arg)) {
						pcmcia_chip_mem_unmap(pct,
						    pch, window);
						ret = 1;
						goto done;
					}
				}
				break;
			}	/* switch */
			/* skip to the next tuple */
			tuple.ptr += 2 + tuple.length;
		}

		/*
		 * the chain is done.  Clean up and move onto the next one,
		 * if any.  The loop is here in the case that there is an MFC
		 * card with no longlink (which defaults to existing, == 0).
		 * In general, this means that if one pointer fails, it will
		 * try the next one, instead of just bailing.
		 */

		while (1) {
			pcmcia_chip_mem_unmap(pct, pch, window);

			if (longlink_present) {
				/*
				 * if the longlink is to attribute memory,
				 * then it is unindexed.  That is, if the
				 * link value is 0x100, then the actual
				 * memory address is 0x200.  This means that
				 * we need to multiply by 2 before calling
				 * mem_map, and then divide the resulting ptr
				 * by 2 after.
				 */

				if (!longlink_common)
					longlink_addr *= 2;

				pcmcia_chip_mem_map(pct, pch, longlink_common ?
				    (PCMCIA_WIDTH_MEM8 | PCMCIA_MEM_COMMON) :
				    PCMCIA_MEM_ATTR,
				    longlink_addr, PCMCIA_CIS_SIZE,
				    &pcmh, &tuple.ptr, &window);

				tuple.memt = pcmh.memt;
				tuple.memh = pcmh.memh;

				if (!longlink_common)
					tuple.ptr /= 2;

				DPRINTF(("cis mem map %x\n",
				    (unsigned int) tuple.memh));

				tuple.mult = longlink_common ? 1 : 2;
				longlink_present = 0;
				longlink_common = 1;
				longlink_addr = 0;
			} else if (mfc_count && (mfc_index < mfc_count)) {
				if (!mfc[mfc_index].common)
					mfc[mfc_index].addr *= 2;

				pcmcia_chip_mem_map(pct, pch,
				    mfc[mfc_index].common ?
				    (PCMCIA_WIDTH_MEM8 | PCMCIA_MEM_COMMON) :
				    PCMCIA_MEM_ATTR,
				    mfc[mfc_index].addr, PCMCIA_CIS_SIZE,
				    &pcmh, &tuple.ptr, &window);

				if (!mfc[mfc_index].common)
					tuple.ptr /= 2;

				DPRINTF(("cis mem map %x\n",
				    (unsigned int) tuple.memh));

				/* set parse state, and point at the next one */

				tuple.mult = mfc[mfc_index].common ? 1 : 2;

				mfc_index++;
			} else {
				goto done;
			}

			/* make sure that the link is valid */
			tuple.code = pcmcia_cis_read_1(&tuple, tuple.ptr);
			if (tuple.code != PCMCIA_CISTPL_LINKTARGET) {
				DPRINTF(("CISTPL_LINKTARGET expected, "
				    "code %02x observed\n", tuple.code));
				continue;
			}
			tuple.length = pcmcia_cis_read_1(&tuple, tuple.ptr + 1);
			if (tuple.length < 3) {
				DPRINTF(("CISTPL_LINKTARGET too short %d\n",
				    tuple.length));
				continue;
			}
			if ((pcmcia_tuple_read_1(&tuple, 0) != 'C') ||
			    (pcmcia_tuple_read_1(&tuple, 1) != 'I') ||
			    (pcmcia_tuple_read_1(&tuple, 2) != 'S')) {
				DPRINTF(("CISTPL_LINKTARGET magic "
				    "%02x%02x%02x incorrect\n",
				    pcmcia_tuple_read_1(&tuple, 0),
				    pcmcia_tuple_read_1(&tuple, 1),
				    pcmcia_tuple_read_1(&tuple, 2)));
				continue;
			}
			tuple.ptr += 2 + tuple.length;

			break;
		}
	}

	pcmcia_chip_mem_unmap(pct, pch, window);

done:
	/* Last, free the allocated memory block */
	pcmcia_chip_mem_free(pct, pch, &pcmh);

	return (ret);
}

/* XXX this is incredibly verbose.  Not sure what trt is */

void
pcmcia_print_cis(sc)
	struct pcmcia_softc *sc;
{
	struct pcmcia_card *card = &sc->card;
	struct pcmcia_function *pf;
	struct pcmcia_config_entry *cfe;
	int i;

	printf("%s: CIS version ", device_xname(sc->dev));
	if (card->cis1_major == 4) {
		if (card->cis1_minor == 0)
			printf("PCMCIA 1.0\n");
		else if (card->cis1_minor == 1)
			printf("PCMCIA 2.0 or 2.1\n");
	} else if (card->cis1_major >= 5)
		printf("PC Card Standard %d.%d\n", card->cis1_major, card->cis1_minor);
	else
		printf("unknown (major=%d, minor=%d)\n",
		    card->cis1_major, card->cis1_minor);

	printf("%s: CIS info: ", device_xname(sc->dev));
	for (i = 0; i < 4; i++) {
		if (card->cis1_info[i] == NULL)
			break;
		if (i)
			printf(", ");
		printf("%s", card->cis1_info[i]);
	}
	printf("\n");

	printf("%s: Manufacturer code 0x%x, product 0x%x\n",
	       device_xname(sc->dev), card->manufacturer, card->product);

	SIMPLEQ_FOREACH(pf, &card->pf_head, pf_list) {
		printf("%s: function %d: ", device_xname(sc->dev), pf->number);

		switch (pf->function) {
		case PCMCIA_FUNCTION_UNSPEC:
			printf("unspecified");
			break;
		case PCMCIA_FUNCTION_MULTIFUNCTION:
			printf("multi-function");
			break;
		case PCMCIA_FUNCTION_MEMORY:
			printf("memory");
			break;
		case PCMCIA_FUNCTION_SERIAL:
			printf("serial port");
			break;
		case PCMCIA_FUNCTION_PARALLEL:
			printf("parallel port");
			break;
		case PCMCIA_FUNCTION_DISK:
			printf("fixed disk");
			switch (pf->pf_funce_disk_interface) {
			case PCMCIA_TPLFE_DDI_PCCARD_ATA:
				printf("(ata)");
				break;
			default:
				break;
			}
			break;
		case PCMCIA_FUNCTION_VIDEO:
			printf("video adapter");
			break;
		case PCMCIA_FUNCTION_NETWORK:
			printf("network adapter");
			break;
		case PCMCIA_FUNCTION_AIMS:
			printf("auto incrementing mass storage");
			break;
		case PCMCIA_FUNCTION_SCSI:
			printf("SCSI bridge");
			break;
		case PCMCIA_FUNCTION_SECURITY:
			printf("Security services");
			break;
		case PCMCIA_FUNCTION_INSTRUMENT:
			printf("Instrument");
			break;
		default:
			printf("unknown (%d)", pf->function);
			break;
		}

		printf(", ccr addr %lx mask %lx\n", pf->ccr_base, pf->ccr_mask);

		SIMPLEQ_FOREACH(cfe, &pf->cfe_head, cfe_list) {
			printf("%s: function %d, config table entry %d: ",
			    device_xname(sc->dev), pf->number, cfe->number);

			switch (cfe->iftype) {
			case PCMCIA_IFTYPE_MEMORY:
				printf("memory card");
				break;
			case PCMCIA_IFTYPE_IO:
				printf("I/O card");
				break;
			default:
				printf("card type unknown");
				break;
			}

			printf("; irq mask %x", cfe->irqmask);

			if (cfe->num_iospace) {
				printf("; iomask %lx, iospace", cfe->iomask);

				for (i = 0; i < cfe->num_iospace; i++) {
					printf(" %lx", cfe->iospace[i].start);
					if (cfe->iospace[i].length)
						printf("-%lx",
						    cfe->iospace[i].start +
						    cfe->iospace[i].length - 1);
				}
			}
			if (cfe->num_memspace) {
				printf("; memspace");

				for (i = 0; i < cfe->num_memspace; i++) {
					printf(" %lx",
					    cfe->memspace[i].cardaddr);
					if (cfe->memspace[i].length)
						printf("-%lx",
						    cfe->memspace[i].cardaddr +
						    cfe->memspace[i].length - 1);
					if (cfe->memspace[i].hostaddr)
						printf("@%lx",
						    cfe->memspace[i].hostaddr);
				}
			}
			if (cfe->maxtwins)
				printf("; maxtwins %d", cfe->maxtwins);

			printf(";");

			if (cfe->flags & PCMCIA_CFE_MWAIT_REQUIRED)
				printf(" mwait_required");
			if (cfe->flags & PCMCIA_CFE_RDYBSY_ACTIVE)
				printf(" rdybsy_active");
			if (cfe->flags & PCMCIA_CFE_WP_ACTIVE)
				printf(" wp_active");
			if (cfe->flags & PCMCIA_CFE_BVD_ACTIVE)
				printf(" bvd_active");
			if (cfe->flags & PCMCIA_CFE_IO8)
				printf(" io8");
			if (cfe->flags & PCMCIA_CFE_IO16)
				printf(" io16");
			if (cfe->flags & PCMCIA_CFE_IRQSHARE)
				printf(" irqshare");
			if (cfe->flags & PCMCIA_CFE_IRQPULSE)
				printf(" irqpulse");
			if (cfe->flags & PCMCIA_CFE_IRQLEVEL)
				printf(" irqlevel");
			if (cfe->flags & PCMCIA_CFE_POWERDOWN)
				printf(" powerdown");
			if (cfe->flags & PCMCIA_CFE_READONLY)
				printf(" readonly");
			if (cfe->flags & PCMCIA_CFE_AUDIO)
				printf(" audio");

			printf("\n");
		}
	}

	if (card->error)
		printf("%s: %d errors found while parsing CIS\n",
		    device_xname(sc->dev), card->error);
}

int
pcmcia_parse_cis_tuple(tuple, arg)
	struct pcmcia_tuple *tuple;
	void *arg;
{
	struct cis_state *state = arg;

	switch (tuple->code) {
	case PCMCIA_CISTPL_END:
		decode_end(tuple, state);
		break;

	case PCMCIA_CISTPL_LONGLINK_MFC:
		decode_longlink_mfc(tuple, state);
		break;

	case PCMCIA_CISTPL_DEVICE:
	case PCMCIA_CISTPL_DEVICE_A:
		decode_device(tuple, state);
		break;

	case PCMCIA_CISTPL_VERS_1:
		decode_vers_1(tuple, state);
		break;

	case PCMCIA_CISTPL_MANFID:
		decode_manfid(tuple, state);
		break;

	case PCMCIA_CISTPL_FUNCID:
		decode_funcid(tuple, state);
		break;

	case PCMCIA_CISTPL_FUNCE:
		decode_funce(tuple, state);
		break;

	case PCMCIA_CISTPL_CONFIG:
		decode_config(tuple, state);
		break;

	case PCMCIA_CISTPL_CFTABLE_ENTRY:
		decode_cftable_entry(tuple, state);
		break;
	default:
		DPRINTF(("unhandled CISTPL %x\n", tuple->code));
		break;
	}

	return (0);
}

static void
decode_end(struct pcmcia_tuple *tuple, struct cis_state *state)
{
	/* if we've seen a LONGLINK_MFC, and this is the first
	 * END after it, reset the function list.
	 *
	 * XXX This might also be the right place to start a
	 * new function, but that assumes that a function
	 * definition never crosses any longlink, and I'm not
	 * sure about that.  This is probably safe for MFC
	 * cards, but what we have now isn't broken, so I'd
	 * rather not change it.
	 */
	if (state->gotmfc == 1) {
		state->gotmfc = 2;
		state->count = 0;
		state->pf = NULL;

		pcmcia_free_pf(&state->card->pf_head);
	}
}

static void
decode_longlink_mfc(struct pcmcia_tuple *tuple,
    struct cis_state *state)
{
	/*
	 * this tuple's structure was dealt with in scan_cis.  here,
	 * record the fact that the MFC tuple was seen, so that
	 * functions declared before the MFC link can be cleaned
	 * up.
	 */
	if (state->gotmfc == 0) {
		state->gotmfc = 1;
	} else {
		DPRINTF(("got LONGLINK_MFC again!"));
	}
}

static void
decode_device(struct pcmcia_tuple *tuple,
    struct cis_state *state)
{
#ifdef PCMCIACISDEBUG
	u_int reg, dtype, dspeed;

	reg = pcmcia_tuple_read_1(tuple, 0);
	dtype = reg & PCMCIA_DTYPE_MASK;
	dspeed = reg & PCMCIA_DSPEED_MASK;

	DPRINTF(("CISTPL_DEVICE%s type=",
	(tuple->code == PCMCIA_CISTPL_DEVICE) ? "" : "_A"));
	switch (dtype) {
	case PCMCIA_DTYPE_NULL:
		DPRINTF(("null"));
		break;
	case PCMCIA_DTYPE_ROM:
		DPRINTF(("rom"));
		break;
	case PCMCIA_DTYPE_OTPROM:
		DPRINTF(("otprom"));
		break;
	case PCMCIA_DTYPE_EPROM:
		DPRINTF(("eprom"));
		break;
	case PCMCIA_DTYPE_EEPROM:
		DPRINTF(("eeprom"));
		break;
	case PCMCIA_DTYPE_FLASH:
		DPRINTF(("flash"));
		break;
	case PCMCIA_DTYPE_SRAM:
		DPRINTF(("sram"));
		break;
	case PCMCIA_DTYPE_DRAM:
		DPRINTF(("dram"));
		break;
	case PCMCIA_DTYPE_FUNCSPEC:
		DPRINTF(("funcspec"));
		break;
	case PCMCIA_DTYPE_EXTEND:
		DPRINTF(("extend"));
		break;
	default:
		DPRINTF(("reserved"));
		break;
	}
	DPRINTF((" speed="));
	switch (dspeed) {
	case PCMCIA_DSPEED_NULL:
		DPRINTF(("null"));
		break;
	case PCMCIA_DSPEED_250NS:
		DPRINTF(("250ns"));
		break;
	case PCMCIA_DSPEED_200NS:
		DPRINTF(("200ns"));
		break;
	case PCMCIA_DSPEED_150NS:
		DPRINTF(("150ns"));
		break;
	case PCMCIA_DSPEED_100NS:
		DPRINTF(("100ns"));
		break;
	case PCMCIA_DSPEED_EXT:
		DPRINTF(("ext"));
		break;
	default:
		DPRINTF(("reserved"));
		break;
	}
	DPRINTF(("\n"));
#endif
}

static void
decode_vers_1(struct pcmcia_tuple *tuple, struct cis_state *state)
{
	int start, i, ch, count;

	if (tuple->length < 6) {
		DPRINTF(("CISTPL_VERS_1 too short %d\n",
		    tuple->length));
		return;
	}
	state->card->cis1_major = pcmcia_tuple_read_1(tuple, 0);
	state->card->cis1_minor = pcmcia_tuple_read_1(tuple, 1);

	for (count = 0, start = 0, i = 0;
	    (count < 4) && ((i + 4) < 256); i++) {
		ch = pcmcia_tuple_read_1(tuple, 2 + i);
		if (ch == 0xff) {
			if (i > start) {
				state->card->cis1_info_buf[i] = 0;
				state->card->cis1_info[count] =
				    state->card->cis1_info_buf + start;
			}
			break;
		}
		state->card->cis1_info_buf[i] = ch;
		if (ch == 0) {
			state->card->cis1_info[count] =
			    state->card->cis1_info_buf + start;
			start = i + 1;
			count++;
		}
	}
	DPRINTF(("CISTPL_VERS_1\n"));
}

static void
decode_manfid(struct pcmcia_tuple *tuple, struct cis_state *state)
{
	if (tuple->length < 4) {
		DPRINTF(("CISTPL_MANFID too short %d\n",
		    tuple->length));
		return;
	}
	state->card->manufacturer = pcmcia_tuple_read_2(tuple, 0);
	state->card->product = pcmcia_tuple_read_2(tuple, 2);
	DPRINTF(("CISTPL_MANFID\n"));
}

static void
decode_funcid(struct pcmcia_tuple *tuple, struct cis_state *state)
{
	if (tuple->length < 1) {
		DPRINTF(("CISTPL_FUNCID too short %d\n",
		    tuple->length));
		return;
	}
	if (state->pf) {
		if (state->pf->function == PCMCIA_FUNCTION_UNSPEC) {
			/*
			 * This looks like a opportunistic function
			 * created by a CONFIG tuple.  Just keep it.
			 */
		} else {
			/*
			 * A function is being defined, end it.
			 */
			state->pf = NULL;
		}
	}
	if (state->pf == NULL)
		create_pf(state);
	state->pf->function = pcmcia_tuple_read_1(tuple, 0);

	DPRINTF(("CISTPL_FUNCID\n"));
}

static void
decode_funce(struct pcmcia_tuple *tuple, struct cis_state *state)
{
	struct pcmcia_function *pf = state->pf;
	int type = pcmcia_tuple_read_1(tuple, 0);

	if (state->pf == NULL || state->pf->function <= 0) {
		DPRINTF(("CISTPL_FUNCE is not followed by "
		    "valid CISTPL_FUNCID\n"));
		return;
	}
	if (tuple->length < 2)
		return;
	switch (pf->function) {
	case PCMCIA_FUNCTION_DISK:
		if (type == PCMCIA_TPLFE_TYPE_DISK_DEVICE_INTERFACE) {
			pf->pf_funce_disk_interface
			    = pcmcia_tuple_read_1(tuple, 1);
		}
		break;
	case PCMCIA_FUNCTION_NETWORK:
		if (type == PCMCIA_TPLFE_TYPE_LAN_NID) {
			int i;
			int len = pcmcia_tuple_read_1(tuple, 1);
			if (tuple->length < 2 + len || len > 8) {
				/* tuple length not enough or nid too long */
				break;
			}
			for (i = 0; i < len; ++i) {
				pf->pf_funce_lan_nid[i]
				    = pcmcia_tuple_read_1(tuple, 2 + i);
			}
			pf->pf_funce_lan_nidlen = len;
		}
		break;
	default:
		break;
	}

	return;
}

static void
decode_config(struct pcmcia_tuple *tuple, struct cis_state *state)
{
	u_int reg, rasz, rmsz, rfsz;
	int i;
	/* most of these are educated guesses */
	static const struct pcmcia_config_entry init_cfe = {
		.number	= -1,
		.flags	= PCMCIA_CFE_RDYBSY_ACTIVE | PCMCIA_CFE_WP_ACTIVE |
			  PCMCIA_CFE_BVD_ACTIVE,
		.iftype	= PCMCIA_IFTYPE_MEMORY,
	};

	if (tuple->length < 3) {
		DPRINTF(("CISTPL_CONFIG too short %d\n", tuple->length));
		return;
	} 
	reg = pcmcia_tuple_read_1(tuple, 0);
	rasz = 1 + ((reg & PCMCIA_TPCC_RASZ_MASK) >>
	    PCMCIA_TPCC_RASZ_SHIFT);
	rmsz = 1 + ((reg & PCMCIA_TPCC_RMSZ_MASK) >>
	    PCMCIA_TPCC_RMSZ_SHIFT);
	rfsz = ((reg & PCMCIA_TPCC_RFSZ_MASK) >>
	    PCMCIA_TPCC_RFSZ_SHIFT);

	if (tuple->length < (rasz + rmsz + rfsz)) {
		DPRINTF(("CISTPL_CONFIG (%d,%d,%d) too short %d\n",
		    rasz, rmsz, rfsz, tuple->length));
		return;
	}
	if (state->pf == NULL) {
		create_pf(state);
		state->pf->function = PCMCIA_FUNCTION_UNSPEC;
	}
	state->pf->last_config_index =
	    pcmcia_tuple_read_1(tuple, 1);

	state->pf->ccr_base = 0;
	for (i = 0; i < rasz; i++)
		state->pf->ccr_base |= ((pcmcia_tuple_read_1(tuple, 2 + i)) <<
		    (i * 8));

	state->pf->ccr_mask = 0;
	for (i = 0; i < rmsz; i++)
		state->pf->ccr_mask |= ((pcmcia_tuple_read_1(tuple,
		    2 + rasz + i)) << (i * 8));

	/* skip the reserved area and subtuples */

	/* reset the default cfe for each cfe list */
	state->temp_cfe = init_cfe;
	state->default_cfe = &state->temp_cfe;
	DPRINTF(("CISTPL_CONFIG\n"));
}

static void
decode_cftable_entry(struct pcmcia_tuple *tuple, struct cis_state *state)
{
	int idx, i, j;
	u_int reg, reg2;
	u_int intface, def, num;
	u_int power, timing, iospace, irq, memspace, misc;
	struct pcmcia_config_entry *cfe;

	idx = 0;

	reg = pcmcia_tuple_read_1(tuple, idx);
	idx++;
	intface = reg & PCMCIA_TPCE_INDX_INTFACE;
	def = reg & PCMCIA_TPCE_INDX_DEFAULT;
	num = reg & PCMCIA_TPCE_INDX_NUM_MASK;

	/*
	 * this is a little messy.  Some cards have only a
	 * cfentry with the default bit set.  So, as we go
	 * through the list, we add new indexes to the queue,
	 * and keep a pointer to the last one with the
	 * default bit set.  if we see a record with the same
	 * index, as the default, we stash the default and
	 * replace the queue entry. otherwise, we just add
	 * new entries to the queue, pointing the default ptr
	 * at them if the default bit is set.  if we get to
	 * the end with the default pointer pointing at a
	 * record which hasn't had a matching index, that's
	 * ok; it just becomes a cfentry like any other.
	 */

	/*
	 * if the index in the cis differs from the default
	 * cis, create new entry in the queue and start it
	 * with the current default
	 */
	if (state->default_cfe == NULL) {
		DPRINTF(("CISTPL_CFTABLE_ENTRY with no "
		    "default\n"));
		return;
	}
	if (num != state->default_cfe->number) {
		cfe = malloc(sizeof(*cfe), M_DEVBUF, M_NOWAIT);
		if (cfe == NULL) {
			printf("Cannot allocate cfe entry\n");
			return;
		}

		*cfe = *state->default_cfe;

		SIMPLEQ_INSERT_TAIL(&state->pf->cfe_head, cfe, cfe_list);

		cfe->number = num;

		/*
		 * if the default bit is set in the cis, then
		 * point the new default at whatever is being
		 * filled in
		 */
		if (def)
			state->default_cfe = cfe;
	} else {
		/*
		 * the cis index matches the default index,
		 * fill in the default cfentry.  It is
		 * assumed that the cfdefault index is in the
		 * queue.  For it to be otherwise, the cis
		 * index would have to be -1 (initial
		 * condition) which is not possible, or there
		 * would have to be a preceding cis entry
		 * which had the same cis index and had the
		 * default bit unset. Neither condition
		 * should happen.  If it does, this cfentry
		 * is lost (written into temp space), which
		 * is an acceptable failure mode.
		 */

		cfe = state->default_cfe;

		/*
		 * if the cis entry does not have the default
		 * bit set, copy the default out of the way
		 * first.
		 */
		if (!def) {
			state->temp_cfe = *state->default_cfe;
			state->default_cfe = &state->temp_cfe;
		}
	}

	if (intface) {
		reg = pcmcia_tuple_read_1(tuple, idx);
		idx++;
		cfe->flags &= ~(PCMCIA_CFE_MWAIT_REQUIRED
		    | PCMCIA_CFE_RDYBSY_ACTIVE
		    | PCMCIA_CFE_WP_ACTIVE
		    | PCMCIA_CFE_BVD_ACTIVE);
		if (reg & PCMCIA_TPCE_IF_MWAIT)
			cfe->flags |= PCMCIA_CFE_MWAIT_REQUIRED;
		if (reg & PCMCIA_TPCE_IF_RDYBSY)
			cfe->flags |= PCMCIA_CFE_RDYBSY_ACTIVE;
		if (reg & PCMCIA_TPCE_IF_WP)
			cfe->flags |= PCMCIA_CFE_WP_ACTIVE;
		if (reg & PCMCIA_TPCE_IF_BVD)
			cfe->flags |= PCMCIA_CFE_BVD_ACTIVE;
		cfe->iftype = reg & PCMCIA_TPCE_IF_IFTYPE;
	}
	reg = pcmcia_tuple_read_1(tuple, idx);
	idx++;

	power = reg & PCMCIA_TPCE_FS_POWER_MASK;
	timing = reg & PCMCIA_TPCE_FS_TIMING;
	iospace = reg & PCMCIA_TPCE_FS_IOSPACE;
	irq = reg & PCMCIA_TPCE_FS_IRQ;
	memspace = reg & PCMCIA_TPCE_FS_MEMSPACE_MASK;
	misc = reg & PCMCIA_TPCE_FS_MISC;

	if (power) {
		/* skip over power, don't save */
		/* for each parameter selection byte */
		for (i = 0; i < power; i++) {
			reg = pcmcia_tuple_read_1(tuple, idx);
			idx++;
			/* for each bit */
			for (j = 0; j < 7; j++) {
				/* if the bit is set */
				if ((reg >> j) & 0x01) {
					/* skip over bytes */
					do {
						reg2 = pcmcia_tuple_read_1(tuple, idx);
						idx++;
						/*
						 * until
						 * non-
						 * extension
						 * byte
						 */
					} while (reg2 & 0x80);
				}
			}
		}
	}
	if (timing) {
		/* skip over timing, don't save */
		reg = pcmcia_tuple_read_1(tuple, idx);
		idx++;

		if ((reg & PCMCIA_TPCE_TD_RESERVED_MASK) !=
		    PCMCIA_TPCE_TD_RESERVED_MASK)
			idx++;
		if ((reg & PCMCIA_TPCE_TD_RDYBSY_MASK) !=
		    PCMCIA_TPCE_TD_RDYBSY_MASK)
			idx++;
		if ((reg & PCMCIA_TPCE_TD_WAIT_MASK) !=
		    PCMCIA_TPCE_TD_WAIT_MASK)
			idx++;
	}
	if (iospace) {
		if (tuple->length <= idx) {
			DPRINTF(("ran out of space before TCPE_IO\n"));
			goto abort_cfe;
		}

		reg = pcmcia_tuple_read_1(tuple, idx);
		idx++;

		cfe->flags &=
		    ~(PCMCIA_CFE_IO8 | PCMCIA_CFE_IO16);
		if (reg & PCMCIA_TPCE_IO_BUSWIDTH_8BIT)
			cfe->flags |= PCMCIA_CFE_IO8;
		if (reg & PCMCIA_TPCE_IO_BUSWIDTH_16BIT)
			cfe->flags |= PCMCIA_CFE_IO16;
		cfe->iomask =
		    reg & PCMCIA_TPCE_IO_IOADDRLINES_MASK;

		if (reg & PCMCIA_TPCE_IO_HASRANGE) {
			reg = pcmcia_tuple_read_1(tuple, idx);
			idx++;

			cfe->num_iospace = 1 + (reg &
			    PCMCIA_TPCE_IO_RANGE_COUNT);

			if (cfe->num_iospace >
			    (sizeof(cfe->iospace) /
			     sizeof(cfe->iospace[0]))) {
				DPRINTF(("too many io "
				    "spaces %d",
				    cfe->num_iospace));
				state->card->error++;
				return;
			}
			for (i = 0; i < cfe->num_iospace; i++) {
				switch (reg & PCMCIA_TPCE_IO_RANGE_ADDRSIZE_MASK) {
				case PCMCIA_TPCE_IO_RANGE_ADDRSIZE_NONE:
					cfe->iospace[i].start =
					    0;
					break;
				case PCMCIA_TPCE_IO_RANGE_ADDRSIZE_ONE:
					cfe->iospace[i].start =
						pcmcia_tuple_read_1(tuple, idx);
					idx++;
					break;
				case PCMCIA_TPCE_IO_RANGE_ADDRSIZE_TWO:
					cfe->iospace[i].start =
						pcmcia_tuple_read_2(tuple, idx);
					idx += 2;
					break;
				case PCMCIA_TPCE_IO_RANGE_ADDRSIZE_FOUR:
					cfe->iospace[i].start =
						pcmcia_tuple_read_4(tuple, idx);
					idx += 4;
					break;
				}
				switch (reg &
					PCMCIA_TPCE_IO_RANGE_LENGTHSIZE_MASK) {
				case PCMCIA_TPCE_IO_RANGE_LENGTHSIZE_NONE:
					cfe->iospace[i].length =
					    0;
					break;
				case PCMCIA_TPCE_IO_RANGE_LENGTHSIZE_ONE:
					cfe->iospace[i].length =
						pcmcia_tuple_read_1(tuple, idx);
					idx++;
					break;
				case PCMCIA_TPCE_IO_RANGE_LENGTHSIZE_TWO:
					cfe->iospace[i].length =
						pcmcia_tuple_read_2(tuple, idx);
					idx += 2;
					break;
				case PCMCIA_TPCE_IO_RANGE_LENGTHSIZE_FOUR:
					cfe->iospace[i].length =
						pcmcia_tuple_read_4(tuple, idx);
					idx += 4;
					break;
				}
				cfe->iospace[i].length++;
			}
		} else {
			cfe->num_iospace = 1;
			cfe->iospace[0].start = 0;
			cfe->iospace[0].length =
			    (1 << cfe->iomask);
		}
	}
	if (irq) {
		if (tuple->length <= idx) {
			DPRINTF(("ran out of space before TCPE_IR\n"));
			goto abort_cfe;
		}

		reg = pcmcia_tuple_read_1(tuple, idx);
		idx++;

		cfe->flags &= ~(PCMCIA_CFE_IRQSHARE
		    | PCMCIA_CFE_IRQPULSE
		    | PCMCIA_CFE_IRQLEVEL);
		if (reg & PCMCIA_TPCE_IR_SHARE)
			cfe->flags |= PCMCIA_CFE_IRQSHARE;
		if (reg & PCMCIA_TPCE_IR_PULSE)
			cfe->flags |= PCMCIA_CFE_IRQPULSE;
		if (reg & PCMCIA_TPCE_IR_LEVEL)
			cfe->flags |= PCMCIA_CFE_IRQLEVEL;

		if (reg & PCMCIA_TPCE_IR_HASMASK) {
			/*
			 * it's legal to ignore the
			 * special-interrupt bits, so I will
			 */

			cfe->irqmask =
			    pcmcia_tuple_read_2(tuple, idx);
			idx += 2;
		} else {
			cfe->irqmask =
			    (1 << (reg & PCMCIA_TPCE_IR_IRQ));
		}
	}
	if (memspace) {
		int lengthsize;
		int cardaddrsize;
		int hostaddrsize;

		if (tuple->length <= idx) {
			DPRINTF(("ran out of space before TCPE_MS\n"));
			goto abort_cfe;
		}

		switch (memspace) {
#ifdef notdef	/* This is 0 */
		case PCMCIA_TPCE_FS_MEMSPACE_NONE:
			cfe->num_memspace = 0;
			break;
#endif

		case PCMCIA_TPCE_FS_MEMSPACE_LENGTH:
			cfe->num_memspace = 1;
			cfe->memspace[0].length = 256 *
			    pcmcia_tuple_read_2(tuple, idx);
			idx += 2;
			cfe->memspace[0].cardaddr = 0;
			cfe->memspace[0].hostaddr = 0;
			break;

		case PCMCIA_TPCE_FS_MEMSPACE_LENGTHADDR:
			cfe->num_memspace = 1;
			cfe->memspace[0].length = 256 *
			    pcmcia_tuple_read_2(tuple, idx);
			idx += 2;
			cfe->memspace[0].cardaddr = 256 *
			    pcmcia_tuple_read_2(tuple, idx);
			idx += 2;
			cfe->memspace[0].hostaddr =
			    cfe->memspace[0].cardaddr;
			break;

		default:
			reg = pcmcia_tuple_read_1(tuple, idx);
			idx++;

			cfe->num_memspace = (reg & PCMCIA_TPCE_MS_COUNT)
			    + 1;

			if (cfe->num_memspace >
			    (sizeof(cfe->memspace) /
				sizeof(cfe->memspace[0]))) {
				DPRINTF(("too many mem spaces %d",
				    cfe->num_memspace));
				state->card->error++;
				return;
			}
			lengthsize = 
			    ((reg & PCMCIA_TPCE_MS_LENGTH_SIZE_MASK) >>
				PCMCIA_TPCE_MS_LENGTH_SIZE_SHIFT);
			cardaddrsize =
			    ((reg &
				PCMCIA_TPCE_MS_CARDADDR_SIZE_MASK) >>
				  PCMCIA_TPCE_MS_CARDADDR_SIZE_SHIFT);
			hostaddrsize =
			    (reg & PCMCIA_TPCE_MS_HOSTADDR) ?
			    cardaddrsize : 0;

			if (lengthsize == 0) {
				DPRINTF(("cfe memspace "
				    "lengthsize == 0"));
				state->card->error++;
			}
			for (i = 0; i < cfe->num_memspace; i++) {
				if (lengthsize) {
					cfe->memspace[i].length = 256 *
					    pcmcia_tuple_read_n(tuple,
						lengthsize, idx);
					idx += lengthsize;
				} else {
					cfe->memspace[i].length = 0;
				}
				if (cfe->memspace[i].length == 0) {
					DPRINTF(("cfe->memspace" 
					    "[%d].length == 0", i));
					state->card->error++;
				}
				if (cardaddrsize) {
					cfe->memspace[i].cardaddr =
					    256 * 
					    pcmcia_tuple_read_n(tuple,
						cardaddrsize, idx);
					idx += cardaddrsize;
				} else {
					cfe->memspace[i].cardaddr = 0;
				}
				if (hostaddrsize) {
					cfe->memspace[i].hostaddr =
					    256 *
					    pcmcia_tuple_read_n(tuple,
						hostaddrsize, idx);
					idx += hostaddrsize;
				} else {
					cfe->memspace[i].hostaddr = 0;
				}
			}
		}
	}

	if (misc) {
		if (tuple->length <= idx) {
			DPRINTF(("ran out of space before TCPE_MI\n"));
			goto abort_cfe;
		}

		reg = pcmcia_tuple_read_1(tuple, idx);
		idx++;

		cfe->flags &= ~(PCMCIA_CFE_POWERDOWN
		    | PCMCIA_CFE_READONLY
		    | PCMCIA_CFE_AUDIO);
		if (reg & PCMCIA_TPCE_MI_PWRDOWN)
			cfe->flags |= PCMCIA_CFE_POWERDOWN;
		if (reg & PCMCIA_TPCE_MI_READONLY)
			cfe->flags |= PCMCIA_CFE_READONLY;
		if (reg & PCMCIA_TPCE_MI_AUDIO)
			cfe->flags |= PCMCIA_CFE_AUDIO;
		cfe->maxtwins = reg & PCMCIA_TPCE_MI_MAXTWINS;

		while (reg & PCMCIA_TPCE_MI_EXT) {
			reg = pcmcia_tuple_read_1(tuple, idx);
			idx++;
		}
	}

	/* skip all the subtuples */
abort_cfe:
	DPRINTF(("CISTPL_CFTABLE_ENTRY\n"));
}