root/sys/dev/std/ieee1212.c
/*      $NetBSD: ieee1212.c,v 1.15 2021/08/07 16:19:16 thorpej Exp $    */

/*
 * Copyright (c) 2000 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by James Chacon.
 *
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``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 FOUNDATION OR CONTRIBUTORS
 * 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: ieee1212.c,v 1.15 2021/08/07 16:19:16 thorpej Exp $");

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

#include <dev/std/ieee1212reg.h>
#include <dev/std/ieee1212var.h>

static const char * const p1212_keytype_strings[] = P1212_KEYTYPE_STRINGS ;
static const char * const p1212_keyvalue_strings[] = P1212_KEYVALUE_STRINGS ;

static u_int16_t p1212_calc_crc(u_int32_t, u_int32_t *, int, int);
static int p1212_parse_directory(struct p1212_dir *, u_int32_t *, u_int32_t);
static struct p1212_leafdata *p1212_parse_leaf(u_int32_t *);
static int p1212_parse_textdir(struct p1212_com *, u_int32_t *);
static struct p1212_textdata *p1212_parse_text_desc(u_int32_t *);
static void p1212_print_node(struct p1212_key *, void *);
static int p1212_validate_offset(u_int16_t, u_int32_t);
static int p1212_validate_immed(u_int16_t, u_int32_t);
static int p1212_validate_leaf(u_int16_t, u_int32_t);
static int p1212_validate_dir(u_int16_t, u_int32_t);

#ifdef P1212_DEBUG
#define DPRINTF(x)      if (p1212debug) printf x
#define DPRINTFN(n,x)   if (p1212debug>(n)) printf x
int     p1212debug = 1;
#else
#define DPRINTF(x)
#define DPRINTFN(n,x)
#endif

/*
 * Routines to parse the ROM into a tree that's usable. Also verify integrity
 * vs. the P1212 standard
 */

/*
 * A buffer of u_int32_t's and a size in quads gets passed in. The output will
 * return -1 on error, or 0 on success and possibly reset *size to a larger
 * value.
 *
 * NOTE: Rom's are guaranteed per the ISO spec to be contiguous but only the
 * first 1k is directly mapped. Anything past 1k is supposed to use a loop
 * around the indirect registers to read in the rom. This code only assumes the
 * buffer passed in represents a total rom regardless of end size. It is the
 * callers responsibility to treat a size > 1024 as a special case.
 */

int
p1212_iscomplete(u_int32_t *t, u_int32_t *size)
{
        u_int16_t infolen, crclen, len;
        u_int32_t newlen, offset, test;
        int complete, i, numdirs, type, val, *dirs;

        dirs = NULL;

        if (*size == 0) {
                DPRINTF(("Invalid size for ROM: %d\n", (unsigned int)*size));
                return -1;
        }

        infolen = P1212_ROMFMT_GET_INFOLEN((ntohl(t[0])));
        if (infolen <= 1) {
                DPRINTF(("ROM not initialized or minimal ROM: Info "
                    "length: %d\n", infolen));
                return -1;
        }
        crclen = P1212_ROMFMT_GET_CRCLEN((ntohl(t[0])));
        if (crclen < infolen) {
                DPRINTF(("CRC len less than info len. CRC len: %d, "
                    "Info len: %d\n", crclen, infolen));
                return -1;
        }

        /*
         * Now loop through it to check if all the offsets referenced are
         * within the image stored so far. If not, get those as well.
         */

        offset = P1212_ROMFMT_GET_INFOLEN((ntohl(t[0]))) + 1;

        /*
         * Make sure at least the bus info block is in memory + the root dir
         * header quad. Add 1 here since offset is an array offset and size is
         * the total array size we want. If this is getting the root dir
         * then add another since infolen doesn't end on the root dir entry but
         * right before it.
         */

        if ((*size == 1) || (*size < (offset + 1))) {
                *size = (crclen > infolen) ? crclen : infolen;
                if (crclen == infolen)
                        (*size)++;
                (*size)++;
                return 0;
        }

        complete = 0;
        numdirs = 0;
        newlen = 0;

        while (!complete) {

                /*
                 * Make sure the whole directory is in memory. If not, bail now
                 * and read it in.
                 */

                newlen = P1212_DIRENT_GET_LEN((ntohl(t[offset])));
                if ((offset + newlen + 1) > *size) {
                        newlen += offset + 1;
                        break;
                }

                if (newlen == 0) {
                        DPRINTF(("Impossible directory length of 0!\n"));
                        return -1;
                }

                /*
                 * Starting with the first byte of the directory, read through
                 * and check the values found. On offsets and directories read
                 * them in if appropriate (always for offsets, if not in memory
                 * for leaf/directories).
                 */

                offset++;
                len = newlen;
                newlen = 0;
                for (i = 0; i < len; i++) {
                        type = P1212_DIRENT_GET_KEYTYPE((ntohl(t[offset+i])));
                        val = P1212_DIRENT_GET_VALUE((ntohl(t[offset+i])));
                        switch (type) {
                        case P1212_KEYTYPE_Immediate:
                        case P1212_KEYTYPE_Offset:
                                break;
                        case P1212_KEYTYPE_Leaf:

                                /*
                                 * If a leaf is found, and it's beyond the
                                 * current rom length and it's beyond the
                                 * current newlen setting,
                                 * then set newlen accordingly.
                                 */

                                test = offset + i + val + 1;
                                if ((test > *size) && (test > newlen)) {
                                        newlen = test;
                                        break;
                                }

                                /*
                                 * For leaf nodes just make sure the whole leaf
                                 * length is in the buffer. There's no data
                                 * inside of them that can refer to outside
                                 * nodes. (Uless it's vendor specific and then
                                 * you're on your own anyways).
                                 */

                                test--;
                                infolen =
                                    P1212_DIRENT_GET_LEN((ntohl(t[test])));
                                test++;
                                test += infolen;
                                if ((test > *size) && (test > newlen)) {
                                        newlen = test;
                                }
                                break;

                        case P1212_KEYTYPE_Directory:

                                /* Make sure the first quad is in memory. */

                                test = offset + i + val + 1;
                                if ((test > *size) && (test > newlen)) {
                                        newlen = test;
                                        break;
                                }

                                /*
                                 * Can't just walk the ROM looking at type
                                 * codes since these are only valid on
                                 * directory entries. So save any directories
                                 * we find into a queue and the bottom of the
                                 * while loop will pop the last one off and
                                 * walk that directory.
                                 */

                                test--;
                                dirs = realloc(dirs,
                                    sizeof(int) * (numdirs + 1), M_DEVBUF,
                                    M_WAITOK);
                                dirs[numdirs++] = test;
                                break;
                        default:
                                panic("Impossible type code: 0x%04hx",
                                    (unsigned short)type);
                                break;
                        }
                }

                if (newlen) {
                        /* Cleanup. */
                        if (dirs)
                                free(dirs, M_DEVBUF);
                        break;
                }
                if (dirs) {
                        offset = dirs[--numdirs];
                        dirs = realloc(dirs, sizeof(int) * numdirs, M_DEVBUF,
                            M_WAITOK);
                } else
                        complete = 1;
        }

        if (newlen)
                *size = newlen;
        return 0;

}

