root/usr/src/uts/sun4u/sunfire/io/fhc_bd.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/ddi_impldefs.h>
#include <sys/obpdefs.h>
#include <sys/promif.h>
#include <sys/cmn_err.h>
#include <sys/errno.h>
#include <sys/kmem.h>
#include <sys/kstat.h>
#include <sys/debug.h>
#include <sys/fhc.h>
#include <sys/jtag.h>
#include <sys/sysctrl.h>

static fhc_bd_resizable_t boards; /* booted and hotplugged boards */
static fhc_bd_resizable_t clocks; /* clocks under central. */

static int fhc_bdmax;
/*
 * !! IMPORTANT !! fhc_bdlist_rwlock is implemented as a single
 * RW_WRITER lock with *no* RW_READERs -- and it should stay that
 * way.  The fhc_bdlist_rwlock should never be used with RW_READER.
 *
 * The lock was originally a mutex, but was changed to a
 * single-writer, zero-reader rwlock to force requesting threads
 * to block (sleep, not spin) when the RW_WRITER lock is already
 * held by a thread currently running.
 */
static krwlock_t fhc_bdlist_rwlock;
static sysc_evt_handle_t fhc_bd_evt;
static sysc_evt_handle_t *fbe = &fhc_bd_evt;

#define fhc_bd_sc_evt(s, e)     (*fbe->update)(fbe->soft, s, e)
#define FHC_INCREMENT 4
#define FHC_B_SEARCH(in_array, board) \
        fhc_b_search(in_array.boards, board, 0, in_array.last);

static int      fhc_bd_disabled(int);
static void     fhc_check_array(int);
static void     fhc_shell_sort(fhc_bd_t **, int, int);
static int      fhc_b_search(fhc_bd_t **, int, int, int);
static void     fhc_check_size(fhc_bd_resizable_t *);
static void     fhc_resize(fhc_bd_t ***, int, int);


/*
 * fhc_bdmax gets set in fhc_bdlist_prime() and does not
 * change thereafter.
 */
int
fhc_max_boards()
{
        return (fhc_bdmax + 1);
}

static int
fhc_bd_disabled(int board)
{
        int index;

        ASSERT(boards.sorted);
        index = FHC_B_SEARCH(boards, board);
        ASSERT(index != -1);
        return (boards.boards[index]->flags & BDF_DISABLED);
}

static void
fhc_check_array(int btype)
{
        if (btype == FHC_BOARDS) {
                ASSERT(fhc_bdlist_locked());
                if (!boards.sorted) {
                        fhc_shell_sort(boards.boards, 0, boards.last);
                        boards.sorted = TRUE;
                }
        } else {
                ASSERT(fhc_bdlist_locked());
                if (!clocks.sorted) {
                        fhc_shell_sort(clocks.boards, 0, clocks.last);
                        clocks.sorted = TRUE;
                }
        }
}

static void
fhc_shell_sort(fhc_bd_t *a[], int lb, int ub)
{
        int n, h, i, j;
        fhc_bd_t *t;

        /* sort array a[lb..ub] */

        /* compute largest increment */
        n = ub - lb + 1;
        h = 1;
        if (n < 14)
                h = 1;
        else {
                while (h < n)
                        h = 3 * h + 1;
                h /= 3;
                h /= 3;
        }

        while (h > 0) {
                /* sort-by-insertion in increments of h */
                for (i = lb + h; i <= ub; i++) {
                        t = a[i];
                        for (j = i - h;
                            j >= lb && a[j]->sc.board > t->sc.board;
                            j -= h) {
                                a[j+h] = a[j];
                        }
                        a[j+h] = t;
                }

                /* compute next increment */
                h /= 3;
        }
}

static int
fhc_b_search(fhc_bd_t *in_array[], int board, int first, int last)
{
        int mid;

        /* Array of length 0 case. */
        if (in_array == NULL)
                return (-1);

        /* Array of length > 0 case. */
        while (first < last) {
                mid = (first + last) / 2;
                if (in_array[mid]->sc.board < board)
                        first = mid + 1;
                else
                        last = mid;
        }

        if (in_array[first]->sc.board == board) {
                return (first);
        } else {
                return (-1);
        }

}

