/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* SBP2 config ROM routines
*/
#include <sys/types.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/1394/ieee1212.h>
#include <sys/sbp2/impl.h>
static int sbp2_cfgrom_rq(sbp2_tgt_t *, void *, uint64_t, uint32_t *);
static int sbp2_cfgrom_parse_dir(sbp2_tgt_t *, void *,
sbp2_cfgrom_parse_arg_t *);
static int sbp2_cfgrom_read_leaf(sbp2_tgt_t *, void *,
sbp2_cfgrom_ent_t *);
static int sbp2_cfgrom_read_bib(sbp2_tgt_t *, void *, sbp2_cfgrom_bib_t *);
static void sbp2_cfgrom_free_bib(sbp2_tgt_t *, sbp2_cfgrom_bib_t *);
static void sbp2_cfgrom_dir_grow(sbp2_cfgrom_dir_t *, int);
static sbp2_cfgrom_ent_t *sbp2_cfgrom_dir_new_ent(sbp2_cfgrom_dir_t *);
static int sbp2_cfgrom_walk_impl(sbp2_cfgrom_ent_t *,
int (*)(void *, sbp2_cfgrom_ent_t *, int), void *, int);
static int sbp2_cfgrom_ent_by_key_walker(void *, sbp2_cfgrom_ent_t *,
int);
static void sbp2_cfgrom_walk_free(sbp2_cfgrom_ent_t *);
static hrtime_t sbp2_cfgrom_read_delay = 20 * 1000000; /* in ns */
/* imitate throwing an exception when read fails */
#define SBP2_CFGROM_RQ(tp, cmd, addr, q) \
if ((ret = sbp2_cfgrom_rq(tp, cmd, addr, q)) != 0) { \
goto rq_error; \
}
static int
sbp2_cfgrom_rq(sbp2_tgt_t *tp, void *cmd, uint64_t addr, uint32_t *q)
{
hrtime_t tm; /* time since last read */
int berr;
int ret;
tm = gethrtime() - tp->t_last_cfgrd;
if (tm < sbp2_cfgrom_read_delay) {
delay(drv_usectohz((sbp2_cfgrom_read_delay - tm) / 1000));
}
ret = SBP2_RQ(tp, cmd, addr, q, &berr);
*q = SBP2_SWAP32(*q);
tp->t_last_cfgrd = gethrtime();
return (ret);
}
int
sbp2_cfgrom_parse(sbp2_tgt_t *tp, sbp2_cfgrom_t *crp)
{
sbp2_cfgrom_ent_t *root_dir = &crp->cr_root;
sbp2_cfgrom_bib_t *bib = &crp->cr_bib;
void *cmd;
int ret;
sbp2_cfgrom_parse_arg_t pa;
if ((ret = SBP2_ALLOC_CMD(tp, &cmd, 0)) != SBP2_SUCCESS) {
return (ret);
}
if ((ret = sbp2_cfgrom_read_bib(tp, cmd, bib)) != SBP2_SUCCESS) {
SBP2_FREE_CMD(tp, cmd);
return (ret);
}
/* parse root directory and everything underneath */
bzero(root_dir, sizeof (sbp2_cfgrom_ent_t));
root_dir->ce_kt = IEEE1212_DIRECTORY_TYPE;
root_dir->ce_offset = SBP2_CFGROM_ADDR(tp) + 4 + bib->cb_len * 4;
pa.pa_dir = root_dir;
pa.pa_pdir = NULL;
pa.pa_ref = NULL;
pa.pa_depth = 0;
if ((ret = sbp2_cfgrom_parse_dir(tp, cmd, &pa)) != SBP2_SUCCESS) {
sbp2_cfgrom_free(tp, crp);
}
SBP2_FREE_CMD(tp, cmd);
return (ret);
}
/*
* Caller must initialize pa and pa->pa_dir.
*/
static int
sbp2_cfgrom_parse_dir(sbp2_tgt_t *tp, void *cmd, sbp2_cfgrom_parse_arg_t *pa)
{
sbp2_cfgrom_ent_t *dir = pa->pa_dir; /* directory being parsed */
sbp2_cfgrom_ent_t *cep; /* current entry structure */
sbp2_cfgrom_ent_t *pcep = NULL; /* previous entry structure */
sbp2_cfgrom_parse_arg_t this_pa; /* parse args */
uint64_t addr; /* current address */
uint32_t entry; /* current entry */
uint8_t t, k; /* key type and value */
uint32_t v; /* entry value */
int i;
int ret = 0;
this_pa.pa_pdir = dir;
this_pa.pa_ref = pa->pa_ref;
this_pa.pa_depth = pa->pa_depth + 1;
/* read directory entry and initialize the structure */
SBP2_CFGROM_RQ(tp, cmd, dir->ce_offset, &entry);
dir->ce_len = IEEE1212_DIR_LEN(entry);
sbp2_cfgrom_dir_grow(&dir->ce_data.dir, dir->ce_len);
/* walk directory entries */
addr = dir->ce_offset + 4;
for (i = 0; i < dir->ce_len; i++, addr += 4) {
SBP2_CFGROM_RQ(tp, cmd, addr, &entry);
CFGROM_TYPE_KEY_VALUE(entry, t, k, v);
cep = sbp2_cfgrom_dir_new_ent(&dir->ce_data.dir);
cep->ce_kt = t;
cep->ce_kv = k;
switch (t) {
case IEEE1212_IMMEDIATE_TYPE:
cep->ce_len = 1;
cep->ce_offset = addr;
cep->ce_data.imm = v;
break;
case IEEE1212_CSR_OFFSET_TYPE:
cep->ce_len = 1;
cep->ce_offset = addr;
cep->ce_data.offset = v;
break;
case IEEE1212_LEAF_TYPE:
cep->ce_offset = addr + 4 * v;
if (dir->ce_kv != IEEE1212_TEXTUAL_DESCRIPTOR) {
/* text leaf describes preceding entry */
cep->ce_ref = pcep;
} else {
/* text directory describes preceding entry */
cep->ce_ref = this_pa.pa_ref;
}
ret = sbp2_cfgrom_read_leaf(tp, cmd, cep);
break;
case IEEE1212_DIRECTORY_TYPE:
cep->ce_offset = addr + 4 * v;
this_pa.pa_dir = cep;
this_pa.pa_ref = pcep;
if (this_pa.pa_depth < SBP2_CFGROM_MAX_DEPTH) {
ret = sbp2_cfgrom_parse_dir(tp, cmd, &this_pa);
}
break;
default:
ASSERT(0);
}
pcep = cep;
}
rq_error:
return (ret);
}
static int
sbp2_cfgrom_read_leaf(sbp2_tgt_t *tp, void *cmd, sbp2_cfgrom_ent_t *cep)
{
uint32_t val;
int ret;
int i;
uint64_t addr = cep->ce_offset;
/* header */
SBP2_CFGROM_RQ(tp, cmd, addr, &val);
addr += 4;
/* verify data length */
cep->ce_len = (val >> 16);
if (cep->ce_len < 1) {
return (SBP2_EDATA);
}
cep->ce_data.leaf = kmem_zalloc(cep->ce_len * 4, KM_SLEEP);
/* data */
for (i = 0; i < cep->ce_len; i++, addr += 4) {
SBP2_CFGROM_RQ(tp, cmd, addr, &cep->ce_data.leaf[i]);
}
return (ret);
rq_error:
if (cep->ce_data.leaf) {
kmem_free(cep->ce_data.leaf, cep->ce_len * 4);
}
return (ret);
}
static int
sbp2_cfgrom_read_bib(sbp2_tgt_t *tp, void *cmd, sbp2_cfgrom_bib_t *cbp)
{
uint32_t val;
int ret;
int i;
uint64_t addr = SBP2_CFGROM_ADDR(tp);
/* header */
SBP2_CFGROM_RQ(tp, cmd, addr, &val);
addr += 4;
/* verify data length */
cbp->cb_len = (val >> 24);
if (cbp->cb_len < 1) {
return (SBP2_EDATA);
}
cbp->cb_buf = kmem_zalloc(cbp->cb_len * 4, KM_SLEEP);
/* data */
for (i = 0; i < cbp->cb_len; i++, addr += 4) {
SBP2_CFGROM_RQ(tp, cmd, addr, &cbp->cb_buf[i]);
}
rq_error:
sbp2_cfgrom_free_bib(tp, cbp);
return (ret);
}
/*ARGSUSED*/
static void
sbp2_cfgrom_free_bib(sbp2_tgt_t *tp, sbp2_cfgrom_bib_t *cbp)
{
if ((cbp->cb_buf != NULL) && (cbp->cb_len > 0)) {
kmem_free(cbp->cb_buf, cbp->cb_len * 4);
cbp->cb_buf = NULL;
}
}
static void
sbp2_cfgrom_dir_grow(sbp2_cfgrom_dir_t *dir, int incr)
{
int new_size, old_size;
void *new_ent;
ASSERT(incr > 0);
new_size = (dir->cd_size + incr) * sizeof (sbp2_cfgrom_ent_t);
new_ent = kmem_zalloc(new_size, KM_SLEEP);
if (dir->cd_size > 0) {
old_size = dir->cd_size * sizeof (sbp2_cfgrom_ent_t);
bcopy(dir->cd_ent, new_ent, old_size);
kmem_free(dir->cd_ent, old_size);
}
dir->cd_ent = new_ent;
dir->cd_size += incr;
}
static sbp2_cfgrom_ent_t *
sbp2_cfgrom_dir_new_ent(sbp2_cfgrom_dir_t *dir)
{
/* grow if out of entries */
if (dir->cd_cnt >= dir->cd_size) {
ASSERT(dir->cd_cnt == dir->cd_size);
sbp2_cfgrom_dir_grow(dir, SBP2_CFGROM_GROW_INCR);
}
return (&dir->cd_ent[dir->cd_cnt++]);
}
/*
* walk Config ROM entries calling the specified function for each
*/
void
sbp2_cfgrom_walk(sbp2_cfgrom_ent_t *dir,
int (*func)(void *, sbp2_cfgrom_ent_t *, int), void *arg)
{
ASSERT(dir->ce_kt == IEEE1212_DIRECTORY_TYPE);
(void) sbp2_cfgrom_walk_impl(dir, func, arg, 0);
}
static int
sbp2_cfgrom_walk_impl(sbp2_cfgrom_ent_t *dir,
int (*func)(void *, sbp2_cfgrom_ent_t *, int), void *arg, int level)
{
int i;
sbp2_cfgrom_ent_t *ent;
for (i = 0; i < dir->ce_data.dir.cd_cnt; i++) {
ent = &dir->ce_data.dir.cd_ent[i];
if (func(arg, ent, level) == SBP2_WALK_STOP) {
return (SBP2_WALK_STOP);
}
if (ent->ce_kt == IEEE1212_DIRECTORY_TYPE) {
if (sbp2_cfgrom_walk_impl(ent, func, arg, level + 1) ==
SBP2_WALK_STOP) {
return (SBP2_WALK_STOP);
}
}
}
return (SBP2_WALK_CONTINUE);
}
sbp2_cfgrom_ent_t *
sbp2_cfgrom_ent_by_key(sbp2_cfgrom_ent_t *dir, int8_t kt, int8_t kv, int num)
{
sbp2_cfgrom_ent_by_key_t ebk;
ebk.kt = kt;
ebk.kv = kv;
ebk.num = num;
ebk.ent = NULL;
ebk.cnt = 0;
sbp2_cfgrom_walk(dir, sbp2_cfgrom_ent_by_key_walker, &ebk);
return (ebk.ent);
}
/*ARGSUSED*/
static int
sbp2_cfgrom_ent_by_key_walker(void *arg, sbp2_cfgrom_ent_t *ent, int level)
{
sbp2_cfgrom_ent_by_key_t *ebk = arg;
if ((ent->ce_kt == ebk->kt) && (ent->ce_kv == ebk->kv)) {
if (ebk->cnt == ebk->num) {
ebk->ent = ent;
return (SBP2_WALK_STOP);
}
ebk->cnt++;
}
return (SBP2_WALK_CONTINUE);
}
void
sbp2_cfgrom_free(sbp2_tgt_t *tp, sbp2_cfgrom_t *crp)
{
sbp2_cfgrom_free_bib(tp, &crp->cr_bib);
sbp2_cfgrom_walk_free(&crp->cr_root);
}
static void
sbp2_cfgrom_walk_free(sbp2_cfgrom_ent_t *dir)
{
int i;
sbp2_cfgrom_dir_t *cdp = &dir->ce_data.dir;
sbp2_cfgrom_ent_t *ent = cdp->cd_ent;
for (i = 0; i < cdp->cd_cnt; i++) {
if (ent[i].ce_kt == IEEE1212_DIRECTORY_TYPE) {
sbp2_cfgrom_walk_free(&ent[i]);
} else if ((ent[i].ce_kt == IEEE1212_LEAF_TYPE) &&
(ent[i].ce_data.leaf != NULL)) {
kmem_free(ent[i].ce_data.leaf, ent[i].ce_len * 4);
}
}
if (ent) {
kmem_free(ent, cdp->cd_size * sizeof (sbp2_cfgrom_ent_t));
}
}