struct p1212_rom *
p1212_parse(u_int32_t *t, u_int32_t size, u_int32_t mask)
{

        u_int16_t crc, romcrc, crc1;
        u_int32_t next, check;
        struct p1212_rom *rom;
        int i;

        check = size;

        if (p1212_iscomplete(t, &check) == -1) {
                DPRINTF(("ROM is not complete\n"));
                return NULL;
        }
        if (check != size) {
                DPRINTF(("ROM is not complete (check != size)\n"));
                return NULL;
        }

        /* Calculate both a good and known bad crc. */

        /* CRC's are calculated from everything except the first quad. */

        crc = p1212_calc_crc(0, &t[1], P1212_ROMFMT_GET_CRCLEN((ntohl(t[0]))),
                0);

        romcrc = P1212_ROMFMT_GET_CRC((ntohl(t[0])));
        if (crc != romcrc) {
                crc1 = p1212_calc_crc(0, &t[1],
                    P1212_ROMFMT_GET_CRCLEN((ntohl(t[0]))), 1);
                if (crc1 != romcrc) {
                        DPRINTF(("Invalid ROM: CRC: 0x%04hx, Calculated "
                            "CRC: 0x%04hx, CRC1: 0x%04hx\n",
                            (unsigned short)romcrc, (unsigned short)crc,
                            (unsigned short)crc1));
                        return NULL;
                }
        }

        /* Now, walk the ROM. */

        /* Get the initial offset for the root dir. */

        rom = malloc(sizeof(struct p1212_rom), M_DEVBUF, M_WAITOK);
        rom->len = P1212_ROMFMT_GET_INFOLEN((ntohl(t[0])));
        next = rom->len + 1;

        if ((rom->len < 1) || (rom->len > size)) {
                DPRINTF(("Invalid ROM info length: %d\n", rom->len));
                free(rom, M_DEVBUF);
                return NULL;
        }

        /* Exclude the quad which covers the bus name. */
        rom->len--;

        if (rom->len) {
                rom->data = malloc(sizeof(u_int32_t) * rom->len, M_DEVBUF,
                    M_WAITOK);
                /* Add 2 to account for info/crc and bus name skipped. */
                for (i = 0; i < rom->len; i++)
                        rom->data[i] = t[i + 2];
        }

        /* The name field is always 4 bytes and always the 2nd field. */
        strncpy(rom->name, (char *)&t[1], 4);
        rom->name[4] = 0;

        /*
         * Fill out the root directory. All these values are hardcoded so the
         * parse/print/match routines have a standard layout to work against.
         */

        rom->root = malloc(sizeof(*rom->root), M_DEVBUF, M_WAITOK|M_ZERO);
        rom->root->com.key.key_type = P1212_KEYTYPE_Directory;
        rom->root->com.key.key_value = 0;
        rom->root->com.key.key = (u_int8_t)P1212_KEYTYPE_Directory;
        rom->root->com.key.val = 0;
        TAILQ_INIT(&rom->root->data_root);
        TAILQ_INIT(&rom->root->subdir_root);

        if (p1212_parse_directory(rom->root, &t[next], mask)) {
                DPRINTF(("Parse error in ROM. Bailing\n"));
                p1212_free(rom);
                return NULL;
        }
        return rom;
}