static void
fhc_check_size(fhc_bd_resizable_t *resizable)
{
        int oldsize;
        int newsize;

        ASSERT(fhc_bdlist_locked());

        if (resizable->size == resizable->last + 1) {
                oldsize = sizeof (fhc_bd_t *) * resizable->size;
                resizable->size += FHC_INCREMENT;
                newsize = sizeof (fhc_bd_t *) * resizable->size;
                fhc_resize(&(resizable->boards), oldsize, newsize);
        }
}

int
fhc_bdlist_locked()
{
        if (panicstr)
                return (1);

        return (rw_owner(&fhc_bdlist_rwlock) == curthread);
}

int
fhc_bd_busy(int board)
{
        int index;

        ASSERT(boards.sorted);
        index = FHC_B_SEARCH(boards, board);
        ASSERT(index != -1);
        return (boards.boards[index]->sc.in_transition);
}

int
fhc_bd_is_jtag_master(int board)
{
        int index;

        ASSERT(boards.sorted);
        index = FHC_B_SEARCH(boards, board);
        ASSERT(index != -1);
        if (boards.boards[index]->softsp == NULL)
                return (FALSE);
        else
                return ((boards.boards[index]->softsp)->jt_master.is_master);
}

int
fhc_bd_is_plus(int board)
{
        int index;

        ASSERT(boards.sorted);
        index = FHC_B_SEARCH(boards, board);
        ASSERT(index != -1);
        if (boards.boards[index]->sc.plus_board)
                return (boards.boards[index]->sc.plus_board);
        else
                return (FALSE);
}

void
fhc_bdlist_init()
{
        ASSERT(!fhc_bdmax);
        rw_init(&fhc_bdlist_rwlock, NULL, RW_DEFAULT, NULL);
        boards.boards = NULL;
        boards.size = 0;
        boards.last = -1;
        boards.sorted = TRUE; /* Array of 0 elements is sorted. */

        clocks.boards = NULL;
        clocks.size = 0;
        clocks.last = -1;
        clocks.sorted = TRUE; /* Array of 0 elements is sorted. */
}

void
fhc_bdlist_fini()
{
        rw_destroy(&fhc_bdlist_rwlock);
}

fhc_bd_t *
fhc_bdlist_lock(int board)
{
        int index;

        ASSERT(!fhc_bdlist_locked());

        /* RW_WRITER *ONLY*.  Never use RW_READER! */
        rw_enter(&fhc_bdlist_rwlock, RW_WRITER);

        if (board == -1)
                return (NULL);
        else {
                ASSERT(boards.sorted);
                index = FHC_B_SEARCH(boards, board);
                ASSERT(index != -1);
                return (boards.boards[index]);
        }
}

void
fhc_bdlist_unlock()
{
        ASSERT(fhc_bdlist_locked());

        rw_exit(&fhc_bdlist_rwlock);
}

static void
fhc_resize(fhc_bd_t ***in_array, int oldsize, int newsize)
{
        fhc_bd_t **temp;

        /* This function only grows arrays. */
        ASSERT(newsize > oldsize);

        /* Allocate new array. */
        temp = kmem_alloc(newsize, KM_SLEEP);

        /* Bcopy old array and free it. */
        if (*in_array != NULL) {
                ASSERT(oldsize > 0);
                bcopy(*in_array, temp, oldsize);
                kmem_free(*in_array, oldsize);
        }
        *in_array = temp;
}

