NetBSD/sys/dev/std/ieee1212.c

1343 lines
34 KiB
C

/* $NetBSD: ieee1212.c,v 1.13 2014/10/18 08:33:28 snj 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.13 2014/10/18 08:33:28 snj 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_ia(sc, "fwnode", udirs, print);
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;
}