static int
p1212_parse_directory(struct p1212_dir *root, u_int32_t *addr, u_int32_t mask)
{
        struct p1212_dir *dir, *sdir;
        struct p1212_data *data;
        struct p1212_com *com;
        u_int32_t *t, desc;
        u_int16_t crclen, crc, crc1, romcrc;
        u_int8_t type, val;
        unsigned long size;
        int i, module_vendor_flag, module_sw_flag, node_sw_flag, unit_sw_flag;
        int node_capabilities_flag, offset, unit_location_flag, unitdir_cnt;
        int leafoff;

        t = addr;
        dir = root;

        module_vendor_flag = 0;
        module_sw_flag = 0;
        node_sw_flag = 0;
        node_capabilities_flag = 0;
        unitdir_cnt = 0;
        offset = 0;

        while (dir) {
                dir->match = 0;
                crclen = P1212_DIRENT_GET_LEN((ntohl(t[offset])));
                romcrc = P1212_DIRENT_GET_CRC((ntohl(t[offset])));

                crc = p1212_calc_crc(0, &t[offset + 1], crclen, 0);
                if (crc != romcrc) {
                        crc1 = p1212_calc_crc(0, &t[offset + 1], crclen, 1);
                        if (crc1 != romcrc) {
                                DPRINTF(("Invalid ROM: CRC: 0x%04hx, "
                                            "Calculated CRC: "
                                            "0x%04hx, CRC1: 0x%04hx\n",
                                            (unsigned short)romcrc,
                                            (unsigned short)crc,
                                            (unsigned short)crc1));
                                return 1;
                        }
                }
                com = NULL;
                unit_sw_flag = 0;
                unit_location_flag = 0;
                offset++;

                if ((dir->parent == NULL) && dir->com.key.val) {
                        DPRINTF(("Invalid root dir. key.val is 0x%0x and not"
                            " 0x0\n", dir->com.key.val));
                        return 1;
                }

                for (i = offset; i < (offset + crclen); i++) {
                        desc = ntohl(t[i]);
                        type = P1212_DIRENT_GET_KEYTYPE(desc);
                        val = P1212_DIRENT_GET_KEYVALUE(desc);

                        /*
                         * Sanity check for valid types/locations/etc.
                         *
                         * See pages 79-100 of
                         * ISO/IEC 13213:1194(ANSI/IEEE Std 1212, 1994 edition)
                         * for specifics.
                         *
                         * XXX: These all really should be broken out into
                         * subroutines as it's grown large and complicated
                         * in certain cases.
                         */

                        switch (val) {
                        case P1212_KEYVALUE_Unit_Spec_Id:
                        case P1212_KEYVALUE_Unit_Sw_Version:
                        case P1212_KEYVALUE_Unit_Dependent_Info:
                        case P1212_KEYVALUE_Unit_Location:
                        case P1212_KEYVALUE_Unit_Poll_Mask:
                                if (dir->parent == NULL) {
                                        DPRINTF(("Invalid ROM: %s is not "
                                            "valid in the root directory.\n",
                                            p1212_keyvalue_strings[val]));
                                        return 1;
                                }
                                break;
                        default:
                                if (dir->com.key.val ==
                                    P1212_KEYVALUE_Unit_Directory) {
                                        DPRINTF(("Invalid ROM: %s is "
                                            "not valid in a unit directory.\n",
                                            p1212_keyvalue_strings[val]));
                                        return 1;
                                }
                                break;
                        }

                        switch (type) {
                        case P1212_KEYTYPE_Immediate:
                                if (p1212_validate_immed(val, mask)) {
                                        DPRINTF(("Invalid ROM: Can't have an "
                                            "immediate type with %s value. Key"
                                            " used at location 0x%0x in ROM\n",
                                            p1212_keyvalue_strings[val],
                                            (unsigned int)(&t[i]-&addr[0])));
                                        return 1;
                                }
                                break;
                        case P1212_KEYTYPE_Offset:
                                if (p1212_validate_offset(val, mask)) {
                                        DPRINTF(("Invalid ROM: Can't have "
                                            "an offset type with key %s."
                                            " Used at location 0x%0x in ROM\n",
                                            p1212_keyvalue_strings[val],
                                            (unsigned int)(&t[i]-&addr[0])));
                                        return 1;
                                }
                                break;
                        case P1212_KEYTYPE_Leaf:
                                if (p1212_validate_leaf(val, mask)) {
                                        DPRINTF(("Invalid ROM: Can't have a "
                                            "leaf type with %s value. Key "
                                            "used at location 0x%0x in ROM\n",
                                            p1212_keyvalue_strings[val],
                                            (unsigned int)(&t[i]-&addr[0])));
                                        return 1;
                                }
                                break;
                        case P1212_KEYTYPE_Directory:
                                if (p1212_validate_dir(val, mask)) {
                                        DPRINTF(("Invalid ROM: Can't have a "
                                            "directory type with %s value. Key"
                                            " used at location 0x%0x in ROM\n",
                                            p1212_keyvalue_strings[val],
                                            (unsigned int)(&t[i]-&addr[0])));
                                        return 1;
                                }
                                break;
                        default:
                                panic("Impossible type code: 0x%04hx",
                                    (unsigned short)type);
                                break;
                        }

                        /* Note flags for required fields. */

                        if (val == P1212_KEYVALUE_Module_Vendor_Id) {
                                module_vendor_flag = 1;
                        }

                        if (val == P1212_KEYVALUE_Node_Capabilities) {
                                node_capabilities_flag = 1;
                        }

                        if (val == P1212_KEYVALUE_Unit_Sw_Version)
                                unit_sw_flag = 1;

                        if (val == P1212_KEYVALUE_Unit_Location)
                                unit_location_flag = 1;

                        /*
                         * This is just easier to spell out. You can't have
                         * a module sw version if you include a node sw version
                         * and vice-versa. Both aren't allowed if you have unit
                         * dirs.
                         */

                        if (val == P1212_KEYVALUE_Module_Sw_Version) {
                                if (node_sw_flag) {
                                        DPRINTF(("Can't have a module software"
                                            " version along with a node "
                                            "software version entry\n"));
                                        return 1;
                                }
                                if (unitdir_cnt) {
                                        DPRINTF(("Can't have unit directories "
                                            "with module software version "
                                            "defined.\n"));
                                        return 1;
                                }
                                module_sw_flag = 1;
                        }

                        if (val == P1212_KEYVALUE_Node_Sw_Version) {
                                if (module_sw_flag) {
                                        DPRINTF(("Can't have a node software "
                                            "version along with a module "
                                            "software version entry\n"));
                                        return 1;
                                }
                                if (unitdir_cnt) {
                                        DPRINTF(("Can't have unit directories "
                                            "with node software version "
                                            "defined.\n"));
                                        return 1;
                                }
                                node_sw_flag = 1;
                        }

                        if (val == P1212_KEYVALUE_Unit_Directory) {
                                if (module_sw_flag || node_sw_flag) {
                                        DPRINTF(("Can't have unit directories "
                                            "with either module or node "
                                            "software version defined.\n"));
                                        return 1;
                                }
                                unitdir_cnt++;
                        }

                        /*
                         * Text descriptors are special. They describe the
                         * last entry they follow. So they need to be included
                         * with its struct and there's nothing in the spec
                         * preventing one from putting text descriptors after
                         * directory descriptors. Also they can be a single
                         * value or a list of them in a directory format so
                         * account for either. Finally if they're in a
                         * directory those can be the only types in a
                         * directory.
                         */

                        if (val == P1212_KEYVALUE_Textual_Descriptor) {

                                size = sizeof(struct p1212_textdata *);
                                leafoff = P1212_DIRENT_GET_VALUE(desc);
                                leafoff += i;

                                if (com == NULL) {
                                        DPRINTF(("Can't have a text descriptor"
                                            " as the first entry in a "
                                            "directory\n"));
                                        return 1;
                                }

                                if (com->textcnt != 0) {
                                        DPRINTF(("Text descriptors can't "
                                            "follow each other in a "
                                            "directory\n"));
                                        return 1;
                                }

                                if (type == P1212_KEYTYPE_Leaf) {
                                        com->text =
                                            malloc(size, M_DEVBUF, M_WAITOK);
                                        com->text[0] =
                                            p1212_parse_text_desc(&t[leafoff]);
                                        if (com->text[0] == NULL) {
                                                DPRINTF(("Got an error parsing"
                                                    " text descriptor at "
                                                    "offset 0x%0x\n",
                                                    &t[leafoff]-&addr[0]));
                                                free(com->text, M_DEVBUF);
                                                return 1;
                                        }
                                        com->textcnt = 1;
                                } else {
                                        i = p1212_parse_textdir(com,
                                                &t[leafoff]);
                                        if (i)
                                                return 1;
                                }
                        }

                        if ((type != P1212_KEYTYPE_Directory) &&
                            (val != P1212_KEYVALUE_Textual_Descriptor)) {
                                data = malloc(sizeof(struct p1212_data),
                                    M_DEVBUF, M_WAITOK|M_ZERO);
                                data->com.key.key_type = type;
                                data->com.key.key_value = val;
                                data->com.key.key =
                                    P1212_DIRENT_GET_KEY((ntohl(t[i])));
                                data->com.key.val =
                                    P1212_DIRENT_GET_VALUE((ntohl(t[i])));
                                com = &data->com;

                                /*
                                 * Don't try and read the offset. It may be
                                 * a register or something special. Generally
                                 * these are node specific so let the upper
                                 * level code figure it out.
                                 */

                                if ((type == P1212_KEYTYPE_Immediate) ||
                                    (type == P1212_KEYTYPE_Offset))
                                        data->val = data->com.key.val;

                                data->leafdata = NULL;
                                TAILQ_INSERT_TAIL(&dir->data_root, data, data);

                                if (type == P1212_KEYTYPE_Leaf) {
                                        leafoff = i + data->com.key.val;
                                        data->leafdata =
                                            p1212_parse_leaf(&t[leafoff]);
                                        if (data->leafdata == NULL) {
                                                DPRINTF(("Error parsing leaf\n"));
                                                return 1;
                                        }
                                }
                        }
                        if (type == P1212_KEYTYPE_Directory) {

                                sdir = malloc(sizeof(struct p1212_dir),
                                        M_DEVBUF, M_WAITOK|M_ZERO);
                                sdir->parent = dir;
                                sdir->com.key.key_type = type;
                                sdir->com.key.key_value = val;
                                sdir->com.key.key =
                                    P1212_DIRENT_GET_KEY((ntohl(t[i])));
                                sdir->com.key.val =
                                    P1212_DIRENT_GET_VALUE((ntohl(t[i])));
                                com = &sdir->com;
                                sdir->match = sdir->com.key.val + i;
                                TAILQ_INIT(&sdir->data_root);
                                TAILQ_INIT(&sdir->subdir_root);
                                TAILQ_INSERT_TAIL(&dir->subdir_root, sdir,dir);
                        }
                }

                /* More validity checks. */

                if (dir->parent == NULL) {
                        if (module_vendor_flag == 0) {
                                DPRINTF(("Missing module vendor entry in root "
                                    "directory.\n"));
                                return 1;
                        }
                        if (node_capabilities_flag == 0) {
                                DPRINTF(("Missing node capabilities entry in "
                                    "root directory.\n"));
                                return 1;
                        }
                } else {
                        if ((unitdir_cnt > 1) && (unit_location_flag == 0)) {
                                DPRINTF(("Must have a unit location in each "
                                    "unit directory when more than one unit "
                                    "directory exists.\n"));
                                return 1;
                        }
                }

                /*
                 * Ok, done with this directory and it's sanity checked. Now
                 * loop through and either find an unparsed subdir or one
                 * farther back up the chain.
                 */

                if (!TAILQ_EMPTY(&dir->subdir_root)) {
                        sdir = TAILQ_FIRST(&dir->subdir_root);
                } else {
                        do {
                                sdir = TAILQ_NEXT(dir, dir);
                                if (sdir == NULL) {
                                        dir = dir->parent;
                                }
                        } while ((sdir == NULL) && (dir != NULL));
                }
                if (dir) {
                        dir = sdir;
                        if (!dir->match) {
                                DPRINTF(("Invalid subdir..Has no offset\n"));
                                return 1;
                        }
                        offset = dir->match;
                }
        }
        return 0;
}