void
fhc_bd_init(struct fhc_soft_state *softsp, int board, enum board_type type)
{
        fhc_bd_t *bdp;
        int index;

        (void) fhc_bdlist_lock(-1);

        /* See if board already exists. */
        ASSERT(boards.sorted);
        ASSERT(clocks.sorted);
        if (softsp->is_central) {
                index = FHC_B_SEARCH(clocks, board);
        } else {
                index = FHC_B_SEARCH(boards, board);
        }

        /* If index == -1 board does not exist. */
        if (index != -1) {
                if (softsp->is_central) {
                        bdp = clocks.boards[index];
                } else {
                        bdp = boards.boards[index];
                }
        } else {
                if (softsp->is_central) {
                        fhc_check_size(&clocks);
                        clocks.boards[clocks.last + 1] =
                            kmem_zalloc(sizeof (fhc_bd_t), KM_SLEEP);
                        bdp = clocks.boards[clocks.last + 1];
                        clocks.last++;
                        clocks.sorted = FALSE;
                } else {
                        fhc_check_size(&boards);
                        boards.boards[boards.last + 1] =
                            kmem_zalloc(sizeof (fhc_bd_t), KM_SLEEP);
                        bdp = boards.boards[boards.last + 1];
                        boards.last++;
                        boards.sorted = FALSE;
                }
        }

        softsp->list = bdp;
        bdp->flags |= BDF_VALID;
        bdp->softsp = softsp;
        bdp->sc.type = type;
        bdp->sc.board = board;
        bdp->sc.plus_board = ISPLUSBRD(*softsp->bsr);

        /* Keep arrays sorted. */
        fhc_check_array(FHC_BOARDS);
        fhc_check_array(FHC_CLOCKS);

        fhc_bdlist_unlock();
}

fhc_bd_t *
fhc_bd(int board)
{
        int index;

        if (fhc_bdmax) {
                ASSERT(fhc_bdlist_locked());
        }
        ASSERT(boards.sorted);
        index = FHC_B_SEARCH(boards, board);
        ASSERT(index != -1);
        return (boards.boards[index]);
}

fhc_bd_t *
fhc_bd_clock(void)
{
        ASSERT(fhc_bdlist_locked());
        ASSERT(clocks.size != 0);

        return (clocks.boards[0]);
}

fhc_bd_t *
fhc_bd_first()
{
        ASSERT(fhc_bdlist_locked());
        if (boards.boards != NULL)
                return (boards.boards[0]);
        else
                return (NULL);
}

fhc_bd_t *
fhc_bd_next(fhc_bd_t *bdp)
{
        int index;

        ASSERT(boards.sorted);
        index = FHC_B_SEARCH(boards, bdp->sc.board);
        ASSERT(index != -1);
        if (index < boards.last)
                return (boards.boards[index + 1]);
        else
                return (NULL);
}

int
fhc_bd_valid(int bd)
{
        int index;

        ASSERT(bd >= 0);
        /* Untill fhc_bdlist_prime runs anything is valid. */
        if (!fhc_bdmax)
                return (TRUE);

        ASSERT(boards.sorted);
        index = FHC_B_SEARCH(boards, bd);
        if (index == -1)
                return (FALSE);
        else
                return (TRUE);
}

enum board_type
fhc_bd_type(int board)
{
        int index;

        ASSERT(boards.sorted);
        index = FHC_B_SEARCH(boards, board);
        if (index == -1)
                return (-1);

        return (boards.boards[index]->sc.type);
}

char *
fhc_bd_typestr(enum board_type type)
{
        char *type_str;

        switch (type) {
        case MEM_BOARD:
                type_str = MEM_BD_NAME;
                break;

        case CPU_BOARD:
                type_str = CPU_BD_NAME;
                break;

        case IO_2SBUS_BOARD:
                type_str = IO_2SBUS_BD_NAME;
                break;

        case IO_SBUS_FFB_BOARD:
                type_str = IO_SBUS_FFB_BD_NAME;
                break;

        case IO_2SBUS_SOCPLUS_BOARD:
                type_str = IO_2SBUS_SOCPLUS_BD_NAME;
                break;

        case IO_SBUS_FFB_SOCPLUS_BOARD:
                type_str = IO_SBUS_FFB_SOCPLUS_BD_NAME;
                break;

        case IO_PCI_BOARD:
                type_str = IO_PCI_BD_NAME;
                break;

        case DISK_BOARD:
                type_str = DISK_BD_NAME;
                break;

        case UNKNOWN_BOARD:
        default:
                type_str = "unknown";
                break;
        }

        return (type_str);
}