static struct p1212_leafdata *
p1212_parse_leaf(u_int32_t *t)
{
        u_int16_t crclen, crc, crc1, romcrc;
        struct p1212_leafdata *leafdata;
        int i;

        crclen = P1212_DIRENT_GET_LEN((ntohl(t[0])));
        romcrc = P1212_DIRENT_GET_CRC((ntohl(t[0])));
        crc = p1212_calc_crc(0, &t[1], crclen, 0);
        crc1 = p1212_calc_crc(0,&t[1], crclen, 1);
        if ((crc != romcrc) && (crc1 != romcrc)) {
                DPRINTF(("Invalid ROM: CRC: 0x%04hx, Calculated CRC: "
                    "0x%04hx, CRC1: 0x%04hx\n", (unsigned short)romcrc,
                    (unsigned short)crc, (unsigned short)crc1));
                return NULL;
        }
        t++;

        /*
         * Most of these are vendor specific so don't bother trying to map them
         * out. Anything which needs them later on can extract them.
         */

        leafdata = malloc(sizeof(struct p1212_leafdata), M_DEVBUF, M_WAITOK);
        leafdata->data = malloc((sizeof(u_int32_t) * crclen), M_DEVBUF,
            M_WAITOK);
        leafdata->len = crclen;
        for (i = 0; i < crclen; i++)
                leafdata->data[i] = ntohl(t[i]);
        return leafdata;
}

static int
p1212_parse_textdir(struct p1212_com *com, u_int32_t *addr)
{
        u_int32_t *t, entry, new;
        u_int16_t crclen, crc, crc1, romcrc;
        u_int8_t type, val;
        int i, size;

        /*
         * A bit more complicated. A directory for a text descriptor can
         * contain text descriptor leaf nodes only.
         */

        com->text = NULL;
        size = sizeof(struct p1212_text *);
        t = addr;

        crclen = P1212_DIRENT_GET_LEN((ntohl(t[0])));
        romcrc = P1212_DIRENT_GET_CRC((ntohl(t[0])));
        crc = p1212_calc_crc(0, &t[1], crclen, 0);
        crc1 = p1212_calc_crc(0,&t[1], crclen, 1);
        if ((crc != romcrc) && (crc1 != romcrc)) {
                DPRINTF(("Invalid ROM: CRC: 0x%04hx, Calculated CRC: "
                            "0x%04hx, CRC1: 0x%04hx\n", (unsigned short)romcrc,
                            (unsigned short)crc, (unsigned short)crc1));
                return 1;
        }
        t++;
        for (i = 0; i < crclen; i++) {
                entry = ntohl(t[i]);

                type = P1212_DIRENT_GET_KEYTYPE(entry);
                val = P1212_DIRENT_GET_KEYVALUE(entry);
                if ((type != P1212_KEYTYPE_Leaf) ||
                    (val != P1212_KEYVALUE_Textual_Descriptor)) {
                        DPRINTF(("Text descriptor directories can only "
                            "contain text descriptors. Type: %s, value: %s "
                            "isn't valid at offset 0x%0x\n",
                            p1212_keytype_strings[type],
                            p1212_keyvalue_strings[val], &t[i]-&addr[0]));
                        return 1;
                }

                new = P1212_DIRENT_GET_VALUE(entry);
                com->text = realloc(com->text, size * (com->textcnt + 1),
                    M_DEVBUF, M_WAITOK);
                if ((com->text[i] = p1212_parse_text_desc(&t[i+new])) == NULL) {
                        DPRINTF(("Got an error parsing text descriptor.\n"));
                        if (com->textcnt == 0)
                                free(com->text, M_DEVBUF);
                        return 1;
                }
                com->textcnt++;
        }
        return 0;
}