void
fhc_bd_env_set(int board, void *env)
{
        fhc_bd_t *bdp;

        bdp = fhc_bd(board);
        bdp->dev_softsp = env;
}

static void
fhc_bd_dlist_init()
{
        int i;
        int len;
        int board;
        pnode_t node;
        char *dlist;
        int index;

        /*
         * Find the disabled board list property if present.
         *
         * The disabled board list is in the options node under root;
         * it is a null terminated list of boards in a string.
         * Each char represents a board. The driver must
         * reject illegal chars in case a user places them in the
         * property.
         */
        if (((node = prom_finddevice("/options")) == OBP_BADNODE) ||
            ((len = prom_getproplen(node, "disabled-board-list")) == -1))
                return;

        dlist = kmem_alloc(len, KM_SLEEP);
        (void) prom_getprop(node, "disabled-board-list", dlist);

        /*
         * now loop thru the string, and create disabled board list
         * entries for all legal boards in the list.
         */
        for (i = 0; (i < len) && (dlist[i] != 0); i++) {
                char ch = dlist[i];

                if (ch >= '0' && ch <= '9')
                        board = ch - '0';
                else if (ch >= 'A' && ch <= 'F')
                        board = ch - 'A' + 10;
                else if (ch >= 'a' && ch <= 'f')
                        board = ch - 'a' + 10;
                else
                        /* junk entry */
                        continue;

                index = FHC_B_SEARCH(boards, board);
                if (index != -1) {
                        boards.boards[index]->flags |= BDF_DISABLED;
                }
        }
        kmem_free(dlist, len);
}

static struct bd_info fhc_bd_info;

static int
fhc_bd_ks_update(kstat_t *ksp, int rw)
{
        fhc_bd_t *bdp;
        sysc_cfga_stat_t *sc;
        struct bd_info *uip;
        enum board_state state;

        if (rw == KSTAT_WRITE)
                return (EACCES);

        bdp = (fhc_bd_t *)ksp->ks_private;
        uip = &fhc_bd_info;
        sc = &bdp->sc;

        ASSERT(fhc_bd_valid(sc->board));

        uip->board = sc->board;
        uip->type = sc->type;
        uip->fhc_compid = sc->fhc_compid;
        uip->ac_compid = sc->ac_compid;
        bcopy((caddr_t)sc->prom_rev, uip->prom_rev, sizeof (uip->prom_rev));
        bcopy((caddr_t)&sc->bd, &uip->bd, sizeof (union bd_un));

        switch (sc->rstate) {
        case SYSC_CFGA_RSTATE_DISCONNECTED:
                switch (sc->condition) {
                case SYSC_CFGA_COND_OK:
                case SYSC_CFGA_COND_UNKNOWN:
                        state = DISABLED_STATE;
                        break;
                case SYSC_CFGA_COND_FAILING:
                case SYSC_CFGA_COND_FAILED:
                case SYSC_CFGA_COND_UNUSABLE:
                        state = FAILED_STATE;
                        break;
                default:
                        state = UNKNOWN_STATE;
                        break;
                }
                break;
        default:
                state = UNKNOWN_STATE;
                break;
        }

        uip->state = state;

        return (0);
}

void
fhc_bd_ks_alloc(fhc_bd_t *bdp)
{
        ASSERT(!bdp->ksp);

        bdp->ksp = kstat_create("unix", bdp->sc.board,
                BDLIST_KSTAT_NAME, "misc", KSTAT_TYPE_RAW,
                sizeof (struct bd_info), KSTAT_FLAG_VIRTUAL);

        if (bdp->ksp != NULL) {
                bdp->ksp->ks_data = &fhc_bd_info;
                bdp->ksp->ks_update = fhc_bd_ks_update;
                bdp->ksp->ks_private = (void *)bdp;
                kstat_install(bdp->ksp);
        }
}