static struct p1212_textdata *
p1212_parse_text_desc(u_int32_t *addr)
{
        u_int32_t *t;
        u_int16_t crclen, crc, crc1, romcrc;
        struct p1212_textdata *text;
        int size;

        t = addr;

        crclen = P1212_DIRENT_GET_LEN((ntohl(t[0])));
        romcrc = P1212_DIRENT_GET_CRC((ntohl(t[0])));

        if (crclen < P1212_TEXT_Min_Leaf_Length) {
                DPRINTF(("Invalid ROM: text descriptor too short\n"));
                return NULL;
        }

        crc = p1212_calc_crc(0, &t[1], crclen, 0);
        if (crc != romcrc) {
                crc1 = p1212_calc_crc(0, &t[1], crclen, 1);
                if (crc1 != romcrc) {
                        DPRINTF(("Invalid ROM: CRC: 0x%04hx, Calculated CRC: "
                            "0x%04hx, CRC1: 0x%04hx\n", (unsigned short)romcrc,
                            (unsigned short)crc, (unsigned short)crc1));
                        return NULL;
                }
        }

        t++;
        text = malloc(sizeof(struct p1212_textdata), M_DEVBUF, M_WAITOK);
        text->spec_type = P1212_TEXT_GET_Spec_Type((ntohl(t[0])));
        text->spec_id = P1212_TEXT_GET_Spec_Id((ntohl(t[0])));
        text->lang_id = ntohl(t[1]);

        t++;
        t++;
        crclen -= 2;
        size = (crclen * sizeof(u_int32_t));

        text->text = malloc(size + 1, M_DEVBUF, M_WAITOK|M_ZERO);

        memcpy(text->text, &t[0], size);

        return text;
}

struct p1212_key **
p1212_find(struct p1212_dir *root, int type, int value, int flags)
{
        struct p1212_key **retkeys;
        struct p1212_dir *dir, *sdir, *parent;
        struct p1212_data *data;
        int numkeys;

        numkeys = 0;
        retkeys = NULL;

        if ((type < P1212_KEYTYPE_Immediate) ||
            (type > P1212_KEYTYPE_Directory)) {
#ifdef DIAGNOSTIC
                printf("p1212_find: invalid type - %d\n", type);
#endif
                return NULL;
        }

        if ((value < -1) ||
            (value > (sizeof(p1212_keyvalue_strings) / sizeof(char *)))) {
#ifdef DIAGNOSTIC
                printf("p1212_find: invalid value - %d\n", value);
#endif
                return NULL;
        }

        if (flags & ~(P1212_FIND_SEARCHALL | P1212_FIND_RETURNALL)) {
#ifdef DIAGNOSTIC
                printf("p1212_find: invalid flags - %d\n", flags);
#endif
                return NULL;
        }

        /*
         * Part of this is copied from p1212_walk to do depth first traversal
         * without using recursion. Using the walk API would have made things
         * more complicated in trying to build up the return struct otherwise.
         */

        dir = root;
        sdir = NULL;

        parent = root->parent;
        root->parent = NULL;

        while (dir) {
                if (type == P1212_KEYTYPE_Directory) {
                        TAILQ_FOREACH(sdir, &dir->subdir_root, dir) {
                                if ((sdir->com.key.key_value == value) ||
                                    (value == -1)) {
                                        numkeys++;
                                        retkeys = realloc(retkeys,
                                            sizeof(struct p1212_key *) *
                                            (numkeys + 1), M_DEVBUF, M_WAITOK);
                                        retkeys[numkeys - 1] = &sdir->com.key;
                                        retkeys[numkeys] = NULL;
                                        if ((flags & P1212_FIND_RETURNALL)
                                            == 0) {
                                                root->parent = parent;
                                                return retkeys;
                                        }
                                }
                        }
                } else {
                        TAILQ_FOREACH(data, &dir->data_root, data) {
                                if (((data->com.key.key_type == type) &&
                                     (data->com.key.key_value == value)) ||
                                    ((data->com.key.key_type == type) &&
                                     (value == -1))) {
                                        numkeys++;
                                        retkeys = realloc(retkeys,
                                            sizeof(struct p1212_key *) *
                                            (numkeys + 1), M_DEVBUF, M_WAITOK);
                                        retkeys[numkeys - 1] = &data->com.key;
                                        retkeys[numkeys] = NULL;
                                        if ((flags & P1212_FIND_RETURNALL)
                                            == 0) {
                                                root->parent = parent;
                                                return retkeys;
                                        }
                                }
                        }
                }
                if (flags & P1212_FIND_SEARCHALL) {
                        do {
                                sdir = TAILQ_NEXT(dir, dir);
                                if (sdir == NULL) {
                                        dir = dir->parent;
                                }
                        } while ((sdir == NULL) && (dir != NULL));
                        dir = sdir;
                } else
                        dir = NULL;
        }
        root->parent = parent;
        return retkeys;
}

void
p1212_walk(struct p1212_dir *root, void *arg,
    void (*func)(struct p1212_key *, void *))
{
        struct p1212_data *data;
        struct p1212_dir *sdir, *dir, *parent;

        dir = root;
        sdir = NULL;

        if (func == NULL) {
#ifdef DIAGNOSTIC
                printf("p1212_walk: Passed in NULL function\n");
#endif
                return;
        }
        if (root == NULL) {
#ifdef DIAGNOSTIC
                printf("p1212_walk: Called with NULL root\n");
#endif
                return;
        }

        /* Allow walking from any point. Just mark the starting point. */
        parent = root->parent;
        root->parent = NULL;

        /*
         * Depth first traversal that doesn't use recursion.
         *
         * Call the function first for the directory node and then loop through
         * all the data nodes and call the function for them.
         *
         * Finally, figure out the next possible directory node if one is
         * available or bail out.
         */

        while (dir) {
                func((struct p1212_key *) dir, arg);
                TAILQ_FOREACH(data, &dir->data_root, data)
                        func((struct p1212_key *) data, arg);
                if (!TAILQ_EMPTY(&dir->subdir_root)) {
                        sdir = TAILQ_FIRST(&dir->subdir_root);
                } else {
                        do {
                                sdir = TAILQ_NEXT(dir, dir);
                                if (sdir == NULL) {
                                        dir = dir->parent;
                                }
                        } while ((sdir == NULL) && dir);
                }
                dir = sdir;
        }

        root->parent = parent;
}