static void
fhc_bdlist_dk_init()
{
        dev_info_t *dnode;

        /*
         * Search the children of root to see if there are any
         * disk boards in the tree.
         */
        for (dnode = ddi_get_child(ddi_root_node());
            dnode != NULL; dnode = ddi_get_next_sibling(dnode)) {
                if (strcmp(ddi_node_name(dnode), "disk-board") == 0) {
                        int id;
                        int board;
                        fhc_bd_t *bdp;
                        sysc_cfga_stat_t *sc;

                        /*
                         * Get the board number property.
                         */
                        if ((board = (int)ddi_getprop(DDI_DEV_T_ANY, dnode,
                                DDI_PROP_DONTPASS, OBP_BOARDNUM, -1)) == -1) {
                                cmn_err(CE_WARN,
                                        "Could not find board number");
                                continue;
                        }
                        bdp = fhc_bd(board);
                        sc = &bdp->sc;

                        if ((id = (int)ddi_getprop(DDI_DEV_T_ANY, dnode,
                            DDI_PROP_DONTPASS, "disk0-scsi-id", -1)) != -1) {
                                sc->bd.dsk.disk_pres[0] = 1;
                                sc->bd.dsk.disk_id[0] = id;
                        } else {
                                sc->bd.dsk.disk_pres[0] = 0;
                        }

                        if ((id = (int)ddi_getprop(DDI_DEV_T_ANY, dnode,
                            DDI_PROP_DONTPASS, "disk1-scsi-id", -1)) != -1) {
                                sc->bd.dsk.disk_pres[1] = 1;
                                sc->bd.dsk.disk_id[1] = id;
                        } else {
                                sc->bd.dsk.disk_pres[1] = 0;
                        }

                }
        }

}

struct jt_mstr *
jtag_master_lock(void)
{
        fhc_bd_t *bdp;
        struct jt_mstr *master = NULL;

        ASSERT(fhc_bdlist_locked());

        /*
         * Now search for the JTAG master and place the addresses for
         * command into the fhc soft state structure.
         * Disk board do not have softsp set.
         */
        for (bdp = fhc_bd_first(); bdp; bdp = fhc_bd_next(bdp))
                if (bdp->softsp && (bdp->softsp->jt_master.is_master == 1)) {
                        master = &bdp->softsp->jt_master;
                        mutex_enter(&master->lock);
                        break;
                }

        return (master);
}

void
jtag_master_unlock(struct jt_mstr *mstr)
{
        ASSERT(fhc_bdlist_locked());
        ASSERT(mutex_owned(&mstr->lock));

        mutex_exit(&mstr->lock);
}

void
fhc_bdlist_prime(int first, int count, int incr)
{
        int board;
        fhc_bd_t *bdp;
        sysc_evt_t se;
        sysc_cfga_stat_t *sc;
        struct jt_mstr *jtm;
        int index;
        int nadded;

        ASSERT(fbe->update);

        (void) fhc_bdlist_lock(-1);
        nadded = 0;
        for (board = first; board < count; board += incr) {
                /*
                 * Search only subset of array. We hold mutex so
                 * noone can add new elements to it.
                 */
                index = fhc_b_search(boards.boards, board, 0,
                    boards.last - nadded);
                if (index == -1) {
                        fhc_check_size(&boards);
                        boards.boards[boards.last + 1] =
                            kmem_zalloc(sizeof (fhc_bd_t), KM_SLEEP);
                        boards.boards[boards.last + 1]->sc.type = UNKNOWN_BOARD;
                        boards.boards[boards.last + 1]->sc.board = board;
                        boards.boards[boards.last + 1]->softsp = NULL;
                        boards.last++;
                        nadded++;
                        boards.sorted = FALSE;
                }
        }
        fhc_check_array(FHC_BOARDS);
        fhc_bdlist_unlock();

        fhc_bdmax = count - 1;

        /*
         * Initialize our copy of the disabled board list.
         */
        fhc_bd_dlist_init();

        (void) fhc_bdlist_lock(-1);

        if ((jtm = jtag_master_lock()) == NULL)
                cmn_err(CE_PANIC, "fhc_bdlist_prime: no jtag master");

        /*
         * Go through the board list, skipping illegal slots
         * and initialize each slot.
         */
        for (bdp = fhc_bd_first(); bdp; bdp = fhc_bd_next(bdp)) {
                sc = &bdp->sc;
                board = sc->board;

                se = SYSC_EVT_BD_PRESENT;

                if (sc->type == UNKNOWN_BOARD) {
                        uint_t fhc_csr;
                        uint_t fhc_bsr;
                        enum board_type type;

                        type = jtag_get_board_type(jtm->jtag_cmd, sc);
                        switch (type) {
                        case EMPTY_BOARD:
                                fhc_bd_sc_evt(sc, SYSC_EVT_BD_EMPTY);
                                continue;
                        case DISK_BOARD:
                                /*
                                 * Disk boards are handled differently
                                 * in that they don't fail POST and have
                                 * no fhc attached.
                                 */
                                sc->type = DISK_BOARD;
                                (void) jtag_init_disk_board(jtm->jtag_cmd,
                                    board,
                                    &fhc_csr, &fhc_bsr);
                                fhc_bd_ks_alloc(bdp);
                                break;
                        default:
                                /*
                                 * Set the condition to FAILED if POST has
                                 * failed. A failed board is physically
                                 * present, is not on the disabled list and
                                 * is of type UNKNOWN.
                                 * NOTE: a non-present board which is
                                 * (potentially) on the disabled board
                                 * list has been ignored in the empty
                                 * slot case.
                                 */
                                if (fhc_bd_disabled(board)) {
                                        fhc_bd_ks_alloc(bdp);
                                        se = SYSC_EVT_BD_DISABLED;
                                } else
                                        se = SYSC_EVT_BD_FAILED;

                                sc->type = type;
                                break;
                        }
                }

                fhc_bd_sc_evt(sc, se);
        }

        /*
         * Do the disk specific initialization.  This routine scans
         * for all disk boards, so we call it only once.
         */
        fhc_bdlist_dk_init();

        jtag_master_unlock(jtm);

        fhc_bdlist_unlock();
}

struct cpu_speed {
        int cpu_freq;
        int sram_mode;
        int system_div;
        int system_dvd;
};

struct cpu_speed ultraI_speed_table[] = {
        { 0,    0,      0,      0},
        { 143,  1,      2,      1},
        { 154,  1,      2,      1},
        { 168,  1,      2,      1},
        { 182,  1,      3,      1},
        { 200,  1,      3,      1},
        { 222,  1,      3,      1},
        { 250,  1,      3,      1}
};

struct cpu_speed ultraII_speed_table[] = {
        { 0,    0,      0,      0},
        { 360,  2,      2,      1},
        { 400,  2,      4,      1},
        { 400,  2,      5,      2},
        { 248,  2,      3,      2},
        { 496,  2,      5,      2},
        { 296,  2,      2,      1},
        { 336,  2,      2,      1}
};

/*
 * set_cpu_info
 *
 * This routine extracts CPU module information used later for
 * determining hotplug compatibility.
 */
static void
set_cpu_info(sysc_cfga_stat_t *sc, uint_t fhc_bsr)
{
        int i;
        int speed_pins;
        struct cpu_speed *table;

        for (i = 0; i < 2; i++) {
                sc->bd.cpu[i].cpu_speed = 0;
                sc->bd.cpu[i].cpu_sram_mode = 0;

                if (!sc->bd.cpu[i].cpu_detected)
                        continue;

                speed_pins = (i == 0) ? CPU_0_PINS(fhc_bsr) :
                                CPU_1_PINS(fhc_bsr);

                switch (sc->bd.cpu[i].cpu_compid & CID_REV_MASK) {
                        case ULTRAI_COMPID:
                                table = ultraI_speed_table;
                                break;
                        case ULTRAII_COMPID:
                                table = ultraII_speed_table;
                                break;
                        default:
                                cmn_err(CE_WARN, "board %d, cpu module %c "
                                        "unknown type", sc->board,
                                        (i == 0) ? 'A' : 'B');
                                sc->bd.cpu[i].cpu_speed = -1;
                                continue;
                }

                sc->bd.cpu[i].cpu_speed = table[speed_pins].cpu_freq;
                sc->bd.cpu[i].cpu_sram_mode = table[speed_pins].sram_mode;
        }
}