void
p1212_print(struct p1212_dir *dir)
{
        int indent;

        indent = 0;

        p1212_walk(dir, &indent, p1212_print_node);
        printf("\n");
}

static void
p1212_print_node(struct p1212_key *key, void *arg)
{

        struct p1212_data *data;
        struct p1212_dir *sdir, *dir;
        int i, j, *indent;

        indent = arg;

        if (key->key_type == P1212_KEYTYPE_Directory) {
                dir = (struct p1212_dir *) key;
                data = NULL;
        } else {
                data = (struct p1212_data *) key;
                dir = NULL;
        }

        /* Recompute the indent level on each directory. */
        if (dir) {
                *indent = 0;
                sdir = dir->parent;
                while (sdir != NULL) {
                        (*indent)++;
                        sdir = sdir->parent;
                }
        }

        if (dir && dir->parent)
                printf("\n");

        /* Set the indent string up. 4 spaces per level. */
        for (i = 0; i < (*indent * 4); i++)
                printf(" ");

        if (dir) {
                printf("Directory: ");
                if (dir->print)
                        dir->print(dir);
                else {
                        if (key->key_value >=
                            (sizeof(p1212_keyvalue_strings) / sizeof(char *)))
                                printf("Unknown type 0x%04hx\n",
                                    (unsigned short)key->key_value);
                        else
                                printf("%s\n",
                                    p1212_keyvalue_strings[key->key_value]);
                }
                if (dir->com.textcnt) {
                        for (i = 0; i < dir->com.textcnt; i++) {
                                for (j = 0; j < (*indent * 4); j++)
                                        printf(" ");
                                printf("Text descriptor: %s\n",
                                    dir->com.text[i]->text);
                        }
                }
                printf("\n");
        } else {
                if (data->print)
                        data->print(data);
                else {
                        if (key->key_value >=
                            (sizeof(p1212_keyvalue_strings) / sizeof(char *)))
                                printf("Unknown type 0x%04hx: ",
                                    (unsigned short)key->key_value);
                        else
                                printf("%s: ",
                                    p1212_keyvalue_strings[key->key_value]);

                        printf("0x%08x\n", key->val);
#ifdef DIAGNOSTIC
                        if ((data->com.key.key_type == P1212_KEYTYPE_Leaf) &&
                            (data->leafdata == NULL))
                                panic("Invalid data node in configrom tree");
#endif

                        if (data->leafdata) {
                                for (i = 0; i < data->leafdata->len; i++) {
                                        for (j = 0; j < (*indent * 4); j++)
                                                printf(" ");
                                        printf ("Leaf data: 0x%08x\n",
                                            data->leafdata->data[i]);
                                }
                        }
                        if (data->com.textcnt)
                                for (i = 0; i < data->com.textcnt; i++) {
                                        for (j = 0; j < (*indent * 4); j++)
                                                printf(" ");
                                        printf("Text descriptor: %s\n",
                                            data->com.text[i]->text);
                                }

                }
        }
}


void
p1212_free(struct p1212_rom *rom)
{
        struct p1212_dir *sdir, *dir;
        struct p1212_data *data;
        int i;

        dir = rom->root;

        /* Avoid recursing. Find the bottom most node and work back. */
        while (dir) {
                if (!TAILQ_EMPTY(&dir->subdir_root)) {
                        sdir = TAILQ_FIRST(&dir->subdir_root);
                        if (TAILQ_EMPTY(&sdir->subdir_root)) {
                                TAILQ_REMOVE(&dir->subdir_root, sdir, dir);
                                dir = sdir;
                        }
                        else {
                                dir = sdir;
                                continue;
                        }
                } else {
                        if (dir->parent)
                                TAILQ_REMOVE(&dir->parent->subdir_root, dir,
                                    dir);
                }

                while ((data = TAILQ_FIRST(&dir->data_root))) {
                        if (data->leafdata) {
                                if (data->leafdata->data)
                                        free(data->leafdata->data, M_DEVBUF);
                                free(data->leafdata, M_DEVBUF);
                        }
                        TAILQ_REMOVE(&dir->data_root, data, data);
                        if (data->com.textcnt) {
                                for (i = 0; i < data->com.textcnt; i++)
                                        free(data->com.text[i], M_DEVBUF);
                                free(data->com.text, M_DEVBUF);
                        }
                        free(data, M_DEVBUF);
                }
                sdir = dir;
                if (dir->parent)
                        dir = dir->parent;
                else
                        dir = NULL;
                if (sdir->com.textcnt) {
                        for (i = 0; i < sdir->com.textcnt; i++)
                                free(sdir->com.text[i], M_DEVBUF);
                        free(sdir->com.text, M_DEVBUF);
                }
                free(sdir, M_DEVBUF);
        }
        if (rom->len)
                free(rom->data, M_DEVBUF);
        free(rom, M_DEVBUF);
}

/*
 * A fairly well published reference implementation of the CRC routine had
 * a typo in it and some devices may be using it rather than the correct one
 * in calculating their ROM CRC's. To compensate an interface for generating
 * either is provided.
 *
 * len is the number of u_int32_t entries, not bytes.
 */