int
fhc_bdlist_scan(sysc_cfga_rstate_t rstate, struct jt_mstr *jtm)
{
        int board;
        int error;
        int found = 0;
        uint_t fhc_csr;
        uint_t fhc_bsr;
        fhc_bd_t *bdp;
        sysc_cfga_stat_t *sc;
        enum board_type type;

        for (bdp = fhc_bd_first(); bdp; bdp = fhc_bd_next(bdp)) {

                sc = &bdp->sc;
                board = sc->board;

                /*
                 * Check the boards in EMPTY and DISCONNECTED
                 * states.  We need to check a board in the
                 * DISCONNECTED state in case it had been replugged.
                 */
                if (sc->in_transition || sc->rstate != rstate)
                        continue;
                else if (sc->rstate == SYSC_CFGA_RSTATE_EMPTY) {
                        type = jtag_get_board_type(jtm->jtag_cmd, sc);
                        if (type == -1)
                                continue;       /* no board present */
                        sc->type = type;
                } else
                        type = sc->type;

                if (type != UNKNOWN_BOARD)
                        (void) jtag_get_board_info(jtm->jtag_cmd, sc);

                error = 0;

                if (type == DISK_BOARD)
                        /*
                         * Scan the FHC to turn off the board insert
                         * interrupt and modify LEDs based on hotplug
                         * status.
                         */
                        (void) jtag_init_disk_board(jtm->jtag_cmd, board,
                                        &fhc_csr, &fhc_bsr);
                else
                        error = jtag_powerdown_board(jtm->jtag_cmd,
                                        board, type, &fhc_csr, &fhc_bsr, FALSE);

                if (error) {
                        fhc_bd_sc_evt(sc, SYSC_EVT_BD_INS_FAILED);
                        continue;
                }

                if (fhc_csr & FHC_NOT_BRD_PRES)
                        continue;

                if (type == CPU_BOARD) {
                        set_cpu_info(sc, fhc_bsr);
                }

                fhc_bd_sc_evt(sc, SYSC_EVT_BD_INSERTED);

                /*
                 * A replugged board will still have its kstat info.
                 */
                if (!bdp->ksp)
                        fhc_bd_ks_alloc(bdp);

                found++;
                break;
        }

        return (found);
}

int
fhc_bd_insert_scan()
{
        struct jt_mstr *jtm;
        int found;

        ASSERT(fhc_bdlist_locked());

        if ((jtm = jtag_master_lock()) == NULL)
                cmn_err(CE_PANIC, "fhc_bd_insert_scan: no jtag master");

        /* first check empty then disconnected */
        found = fhc_bdlist_scan(SYSC_CFGA_RSTATE_EMPTY, jtm);
        if (!found)
                found |= fhc_bdlist_scan(SYSC_CFGA_RSTATE_DISCONNECTED, jtm);
        if (!found)
                cmn_err(CE_WARN, "Could not find hotplugged core system board");

        jtag_master_unlock(jtm);

        return (found);
}

int
fhc_bd_remove_scan()
{
        int poll = 0;
        fhc_bd_t *bdp;
        struct jt_mstr *jtm;
        sysc_cfga_stat_t *sc;

        ASSERT(fhc_bdlist_locked());

        if ((jtm = jtag_master_lock()) == NULL)
                cmn_err(CE_PANIC, "fhc_bd_remove_scan: no jtag master");

        for (bdp = fhc_bd_first(); bdp; bdp = fhc_bd_next(bdp)) {
                sc = &bdp->sc;

                if (sc->rstate != SYSC_CFGA_RSTATE_DISCONNECTED)
                        continue;
                /*
                 * While there is a board in the disconnected state
                 * continue polling. When the last board is removed,
                 * we will get one last scan.
                 */
                poll++;

                if (sc->in_transition)
                        continue;

                /*
                 * Scan to see if the board is still in.
                 */
                if (jtag_get_board_type(jtm->jtag_cmd, sc) == -1) {
                        if (bdp->ksp) {
                                kstat_delete(bdp->ksp);
                                bdp->ksp = NULL;
                        }
                        fhc_bd_sc_evt(sc, SYSC_EVT_BD_REMOVED);
                }
        }

        jtag_master_unlock(jtm);

        return (poll);
}

int
fhc_bd_detachable(int board)
{
        fhc_bd_t *bdp = fhc_bd(board);

        if (bdp->softsp != NULL)
                return (bdp->flags & BDF_DETACH);
        else
                return (FALSE);
}

void
fhc_bd_sc_register(void (*f)(void *, sysc_cfga_stat_t *, sysc_evt_t), void *sp)
{
        fhc_bd_evt.update = f;
        fhc_bd_evt.soft = sp;
}

void
fhc_bd_update(int board, sysc_evt_t evt)
{
        fhc_bd_t *bdp;

        ASSERT(fhc_bd_valid(board));

        /*
         * There is a window where this routine might be called
         * as a result of the environ thread before sysctrl has
         * attached and registered the callback.
         */
        if (!(fbe->update))
                return;

        bdp = fhc_bdlist_lock(board);

        fhc_bd_sc_evt(&bdp->sc, evt);

        fhc_bdlist_unlock();
}

/* ARGSUSED */
int
fhc_bd_test(int board, sysc_cfga_pkt_t *pkt)
{
        uint_t fhc_csr, fhc_bsr;
        fhc_bd_t *bdp;
        struct jt_mstr *jtm;
        sysc_cfga_stat_t *sc;

        ASSERT(fhc_bdlist_locked());
        ASSERT(fhc_bd_busy(board));

        bdp = fhc_bd(board);
        sc = &bdp->sc;

        switch (sc->rstate) {
        case SYSC_CFGA_RSTATE_EMPTY:
                cmn_err(CE_NOTE, "fhc_bd_test: simulate board %d insertion",
                    board);

                jtm = jtag_master_lock();
                ASSERT(jtm);
                jtag_master_unlock(jtm);

                (void) jtag_powerdown_board(jtm->jtag_cmd, board,
                        sc->type, &fhc_csr, &fhc_bsr, TRUE);
                break;
        case SYSC_CFGA_RSTATE_DISCONNECTED:
                cmn_err(CE_NOTE, "fhc_bd_test: simulate board %d removal",
                    board);

                if (bdp->ksp) {
                        kstat_delete(bdp->ksp);
                        bdp->ksp = NULL;
                }
                fhc_bd_sc_evt(sc, SYSC_EVT_BD_REMOVED);
                break;
        default:
                cmn_err(CE_NOTE,
                        "fhc_bd_test: invalid board state: %d", board);
                break;
        }

        return (0);
}

/*
 * force a board condition for test purpose
 */
/* ARGSUSED */
int
fhc_bd_test_set_cond(int board, sysc_cfga_pkt_t *sysc_pkt)
{
        fhc_bd_t *bdp;
        sysc_cfga_stat_t *sc;
        sysc_cfga_cond_t cond;

        ASSERT(fhc_bdlist_locked());
        ASSERT(fhc_bd_busy(board));

        bdp = fhc_bd(board);
        sc = &bdp->sc;

        cond = (sysc_cfga_cond_t)sysc_pkt->cmd_cfga.arg;

        switch (cond) {
        case SYSC_CFGA_COND_UNKNOWN:
        case SYSC_CFGA_COND_OK:
        case SYSC_CFGA_COND_FAILING:
        case SYSC_CFGA_COND_FAILED:
        case SYSC_CFGA_COND_UNUSABLE:
                sc->condition = cond;
                return (0);
        default:
                return (EINVAL);
        }
}