static u_int16_t
p1212_calc_crc(u_int32_t crc, u_int32_t *data, int len, int broke)
{
        int shift;
        u_int32_t sum;
        int i;

        for (i = 0; i < len; i++) {
                for (shift = 28; shift > 0; shift -= 4) {
                        sum = ((crc >> 12) ^ (ntohl(data[i]) >> shift)) &
                            0x0000000f;
                        crc = (crc << 4) ^ (sum << 12) ^ (sum << 5) ^ sum;
                }


                /* The broken implementation doesn't do the last shift. */
                if (!broke) {
                        sum = ((crc >> 12) ^ ntohl(data[i])) & 0x0000000f;
                        crc = (crc << 4) ^ (sum << 12) ^ (sum << 5) ^ sum;
                }
        }
        return (u_int16_t)crc;
}

/*
 * This is almost identical to the standard autoconf *match idea except it
 * can match and attach multiple children in one pass.
 */

device_t *
p1212_match_units(device_t sc, struct p1212_dir *dir,
    int (*print)(void *, const char *))
{
        struct p1212_dir **udirs;
        device_t *devret, *dev;
        int numdev;

        /*
         * Setup typical return val. Always allocate one extra pointer for a
         * NULL guard end pointer.
         */

        numdev = 0;
        devret = malloc(sizeof(device_t) * 2, M_DEVBUF, M_WAITOK);
        devret[1] = NULL;

        udirs = (struct p1212_dir **)p1212_find(dir, P1212_KEYTYPE_Directory,
            P1212_KEYVALUE_Unit_Directory,
            P1212_FIND_SEARCHALL|P1212_FIND_RETURNALL);

        if (udirs) {
                do {
                        dev = config_found(sc, udirs, print,
                            CFARGS(.iattr = "fwnode"));
                        if (dev && numdev) {
                                devret = realloc(devret,
                                    sizeof(device_t) *
                                    (numdev + 2), M_DEVBUF, M_WAITOK);
                                devret[numdev++] = dev;
                                devret[numdev] = NULL;
                        } else if (dev) {
                                devret[0] = dev;
                                numdev++;
                        }
                        udirs++;
                } while (*udirs);
        }
        if (numdev == 0) {
                free(devret, M_DEVBUF);
                return NULL;
        }
        return devret;
}

/*
 * Make these their own functions as they have slightly complicated rules.
 *
 * For example:
 *
 * Under normal circumstances only the 2 extent types can be offset
 * types. However some spec's which use p1212 like SBP2 for
 * firewire/1394 will define a dependent info type as an offset value.
 * Allow the upper level code to flag this and pass it down during
 * parsing. The same thing applies to immediate types.
 */

static int
p1212_validate_offset(u_int16_t val, u_int32_t mask)
{
        if ((val == P1212_KEYVALUE_Node_Units_Extent) ||
            (val == P1212_KEYVALUE_Node_Memory_Extent) ||
            ((mask & P1212_ALLOW_DEPENDENT_INFO_OFFSET_TYPE) &&
             ((val == P1212_KEYVALUE_Unit_Dependent_Info) ||
              (val == P1212_KEYVALUE_Node_Dependent_Info) ||
              (val == P1212_KEYVALUE_Module_Dependent_Info))))
                return 0;
        return 1;
}

static int
p1212_validate_immed(u_int16_t val, u_int32_t mask)
{
        switch (val) {
        case P1212_KEYVALUE_Textual_Descriptor:
        case P1212_KEYVALUE_Bus_Dependent_Info:
        case P1212_KEYVALUE_Module_Dependent_Info:
        case P1212_KEYVALUE_Node_Unique_Id:
        case P1212_KEYVALUE_Node_Dependent_Info:
        case P1212_KEYVALUE_Unit_Directory:
        case P1212_KEYVALUE_Unit_Dependent_Info:
        case P1212_KEYVALUE_Unit_Location:
                if ((mask & P1212_ALLOW_DEPENDENT_INFO_IMMED_TYPE) &&
                    ((val == P1212_KEYVALUE_Module_Dependent_Info) ||
                     (val == P1212_KEYVALUE_Node_Dependent_Info) ||
                     (val == P1212_KEYVALUE_Unit_Dependent_Info)))
                        break;
                return 1;
                break;
        default:
                break;
        }
        return 0;
}

static int
p1212_validate_leaf(u_int16_t val, u_int32_t mask)
{
        switch(val) {
        case P1212_KEYVALUE_Textual_Descriptor:
        case P1212_KEYVALUE_Bus_Dependent_Info:
        case P1212_KEYVALUE_Module_Dependent_Info:
        case P1212_KEYVALUE_Node_Unique_Id:
        case P1212_KEYVALUE_Node_Dependent_Info:
        case P1212_KEYVALUE_Unit_Dependent_Info:
        case P1212_KEYVALUE_Unit_Location:
                break;
        default:
                return 1;
                break;
        }
        return 0;
}

static int
p1212_validate_dir(u_int16_t val, u_int32_t mask)
{
        switch(val) {
        case P1212_KEYVALUE_Textual_Descriptor:
        case P1212_KEYVALUE_Bus_Dependent_Info:
        case P1212_KEYVALUE_Module_Dependent_Info:
        case P1212_KEYVALUE_Node_Dependent_Info:
        case P1212_KEYVALUE_Unit_Directory:
        case P1212_KEYVALUE_Unit_Dependent_Info:
                break;
        default:
                if ((mask & P1212_ALLOW_VENDOR_DIRECTORY_TYPE) &&
                    (val == P1212_KEYVALUE_Module_Vendor_Id))
                        break;
                return 1;
                break;
        }
        return 0;
}