diff options
Diffstat (limited to 'usr/src/uts/sun4u/io/sbd.c')
| -rw-r--r-- | usr/src/uts/sun4u/io/sbd.c | 5204 |
1 files changed, 5204 insertions, 0 deletions
diff --git a/usr/src/uts/sun4u/io/sbd.c b/usr/src/uts/sun4u/io/sbd.c new file mode 100644 index 0000000000..933dbb5af1 --- /dev/null +++ b/usr/src/uts/sun4u/io/sbd.c @@ -0,0 +1,5204 @@ +/* + * 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 2005 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * safari system board DR module. + */ + +#include <sys/debug.h> +#include <sys/types.h> +#include <sys/errno.h> +#include <sys/cred.h> +#include <sys/dditypes.h> +#include <sys/devops.h> +#include <sys/modctl.h> +#include <sys/poll.h> +#include <sys/conf.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/sunndi.h> +#include <sys/ndi_impldefs.h> +#include <sys/stat.h> +#include <sys/kmem.h> +#include <sys/cpuvar.h> +#include <sys/mem_config.h> +#include <sys/mem_cage.h> + +#include <sys/autoconf.h> +#include <sys/cmn_err.h> + +#include <sys/ddi_impldefs.h> +#include <sys/machsystm.h> +#include <sys/param.h> + +#include <sys/sbdpriv.h> +#include <sys/sbd_io.h> + +/* start sbd includes */ + +#include <sys/systm.h> +#include <sys/sysmacros.h> +#include <sys/x_call.h> +#include <sys/membar.h> +#include <vm/seg_kmem.h> + +extern int nulldev(); +extern int nodev(); + +typedef struct { /* arg to sbd_get_handle */ + dev_t dev; + int cmd; + int mode; + sbd_ioctl_arg_t *ioargp; +} sbd_init_arg_t; + + +/* + * sbd support operations. + */ +static void sbd_exec_op(sbd_handle_t *hp); +static void sbd_dev_configure(sbd_handle_t *hp); +static int sbd_dev_release(sbd_handle_t *hp); +static int sbd_dev_unconfigure(sbd_handle_t *hp); +static void sbd_attach_cpu(sbd_handle_t *hp, sbderror_t *ep, + dev_info_t *dip, int unit); +static void sbd_detach_cpu(sbd_handle_t *hp, sbderror_t *ep, + dev_info_t *dip, int unit); +static int sbd_detach_mem(sbd_handle_t *hp, sbderror_t *ep, int unit); +static void sbd_cancel(sbd_handle_t *hp); +void sbd_errno_decode(int err, sbderror_t *ep, dev_info_t *dip); +int sbd_dealloc_instance(sbd_board_t *sbp, int max_boards); +int sbd_errno2ecode(int error); +#pragma weak sbdp_cpu_get_impl + +#ifdef DEBUG +uint_t sbd_debug = (uint_t)0x0; + +#ifdef SBD_DEBUG_ERRS +/* controls which errors are injected */ +uint_t sbd_err_debug = (uint_t)0x0; + +/* controls printing about error injection */ +uint_t sbd_print_errs = (uint_t)0x0; + +#endif /* SBD_DEBUG_ERRS */ + +#endif /* DEBUG */ + +char *sbd_state_str[] = { + "EMPTY", "OCCUPIED", "CONNECTED", "UNCONFIGURED", + "PARTIAL", "CONFIGURED", "RELEASE", "UNREFERENCED", + "FATAL" +}; + +/* Note: this must be changed in tandem with sbd_ioctl.h */ +char *sbd_ct_str[] = { + "NONE", "CPU", "MEM", "IO", "UNKNOWN" +}; + +/* Note: this must also be changed in tandem with sbd_ioctl.h */ +#define SBD_CMD_STR(c) \ + (((c) == SBD_CMD_ASSIGN) ? "ASSIGN" : \ + ((c) == SBD_CMD_UNASSIGN) ? "UNASSIGN" : \ + ((c) == SBD_CMD_POWERON) ? "POWERON" : \ + ((c) == SBD_CMD_POWEROFF) ? "POWEROFF" : \ + ((c) == SBD_CMD_TEST) ? "TEST" : \ + ((c) == SBD_CMD_CONNECT) ? "CONNECT" : \ + ((c) == SBD_CMD_CONFIGURE) ? "CONFIGURE" : \ + ((c) == SBD_CMD_UNCONFIGURE) ? "UNCONFIGURE" : \ + ((c) == SBD_CMD_DISCONNECT) ? "DISCONNECT" : \ + ((c) == SBD_CMD_STATUS) ? "STATUS" : \ + ((c) == SBD_CMD_GETNCM) ? "GETNCM" : \ + ((c) == SBD_CMD_PASSTHRU) ? "PASSTHRU" : "unknown") + +/* + * Defines and structures for device tree naming and mapping + * to node types + */ + +sbd_devattr_t *sbd_devattr; + +/* defines to access the attribute struct */ +#define SBD_DEVNAME(i) sbd_devattr[i].s_devname +#define SBD_OTYPE(i) sbd_devattr[(i)].s_obp_type +#define SBD_COMP(i) sbd_devattr[i].s_dnodetype + +/* + * State transition table. States valid transitions for "board" state. + * Recall that non-zero return value terminates operation, however + * the herrno value is what really indicates an error , if any. + */ +static int +_cmd2index(int c) +{ + /* + * Translate DR CMD to index into sbd_state_transition. + */ + switch (c) { + case SBD_CMD_CONNECT: return (0); + case SBD_CMD_DISCONNECT: return (1); + case SBD_CMD_CONFIGURE: return (2); + case SBD_CMD_UNCONFIGURE: return (3); + case SBD_CMD_POWEROFF: return (4); + case SBD_CMD_POWERON: return (5); + case SBD_CMD_UNASSIGN: return (6); + case SBD_CMD_ASSIGN: return (7); + case SBD_CMD_TEST: return (8); + default: return (-1); + } +} + +#define CMD2INDEX(c) _cmd2index(c) + +static struct sbd_state_trans { + int x_cmd; + struct { + int x_rv; /* return value of pre_op */ + int x_err; /* errno, if any */ + } x_op[SBD_NUM_STATES]; +} sbd_state_transition[] = { + { SBD_CMD_CONNECT, + { + { 0, 0 }, /* empty */ + { 0, 0 }, /* occupied */ + { 1, EIO }, /* connected */ + { 1, EIO }, /* unconfigured */ + { 1, EIO }, /* partial */ + { 1, EIO }, /* configured */ + { 1, EIO }, /* release */ + { 1, EIO }, /* unreferenced */ + { 1, EIO }, /* fatal */ + } + }, + { SBD_CMD_DISCONNECT, + { + { 1, EIO }, /* empty */ + { 0, 0 }, /* occupied */ + { 0, 0 }, /* connected */ + { 0, 0 }, /* unconfigured */ + { 1, EIO }, /* partial */ + { 1, EIO }, /* configured */ + { 1, EIO }, /* release */ + { 1, EIO }, /* unreferenced */ + { 1, EIO }, /* fatal */ + } + }, + { SBD_CMD_CONFIGURE, + { + { 1, EIO }, /* empty */ + { 1, EIO }, /* occupied */ + { 0, 0 }, /* connected */ + { 0, 0 }, /* unconfigured */ + { 0, 0 }, /* partial */ + { 1, 0 }, /* configured */ + { 0, 0 }, /* release */ + { 0, 0 }, /* unreferenced */ + { 1, EIO }, /* fatal */ + } + }, + { SBD_CMD_UNCONFIGURE, + { + { 1, EIO }, /* empty */ + { 1, EIO }, /* occupied */ + { 1, EIO }, /* connected */ + { 1, EIO }, /* unconfigured */ + { 1, EIO }, /* partial */ + { 0, 0 }, /* configured */ + { 0, 0 }, /* release */ + { 0, 0 }, /* unreferenced */ + { 1, EIO }, /* fatal */ + } + }, + { SBD_CMD_POWEROFF, + { + { 1, EIO }, /* empty */ + { 0, 0 }, /* occupied */ + { 1, EIO }, /* connected */ + { 1, EIO }, /* unconfigured */ + { 1, EIO }, /* partial */ + { 1, EIO }, /* configured */ + { 1, EIO }, /* release */ + { 1, EIO }, /* unreferenced */ + { 1, EIO }, /* fatal */ + } + }, + { SBD_CMD_POWERON, + { + { 1, EIO }, /* empty */ + { 0, 0 }, /* occupied */ + { 1, EIO }, /* connected */ + { 1, EIO }, /* unconfigured */ + { 1, EIO }, /* partial */ + { 1, EIO }, /* configured */ + { 1, EIO }, /* release */ + { 1, EIO }, /* unreferenced */ + { 1, EIO }, /* fatal */ + } + }, + { SBD_CMD_UNASSIGN, + { + { 1, EIO }, /* empty */ + { 0, 0 }, /* occupied */ + { 1, EIO }, /* connected */ + { 1, EIO }, /* unconfigured */ + { 1, EIO }, /* partial */ + { 1, EIO }, /* configured */ + { 1, EIO }, /* release */ + { 1, EIO }, /* unreferenced */ + { 1, EIO }, /* fatal */ + } + }, + { SBD_CMD_ASSIGN, + { + { 1, EIO }, /* empty */ + { 0, 0 }, /* occupied */ + { 1, EIO }, /* connected */ + { 1, EIO }, /* unconfigured */ + { 1, EIO }, /* partial */ + { 1, EIO }, /* configured */ + { 1, EIO }, /* release */ + { 1, EIO }, /* unreferenced */ + { 1, EIO }, /* fatal */ + } + }, + { SBD_CMD_TEST, + { + { 1, EIO }, /* empty */ + { 0, 0 }, /* occupied */ + { 1, EIO }, /* connected */ + { 1, EIO }, /* unconfigured */ + { 1, EIO }, /* partial */ + { 1, EIO }, /* configured */ + { 1, EIO }, /* release */ + { 1, EIO }, /* unreferenced */ + { 1, EIO }, /* fatal */ + } + }, +}; + +/* + * Global R/W lock to synchronize access across + * multiple boards. Users wanting multi-board access + * must grab WRITE lock, others must grab READ lock. + */ +krwlock_t sbd_grwlock; + +/* + * Global to determine if an event needs to be sent + */ +char send_event = 0; + +/* + * Required/Expected functions. + */ + +static sbd_handle_t *sbd_get_handle(dev_t dev, sbd_softstate_t *softsp, + intptr_t arg, sbd_init_arg_t *iap); +static void sbd_release_handle(sbd_handle_t *hp); +static int sbd_pre_op(sbd_handle_t *hp); +static void sbd_post_op(sbd_handle_t *hp); +static int sbd_probe_board(sbd_handle_t *hp); +static int sbd_deprobe_board(sbd_handle_t *hp); +static void sbd_connect(sbd_handle_t *hp); +static void sbd_assign_board(sbd_handle_t *hp); +static void sbd_unassign_board(sbd_handle_t *hp); +static void sbd_poweron_board(sbd_handle_t *hp); +static void sbd_poweroff_board(sbd_handle_t *hp); +static void sbd_test_board(sbd_handle_t *hp); + +static int sbd_disconnect(sbd_handle_t *hp); +static sbd_devlist_t *sbd_get_attach_devlist(sbd_handle_t *hp, + int32_t *devnump, int32_t pass); +static int sbd_pre_attach_devlist(sbd_handle_t *hp, + sbd_devlist_t *devlist, int32_t devnum); +static int sbd_post_attach_devlist(sbd_handle_t *hp, + sbd_devlist_t *devlist, int32_t devnum); +static sbd_devlist_t *sbd_get_release_devlist(sbd_handle_t *hp, + int32_t *devnump, int32_t pass); +static int sbd_pre_release_devlist(sbd_handle_t *hp, + sbd_devlist_t *devlist, int32_t devnum); +static int sbd_post_release_devlist(sbd_handle_t *hp, + sbd_devlist_t *devlist, int32_t devnum); +static void sbd_release_done(sbd_handle_t *hp, + sbd_comp_type_t nodetype, + dev_info_t *dip); +static sbd_devlist_t *sbd_get_detach_devlist(sbd_handle_t *hp, + int32_t *devnump, int32_t pass); +static int sbd_pre_detach_devlist(sbd_handle_t *hp, + sbd_devlist_t *devlist, int32_t devnum); +static int sbd_post_detach_devlist(sbd_handle_t *hp, + sbd_devlist_t *devlist, int32_t devnum); +static void sbd_status(sbd_handle_t *hp); +static void sbd_get_ncm(sbd_handle_t *hp); + + +/* + * Support functions. + */ +static sbd_devset_t sbd_dev2devset(sbd_comp_id_t *cid); +static int sbd_copyin_ioarg(sbd_handle_t *hp, int mode, int cmd, + sbd_cmd_t *cmdp, sbd_ioctl_arg_t *iap); +static int sbd_copyout_errs(int mode, sbd_ioctl_arg_t *iap, + void *arg); +static int sbd_copyout_ioarg(int mode, int cmd, sbd_cmd_t *scp, + sbd_ioctl_arg_t *iap); +static int sbd_check_transition(sbd_board_t *sbp, + sbd_devset_t *devsetp, + struct sbd_state_trans *transp); +static sbd_devlist_t *sbd_get_devlist(sbd_handle_t *hp, + sbd_board_t *sbp, + sbd_comp_type_t nodetype, + int max_units, uint_t uset, + int *count, int present_only); +static int sbd_mem_status(sbd_handle_t *hp, sbd_devset_t devset, + sbd_dev_stat_t *dsp); + +static int sbd_init_devlists(sbd_board_t *sbp); +static int sbd_name_to_idx(char *name); +static int sbd_otype_to_idx(char *otpye); +static int sbd_setup_devlists(dev_info_t *dip, void *arg); +static void sbd_init_mem_devlists(sbd_board_t *sbp); +static void sbd_init_cpu_unit(sbd_board_t *sbp, int unit); +static void sbd_board_discovery(sbd_board_t *sbp); +static void sbd_board_init(sbd_board_t *sbp, + sbd_softstate_t *softsp, + int bd, dev_info_t *dip, int wnode); +static void sbd_board_destroy(sbd_board_t *sbp); +static int sbd_check_unit_attached(sbd_board_t *sbp, + dev_info_t *dip, int unit, + sbd_comp_type_t nodetype, sbderror_t *ep); + +static sbd_state_t rstate_cvt(sbd_istate_t state); + +/* + * Autoconfiguration data structures + */ + +extern struct mod_ops mod_miscops; + +static struct modlmisc modlmisc = { + &mod_miscops, + "System Board DR v%I%" +}; + +static struct modlinkage modlinkage = { + MODREV_1, + (void *)&modlmisc, + NULL +}; + +static int sbd_instances = 0; + +/* + * dr Global data elements + */ +sbd_global sbd_g; + +/* + * We want to be able to unload the module when we wish to do so, but we don't + * want anything else to unload it. Unloading cannot occur until + * sbd_teardown_instance is called by an explicit IOCTL into the parent node. + * This support is for debugging purposes and should it be expected to work + * on the field, it should be enhanced: + * Currently, there is still a window where sbd_teardow_instance gets called, + * sbd_prevent_unloading now = 0, the driver doesn't get unloaded, and + * sbd_setup_instance gets called. This may cause a panic. + */ +int sbd_prevent_unloading = 1; + +/* + * Driver entry points. + */ +int +_init(void) +{ + int err; + + /* + * If you need to support multiple nodes (instances), then + * whatever the maximum number of supported nodes is would + * need to passed as the third parameter to ddi_soft_state_init(). + * Alternative would be to dynamically fini and re-init the + * soft state structure each time a node is attached. + */ + err = ddi_soft_state_init((void **)&sbd_g.softsp, + sizeof (sbd_softstate_t), SBD_MAX_INSTANCES); + if (err) + return (err); + + if ((err = mod_install(&modlinkage)) != 0) { + ddi_soft_state_fini((void **)&sbd_g.softsp); + return (err); + } + + /* Get the array of names from platform helper routine */ + sbd_devattr = sbdp_get_devattr(); + + return (err); +} + +int +_fini(void) +{ + int err; + + if (sbd_prevent_unloading) + return (DDI_FAILURE); + + ASSERT(sbd_instances == 0); + + if ((err = mod_remove(&modlinkage)) != 0) + return (err); + + ddi_soft_state_fini((void **)&sbd_g.softsp); + + return (0); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + +int +sbd_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, char *event) +{ + int rv = 0, instance; + sbd_handle_t *hp; + sbd_softstate_t *softsp; + sbd_init_arg_t init_arg; + static fn_t f = "sbd_ioctl"; + int dr_avail; + + PR_BYP("sbd_ioctl cmd=%x, arg=%x\n", cmd, arg); + + /* Note: this must also be changed in tandem with sbd_ioctl.h */ + switch (cmd) { + case SBD_CMD_ASSIGN: + case SBD_CMD_UNASSIGN: + case SBD_CMD_POWERON: + case SBD_CMD_POWEROFF: + case SBD_CMD_TEST: + case SBD_CMD_CONNECT: + case SBD_CMD_CONFIGURE: + case SBD_CMD_UNCONFIGURE: + case SBD_CMD_DISCONNECT: + case SBD_CMD_STATUS: + case SBD_CMD_GETNCM: + case SBD_CMD_PASSTHRU: + break; + default: + return (ENOTTY); + } + + instance = SBD_GET_MINOR2INST(getminor(dev)); + if ((softsp = (sbd_softstate_t *)GET_SOFTC(instance)) == NULL) { + cmn_err(CE_WARN, + "sbd:%s:%d: module not yet attached", + f, instance); + return (ENXIO); + } + + init_arg.dev = dev; + init_arg.cmd = cmd; + init_arg.mode = mode; + init_arg.ioargp = (sbd_ioctl_arg_t *)arg; + + hp = sbd_get_handle(dev, softsp, arg, &init_arg); + /* Check to see if we support dr */ + dr_avail = sbdp_dr_avail(); + if (dr_avail != 1) { + switch (hp->h_cmd) { + case SBD_CMD_STATUS: + case SBD_CMD_GETNCM: + case SBD_CMD_PASSTHRU: + break; + default: + sbd_release_handle(hp); + return (ENOTSUP); + } + } + + switch (hp->h_cmd) { + case SBD_CMD_STATUS: + case SBD_CMD_GETNCM: + case SBD_CMD_PASSTHRU: + /* no locks needed for these commands */ + break; + + default: + rw_enter(&sbd_grwlock, RW_WRITER); + mutex_enter(&SBDH2BD(hp->h_sbd)->sb_mutex); + + /* + * If we're dealing with memory at all, then we have + * to keep the "exclusive" global lock held. This is + * necessary since we will probably need to look at + * multiple board structs. Otherwise, we only have + * to deal with the board in question and so can drop + * the global lock to "shared". + */ + /* + * XXX This is incorrect. The sh_devset has not + * been set at this point - it is 0. + */ + rv = DEVSET_IN_SET(HD2MACHHD(hp)->sh_devset, + SBD_COMP_MEM, DEVSET_ANYUNIT); + if (rv == 0) + rw_downgrade(&sbd_grwlock); + break; + } + + /* + * Before any operations happen, reset the event flag + */ + send_event = 0; + + if (sbd_pre_op(hp) == 0) { + sbd_exec_op(hp); + sbd_post_op(hp); + } + + rv = SBD_GET_ERRNO(SBD_HD2ERR(hp)); + *event = send_event; + + /* undo locking, if any, done before sbd_pre_op */ + switch (hp->h_cmd) { + case SBD_CMD_STATUS: + case SBD_CMD_GETNCM: + case SBD_CMD_PASSTHRU: + break; + default: + mutex_exit(&SBDH2BD(hp->h_sbd)->sb_mutex); + rw_exit(&sbd_grwlock); + } + + sbd_release_handle(hp); + + return (rv); +} + +int +sbd_setup_instance(int instance, dev_info_t *root, int max_boards, int wnode, + caddr_t sbdp_arg) +{ + int b; + sbd_softstate_t *softsp; + sbd_board_t *sbd_boardlist; + static fn_t f = "sbd_setup_instance"; + + sbd_instances++; + + if (sbdp_setup_instance(sbdp_arg) != DDI_SUCCESS) { + sbd_instances--; + return (DDI_FAILURE); + } + + if (ALLOC_SOFTC(instance) != DDI_SUCCESS) { + cmn_err(CE_WARN, + "sbd:%s:%d: failed to alloc soft-state", + f, instance); + sbdp_teardown_instance(sbdp_arg); + sbd_instances--; + return (DDI_FAILURE); + } + + softsp = (sbd_softstate_t *)GET_SOFTC(instance); + + if (softsp == NULL) { + cmn_err(CE_WARN, + "sbd:%s:%d: failed to get soft-state instance", + f, instance); + goto exit; + } + + sbd_boardlist = GETSTRUCT(sbd_board_t, max_boards); + if (sbd_boardlist == NULL) { + cmn_err(CE_WARN, + "sbd:%s: failed to alloc board list %d", + f, instance); + goto exit; + } + + + softsp->sbd_boardlist = (void *)sbd_boardlist; + softsp->max_boards = max_boards; + softsp->wnode = wnode; + + + for (b = 0; b < max_boards; b++) { + sbd_board_init(sbd_boardlist++, softsp, b, root, wnode); + } + + + return (DDI_SUCCESS); +exit: + (void) sbdp_teardown_instance(sbdp_arg); + FREE_SOFTC(instance); + sbd_instances--; + return (DDI_FAILURE); +} + +int +sbd_teardown_instance(int instance, caddr_t sbdp_arg) +{ + sbd_softstate_t *softsp; + + if (sbdp_teardown_instance(sbdp_arg) != DDI_SUCCESS) + return (DDI_FAILURE); + + softsp = (sbd_softstate_t *)GET_SOFTC(instance); + if (softsp == NULL) { + return (DDI_FAILURE); + } + + (void) sbd_dealloc_instance((sbd_board_t *)softsp->sbd_boardlist, + softsp->max_boards); + + FREE_SOFTC(instance); + sbd_instances--; + sbd_prevent_unloading = 0; + + return (DDI_SUCCESS); +} + +static void +sbd_exec_op(sbd_handle_t *hp) +{ + sbd_board_t *sbp = SBDH2BD(hp->h_sbd); + static fn_t f = "sbd_exec_op"; + + switch (hp->h_cmd) { + int dev_canceled; + + case SBD_CMD_CONNECT: + if (sbd_probe_board(hp)) + break; + + sbd_connect(hp); + break; + + case SBD_CMD_CONFIGURE: + sbd_dev_configure(hp); + break; + + case SBD_CMD_UNCONFIGURE: + if (((dev_canceled = sbd_dev_release(hp)) == 0) && + (SBD_GET_ERRNO(SBD_HD2ERR(hp)) == 0 && + SBD_GET_ERR(SBD_HD2ERR(hp)) == 0)) + dev_canceled = sbd_dev_unconfigure(hp); + + if (dev_canceled) + sbd_cancel(hp); + break; + + case SBD_CMD_DISCONNECT: + mutex_enter(&sbp->sb_slock); + if (sbd_disconnect(hp) == 0) + (void) sbd_deprobe_board(hp); + mutex_exit(&sbp->sb_slock); + break; + + case SBD_CMD_STATUS: + sbd_status(hp); + break; + + case SBD_CMD_GETNCM: + sbd_get_ncm(hp); + break; + + case SBD_CMD_ASSIGN: + sbd_assign_board(hp); + break; + + case SBD_CMD_UNASSIGN: + sbd_unassign_board(hp); + break; + + case SBD_CMD_POWEROFF: + sbd_poweroff_board(hp); + break; + + case SBD_CMD_POWERON: + sbd_poweron_board(hp); + break; + + case SBD_CMD_TEST: + sbd_test_board(hp); + break; + + case SBD_CMD_PASSTHRU: + { + int rv; + sbdp_handle_t *hdp; + sbderror_t *ep = SBD_HD2ERR(hp); + sbdp_ioctl_arg_t ia, *iap; + + iap = &ia; + + iap->h_dev = hp->h_dev; + iap->h_cmd = hp->h_cmd; + iap->h_iap = (intptr_t)hp->h_iap; + iap->h_mode = hp->h_mode; + + hdp = sbd_get_sbdp_handle(sbp, hp); + rv = sbdp_ioctl(hdp, iap); + if (rv != 0) { + SBD_GET_PERR(hdp->h_err, SBD_HD2ERR(hp)); + ep->e_errno = rv; + } + sbd_release_sbdp_handle(hdp); + break; + } + + default: + SBD_SET_ERRNO(SBD_HD2ERR(hp), ENOTTY); + cmn_err(CE_WARN, + "sbd:%s: unknown command (%d)", + f, hp->h_cmd); + break; + + } + + if (SBD_GET_ERR(SBD_HD2ERR(hp))) + PR_BYP("XXX e_code=%d", SBD_GET_ERR(SBD_HD2ERR(hp))); + if (SBD_GET_ERRNO(SBD_HD2ERR(hp))) + PR_BYP("XXX errno=%d", SBD_GET_ERRNO(SBD_HD2ERR(hp))); +} + +sbd_comp_type_t +sbd_get_devtype(sbd_handle_t *hp, dev_info_t *dip) +{ + sbd_board_t *sbp = hp ? SBDH2BD(hp->h_sbd) : NULL; + sbd_istate_t bstate; + dev_info_t **devlist; + int i; + char device[OBP_MAXDRVNAME]; + int devicelen; + + devicelen = sizeof (device); + + bstate = sbp ? SBD_BOARD_STATE(sbp) : SBD_STATE_EMPTY; + /* + * if the board's connected or configured, search the + * devlists. Otherwise check the device tree + */ + switch (bstate) { + + case SBD_STATE_CONNECTED: + case SBD_STATE_CONFIGURED: + case SBD_STATE_UNREFERENCED: + case SBD_STATE_UNCONFIGURED: + devlist = sbp->sb_devlist[NIX(SBD_COMP_MEM)]; + for (i = 0; i < MAX_MEM_UNITS_PER_BOARD; i++) + if (devlist[i] == dip) + return (SBD_COMP_MEM); + + devlist = sbp->sb_devlist[NIX(SBD_COMP_CPU)]; + for (i = 0; i < MAX_CPU_UNITS_PER_BOARD; i++) + if (devlist[i] == dip) + return (SBD_COMP_CPU); + + devlist = sbp->sb_devlist[NIX(SBD_COMP_IO)]; + for (i = 0; i < MAX_IO_UNITS_PER_BOARD; i++) + if (devlist[i] == dip) + return (SBD_COMP_IO); + /*FALLTHROUGH*/ + + default: + if (ddi_getlongprop_buf(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + OBP_DEVICETYPE, (caddr_t)device, &devicelen)) + break; + + for (i = 0; SBD_COMP(i) != SBD_COMP_UNKNOWN; i++) { + if (strcmp(device, SBD_OTYPE(i)) != 0) + continue; + return (SBD_COMP(i)); + } + + break; + } + return (SBD_COMP_UNKNOWN); +} + +static void +sbd_dev_configure(sbd_handle_t *hp) +{ + int n, unit; + int32_t pass, devnum; + dev_info_t *dip; + sbd_devlist_t *devlist; + sbdp_handle_t *hdp; + sbd_comp_type_t nodetype; + sbd_board_t *sbp = SBDH2BD(hp->h_sbd); + + pass = 1; + + hdp = sbd_get_sbdp_handle(sbp, hp); + while ((devlist = sbd_get_attach_devlist(hp, &devnum, pass)) != NULL) { + int err; + + err = sbd_pre_attach_devlist(hp, devlist, devnum); + if (err < 0) { + break; + } else if (err > 0) { + pass++; + continue; + } + + for (n = 0; n < devnum; n++) { + sbderror_t *ep; + + ep = &devlist[n].dv_error; + SBD_SET_ERRNO(ep, 0); + SBD_SET_ERR(ep, 0); + dip = devlist[n].dv_dip; + nodetype = sbd_get_devtype(hp, dip); + + unit = sbdp_get_unit_num(hdp, dip); + if (unit < 0) { + SBD_GET_PERR(hdp->h_err, SBD_HD2ERR(hp)); + break; + } + + switch (nodetype) { + case SBD_COMP_MEM: + sbd_attach_mem(hp, ep); + if (SBD_GET_ERR(ep) == ESBD_CPUONLINE) { + FREESTRUCT(devlist, sbd_devlist_t, + MAX_MEM_UNITS_PER_BOARD); + sbd_release_sbdp_handle(hdp); + return; + } + break; + + case SBD_COMP_CPU: + sbd_attach_cpu(hp, ep, dip, unit); + break; + + case SBD_COMP_IO: + sbd_attach_io(hp, ep, dip, unit); + break; + + default: + SBD_SET_ERRNO(ep, ENOTTY); + break; + } + + if (sbd_set_err_in_hdl(hp, ep) == 0) + continue; + } + + err = sbd_post_attach_devlist(hp, devlist, devnum); + if (err < 0) + break; + + pass++; + } + sbd_release_sbdp_handle(hdp); +} + +static int +sbd_dev_release(sbd_handle_t *hp) +{ + int n, unit; + int32_t pass, devnum; + dev_info_t *dip; + sbd_board_t *sbp = SBDH2BD(hp->h_sbd); + sbdp_handle_t *hdp; + sbd_devlist_t *devlist; + sbd_comp_type_t nodetype; + int err = 0; + int dev_canceled; + + pass = 1; + hdp = sbd_get_sbdp_handle(sbp, hp); + + sbp->sb_busy = 1; + while ((devlist = + sbd_get_release_devlist(hp, &devnum, pass)) != NULL) { + + err = sbd_pre_release_devlist(hp, devlist, devnum); + if (err < 0) { + dev_canceled = 1; + break; + } else if (err > 0) { + pass++; + continue; + } + + dev_canceled = 0; + for (n = 0; n < devnum; n++) { + dip = devlist[n].dv_dip; + nodetype = sbd_get_devtype(hp, dip); + + unit = sbdp_get_unit_num(hdp, dip); + if (unit < 0) { + SBD_GET_PERR(hdp->h_err, SBD_HD2ERR(hp)); + break; + } + + if ((nodetype == SBD_COMP_MEM) && + sbd_release_mem(hp, dip, unit)) { + + dev_canceled++; + } + + sbd_release_done(hp, nodetype, dip); + } + + err = sbd_post_release_devlist(hp, devlist, devnum); + + if (err < 0) + break; + + if (dev_canceled) + break; + + pass++; + } + sbp->sb_busy = 0; + + sbd_release_sbdp_handle(hdp); + + if (dev_canceled) + return (dev_canceled); + + return (err); +} + +static int +sbd_dev_unconfigure(sbd_handle_t *hp) +{ + int n, unit; + int32_t pass, devnum; + dev_info_t *dip; + sbd_devlist_t *devlist; + sbdp_handle_t *hdp; + sbd_comp_type_t nodetype; + sbd_board_t *sbp = SBDH2BD(hp->h_sbd); + int dev_canceled = 0; + static fn_t f = "sbd_dev_unconfigure"; + + PR_ALL("%s...\n", f); + + pass = 1; + hdp = sbd_get_sbdp_handle(sbp, hp); + + while ((devlist = sbd_get_detach_devlist(hp, &devnum, pass)) != NULL) { + int err, detach_err = 0; + + err = sbd_pre_detach_devlist(hp, devlist, devnum); + if (err) { + /* + * Only cancel the operation for memory in + * case of failure. + */ + nodetype = sbd_get_devtype(hp, devlist->dv_dip); + if (nodetype == SBD_COMP_MEM) + dev_canceled = 1; + (void) sbd_post_detach_devlist(hp, devlist, devnum); + break; + } + + for (n = 0; n < devnum; n++) { + sbderror_t *ep; + + ep = &devlist[n].dv_error; + SBD_SET_ERRNO(ep, 0); + SBD_SET_ERR(ep, 0); + dip = devlist[n].dv_dip; + nodetype = sbd_get_devtype(hp, dip); + + unit = sbdp_get_unit_num(hdp, dip); + if (unit < 0) { + SBD_GET_PERR(hdp->h_err, SBD_HD2ERR(hp)); + break; + } + + switch (nodetype) { + case SBD_COMP_MEM: + dev_canceled = sbd_detach_mem(hp, ep, unit); + break; + + case SBD_COMP_CPU: + sbd_detach_cpu(hp, ep, dip, unit); + break; + + case SBD_COMP_IO: + sbd_detach_io(hp, ep, dip, unit); + break; + + default: + SBD_SET_ERRNO(ep, ENOTTY); + break; + } + + if (sbd_set_err_in_hdl(hp, ep) == 0) { + detach_err = -1; + break; + } + + } + err = sbd_post_detach_devlist(hp, devlist, devnum); + if ((err < 0) || (detach_err < 0)) + break; + + pass++; + } + + sbd_release_sbdp_handle(hdp); + return (dev_canceled); +} + +int +sbd_errno2ecode(int error) +{ + int rv; + + switch (error) { + case EBUSY: + rv = ESBD_BUSY; + break; + case EINVAL: + rv = ESBD_INVAL; + break; + case EALREADY: + rv = ESBD_ALREADY; + break; + case ENODEV: + rv = ESBD_NODEV; + break; + case ENOMEM: + rv = ESBD_NOMEM; + break; + default: + rv = ESBD_INVAL; + } + + return (rv); +} + +static void +sbd_attach_cpu(sbd_handle_t *hp, sbderror_t *ep, dev_info_t *dip, int unit) +{ + int rv = 0; + processorid_t cpuid; + sbdp_handle_t *hdp; + sbd_board_t *sbp = SBDH2BD(hp->h_sbd); + static fn_t f = "sbd_attach_cpu"; + char *pathname; + + ASSERT(MUTEX_HELD(&cpu_lock)); + + ASSERT(dip); + + /* + * With the introduction of CMP devices, the CPU nodes + * are no longer directly under the top node. Since + * there is no plan to support CPU attach in the near + * future, a branch configure operation is not required. + */ + + hdp = sbd_get_sbdp_handle(sbp, hp); + cpuid = sbdp_get_cpuid(hdp, dip); + if (cpuid < 0) { + rv = -1; + SBD_GET_PERR(hdp->h_err, ep); + } else if ((rv = cpu_configure(cpuid)) != 0) { + cmn_err(CE_WARN, + "sbd:%s: cpu_configure for cpuid %d failed", + f, cpuid); + SBD_SET_ERR(ep, sbd_errno2ecode(rv)); + } + sbd_release_sbdp_handle(hdp); + + if (rv == 0) { + ASSERT(sbp->sb_cpupath[unit] != NULL); + pathname = sbp->sb_cpupath[unit]; + (void) ddi_pathname(dip, pathname); + } +} + +/* + * translate errno + */ +void +sbd_errno_decode(int err, sbderror_t *ep, dev_info_t *dip) +{ + ASSERT(err != 0); + + switch (err) { + case ENOMEM: + SBD_SET_ERR(ep, ESBD_NOMEM); + break; + + case EBUSY: + SBD_SET_ERR(ep, ESBD_BUSY); + break; + + case EIO: + SBD_SET_ERR(ep, ESBD_IO); + break; + + case ENXIO: + SBD_SET_ERR(ep, ESBD_NODEV); + break; + + case EINVAL: + SBD_SET_ERR(ep, ESBD_INVAL); + break; + + case EFAULT: + default: + SBD_SET_ERR(ep, ESBD_FAULT); + break; + } + + (void) ddi_pathname(dip, SBD_GET_ERRSTR(ep)); +} + +static void +sbd_detach_cpu(sbd_handle_t *hp, sbderror_t *ep, dev_info_t *dip, int unit) +{ + processorid_t cpuid; + int rv; + sbdp_handle_t *hdp; + sbd_board_t *sbp = SBDH2BD(hp->h_sbd); + sbd_error_t *spe; + static fn_t f = "sbd_detach_cpu"; + + ASSERT(MUTEX_HELD(&cpu_lock)); + + ASSERT(dip); + hdp = sbd_get_sbdp_handle(sbp, hp); + spe = hdp->h_err; + cpuid = sbdp_get_cpuid(hdp, dip); + if (cpuid < 0) { + SBD_GET_PERR(spe, ep); + sbd_release_sbdp_handle(hdp); + return; + } + + if ((rv = cpu_unconfigure(cpuid)) != 0) { + SBD_SET_ERR(ep, sbd_errno2ecode(rv)); + SBD_SET_ERRSTR(ep, sbp->sb_cpupath[unit]); + cmn_err(CE_WARN, + "sbd:%s: cpu_unconfigure for cpu %d failed", + f, cpuid); + sbd_release_sbdp_handle(hdp); + return; + } + sbd_release_sbdp_handle(hdp); + + /* + * Since CPU nodes are no longer configured in CPU + * attach, the corresponding branch unconfigure + * operation that would be performed here is also + * no longer required. + */ +} + + +int +sbd_detach_mem(sbd_handle_t *hp, sbderror_t *ep, int unit) +{ + sbd_mem_unit_t *mp; + sbd_board_t *sbp = SBDH2BD(hp->h_sbd); + int i, rv; + static fn_t f = "sbd_detach_mem"; + + mp = SBD_GET_BOARD_MEMUNIT(sbp, unit); + + if (sbd_detach_memory(hp, ep, mp, unit)) { + cmn_err(CE_WARN, "%s: detach fail", f); + return (-1); + } + + /* + * Now detach mem devinfo nodes with status lock held. + */ + for (i = 0; i < SBD_NUM_MC_PER_BOARD; i++) { + dev_info_t *fdip = NULL; + + if (mp->sbm_dip[i] == NULL) + continue; + ASSERT(e_ddi_branch_held(mp->sbm_dip[i])); + mutex_enter(&sbp->sb_slock); + rv = e_ddi_branch_unconfigure(mp->sbm_dip[i], &fdip, + DEVI_BRANCH_EVENT); + mutex_exit(&sbp->sb_slock); + if (rv) { + /* + * If non-NULL, fdip is returned held and must be + * released. + */ + if (fdip != NULL) { + sbd_errno_decode(rv, ep, fdip); + ddi_release_devi(fdip); + } else { + sbd_errno_decode(rv, ep, mp->sbm_dip[i]); + } + } + } + + return (0); +} + +/* start beginning of sbd.c */ + +/* + * MDR memory support - somewhat disabled for now. + * UNSAFE unsafe driver code - I don't think we want this. + * need to check. + * DEVNODE This driver creates attachment points for individual + * components as well as boards. We only need board + * support. + * DEV2DEVSET Put only present devices in devset. + */ + + +static sbd_state_t +rstate_cvt(sbd_istate_t state) +{ + sbd_state_t cs; + + switch (state) { + case SBD_STATE_EMPTY: + cs = SBD_STAT_EMPTY; + break; + case SBD_STATE_OCCUPIED: + case SBD_STATE_FATAL: + cs = SBD_STAT_DISCONNECTED; + break; + case SBD_STATE_CONFIGURED: + case SBD_STATE_CONNECTED: + case SBD_STATE_UNCONFIGURED: + case SBD_STATE_PARTIAL: + case SBD_STATE_RELEASE: + case SBD_STATE_UNREFERENCED: + cs = SBD_STAT_CONNECTED; + break; + default: + cs = SBD_STAT_NONE; + break; + } + + return (cs); +} + + +sbd_state_t +ostate_cvt(sbd_istate_t state) +{ + sbd_state_t cs; + + switch (state) { + case SBD_STATE_EMPTY: + case SBD_STATE_OCCUPIED: + case SBD_STATE_UNCONFIGURED: + case SBD_STATE_CONNECTED: + case SBD_STATE_FATAL: + cs = SBD_STAT_UNCONFIGURED; + break; + case SBD_STATE_PARTIAL: + case SBD_STATE_CONFIGURED: + case SBD_STATE_RELEASE: + case SBD_STATE_UNREFERENCED: + cs = SBD_STAT_CONFIGURED; + break; + default: + cs = SBD_STAT_NONE; + break; + } + + return (cs); +} + +int +sbd_dealloc_instance(sbd_board_t *sbp, int max_boards) +{ + int b; + sbd_board_t *list = sbp; + static fn_t f = "sbd_dealloc_instance"; + + PR_ALL("%s...\n", f); + + if (sbp == NULL) { + return (-1); + } + + for (b = 0; b < max_boards; b++) { + sbd_board_destroy(sbp++); + } + + FREESTRUCT(list, sbd_board_t, max_boards); + + return (0); +} + +static sbd_devset_t +sbd_dev2devset(sbd_comp_id_t *cid) +{ + static fn_t f = "sbd_dev2devset"; + + sbd_devset_t devset; + int unit = cid->c_unit; + + switch (cid->c_type) { + case SBD_COMP_NONE: + devset = DEVSET(SBD_COMP_CPU, DEVSET_ANYUNIT); + devset |= DEVSET(SBD_COMP_MEM, DEVSET_ANYUNIT); + devset |= DEVSET(SBD_COMP_IO, DEVSET_ANYUNIT); + break; + + case SBD_COMP_CPU: + if ((unit > MAX_CPU_UNITS_PER_BOARD) || (unit < 0)) { + PR_ALL("%s: invalid cpu unit# = %d", + f, unit); + devset = 0; + } else + /* + * Generate a devset that includes all the + * cores of a CMP device. If this is not a + * CMP, the extra cores will be eliminated + * later since they are not present. This is + * also true for CMP devices that do not have + * all cores active. + */ + devset = DEVSET(SBD_COMP_CMP, unit); + + break; + + case SBD_COMP_MEM: + + if ((unit > MAX_MEM_UNITS_PER_BOARD) || (unit < 0)) { +#ifdef XXX_jeffco + PR_ALL("%s: invalid mem unit# = %d", + f, unit); + devset = 0; +#endif + devset = DEVSET(cid->c_type, 0); + PR_ALL("%s: adjusted MEM devset = 0x%x\n", + f, devset); + } else + devset = DEVSET(cid->c_type, unit); + break; + + case SBD_COMP_IO: + if ((unit > MAX_IO_UNITS_PER_BOARD) || (unit < 0)) { + PR_ALL("%s: invalid io unit# = %d", + f, unit); + devset = 0; + } else + devset = DEVSET(cid->c_type, unit); + + break; + + default: + case SBD_COMP_UNKNOWN: + devset = 0; + break; + } + + return (devset); +} + +/* + * Simple mutex for covering handle list ops as it is only + * used "infrequently". No need to add another mutex to the sbd_board_t. + */ +static kmutex_t sbd_handle_list_mutex; + +static sbd_handle_t * +sbd_get_handle(dev_t dev, sbd_softstate_t *softsp, intptr_t arg, + sbd_init_arg_t *iap) +{ + sbd_handle_t *hp; + sbderror_t *ep; + sbd_priv_handle_t *shp; + sbd_board_t *sbp = softsp->sbd_boardlist; + int board; + + board = SBDGETSLOT(dev); + ASSERT(board < softsp->max_boards); + sbp += board; + + /* + * Brand-new handle. + */ + shp = kmem_zalloc(sizeof (sbd_priv_handle_t), KM_SLEEP); + shp->sh_arg = (void *)arg; + + hp = MACHHD2HD(shp); + + ep = &shp->sh_err; + + hp->h_err = ep; + hp->h_sbd = (void *) sbp; + hp->h_dev = iap->dev; + hp->h_cmd = iap->cmd; + hp->h_mode = iap->mode; + sbd_init_err(ep); + + mutex_enter(&sbd_handle_list_mutex); + shp->sh_next = sbp->sb_handle; + sbp->sb_handle = shp; + mutex_exit(&sbd_handle_list_mutex); + + return (hp); +} + +void +sbd_init_err(sbderror_t *ep) +{ + ep->e_errno = 0; + ep->e_code = 0; + ep->e_rsc[0] = '\0'; +} + +int +sbd_set_err_in_hdl(sbd_handle_t *hp, sbderror_t *ep) +{ + sbderror_t *hep = SBD_HD2ERR(hp); + + /* + * If there is an error logged already, don't rewrite it + */ + if (SBD_GET_ERR(hep) || SBD_GET_ERRNO(hep)) { + return (0); + } + + if (SBD_GET_ERR(ep) || SBD_GET_ERRNO(ep)) { + SBD_SET_ERR(hep, SBD_GET_ERR(ep)); + SBD_SET_ERRNO(hep, SBD_GET_ERRNO(ep)); + SBD_SET_ERRSTR(hep, SBD_GET_ERRSTR(ep)); + return (0); + } + + return (-1); +} + +static void +sbd_release_handle(sbd_handle_t *hp) +{ + sbd_priv_handle_t *shp, **shpp; + sbd_board_t *sbp; + static fn_t f = "sbd_release_handle"; + + if (hp == NULL) + return; + + sbp = SBDH2BD(hp->h_sbd); + + shp = HD2MACHHD(hp); + + mutex_enter(&sbd_handle_list_mutex); + /* + * Locate the handle in the board's reference list. + */ + for (shpp = &sbp->sb_handle; (*shpp) && ((*shpp) != shp); + shpp = &((*shpp)->sh_next)) + /* empty */; + + if (*shpp == NULL) { + cmn_err(CE_PANIC, + "sbd:%s: handle not found in board %d", + f, sbp->sb_num); + /*NOTREACHED*/ + } else { + *shpp = shp->sh_next; + } + mutex_exit(&sbd_handle_list_mutex); + + if (hp->h_opts.copts != NULL) { + FREESTRUCT(hp->h_opts.copts, char, hp->h_opts.size); + } + + FREESTRUCT(shp, sbd_priv_handle_t, 1); +} + +sbdp_handle_t * +sbd_get_sbdp_handle(sbd_board_t *sbp, sbd_handle_t *hp) +{ + sbdp_handle_t *hdp; + + hdp = kmem_zalloc(sizeof (sbdp_handle_t), KM_SLEEP); + hdp->h_err = kmem_zalloc(sizeof (sbd_error_t), KM_SLEEP); + if (sbp == NULL) { + hdp->h_board = -1; + hdp->h_wnode = -1; + } else { + hdp->h_board = sbp->sb_num; + hdp->h_wnode = sbp->sb_wnode; + } + + if (hp == NULL) { + hdp->h_flags = 0; + hdp->h_opts = NULL; + } else { + hdp->h_flags = SBD_2_SBDP_FLAGS(hp->h_flags); + hdp->h_opts = &hp->h_opts; + } + + return (hdp); +} + +void +sbd_release_sbdp_handle(sbdp_handle_t *hdp) +{ + if (hdp == NULL) + return; + + kmem_free(hdp->h_err, sizeof (sbd_error_t)); + kmem_free(hdp, sizeof (sbdp_handle_t)); +} + +void +sbd_reset_error_sbdph(sbdp_handle_t *hdp) +{ + if ((hdp != NULL) && (hdp->h_err != NULL)) { + bzero(hdp->h_err, sizeof (sbd_error_t)); + } +} + +static int +sbd_copyin_ioarg(sbd_handle_t *hp, int mode, int cmd, sbd_cmd_t *cmdp, + sbd_ioctl_arg_t *iap) +{ + static fn_t f = "sbd_copyin_ioarg"; + + if (iap == NULL) + return (EINVAL); + + bzero((caddr_t)cmdp, sizeof (sbd_cmd_t)); + +#ifdef _MULTI_DATAMODEL + if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) { + sbd_cmd32_t scmd32; + + bzero((caddr_t)&scmd32, sizeof (sbd_cmd32_t)); + + if (ddi_copyin((void *)iap, (void *)&scmd32, + sizeof (sbd_cmd32_t), mode)) { + cmn_err(CE_WARN, + "sbd:%s: (32bit) failed to copyin " + "sbdcmd-struct", f); + return (EFAULT); + } + cmdp->cmd_cm.c_id.c_type = scmd32.cmd_cm.c_id.c_type; + cmdp->cmd_cm.c_id.c_unit = scmd32.cmd_cm.c_id.c_unit; + bcopy(&scmd32.cmd_cm.c_id.c_name[0], + &cmdp->cmd_cm.c_id.c_name[0], OBP_MAXPROPNAME); + cmdp->cmd_cm.c_flags = scmd32.cmd_cm.c_flags; + cmdp->cmd_cm.c_len = scmd32.cmd_cm.c_len; + cmdp->cmd_cm.c_opts = (caddr_t)scmd32.cmd_cm.c_opts; + + if (cmd == SBD_CMD_PASSTHRU) { + PR_BYP("passthru copyin: iap=%p, sz=%d", iap, + sizeof (sbd_cmd32_t)); + PR_BYP("passthru copyin: c_opts=%p, c_len=%d", + scmd32.cmd_cm.c_opts, + scmd32.cmd_cm.c_len); + } + + switch (cmd) { + case SBD_CMD_STATUS: + cmdp->cmd_stat.s_nbytes = scmd32.cmd_stat.s_nbytes; + cmdp->cmd_stat.s_statp = + (caddr_t)scmd32.cmd_stat.s_statp; + break; + default: + break; + + } + } else +#endif /* _MULTI_DATAMODEL */ + if (ddi_copyin((void *)iap, (void *)cmdp, + sizeof (sbd_cmd_t), mode) != 0) { + cmn_err(CE_WARN, + "sbd:%s: failed to copyin sbd cmd_t struct", f); + return (EFAULT); + } + /* + * A user may set platform specific options so we need to + * copy them in + */ + if ((cmd != SBD_CMD_STATUS) && ((hp->h_opts.size = cmdp->cmd_cm.c_len) + > 0)) { + hp->h_opts.size += 1; /* For null termination of string. */ + hp->h_opts.copts = GETSTRUCT(char, hp->h_opts.size); + if (ddi_copyin((void *)cmdp->cmd_cm.c_opts, + (void *)hp->h_opts.copts, + cmdp->cmd_cm.c_len, hp->h_mode) != 0) { + /* copts is freed in sbd_release_handle(). */ + cmn_err(CE_WARN, + "sbd:%s: failed to copyin options", f); + return (EFAULT); + } + } + + return (0); +} + +static int +sbd_copyout_ioarg(int mode, int cmd, sbd_cmd_t *scp, sbd_ioctl_arg_t *iap) +{ + static fn_t f = "sbd_copyout_ioarg"; + + if ((iap == NULL) || (scp == NULL)) + return (EINVAL); + +#ifdef _MULTI_DATAMODEL + if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) { + sbd_cmd32_t scmd32; + + scmd32.cmd_cm.c_id.c_type = scp->cmd_cm.c_id.c_type; + scmd32.cmd_cm.c_id.c_unit = scp->cmd_cm.c_id.c_unit; + bcopy(scp->cmd_cm.c_id.c_name, + scmd32.cmd_cm.c_id.c_name, OBP_MAXPROPNAME); + + scmd32.cmd_cm.c_flags = scp->cmd_cm.c_flags; + + switch (cmd) { + case SBD_CMD_GETNCM: + scmd32.cmd_getncm.g_ncm = scp->cmd_getncm.g_ncm; + break; + default: + break; + } + + if (ddi_copyout((void *)&scmd32, (void *)iap, + sizeof (sbd_cmd32_t), mode)) { + cmn_err(CE_WARN, + "sbd:%s: (32bit) failed to copyout " + "sbdcmd struct", f); + return (EFAULT); + } + } else +#endif /* _MULTI_DATAMODEL */ + if (ddi_copyout((void *)scp, (void *)iap, + sizeof (sbd_cmd_t), mode) != 0) { + cmn_err(CE_WARN, + "sbd:%s: failed to copyout sbdcmd struct", f); + return (EFAULT); + } + + return (0); +} + +static int +sbd_copyout_errs(int mode, sbd_ioctl_arg_t *iap, void *arg) +{ + static fn_t f = "sbd_copyout_errs"; + sbd_ioctl_arg_t *uap; + + uap = (sbd_ioctl_arg_t *)arg; + +#ifdef _MULTI_DATAMODEL + if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) { + sbd_error32_t err32; + sbd_ioctl_arg32_t *uap32; + + uap32 = (sbd_ioctl_arg32_t *)arg; + + err32.e_code = iap->ie_code; + (void) strcpy(err32.e_rsc, iap->ie_rsc); + + if (ddi_copyout((void *)&err32, (void *)&uap32->i_err, + sizeof (sbd_error32_t), mode)) { + cmn_err(CE_WARN, + "sbd:%s: failed to copyout ioctl32 errs", + f); + return (EFAULT); + } + } else +#endif /* _MULTI_DATAMODEL */ + if (ddi_copyout((void *)&iap->i_err, (void *)&uap->i_err, + sizeof (sbd_error_t), mode) != 0) { + cmn_err(CE_WARN, + "sbd:%s: failed to copyout ioctl errs", f); + return (EFAULT); + } + + return (0); +} + +/* + * State transition policy is that if at least one + * device cannot make the transition, then none of + * the requested devices are allowed to transition. + * + * Returns the state that is in error, if any. + */ +static int +sbd_check_transition(sbd_board_t *sbp, sbd_devset_t *devsetp, + struct sbd_state_trans *transp) +{ + int s, ut; + int state_err = 0; + sbd_devset_t devset; + static fn_t f = "sbd_check_transition"; + + devset = *devsetp; + + if (!devset) { + /* + * Transition does not deal with any components. + * This is the case for addboard/deleteboard. + */ + PR_ALL("%s: no devs: requested devset = 0x%x," + " final devset = 0x%x\n", + f, (uint_t)*devsetp, (uint_t)devset); + + return (0); + } + + if (DEVSET_IN_SET(devset, SBD_COMP_MEM, DEVSET_ANYUNIT)) { + for (ut = 0; ut < MAX_MEM_UNITS_PER_BOARD; ut++) { + if (DEVSET_IN_SET(devset, SBD_COMP_MEM, ut) == 0) + continue; + s = (int)SBD_DEVICE_STATE(sbp, SBD_COMP_MEM, ut); + if (transp->x_op[s].x_rv) { + if (!state_err) + state_err = s; + DEVSET_DEL(devset, SBD_COMP_MEM, ut); + } + } + } + + if (DEVSET_IN_SET(devset, SBD_COMP_CPU, DEVSET_ANYUNIT)) { + for (ut = 0; ut < MAX_CPU_UNITS_PER_BOARD; ut++) { + if (DEVSET_IN_SET(devset, SBD_COMP_CPU, ut) == 0) + continue; + s = (int)SBD_DEVICE_STATE(sbp, SBD_COMP_CPU, ut); + if (transp->x_op[s].x_rv) { + if (!state_err) + state_err = s; + DEVSET_DEL(devset, SBD_COMP_CPU, ut); + } + } + } + + if (DEVSET_IN_SET(devset, SBD_COMP_IO, DEVSET_ANYUNIT)) { + for (ut = 0; ut < MAX_IO_UNITS_PER_BOARD; ut++) { + if (DEVSET_IN_SET(devset, SBD_COMP_IO, ut) == 0) + continue; + s = (int)SBD_DEVICE_STATE(sbp, SBD_COMP_IO, ut); + if (transp->x_op[s].x_rv) { + if (!state_err) + state_err = s; + DEVSET_DEL(devset, SBD_COMP_IO, ut); + } + } + } + + PR_ALL("%s: requested devset = 0x%x, final devset = 0x%x\n", + f, (uint_t)*devsetp, (uint_t)devset); + + *devsetp = devset; + /* + * If there are some remaining components for which + * this state transition is valid, then allow them + * through, otherwise if none are left then return + * the state error. + */ + return (devset ? 0 : state_err); +} + +/* + * pre-op entry point must SET_ERRNO(), if needed. + * Return value of non-zero indicates failure. + */ +static int +sbd_pre_op(sbd_handle_t *hp) +{ + int rv = 0, t; + int cmd, serr = 0; + sbd_devset_t devset; + sbd_board_t *sbp = SBDH2BD(hp->h_sbd); + sbd_priv_handle_t *shp = HD2MACHHD(hp); + sbderror_t *ep = SBD_HD2ERR(hp); + sbd_cmd_t *cmdp; + static fn_t f = "sbd_pre_op"; + + cmd = hp->h_cmd; + devset = shp->sh_devset; + + switch (cmd) { + case SBD_CMD_CONNECT: + case SBD_CMD_DISCONNECT: + case SBD_CMD_UNCONFIGURE: + case SBD_CMD_CONFIGURE: + case SBD_CMD_ASSIGN: + case SBD_CMD_UNASSIGN: + case SBD_CMD_POWERON: + case SBD_CMD_POWEROFF: + case SBD_CMD_TEST: + /* ioctls allowed if caller has write permission */ + if (!(hp->h_mode & FWRITE)) { + SBD_SET_ERRNO(ep, EPERM); + return (-1); + } + + default: + break; + } + + hp->h_iap = GETSTRUCT(sbd_ioctl_arg_t, 1); + rv = sbd_copyin_ioarg(hp, hp->h_mode, cmd, + (sbd_cmd_t *)hp->h_iap, shp->sh_arg); + if (rv) { + SBD_SET_ERRNO(ep, rv); + FREESTRUCT(hp->h_iap, sbd_ioctl_arg_t, 1); + hp->h_iap = NULL; + cmn_err(CE_WARN, "%s: copyin fail", f); + return (-1); + } else { + cmdp = (sbd_cmd_t *)hp->h_iap; + if (cmdp->cmd_cm.c_id.c_name[0] != '\0') { + + cmdp->cmd_cm.c_id.c_type = SBD_COMP(sbd_name_to_idx( + cmdp->cmd_cm.c_id.c_name)); + if (cmdp->cmd_cm.c_id.c_type == SBD_COMP_MEM) { + if (cmdp->cmd_cm.c_id.c_unit == -1) + cmdp->cmd_cm.c_id.c_unit = 0; + } + } + devset = shp->sh_orig_devset = shp->sh_devset = + sbd_dev2devset(&cmdp->cmd_cm.c_id); + if (devset == 0) { + SBD_SET_ERRNO(ep, EINVAL); + FREESTRUCT(hp->h_iap, sbd_ioctl_arg_t, 1); + hp->h_iap = NULL; + return (-1); + } + } + + /* + * Always turn on these bits ala Sunfire DR. + */ + hp->h_flags |= SBD_FLAG_DEVI_FORCE; + + if (cmdp->cmd_cm.c_flags & SBD_FLAG_FORCE) + hp->h_flags |= SBD_IOCTL_FLAG_FORCE; + + /* + * Check for valid state transitions. + */ + if (!serr && ((t = CMD2INDEX(cmd)) != -1)) { + struct sbd_state_trans *transp; + int state_err; + + transp = &sbd_state_transition[t]; + ASSERT(transp->x_cmd == cmd); + + state_err = sbd_check_transition(sbp, &devset, transp); + + if (state_err < 0) { + /* + * Invalidate device. + */ + SBD_SET_ERRNO(ep, ENOTTY); + serr = -1; + PR_ALL("%s: invalid devset (0x%x)\n", + f, (uint_t)devset); + } else if (state_err != 0) { + /* + * State transition is not a valid one. + */ + SBD_SET_ERRNO(ep, transp->x_op[state_err].x_err); + serr = transp->x_op[state_err].x_rv; + PR_ALL("%s: invalid state %s(%d) for cmd %s(%d)\n", + f, sbd_state_str[state_err], state_err, + SBD_CMD_STR(cmd), cmd); + } + if (serr && SBD_GET_ERRNO(ep) != 0) { + /* + * A state transition error occurred. + */ + if (serr < 0) { + SBD_SET_ERR(ep, ESBD_INVAL); + } else { + SBD_SET_ERR(ep, ESBD_STATE); + } + PR_ALL("%s: invalid state transition\n", f); + } else { + shp->sh_devset = devset; + } + } + + if (serr && !rv && hp->h_iap) { + + /* + * There was a state error. We successfully copied + * in the ioctl argument, so let's fill in the + * error and copy it back out. + */ + + if (SBD_GET_ERR(ep) && SBD_GET_ERRNO(ep) == 0) + SBD_SET_ERRNO(ep, EIO); + + SBD_SET_IOCTL_ERR(&hp->h_iap->i_err, + ep->e_code, + ep->e_rsc); + (void) sbd_copyout_errs(hp->h_mode, hp->h_iap, shp->sh_arg); + FREESTRUCT(hp->h_iap, sbd_ioctl_arg_t, 1); + hp->h_iap = NULL; + rv = -1; + } + + return (rv); +} + +static void +sbd_post_op(sbd_handle_t *hp) +{ + int cmd; + sbderror_t *ep = SBD_HD2ERR(hp); + sbd_priv_handle_t *shp = HD2MACHHD(hp); + sbd_board_t *sbp = SBDH2BD(hp->h_sbd); + + cmd = hp->h_cmd; + + switch (cmd) { + case SBD_CMD_CONFIGURE: + case SBD_CMD_UNCONFIGURE: + case SBD_CMD_CONNECT: + case SBD_CMD_DISCONNECT: + sbp->sb_time = gethrestime_sec(); + break; + + default: + break; + } + + if (SBD_GET_ERR(ep) && SBD_GET_ERRNO(ep) == 0) { + SBD_SET_ERRNO(ep, EIO); + } + + if (shp->sh_arg != NULL) { + + if (SBD_GET_ERR(ep) != ESBD_NOERROR) { + + SBD_SET_IOCTL_ERR(&hp->h_iap->i_err, + ep->e_code, + ep->e_rsc); + + (void) sbd_copyout_errs(hp->h_mode, hp->h_iap, + shp->sh_arg); + } + + if (hp->h_iap != NULL) { + FREESTRUCT(hp->h_iap, sbd_ioctl_arg_t, 1); + hp->h_iap = NULL; + } + } +} + +static int +sbd_probe_board(sbd_handle_t *hp) +{ + int rv; + sbd_board_t *sbp; + sbdp_handle_t *hdp; + static fn_t f = "sbd_probe_board"; + + sbp = SBDH2BD(hp->h_sbd); + + ASSERT(sbp != NULL); + PR_ALL("%s for board %d", f, sbp->sb_num); + + + hdp = sbd_get_sbdp_handle(sbp, hp); + + if ((rv = sbdp_connect_board(hdp)) != 0) { + sbderror_t *ep = SBD_HD2ERR(hp); + + SBD_GET_PERR(hdp->h_err, ep); + } + + /* + * We need to force a recache after the connect. The cached + * info may be incorrect + */ + mutex_enter(&sbp->sb_flags_mutex); + sbp->sb_flags &= ~SBD_BOARD_STATUS_CACHED; + mutex_exit(&sbp->sb_flags_mutex); + + SBD_INJECT_ERR(SBD_PROBE_BOARD_PSEUDO_ERR, hp->h_err, EIO, + ESGT_PROBE, NULL); + + sbd_release_sbdp_handle(hdp); + + return (rv); +} + +static int +sbd_deprobe_board(sbd_handle_t *hp) +{ + int rv; + sbdp_handle_t *hdp; + sbd_board_t *sbp; + static fn_t f = "sbd_deprobe_board"; + + PR_ALL("%s...\n", f); + + sbp = SBDH2BD(hp->h_sbd); + + hdp = sbd_get_sbdp_handle(sbp, hp); + + if ((rv = sbdp_disconnect_board(hdp)) != 0) { + sbderror_t *ep = SBD_HD2ERR(hp); + + SBD_GET_PERR(hdp->h_err, ep); + } + + mutex_enter(&sbp->sb_flags_mutex); + sbp->sb_flags &= ~SBD_BOARD_STATUS_CACHED; + mutex_exit(&sbp->sb_flags_mutex); + + SBD_INJECT_ERR(SBD_DEPROBE_BOARD_PSEUDO_ERR, hp->h_err, EIO, + ESGT_DEPROBE, NULL); + + sbd_release_sbdp_handle(hdp); + return (rv); +} + +/* + * Check if a CPU node is part of a CMP. + */ +int +sbd_is_cmp_child(dev_info_t *dip) +{ + dev_info_t *pdip; + + if (strcmp(ddi_node_name(dip), "cpu") != 0) { + return (0); + } + + pdip = ddi_get_parent(dip); + + ASSERT(pdip); + + if (strcmp(ddi_node_name(pdip), "cmp") == 0) { + return (1); + } + + return (0); +} + +/* + * Returns the nodetype if dip is a top dip on the board of + * interest or SBD_COMP_UNKNOWN otherwise + */ +static sbd_comp_type_t +get_node_type(sbd_board_t *sbp, dev_info_t *dip, int *unitp) +{ + int idx, unit; + sbd_handle_t *hp; + sbdp_handle_t *hdp; + char otype[OBP_MAXDRVNAME]; + int otypelen; + + ASSERT(sbp); + + if (unitp) + *unitp = -1; + + hp = MACHBD2HD(sbp); + + hdp = sbd_get_sbdp_handle(sbp, hp); + if (sbdp_get_board_num(hdp, dip) != sbp->sb_num) { + sbd_release_sbdp_handle(hdp); + return (SBD_COMP_UNKNOWN); + } + + /* + * sbdp_get_unit_num will return (-1) for cmp as there + * is no "device_type" property associated with cmp. + * Therefore we will just skip getting unit number for + * cmp. Callers of this function need to check the + * value set in unitp before using it to dereference + * an array. + */ + if (strcmp(ddi_node_name(dip), "cmp") == 0) { + sbd_release_sbdp_handle(hdp); + return (SBD_COMP_CMP); + } + + otypelen = sizeof (otype); + if (ddi_getlongprop_buf(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + OBP_DEVICETYPE, (caddr_t)otype, &otypelen)) { + sbd_release_sbdp_handle(hdp); + return (SBD_COMP_UNKNOWN); + } + + idx = sbd_otype_to_idx(otype); + + if (SBD_COMP(idx) == SBD_COMP_UNKNOWN) { + sbd_release_sbdp_handle(hdp); + return (SBD_COMP_UNKNOWN); + } + + unit = sbdp_get_unit_num(hdp, dip); + if (unit == -1) { + cmn_err(CE_WARN, + "get_node_type: %s unit fail %p", otype, (void *)dip); + sbd_release_sbdp_handle(hdp); + return (SBD_COMP_UNKNOWN); + } + + sbd_release_sbdp_handle(hdp); + + if (unitp) + *unitp = unit; + + return (SBD_COMP(idx)); +} + +typedef struct { + sbd_board_t *sbp; + int nmc; + int hold; +} walk_tree_t; + +static int +sbd_setup_devlists(dev_info_t *dip, void *arg) +{ + walk_tree_t *wp; + dev_info_t **devlist = NULL; + char *pathname = NULL; + sbd_mem_unit_t *mp; + static fn_t f = "sbd_setup_devlists"; + sbd_board_t *sbp; + int unit; + sbd_comp_type_t nodetype; + + ASSERT(dip); + + wp = (walk_tree_t *)arg; + + if (wp == NULL) { + PR_ALL("%s:bad arg\n", f); + return (DDI_WALK_TERMINATE); + } + + sbp = wp->sbp; + + nodetype = get_node_type(sbp, dip, &unit); + + switch (nodetype) { + + case SBD_COMP_CPU: + pathname = sbp->sb_cpupath[unit]; + break; + + case SBD_COMP_MEM: + pathname = sbp->sb_mempath[unit]; + break; + + case SBD_COMP_IO: + pathname = sbp->sb_iopath[unit]; + break; + + case SBD_COMP_CMP: + case SBD_COMP_UNKNOWN: + /* + * This dip is not of interest to us + */ + return (DDI_WALK_CONTINUE); + + default: + ASSERT(0); + return (DDI_WALK_CONTINUE); + } + + /* + * dip's parent is being held busy by ddi_walk_devs(), + * so dip doesn't have to be held while calling ddi_pathname() + */ + if (pathname) { + (void) ddi_pathname(dip, pathname); + } + + devlist = sbp->sb_devlist[NIX(nodetype)]; + + /* + * The branch rooted at dip should already be held, + * unless we are dealing with a core of a CMP. + */ + ASSERT(sbd_is_cmp_child(dip) || e_ddi_branch_held(dip)); + devlist[unit] = dip; + + /* + * This test is required if multiple devices are considered + * as one. This is the case for memory-controller nodes. + */ + if (!SBD_DEV_IS_PRESENT(sbp, nodetype, unit)) { + sbp->sb_ndev++; + SBD_DEV_SET_PRESENT(sbp, nodetype, unit); + } + + if (nodetype == SBD_COMP_MEM) { + mp = SBD_GET_BOARD_MEMUNIT(sbp, unit); + ASSERT(wp->nmc < SBD_NUM_MC_PER_BOARD); + mp->sbm_dip[wp->nmc++] = dip; + } + + return (DDI_WALK_CONTINUE); +} + +/* + * This routine is used to construct the memory devlist. + * In Starcat and Serengeti platforms, a system board can contain up to + * four memory controllers (MC). The MCs have been programmed by POST for + * optimum memory interleaving amongst their peers on the same board. + * This DR driver does not support deinterleaving. Therefore, the smallest + * unit of memory that can be manipulated by this driver is all of the + * memory on a board. Because of this restriction, a board's memory devlist + * is populated with only one of the four (possible) MC dnodes on that board. + * Care must be taken to ensure that the selected MC dnode represents the + * lowest physical address to which memory on the board will respond to. + * This is required in order to preserve the semantics of + * sbdp_get_base_physaddr() when applied to a MC dnode stored in the + * memory devlist. + */ +static void +sbd_init_mem_devlists(sbd_board_t *sbp) +{ + dev_info_t **devlist; + sbd_mem_unit_t *mp; + dev_info_t *mc_dip; + sbdp_handle_t *hdp; + uint64_t mc_pa, lowest_pa; + int i; + sbd_handle_t *hp = MACHBD2HD(sbp); + + devlist = sbp->sb_devlist[NIX(SBD_COMP_MEM)]; + + mp = SBD_GET_BOARD_MEMUNIT(sbp, 0); + + mc_dip = mp->sbm_dip[0]; + if (mc_dip == NULL) + return; /* No MC dips found for this board */ + + hdp = sbd_get_sbdp_handle(sbp, hp); + + if (sbdphw_get_base_physaddr(hdp, mc_dip, &mc_pa)) { + /* TODO: log complaint about dnode */ + +pretend_no_mem: + /* + * We are here because sbdphw_get_base_physaddr() failed. + * Although it is very unlikely to happen, it did. Lucky us. + * Since we can no longer examine _all_ of the MCs on this + * board to determine which one is programmed to the lowest + * physical address, we cannot involve any of the MCs on + * this board in DR operations. To ensure this, we pretend + * that this board does not contain any memory. + * + * Paranoia: clear the dev_present mask. + */ + if (SBD_DEV_IS_PRESENT(sbp, SBD_COMP_MEM, 0)) { + ASSERT(sbp->sb_ndev != 0); + SBD_DEV_CLR_PRESENT(sbp, SBD_COMP_MEM, 0); + sbp->sb_ndev--; + } + + for (i = 0; i < SBD_NUM_MC_PER_BOARD; i++) { + mp->sbm_dip[i] = NULL; + } + + sbd_release_sbdp_handle(hdp); + return; + } + + /* assume this one will win. */ + devlist[0] = mc_dip; + mp->sbm_cm.sbdev_dip = mc_dip; + lowest_pa = mc_pa; + + /* + * We know the base physical address of one of the MC devices. Now + * we will enumerate through all of the remaining MC devices on + * the board to find which of them is programmed to the lowest + * physical address. + */ + for (i = 1; i < SBD_NUM_MC_PER_BOARD; i++) { + mc_dip = mp->sbm_dip[i]; + if (mc_dip == NULL) { + break; + } + + if (sbdphw_get_base_physaddr(hdp, mc_dip, &mc_pa)) { + cmn_err(CE_NOTE, "No mem on board %d unit %d", + sbp->sb_num, i); + break; + } + if (mc_pa < lowest_pa) { + mp->sbm_cm.sbdev_dip = mc_dip; + devlist[0] = mc_dip; + lowest_pa = mc_pa; + } + } + + sbd_release_sbdp_handle(hdp); +} + +static int +sbd_name_to_idx(char *name) +{ + int idx; + + for (idx = 0; SBD_COMP(idx) != SBD_COMP_UNKNOWN; idx++) { + if (strcmp(name, SBD_DEVNAME(idx)) == 0) { + break; + } + } + + return (idx); +} + +static int +sbd_otype_to_idx(char *otype) +{ + int idx; + + for (idx = 0; SBD_COMP(idx) != SBD_COMP_UNKNOWN; idx++) { + + if (strcmp(otype, SBD_OTYPE(idx)) == 0) { + break; + } + } + + return (idx); +} + +static int +sbd_init_devlists(sbd_board_t *sbp) +{ + int i; + sbd_dev_unit_t *dp; + sbd_mem_unit_t *mp; + walk_tree_t *wp, walk = {0}; + dev_info_t *pdip; + static fn_t f = "sbd_init_devlists"; + + PR_ALL("%s (board = %d)...\n", f, sbp->sb_num); + + wp = &walk; + + SBD_DEVS_DISCONNECT(sbp, (uint_t)-1); + + /* + * Clear out old entries, if any. + */ + + for (i = 0; i < MAX_MEM_UNITS_PER_BOARD; i++) { + sbp->sb_devlist[NIX(SBD_COMP_MEM)][i] = NULL; + dp = (sbd_dev_unit_t *)SBD_GET_BOARD_MEMUNIT(sbp, i); + dp->u_common.sbdev_sbp = sbp; + dp->u_common.sbdev_unum = i; + dp->u_common.sbdev_type = SBD_COMP_MEM; + } + + mp = SBD_GET_BOARD_MEMUNIT(sbp, 0); + ASSERT(mp != NULL); + for (i = 0; i < SBD_NUM_MC_PER_BOARD; i++) { + mp->sbm_dip[i] = NULL; + } + + for (i = 0; i < MAX_CPU_UNITS_PER_BOARD; i++) { + sbp->sb_devlist[NIX(SBD_COMP_CPU)][i] = NULL; + dp = (sbd_dev_unit_t *)SBD_GET_BOARD_CPUUNIT(sbp, i); + dp->u_common.sbdev_sbp = sbp; + dp->u_common.sbdev_unum = i; + dp->u_common.sbdev_type = SBD_COMP_CPU; + } + for (i = 0; i < MAX_IO_UNITS_PER_BOARD; i++) { + sbp->sb_devlist[NIX(SBD_COMP_IO)][i] = NULL; + dp = (sbd_dev_unit_t *)SBD_GET_BOARD_IOUNIT(sbp, i); + dp->u_common.sbdev_sbp = sbp; + dp->u_common.sbdev_unum = i; + dp->u_common.sbdev_type = SBD_COMP_IO; + } + + wp->sbp = sbp; + wp->nmc = 0; + sbp->sb_ndev = 0; + + /* + * ddi_walk_devs() requires that topdip's parent be held. + */ + pdip = ddi_get_parent(sbp->sb_topdip); + if (pdip) { + ndi_hold_devi(pdip); + ndi_devi_enter(pdip, &i); + } + ddi_walk_devs(sbp->sb_topdip, sbd_setup_devlists, (void *) wp); + if (pdip) { + ndi_devi_exit(pdip, i); + ndi_rele_devi(pdip); + } + + /* + * There is no point checking all the components if there + * are no devices. + */ + if (sbp->sb_ndev == 0) { + sbp->sb_memaccess_ok = 0; + return (sbp->sb_ndev); + } + + /* + * Initialize cpu sections before calling sbd_init_mem_devlists + * which will access the mmus. + */ + sbp->sb_memaccess_ok = 1; + for (i = 0; i < MAX_CPU_UNITS_PER_BOARD; i++) { + if (SBD_DEV_IS_PRESENT(sbp, SBD_COMP_CPU, i)) { + sbd_init_cpu_unit(sbp, i); + if (sbd_connect_cpu(sbp, i)) { + SBD_SET_ERR(HD2MACHERR(MACHBD2HD(sbp)), + ESBD_CPUSTART); + } + + } + } + + if (sbp->sb_memaccess_ok) { + sbd_init_mem_devlists(sbp); + } else { + cmn_err(CE_WARN, "unable to access memory on board %d", + sbp->sb_num); + } + + return (sbp->sb_ndev); +} + +static void +sbd_init_cpu_unit(sbd_board_t *sbp, int unit) +{ + sbd_istate_t new_state; + sbd_cpu_unit_t *cp; + int cpuid; + dev_info_t *dip; + sbdp_handle_t *hdp; + sbd_handle_t *hp = MACHBD2HD(sbp); + extern kmutex_t cpu_lock; + + if (SBD_DEV_IS_ATTACHED(sbp, SBD_COMP_CPU, unit)) { + new_state = SBD_STATE_CONFIGURED; + } else if (SBD_DEV_IS_PRESENT(sbp, SBD_COMP_CPU, unit)) { + new_state = SBD_STATE_CONNECTED; + } else { + new_state = SBD_STATE_EMPTY; + } + + dip = sbp->sb_devlist[NIX(SBD_COMP_CPU)][unit]; + + cp = SBD_GET_BOARD_CPUUNIT(sbp, unit); + + hdp = sbd_get_sbdp_handle(sbp, hp); + + cpuid = sbdp_get_cpuid(hdp, dip); + + cp->sbc_cpu_id = cpuid; + + if (&sbdp_cpu_get_impl) + cp->sbc_cpu_impl = sbdp_cpu_get_impl(hdp, dip); + else + cp->sbc_cpu_impl = -1; + + mutex_enter(&cpu_lock); + if ((cpuid >= 0) && cpu[cpuid]) + cp->sbc_cpu_flags = cpu[cpuid]->cpu_flags; + else + cp->sbc_cpu_flags = CPU_OFFLINE | CPU_POWEROFF; + mutex_exit(&cpu_lock); + + sbd_cpu_set_prop(cp, dip); + + cp->sbc_cm.sbdev_cond = sbd_get_comp_cond(dip); + sbd_release_sbdp_handle(hdp); + + /* + * Any changes to the cpu should be performed above + * this call to ensure the cpu is fully initialized + * before transitioning to the new state. + */ + SBD_DEVICE_TRANSITION(sbp, SBD_COMP_CPU, unit, new_state); +} + +/* + * Only do work if called to operate on an entire board + * which doesn't already have components present. + */ +static void +sbd_connect(sbd_handle_t *hp) +{ + sbd_board_t *sbp; + sbderror_t *ep; + static fn_t f = "sbd_connect"; + + sbp = SBDH2BD(hp->h_sbd); + + PR_ALL("%s board %d\n", f, sbp->sb_num); + + ep = HD2MACHERR(hp); + + if (SBD_DEVS_PRESENT(sbp)) { + /* + * Board already has devices present. + */ + PR_ALL("%s: devices already present (0x%x)\n", + f, SBD_DEVS_PRESENT(sbp)); + SBD_SET_ERRNO(ep, EINVAL); + return; + } + + if (sbd_init_devlists(sbp) == 0) { + cmn_err(CE_WARN, "%s: no devices present on board %d", + f, sbp->sb_num); + SBD_SET_ERR(ep, ESBD_NODEV); + return; + } else { + int i; + + /* + * Initialize mem-unit section of board structure. + */ + for (i = 0; i < MAX_MEM_UNITS_PER_BOARD; i++) + if (SBD_DEV_IS_PRESENT(sbp, SBD_COMP_MEM, i)) + sbd_init_mem_unit(sbp, i, SBD_HD2ERR(hp)); + + /* + * Initialize sb_io sections. + */ + for (i = 0; i < MAX_IO_UNITS_PER_BOARD; i++) + if (SBD_DEV_IS_PRESENT(sbp, SBD_COMP_IO, i)) + sbd_init_io_unit(sbp, i); + + SBD_BOARD_TRANSITION(sbp, SBD_STATE_CONNECTED); + sbp->sb_rstate = SBD_STAT_CONNECTED; + sbp->sb_ostate = SBD_STAT_UNCONFIGURED; + (void) drv_getparm(TIME, (void *)&sbp->sb_time); + SBD_INJECT_ERR(SBD_CONNECT_BOARD_PSEUDO_ERR, hp->h_err, EIO, + ESBD_INTERNAL, NULL); + } +} + +static int +sbd_disconnect(sbd_handle_t *hp) +{ + int i; + sbd_devset_t devset; + sbd_board_t *sbp; + static fn_t f = "sbd_disconnect it"; + + PR_ALL("%s ...\n", f); + + sbp = SBDH2BD(hp->h_sbd); + + /* + * Only devices which are present, but + * unattached can be disconnected. + */ + devset = HD2MACHHD(hp)->sh_devset & SBD_DEVS_PRESENT(sbp) & + SBD_DEVS_UNATTACHED(sbp); + + ASSERT((SBD_DEVS_ATTACHED(sbp) & devset) == 0); + + /* + * Update per-device state transitions. + */ + + for (i = 0; i < MAX_MEM_UNITS_PER_BOARD; i++) + if (DEVSET_IN_SET(devset, SBD_COMP_MEM, i)) { + if (sbd_disconnect_mem(hp, i) == 0) { + SBD_DEVICE_TRANSITION(sbp, SBD_COMP_MEM, i, + SBD_STATE_EMPTY); + SBD_DEV_CLR_PRESENT(sbp, SBD_COMP_MEM, i); + } + } + + for (i = 0; i < MAX_CPU_UNITS_PER_BOARD; i++) + if (DEVSET_IN_SET(devset, SBD_COMP_CPU, i)) { + if (sbd_disconnect_cpu(hp, i) == 0) { + SBD_DEVICE_TRANSITION(sbp, SBD_COMP_CPU, i, + SBD_STATE_EMPTY); + SBD_DEV_CLR_PRESENT(sbp, SBD_COMP_CPU, i); + } + } + + for (i = 0; i < MAX_IO_UNITS_PER_BOARD; i++) + if (DEVSET_IN_SET(devset, SBD_COMP_IO, i)) { + if (sbd_disconnect_io(hp, i) == 0) { + SBD_DEVICE_TRANSITION(sbp, SBD_COMP_IO, i, + SBD_STATE_EMPTY); + SBD_DEV_CLR_PRESENT(sbp, SBD_COMP_IO, i); + } + } + + /* + * Once all the components on a board have been disconnect + * the board's state can transition to disconnected and + * we can allow the deprobe to take place. + */ + if (SBD_DEVS_PRESENT(sbp) == 0) { + SBD_BOARD_TRANSITION(sbp, SBD_STATE_OCCUPIED); + sbp->sb_rstate = SBD_STAT_DISCONNECTED; + sbp->sb_ostate = SBD_STAT_UNCONFIGURED; + (void) drv_getparm(TIME, (void *)&sbp->sb_time); + SBD_INJECT_ERR(SBD_DISCONNECT_BOARD_PSEUDO_ERR, hp->h_err, EIO, + ESBD_INTERNAL, NULL); + return (0); + } else { + cmn_err(CE_WARN, "%s: could not disconnect devices on board %d", + f, sbp->sb_num); + return (-1); + } +} + +static void +sbd_test_board(sbd_handle_t *hp) +{ + sbd_board_t *sbp; + sbdp_handle_t *hdp; + + sbp = SBDH2BD(hp->h_sbd); + + PR_ALL("sbd_test_board: board %d\n", sbp->sb_num); + + + hdp = sbd_get_sbdp_handle(sbp, hp); + + if (sbdp_test_board(hdp, &hp->h_opts) != 0) { + sbderror_t *ep = SBD_HD2ERR(hp); + + SBD_GET_PERR(hdp->h_err, ep); + } + + SBD_INJECT_ERR(SBD_TEST_BOARD_PSEUDO_ERR, hp->h_err, EIO, + ESBD_INTERNAL, NULL); + + sbd_release_sbdp_handle(hdp); +} + +static void +sbd_assign_board(sbd_handle_t *hp) +{ + sbd_board_t *sbp; + sbdp_handle_t *hdp; + + sbp = SBDH2BD(hp->h_sbd); + + PR_ALL("sbd_assign_board: board %d\n", sbp->sb_num); + + hdp = sbd_get_sbdp_handle(sbp, hp); + + if (sbdp_assign_board(hdp) != 0) { + sbderror_t *ep = SBD_HD2ERR(hp); + + SBD_GET_PERR(hdp->h_err, ep); + } + + SBD_INJECT_ERR(SBD_ASSIGN_BOARD_PSEUDO_ERR, hp->h_err, EIO, + ESBD_INTERNAL, NULL); + + sbd_release_sbdp_handle(hdp); +} + +static void +sbd_unassign_board(sbd_handle_t *hp) +{ + sbd_board_t *sbp; + sbdp_handle_t *hdp; + + sbp = SBDH2BD(hp->h_sbd); + + PR_ALL("sbd_unassign_board: board %d\n", sbp->sb_num); + + hdp = sbd_get_sbdp_handle(sbp, hp); + + if (sbdp_unassign_board(hdp) != 0) { + sbderror_t *ep = SBD_HD2ERR(hp); + + SBD_GET_PERR(hdp->h_err, ep); + } + + SBD_INJECT_ERR(SBD_ASSIGN_BOARD_PSEUDO_ERR, hp->h_err, EIO, + ESBD_INTERNAL, NULL); + + sbd_release_sbdp_handle(hdp); +} + +static void +sbd_poweron_board(sbd_handle_t *hp) +{ + sbd_board_t *sbp; + sbdp_handle_t *hdp; + + sbp = SBDH2BD(hp->h_sbd); + + PR_ALL("sbd_poweron_board: %d\n", sbp->sb_num); + + hdp = sbd_get_sbdp_handle(sbp, hp); + + if (sbdp_poweron_board(hdp) != 0) { + sbderror_t *ep = SBD_HD2ERR(hp); + + SBD_GET_PERR(hdp->h_err, ep); + } + + SBD_INJECT_ERR(SBD_POWERON_BOARD_PSEUDO_ERR, hp->h_err, EIO, + ESBD_INTERNAL, NULL); + + sbd_release_sbdp_handle(hdp); +} + +static void +sbd_poweroff_board(sbd_handle_t *hp) +{ + sbd_board_t *sbp; + sbdp_handle_t *hdp; + + sbp = SBDH2BD(hp->h_sbd); + + PR_ALL("sbd_poweroff_board: %d\n", sbp->sb_num); + + hdp = sbd_get_sbdp_handle(sbp, hp); + + if (sbdp_poweroff_board(hdp) != 0) { + sbderror_t *ep = SBD_HD2ERR(hp); + + SBD_GET_PERR(hdp->h_err, ep); + } + + SBD_INJECT_ERR(SBD_POWEROFF_BOARD_PSEUDO_ERR, hp->h_err, EIO, + ESBD_INTERNAL, NULL); + + sbd_release_sbdp_handle(hdp); +} + + +/* + * Return a list of the dip's of devices that are + * either present and attached, or present only but + * not yet attached for the given board. + */ +sbd_devlist_t * +sbd_get_devlist(sbd_handle_t *hp, sbd_board_t *sbp, sbd_comp_type_t nodetype, + int max_units, uint_t uset, int *count, int present_only) +{ + int i, ix; + sbd_devlist_t *ret_devlist; + dev_info_t **devlist; + sbdp_handle_t *hdp; + + *count = 0; + ret_devlist = GETSTRUCT(sbd_devlist_t, max_units); + devlist = sbp->sb_devlist[NIX(nodetype)]; + /* + * Turn into binary value since we're going + * to be using XOR for a comparison. + * if (present_only) then + * dev must be PRESENT, but NOT ATTACHED. + * else + * dev must be PRESENT AND ATTACHED. + * endif + */ + if (present_only) + present_only = 1; + + hdp = sbd_get_sbdp_handle(sbp, hp); + + for (i = ix = 0; (i < max_units) && uset; i++) { + int ut, is_present, is_attached; + dev_info_t *dip; + sbderror_t *ep = SBD_HD2ERR(hp); + int nunits, distance, j; + + /* + * For CMPs, we would like to perform DR operation on + * all the cores before moving onto the next chip. + * Therefore, when constructing the devlist, we process + * all the cores together. + */ + if (nodetype == SBD_COMP_CPU) { + /* + * Number of units to process in the inner loop + */ + nunits = MAX_CORES_PER_CMP; + /* + * The distance between the units in the + * board's sb_devlist structure. + */ + distance = MAX_CMP_UNITS_PER_BOARD; + } else { + nunits = 1; + distance = 0; + } + + for (j = 0; j < nunits; j++) { + if ((dip = devlist[i + j * distance]) == NULL) + continue; + + ut = sbdp_get_unit_num(hdp, dip); + + if (ut == -1) { + SBD_GET_PERR(hdp->h_err, ep); + PR_ALL("sbd_get_devlist bad unit %d" + " code %d errno %d", + i, ep->e_code, ep->e_errno); + } + + if ((uset & (1 << ut)) == 0) + continue; + uset &= ~(1 << ut); + is_present = SBD_DEV_IS_PRESENT(sbp, nodetype, ut) ? + 1 : 0; + is_attached = SBD_DEV_IS_ATTACHED(sbp, nodetype, ut) ? + 1 : 0; + + if (is_present && (present_only ^ is_attached)) { + ret_devlist[ix].dv_dip = dip; + sbd_init_err(&ret_devlist[ix].dv_error); + ix++; + } + } + } + sbd_release_sbdp_handle(hdp); + + if ((*count = ix) == 0) { + FREESTRUCT(ret_devlist, sbd_devlist_t, max_units); + ret_devlist = NULL; + } + + return (ret_devlist); +} + +static sbd_devlist_t * +sbd_get_attach_devlist(sbd_handle_t *hp, int32_t *devnump, int32_t pass) +{ + sbd_board_t *sbp; + uint_t uset; + sbd_devset_t devset; + sbd_devlist_t *attach_devlist; + static int next_pass = 1; + static fn_t f = "sbd_get_attach_devlist"; + + PR_ALL("%s (pass = %d)...\n", f, pass); + + sbp = SBDH2BD(hp->h_sbd); + devset = HD2MACHHD(hp)->sh_devset; + + *devnump = 0; + attach_devlist = NULL; + + /* + * We switch on next_pass for the cases where a board + * does not contain a particular type of component. + * In these situations we don't want to return NULL + * prematurely. We need to check other devices and + * we don't want to check the same type multiple times. + * For example, if there were no cpus, then on pass 1 + * we would drop through and return the memory nodes. + * However, on pass 2 we would switch back to the memory + * nodes thereby returning them twice! Using next_pass + * forces us down to the end (or next item). + */ + if (pass == 1) + next_pass = 1; + + switch (next_pass) { + case 1: + if (DEVSET_IN_SET(devset, SBD_COMP_CPU, DEVSET_ANYUNIT)) { + uset = DEVSET_GET_UNITSET(devset, SBD_COMP_CPU); + + attach_devlist = sbd_get_devlist(hp, sbp, SBD_COMP_CPU, + MAX_CPU_UNITS_PER_BOARD, + uset, devnump, 1); + + DEVSET_DEL(devset, SBD_COMP_CPU, DEVSET_ANYUNIT); + if (!devset || attach_devlist) { + next_pass = 2; + return (attach_devlist); + } + /* + * If the caller is interested in the entire + * board, but there aren't any cpus, then just + * fall through to check for the next component. + */ + } + /*FALLTHROUGH*/ + + case 2: + if (DEVSET_IN_SET(devset, SBD_COMP_MEM, DEVSET_ANYUNIT)) { + uset = DEVSET_GET_UNITSET(devset, SBD_COMP_MEM); + + attach_devlist = sbd_get_devlist(hp, sbp, SBD_COMP_MEM, + MAX_MEM_UNITS_PER_BOARD, + uset, devnump, 1); + + DEVSET_DEL(devset, SBD_COMP_MEM, DEVSET_ANYUNIT); + if (!devset || attach_devlist) { + next_pass = 3; + return (attach_devlist); + } + /* + * If the caller is interested in the entire + * board, but there isn't any memory, then + * just fall through to next component. + */ + } + /*FALLTHROUGH*/ + + + case 3: + next_pass = -1; + if (DEVSET_IN_SET(devset, SBD_COMP_IO, DEVSET_ANYUNIT)) { + uset = DEVSET_GET_UNITSET(devset, SBD_COMP_IO); + + attach_devlist = sbd_get_devlist(hp, sbp, SBD_COMP_IO, + MAX_IO_UNITS_PER_BOARD, + uset, devnump, 1); + + DEVSET_DEL(devset, SBD_COMP_IO, DEVSET_ANYUNIT); + if (!devset || attach_devlist) { + next_pass = 4; + return (attach_devlist); + } + } + /*FALLTHROUGH*/ + + default: + *devnump = 0; + return (NULL); + } + /*NOTREACHED*/ +} + +static int +sbd_pre_attach_devlist(sbd_handle_t *hp, sbd_devlist_t *devlist, + int32_t devnum) +{ + int max_units = 0, rv = 0; + sbd_comp_type_t nodetype; + static fn_t f = "sbd_pre_attach_devlist"; + + /* + * In this driver, all entries in a devlist[] are + * of the same nodetype. + */ + nodetype = sbd_get_devtype(hp, devlist->dv_dip); + + PR_ALL("%s (nt = %s(%d), num = %d)...\n", + f, sbd_ct_str[(int)nodetype], (int)nodetype, devnum); + + switch (nodetype) { + + case SBD_COMP_MEM: + max_units = MAX_MEM_UNITS_PER_BOARD; + rv = sbd_pre_attach_mem(hp, devlist, devnum); + break; + + case SBD_COMP_CPU: + max_units = MAX_CPU_UNITS_PER_BOARD; + rv = sbd_pre_attach_cpu(hp, devlist, devnum); + break; + + case SBD_COMP_IO: + max_units = MAX_IO_UNITS_PER_BOARD; + break; + + default: + rv = -1; + break; + } + + if (rv && max_units) { + int i; + /* + * Need to clean up devlist + * if pre-op is going to fail. + */ + for (i = 0; i < max_units; i++) { + if (SBD_GET_ERRSTR(&devlist[i].dv_error)) { + SBD_FREE_ERR(&devlist[i].dv_error); + } else { + break; + } + } + FREESTRUCT(devlist, sbd_devlist_t, max_units); + } + + /* + * If an error occurred, return "continue" + * indication so that we can continue attaching + * as much as possible. + */ + return (rv ? -1 : 0); +} + +static int +sbd_post_attach_devlist(sbd_handle_t *hp, sbd_devlist_t *devlist, + int32_t devnum) +{ + int i, max_units = 0, rv = 0; + sbd_devset_t devs_unattached, devs_present; + sbd_comp_type_t nodetype; + sbd_board_t *sbp = SBDH2BD(hp->h_sbd); + sbdp_handle_t *hdp; + static fn_t f = "sbd_post_attach_devlist"; + + sbp = SBDH2BD(hp->h_sbd); + nodetype = sbd_get_devtype(hp, devlist->dv_dip); + + PR_ALL("%s (nt = %s(%d), num = %d)...\n", + f, sbd_ct_str[(int)nodetype], (int)nodetype, devnum); + + hdp = sbd_get_sbdp_handle(sbp, hp); + + /* + * Need to free up devlist[] created earlier in + * sbd_get_attach_devlist(). + */ + switch (nodetype) { + case SBD_COMP_CPU: + max_units = MAX_CPU_UNITS_PER_BOARD; + rv = sbd_post_attach_cpu(hp, devlist, devnum); + break; + + + case SBD_COMP_MEM: + max_units = MAX_MEM_UNITS_PER_BOARD; + + rv = sbd_post_attach_mem(hp, devlist, devnum); + break; + + case SBD_COMP_IO: + max_units = MAX_IO_UNITS_PER_BOARD; + break; + + default: + rv = -1; + break; + } + + + for (i = 0; i < devnum; i++) { + int unit; + dev_info_t *dip; + sbderror_t *ep; + + ep = &devlist[i].dv_error; + + if (sbd_set_err_in_hdl(hp, ep) == 0) + continue; + + dip = devlist[i].dv_dip; + nodetype = sbd_get_devtype(hp, dip); + unit = sbdp_get_unit_num(hdp, dip); + + if (unit == -1) { + SBD_GET_PERR(hdp->h_err, ep); + continue; + } + + unit = sbd_check_unit_attached(sbp, dip, unit, nodetype, ep); + + if (unit == -1) { + PR_ALL("%s: ERROR (nt=%s, b=%d, u=%d) not attached\n", + f, sbd_ct_str[(int)nodetype], sbp->sb_num, i); + continue; + } + + SBD_DEV_SET_ATTACHED(sbp, nodetype, unit); + SBD_DEVICE_TRANSITION(sbp, nodetype, unit, + SBD_STATE_CONFIGURED); + } + sbd_release_sbdp_handle(hdp); + + if (rv) { + PR_ALL("%s: errno %d, ecode %d during attach\n", + f, SBD_GET_ERRNO(SBD_HD2ERR(hp)), + SBD_GET_ERR(HD2MACHERR(hp))); + } + + devs_present = SBD_DEVS_PRESENT(sbp); + devs_unattached = SBD_DEVS_UNATTACHED(sbp); + + switch (SBD_BOARD_STATE(sbp)) { + case SBD_STATE_CONNECTED: + case SBD_STATE_UNCONFIGURED: + ASSERT(devs_present); + + if (devs_unattached == 0) { + /* + * All devices finally attached. + */ + SBD_BOARD_TRANSITION(sbp, SBD_STATE_CONFIGURED); + sbp->sb_rstate = SBD_STAT_CONNECTED; + sbp->sb_ostate = SBD_STAT_CONFIGURED; + } else if (devs_present != devs_unattached) { + /* + * Only some devices are fully attached. + */ + SBD_BOARD_TRANSITION(sbp, SBD_STATE_PARTIAL); + sbp->sb_rstate = SBD_STAT_CONNECTED; + sbp->sb_ostate = SBD_STAT_UNCONFIGURED; + } + (void) drv_getparm(TIME, (void *)&sbp->sb_time); + break; + + case SBD_STATE_PARTIAL: + ASSERT(devs_present); + /* + * All devices finally attached. + */ + if (devs_unattached == 0) { + SBD_BOARD_TRANSITION(sbp, SBD_STATE_CONFIGURED); + sbp->sb_rstate = SBD_STAT_CONNECTED; + sbp->sb_ostate = SBD_STAT_CONFIGURED; + (void) drv_getparm(TIME, (void *)&sbp->sb_time); + } + break; + + default: + break; + } + + if (max_units && devlist) { + int i; + + for (i = 0; i < max_units; i++) { + if (SBD_GET_ERRSTR(&devlist[i].dv_error)) { + SBD_FREE_ERR(&devlist[i].dv_error); + } else { + break; + } + } + FREESTRUCT(devlist, sbd_devlist_t, max_units); + } + + /* + * Our policy is to attach all components that are + * possible, thus we always return "success" on the + * pre and post operations. + */ + return (0); +} + +/* + * We only need to "release" cpu and memory devices. + */ +static sbd_devlist_t * +sbd_get_release_devlist(sbd_handle_t *hp, int32_t *devnump, int32_t pass) +{ + sbd_board_t *sbp; + uint_t uset; + sbd_devset_t devset; + sbd_devlist_t *release_devlist; + static int next_pass = 1; + static fn_t f = "sbd_get_release_devlist"; + + PR_ALL("%s (pass = %d)...\n", f, pass); + + sbp = SBDH2BD(hp->h_sbd); + devset = HD2MACHHD(hp)->sh_devset; + + *devnump = 0; + release_devlist = NULL; + + /* + * We switch on next_pass for the cases where a board + * does not contain a particular type of component. + * In these situations we don't want to return NULL + * prematurely. We need to check other devices and + * we don't want to check the same type multiple times. + * For example, if there were no cpus, then on pass 1 + * we would drop through and return the memory nodes. + * However, on pass 2 we would switch back to the memory + * nodes thereby returning them twice! Using next_pass + * forces us down to the end (or next item). + */ + if (pass == 1) + next_pass = 1; + + switch (next_pass) { + case 1: + if (DEVSET_IN_SET(devset, SBD_COMP_MEM, DEVSET_ANYUNIT)) { + uset = DEVSET_GET_UNITSET(devset, SBD_COMP_MEM); + + release_devlist = sbd_get_devlist(hp, sbp, + SBD_COMP_MEM, + MAX_MEM_UNITS_PER_BOARD, + uset, devnump, 0); + + DEVSET_DEL(devset, SBD_COMP_MEM, DEVSET_ANYUNIT); + if (!devset || release_devlist) { + next_pass = 2; + return (release_devlist); + } + /* + * If the caller is interested in the entire + * board, but there isn't any memory, then + * just fall through to next component. + */ + } + /*FALLTHROUGH*/ + + + case 2: + if (DEVSET_IN_SET(devset, SBD_COMP_CPU, DEVSET_ANYUNIT)) { + uset = DEVSET_GET_UNITSET(devset, SBD_COMP_CPU); + + release_devlist = sbd_get_devlist(hp, sbp, + SBD_COMP_CPU, + MAX_CPU_UNITS_PER_BOARD, + uset, devnump, 0); + + DEVSET_DEL(devset, SBD_COMP_CPU, DEVSET_ANYUNIT); + if (!devset || release_devlist) { + next_pass = 3; + return (release_devlist); + } + /* + * If the caller is interested in the entire + * board, but there aren't any cpus, then just + * fall through to check for the next component. + */ + } + /*FALLTHROUGH*/ + + + case 3: + next_pass = -1; + if (DEVSET_IN_SET(devset, SBD_COMP_IO, DEVSET_ANYUNIT)) { + uset = DEVSET_GET_UNITSET(devset, SBD_COMP_IO); + + release_devlist = sbd_get_devlist(hp, sbp, + SBD_COMP_IO, + MAX_IO_UNITS_PER_BOARD, + uset, devnump, 0); + + DEVSET_DEL(devset, SBD_COMP_IO, DEVSET_ANYUNIT); + if (!devset || release_devlist) { + next_pass = 4; + return (release_devlist); + } + } + /*FALLTHROUGH*/ + + default: + *devnump = 0; + return (NULL); + } + /*NOTREACHED*/ +} + +static int +sbd_pre_release_devlist(sbd_handle_t *hp, sbd_devlist_t *devlist, + int32_t devnum) +{ + int max_units = 0, rv = 0; + sbd_comp_type_t nodetype; + static fn_t f = "sbd_pre_release_devlist"; + + nodetype = sbd_get_devtype(hp, devlist->dv_dip); + + PR_ALL("%s (nt = %s(%d), num = %d)...\n", + f, sbd_ct_str[(int)nodetype], (int)nodetype, devnum); + + switch (nodetype) { + case SBD_COMP_CPU: { + int i, mem_present = 0; + sbd_board_t *sbp = SBDH2BD(hp->h_sbd); + sbd_devset_t devset; + sbd_priv_handle_t *shp = HD2MACHHD(hp); + + max_units = MAX_CPU_UNITS_PER_BOARD; + + devset = shp->sh_orig_devset; + + for (i = 0; i < MAX_MEM_UNITS_PER_BOARD; i++) { + /* + * if client also requested to unconfigure memory + * the we allow the operation. Therefore + * we need to warranty that memory gets unconfig + * before cpus + */ + + if (DEVSET_IN_SET(devset, SBD_COMP_MEM, i)) { + continue; + } + if (SBD_DEV_IS_ATTACHED(sbp, SBD_COMP_MEM, i)) { + mem_present = 1; + break; + } + } + if (mem_present) { + sbderror_t *ep = SBD_HD2ERR(hp); + SBD_SET_ERR(ep, ESBD_MEMONLINE); + SBD_SET_ERRSTR(ep, sbp->sb_mempath[i]); + rv = -1; + } else { + rv = sbd_pre_release_cpu(hp, devlist, devnum); + } + + break; + + } + case SBD_COMP_MEM: + max_units = MAX_MEM_UNITS_PER_BOARD; + rv = sbd_pre_release_mem(hp, devlist, devnum); + break; + + + case SBD_COMP_IO: + max_units = MAX_IO_UNITS_PER_BOARD; + rv = sbd_pre_release_io(hp, devlist, devnum); + break; + + default: + rv = -1; + break; + } + + if (rv && max_units) { + int i; + + /* + * the individual pre_release component routines should + * have set the error in the handle. No need to set it + * here + * + * Need to clean up dynamically allocated devlist + * if pre-op is going to fail. + */ + for (i = 0; i < max_units; i++) { + if (SBD_GET_ERRSTR(&devlist[i].dv_error)) { + SBD_FREE_ERR(&devlist[i].dv_error); + } else { + break; + } + } + FREESTRUCT(devlist, sbd_devlist_t, max_units); + } + + return (rv ? -1 : 0); +} + +static int +sbd_post_release_devlist(sbd_handle_t *hp, sbd_devlist_t *devlist, + int32_t devnum) +{ + int i, max_units = 0; + sbd_comp_type_t nodetype; + sbd_board_t *sbp = SBDH2BD(hp->h_sbd); + sbdp_handle_t *hdp; + sbd_error_t *spe; + static fn_t f = "sbd_post_release_devlist"; + + nodetype = sbd_get_devtype(hp, devlist->dv_dip); + ASSERT(nodetype >= SBD_COMP_CPU && nodetype <= SBD_COMP_IO); + + PR_ALL("%s (nt = %s(%d), num = %d)...\n", + f, sbd_ct_str[(int)nodetype], (int)nodetype, devnum); + + /* + * Need to free up devlist[] created earlier in + * sbd_get_release_devlist(). + */ + switch (nodetype) { + case SBD_COMP_CPU: + max_units = MAX_CPU_UNITS_PER_BOARD; + break; + + case SBD_COMP_MEM: + max_units = MAX_MEM_UNITS_PER_BOARD; + break; + + case SBD_COMP_IO: + /* + * Need to check if specific I/O is referenced and + * fail post-op. + */ + + if (sbd_check_io_refs(hp, devlist, devnum) > 0) { + PR_IO("%s: error - I/O devices ref'd\n", f); + } + + max_units = MAX_IO_UNITS_PER_BOARD; + break; + + default: + { + cmn_err(CE_WARN, "%s: invalid nodetype (%d)", + f, (int)nodetype); + SBD_SET_ERR(HD2MACHERR(hp), ESBD_INVAL); + } + break; + } + hdp = sbd_get_sbdp_handle(sbp, hp); + spe = hdp->h_err; + + for (i = 0; i < devnum; i++) { + int unit; + sbderror_t *ep; + + ep = &devlist[i].dv_error; + + if (sbd_set_err_in_hdl(hp, ep) == 0) { + continue; + } + + unit = sbdp_get_unit_num(hdp, devlist[i].dv_dip); + if (unit == -1) { + SBD_GET_PERR(hdp->h_err, SBD_HD2ERR(hp)); + PR_ALL("%s bad unit num: %d code %d", + f, unit, spe->e_code); + continue; + } + } + sbd_release_sbdp_handle(hdp); + + if (SBD_GET_ERRNO(SBD_HD2ERR(hp))) { + PR_ALL("%s: errno %d, ecode %d during release\n", + f, SBD_GET_ERRNO(SBD_HD2ERR(hp)), + SBD_GET_ERR(SBD_HD2ERR(hp))); + } + + if (max_units && devlist) { + int i; + + for (i = 0; i < max_units; i++) { + if (SBD_GET_ERRSTR(&devlist[i].dv_error)) { + SBD_FREE_ERR(&devlist[i].dv_error); + } else { + break; + } + } + FREESTRUCT(devlist, sbd_devlist_t, max_units); + } + + return (SBD_GET_ERRNO(SBD_HD2ERR(hp)) ? -1 : 0); +} + +static void +sbd_release_dev_done(sbd_board_t *sbp, sbd_comp_type_t nodetype, int unit) +{ + SBD_DEV_SET_UNREFERENCED(sbp, nodetype, unit); + SBD_DEVICE_TRANSITION(sbp, nodetype, unit, SBD_STATE_UNREFERENCED); +} + +static void +sbd_release_done(sbd_handle_t *hp, sbd_comp_type_t nodetype, dev_info_t *dip) +{ + int unit; + sbd_board_t *sbp = SBDH2BD(hp->h_sbd); + sbderror_t *ep; + static fn_t f = "sbd_release_done"; + sbdp_handle_t *hdp; + + PR_ALL("%s...\n", f); + + hdp = sbd_get_sbdp_handle(sbp, hp); + ep = SBD_HD2ERR(hp); + + if ((unit = sbdp_get_unit_num(hdp, dip)) < 0) { + cmn_err(CE_WARN, + "sbd:%s: unable to get unit for dip (0x%p)", + f, (void *)dip); + SBD_GET_PERR(hdp->h_err, ep); + sbd_release_sbdp_handle(hdp); + return; + } + sbd_release_sbdp_handle(hdp); + + /* + * Transfer the device which just completed its release + * to the UNREFERENCED state. + */ + switch (nodetype) { + + case SBD_COMP_MEM: + sbd_release_mem_done((void *)hp, unit); + break; + + default: + sbd_release_dev_done(sbp, nodetype, unit); + break; + } + + /* + * If the entire board was released and all components + * unreferenced then transfer it to the UNREFERENCED state. + */ + if (SBD_DEVS_RELEASED(sbp) == SBD_DEVS_UNREFERENCED(sbp)) { + SBD_BOARD_TRANSITION(sbp, SBD_STATE_UNREFERENCED); + (void) drv_getparm(TIME, (void *)&sbp->sb_time); + } +} + +static sbd_devlist_t * +sbd_get_detach_devlist(sbd_handle_t *hp, int32_t *devnump, int32_t pass) +{ + sbd_board_t *sbp; + uint_t uset; + sbd_devset_t devset; + sbd_devlist_t *detach_devlist; + static int next_pass = 1; + static fn_t f = "sbd_get_detach_devlist"; + + PR_ALL("%s (pass = %d)...\n", f, pass); + + sbp = SBDH2BD(hp->h_sbd); + devset = HD2MACHHD(hp)->sh_devset; + + *devnump = 0; + detach_devlist = NULL; + + /* + * We switch on next_pass for the cases where a board + * does not contain a particular type of component. + * In these situations we don't want to return NULL + * prematurely. We need to check other devices and + * we don't want to check the same type multiple times. + * For example, if there were no cpus, then on pass 1 + * we would drop through and return the memory nodes. + * However, on pass 2 we would switch back to the memory + * nodes thereby returning them twice! Using next_pass + * forces us down to the end (or next item). + */ + if (pass == 1) + next_pass = 1; + + switch (next_pass) { + case 1: + if (DEVSET_IN_SET(devset, SBD_COMP_MEM, DEVSET_ANYUNIT)) { + uset = DEVSET_GET_UNITSET(devset, SBD_COMP_MEM); + + detach_devlist = sbd_get_devlist(hp, sbp, + SBD_COMP_MEM, + MAX_MEM_UNITS_PER_BOARD, + uset, devnump, 0); + + DEVSET_DEL(devset, SBD_COMP_MEM, DEVSET_ANYUNIT); + if (!devset || detach_devlist) { + next_pass = 2; + return (detach_devlist); + } + /* + * If the caller is interested in the entire + * board, but there isn't any memory, then + * just fall through to next component. + */ + } + /*FALLTHROUGH*/ + + case 2: + if (DEVSET_IN_SET(devset, SBD_COMP_CPU, DEVSET_ANYUNIT)) { + uset = DEVSET_GET_UNITSET(devset, SBD_COMP_CPU); + + detach_devlist = sbd_get_devlist(hp, sbp, + SBD_COMP_CPU, + MAX_CPU_UNITS_PER_BOARD, + uset, devnump, 0); + + DEVSET_DEL(devset, SBD_COMP_CPU, DEVSET_ANYUNIT); + if (!devset || detach_devlist) { + next_pass = 2; + return (detach_devlist); + } + /* + * If the caller is interested in the entire + * board, but there aren't any cpus, then just + * fall through to check for the next component. + */ + } + /*FALLTHROUGH*/ + + case 3: + next_pass = -1; + if (DEVSET_IN_SET(devset, SBD_COMP_IO, DEVSET_ANYUNIT)) { + uset = DEVSET_GET_UNITSET(devset, SBD_COMP_IO); + + detach_devlist = sbd_get_devlist(hp, sbp, + SBD_COMP_IO, + MAX_IO_UNITS_PER_BOARD, + uset, devnump, 0); + + DEVSET_DEL(devset, SBD_COMP_IO, DEVSET_ANYUNIT); + if (!devset || detach_devlist) { + next_pass = 4; + return (detach_devlist); + } + } + /*FALLTHROUGH*/ + + default: + *devnump = 0; + return (NULL); + } + /*NOTREACHED*/ +} + +static int +sbd_pre_detach_devlist(sbd_handle_t *hp, sbd_devlist_t *devlist, + int32_t devnum) +{ + int rv = 0; + sbd_comp_type_t nodetype; + static fn_t f = "sbd_pre_detach_devlist"; + + nodetype = sbd_get_devtype(hp, devlist->dv_dip); + + PR_ALL("%s (nt = %s(%d), num = %d)...\n", + f, sbd_ct_str[(int)nodetype], (int)nodetype, devnum); + + switch (nodetype) { + case SBD_COMP_CPU: + rv = sbd_pre_detach_cpu(hp, devlist, devnum); + break; + + case SBD_COMP_MEM: + rv = sbd_pre_detach_mem(hp, devlist, devnum); + break; + + case SBD_COMP_IO: + rv = sbd_pre_detach_io(hp, devlist, devnum); + break; + + default: + rv = -1; + break; + } + + /* + * We want to continue attempting to detach + * other components. + */ + return (rv); +} + +static int +sbd_post_detach_devlist(sbd_handle_t *hp, sbd_devlist_t *devlist, + int32_t devnum) +{ + int i, max_units = 0, rv = 0; + sbd_comp_type_t nodetype; + sbd_board_t *sbp; + sbd_istate_t bstate; + static fn_t f = "sbd_post_detach_devlist"; + sbdp_handle_t *hdp; + + sbp = SBDH2BD(hp->h_sbd); + nodetype = sbd_get_devtype(hp, devlist->dv_dip); + + hdp = sbd_get_sbdp_handle(sbp, hp); + + PR_ALL("%s (nt = %s(%d), num = %d)...\n", + f, sbd_ct_str[(int)nodetype], (int)nodetype, devnum); + + /* + * Need to free up devlist[] created earlier in + * sbd_get_detach_devlist(). + */ + switch (nodetype) { + case SBD_COMP_CPU: + max_units = MAX_CPU_UNITS_PER_BOARD; + rv = sbd_post_detach_cpu(hp, devlist, devnum); + break; + + case SBD_COMP_MEM: + max_units = MAX_MEM_UNITS_PER_BOARD; + rv = sbd_post_detach_mem(hp, devlist, devnum); + break; + + case SBD_COMP_IO: + max_units = MAX_IO_UNITS_PER_BOARD; + rv = sbd_post_detach_io(hp, devlist, devnum); + break; + + default: + rv = -1; + break; + } + + + for (i = 0; i < devnum; i++) { + int unit; + sbderror_t *ep; + dev_info_t *dip; + + ep = &devlist[i].dv_error; + + if (sbd_set_err_in_hdl(hp, ep) == 0) + continue; + + dip = devlist[i].dv_dip; + unit = sbdp_get_unit_num(hdp, dip); + if (unit == -1) { + if (hp->h_flags & SBD_IOCTL_FLAG_FORCE) + continue; + else { + SBD_GET_PERR(hdp->h_err, ep); + break; + } + } + nodetype = sbd_get_devtype(hp, dip); + + if (sbd_check_unit_attached(sbp, dip, unit, nodetype, + ep) >= 0) { + /* + * Device is still attached probably due + * to an error. Need to keep track of it. + */ + PR_ALL("%s: ERROR (nt=%s, b=%d, u=%d) not detached\n", + f, sbd_ct_str[(int)nodetype], sbp->sb_num, + unit); + continue; + } + + SBD_DEV_CLR_ATTACHED(sbp, nodetype, unit); + SBD_DEV_CLR_RELEASED(sbp, nodetype, unit); + SBD_DEV_CLR_UNREFERENCED(sbp, nodetype, unit); + SBD_DEVICE_TRANSITION(sbp, nodetype, unit, + SBD_STATE_UNCONFIGURED); + } + sbd_release_sbdp_handle(hdp); + + bstate = SBD_BOARD_STATE(sbp); + if (bstate != SBD_STATE_UNCONFIGURED) { + if (SBD_DEVS_PRESENT(sbp) == SBD_DEVS_UNATTACHED(sbp)) { + /* + * All devices are finally detached. + */ + SBD_BOARD_TRANSITION(sbp, SBD_STATE_UNCONFIGURED); + } else if ((SBD_BOARD_STATE(sbp) != SBD_STATE_PARTIAL) && + SBD_DEVS_ATTACHED(sbp)) { + /* + * Some devices remain attached. + */ + SBD_BOARD_TRANSITION(sbp, SBD_STATE_PARTIAL); + } + } + + if (rv) { + PR_ALL("%s: errno %d, ecode %d during detach\n", + f, SBD_GET_ERRNO(SBD_HD2ERR(hp)), + SBD_GET_ERR(HD2MACHERR(hp))); + } + + if (max_units && devlist) { + int i; + + for (i = 0; i < max_units; i++) { + if (SBD_GET_ERRSTR(&devlist[i].dv_error)) { + SBD_FREE_ERR(&devlist[i].dv_error); + } else { + break; + } + } + FREESTRUCT(devlist, sbd_devlist_t, max_units); + } + + return (SBD_GET_ERRNO(SBD_HD2ERR(hp)) ? -1 : 0); +} + +/* + * Return the unit number of the respective dip if + * it's found to be attached. + */ +static int +sbd_check_unit_attached(sbd_board_t *sbp, dev_info_t *dip, int unit, + sbd_comp_type_t nodetype, sbderror_t *ep) +{ + int rv = -1; + processorid_t cpuid; + uint64_t basepa, endpa; + struct memlist *ml; + extern struct memlist *phys_install; + sbdp_handle_t *hdp; + sbd_handle_t *hp = MACHBD2HD(sbp); + static fn_t f = "sbd_check_unit_attached"; + + hdp = sbd_get_sbdp_handle(sbp, hp); + + switch (nodetype) { + + case SBD_COMP_CPU: + cpuid = sbdp_get_cpuid(hdp, dip); + if (cpuid < 0) { + break; + } + mutex_enter(&cpu_lock); + if (cpu_get(cpuid) != NULL) + rv = unit; + mutex_exit(&cpu_lock); + break; + + case SBD_COMP_MEM: + if (sbdphw_get_base_physaddr(hdp, dip, &basepa)) { + break; + } + if (sbdp_get_mem_alignment(hdp, dip, &endpa)) { + cmn_err(CE_WARN, "%s sbdp_get_mem_alignment fail", f); + break; + } + + basepa &= ~(endpa - 1); + endpa += basepa; + /* + * Check if base address is in phys_install. + */ + memlist_read_lock(); + for (ml = phys_install; ml; ml = ml->next) + if ((endpa <= ml->address) || + (basepa >= (ml->address + ml->size))) + continue; + else + break; + memlist_read_unlock(); + if (ml != NULL) + rv = unit; + break; + + case SBD_COMP_IO: + { + dev_info_t *tdip, *pdip; + + tdip = dip; + + /* + * ddi_walk_devs() requires that topdip's parent be held. + */ + pdip = ddi_get_parent(sbp->sb_topdip); + if (pdip) { + ndi_hold_devi(pdip); + ndi_devi_enter(pdip, &rv); + } + ddi_walk_devs(sbp->sb_topdip, sbd_check_io_attached, + (void *)&tdip); + if (pdip) { + ndi_devi_exit(pdip, rv); + ndi_rele_devi(pdip); + } + + if (tdip == NULL) + rv = unit; + else + rv = -1; + break; + } + + default: + PR_ALL("%s: unexpected nodetype(%d) for dip 0x%p\n", + f, nodetype, (void *)dip); + rv = -1; + break; + } + + /* + * Save the error that sbdp sent us and report it + */ + if (rv == -1) + SBD_GET_PERR(hdp->h_err, ep); + + sbd_release_sbdp_handle(hdp); + + return (rv); +} + +/* + * Return memhandle, if in fact, this memunit is the owner of + * a scheduled memory delete. + */ +int +sbd_get_memhandle(sbd_handle_t *hp, dev_info_t *dip, memhandle_t *mhp) +{ + sbd_board_t *sbp = SBDH2BD(hp->h_sbd); + sbd_mem_unit_t *mp; + sbdp_handle_t *hdp; + int unit; + static fn_t f = "sbd_get_memhandle"; + + PR_MEM("%s...\n", f); + + hdp = sbd_get_sbdp_handle(sbp, hp); + + unit = sbdp_get_unit_num(hdp, dip); + if (unit == -1) { + SBD_GET_PERR(hdp->h_err, SBD_HD2ERR(hp)); + sbd_release_sbdp_handle(hdp); + return (-1); + } + sbd_release_sbdp_handle(hdp); + + mp = SBD_GET_BOARD_MEMUNIT(sbp, unit); + + if (mp->sbm_flags & SBD_MFLAG_RELOWNER) { + *mhp = mp->sbm_memhandle; + return (0); + } else { + SBD_SET_ERR(SBD_HD2ERR(hp), ESBD_INTERNAL); + SBD_SET_ERRSTR(SBD_HD2ERR(hp), sbp->sb_mempath[unit]); + return (-1); + } + /*NOTREACHED*/ +} + + +static int +sbd_cpu_cnt(sbd_handle_t *hp, sbd_devset_t devset) +{ + int c, cix; + sbd_board_t *sbp; + + sbp = SBDH2BD(hp->h_sbd); + + /* + * Only look for requested devices that are actually present. + */ + devset &= SBD_DEVS_PRESENT(sbp); + + for (c = cix = 0; c < MAX_CMP_UNITS_PER_BOARD; c++) { + /* + * Index for core 1 , if exists. + * With the current implementation it is + * MAX_CMP_UNITS_PER_BOARD off from core 0. + * The calculation will need to change if + * the assumption is no longer true. + */ + int c1 = c + MAX_CMP_UNITS_PER_BOARD; + + if (DEVSET_IN_SET(devset, SBD_COMP_CMP, c) == 0) { + continue; + } + + /* + * Check to see if the dip(s) exist for this chip + */ + if ((sbp->sb_devlist[NIX(SBD_COMP_CMP)][c] == NULL) && + (sbp->sb_devlist[NIX(SBD_COMP_CMP)][c1] == NULL)) + continue; + + cix++; + } + + return (cix); +} + +static int +sbd_mem_cnt(sbd_handle_t *hp, sbd_devset_t devset) +{ + int i, ix; + sbd_board_t *sbp = SBDH2BD(hp->h_sbd); + + /* + * Only look for requested devices that are actually present. + */ + devset &= SBD_DEVS_PRESENT(sbp); + + for (i = ix = 0; i < MAX_MEM_UNITS_PER_BOARD; i++) { + dev_info_t *dip; + + if (DEVSET_IN_SET(devset, SBD_COMP_MEM, i) == 0) { + continue; + } + + dip = sbp->sb_devlist[NIX(SBD_COMP_MEM)][i]; + if (dip == NULL) + continue; + + ix++; + } + + return (ix); +} + +/* + * NOTE: This routine is only partially smart about multiple + * mem-units. Need to make mem-status structure smart + * about them also. + */ +static int +sbd_mem_status(sbd_handle_t *hp, sbd_devset_t devset, sbd_dev_stat_t *dsp) +{ + int m, mix, rv; + memdelstat_t mdst; + memquery_t mq; + sbd_board_t *sbp; + sbd_mem_unit_t *mp; + sbd_mem_stat_t *msp; + extern int kcage_on; + int i; + static fn_t f = "sbd_mem_status"; + + sbp = SBDH2BD(hp->h_sbd); + + /* + * Check the present devset and access the dip with + * status lock held to protect agains a concurrent + * unconfigure or disconnect thread. + */ + mutex_enter(&sbp->sb_slock); + + /* + * Only look for requested devices that are actually present. + */ + devset &= SBD_DEVS_PRESENT(sbp); + + for (m = mix = 0; m < MAX_MEM_UNITS_PER_BOARD; m++) { + dev_info_t *dip; + + + if (DEVSET_IN_SET(devset, SBD_COMP_MEM, m) == 0) + continue; + + /* + * Check to make sure the memory unit is in a state + * where its fully initialized. + */ + if (SBD_DEVICE_STATE(sbp, SBD_COMP_MEM, m) == SBD_STATE_EMPTY) + continue; + + dip = sbp->sb_devlist[NIX(SBD_COMP_MEM)][m]; + if (dip == NULL) + continue; + + mp = SBD_GET_BOARD_MEMUNIT(sbp, m); + + msp = &dsp->d_mem; + + bzero((caddr_t)msp, sizeof (*msp)); + msp->ms_type = SBD_COMP_MEM; + + /* + * The plugin expects -1 for the mem unit + */ + msp->ms_cm.c_id.c_unit = -1; + + /* + * Get the memory name from what sbdp gave us + */ + for (i = 0; SBD_COMP(i) != SBD_COMP_UNKNOWN; i++) { + if (SBD_COMP(i) == SBD_COMP_MEM) { + (void) strcpy(msp->ms_name, SBD_DEVNAME(i)); + } + } + msp->ms_cm.c_cond = mp->sbm_cm.sbdev_cond; + msp->ms_cm.c_busy = mp->sbm_cm.sbdev_busy; + msp->ms_cm.c_time = mp->sbm_cm.sbdev_time; + + /* XXX revisit this after memory conversion */ + msp->ms_ostate = ostate_cvt(SBD_DEVICE_STATE( + sbp, SBD_COMP_MEM, m)); + + msp->ms_basepfn = mp->sbm_basepfn; + msp->ms_pageslost = mp->sbm_pageslost; + msp->ms_cage_enabled = kcage_on; + msp->ms_interleave = mp->sbm_interleave; + + if (mp->sbm_flags & SBD_MFLAG_RELOWNER) + rv = kphysm_del_status(mp->sbm_memhandle, &mdst); + else + rv = KPHYSM_EHANDLE; /* force 'if' to fail */ + + if (rv == KPHYSM_OK) { + msp->ms_totpages += mdst.phys_pages; + + /* + * Any pages above managed is "free", + * i.e. it's collected. + */ + msp->ms_detpages += (uint_t)(mdst.collected + + mdst.phys_pages - + mdst.managed); + } else { + msp->ms_totpages += (uint_t)mp->sbm_npages; + + /* + * If we're UNREFERENCED or UNCONFIGURED, + * then the number of detached pages is + * however many pages are on the board. + * I.e. detached = not in use by OS. + */ + switch (msp->ms_cm.c_ostate) { + /* + * changed to use cfgadm states + * + * was: + * case SFDR_STATE_UNREFERENCED: + * case SFDR_STATE_UNCONFIGURED: + */ + case SBD_STAT_UNCONFIGURED: + msp->ms_detpages = msp->ms_totpages; + break; + + default: + break; + } + } + + rv = kphysm_del_span_query(mp->sbm_basepfn, + mp->sbm_npages, &mq); + if (rv == KPHYSM_OK) { + msp->ms_managed_pages = mq.managed; + msp->ms_noreloc_pages = mq.nonrelocatable; + msp->ms_noreloc_first = mq.first_nonrelocatable; + msp->ms_noreloc_last = mq.last_nonrelocatable; + msp->ms_cm.c_sflags = 0; + if (mq.nonrelocatable) { + SBD_SET_SUSPEND(SBD_CMD_UNCONFIGURE, + dsp->ds_suspend); + } + } else { + PR_MEM("%s: kphysm_del_span_query() = %d\n", f, rv); + } + + mix++; + dsp++; + } + + mutex_exit(&sbp->sb_slock); + + return (mix); +} + +static void +sbd_cancel(sbd_handle_t *hp) +{ + int i; + sbd_devset_t devset; + sbd_board_t *sbp = SBDH2BD(hp->h_sbd); + static fn_t f = "sbd_cancel"; + int rv; + + PR_ALL("%s...\n", f); + + /* + * Only devices which have been "released" are + * subject to cancellation. + */ + devset = HD2MACHHD(hp)->sh_devset & SBD_DEVS_UNREFERENCED(sbp); + + /* + * Nothing to do for CPUs or IO other than change back + * their state. + */ + for (i = 0; i < MAX_CPU_UNITS_PER_BOARD; i++) { + if (!DEVSET_IN_SET(devset, SBD_COMP_CPU, i)) + continue; + if (sbd_cancel_cpu(hp, i) != SBD_CPUERR_FATAL) { + SBD_DEVICE_TRANSITION(sbp, SBD_COMP_CPU, i, + SBD_STATE_CONFIGURED); + } else { + SBD_DEVICE_TRANSITION(sbp, SBD_COMP_CPU, i, + SBD_STATE_FATAL); + } + } + + for (i = 0; i < MAX_IO_UNITS_PER_BOARD; i++) { + if (!DEVSET_IN_SET(devset, SBD_COMP_IO, i)) + continue; + SBD_DEVICE_TRANSITION(sbp, SBD_COMP_IO, i, + SBD_STATE_CONFIGURED); + } + + for (i = 0; i < MAX_MEM_UNITS_PER_BOARD; i++) { + if (!DEVSET_IN_SET(devset, SBD_COMP_MEM, i)) + continue; + if ((rv = sbd_cancel_mem(hp, i)) == 0) { + SBD_DEVICE_TRANSITION(sbp, SBD_COMP_MEM, i, + SBD_STATE_CONFIGURED); + } else if (rv == -1) { + SBD_DEVICE_TRANSITION(sbp, SBD_COMP_MEM, i, + SBD_STATE_FATAL); + } + } + + PR_ALL("%s: unreleasing devset (0x%x)\n", f, (uint_t)devset); + + SBD_DEVS_CANCEL(sbp, devset); + + if (SBD_DEVS_UNREFERENCED(sbp) == 0) { + sbd_istate_t new_state; + /* + * If the board no longer has any released devices + * than transfer it back to the CONFIG/PARTIAL state. + */ + if (SBD_DEVS_ATTACHED(sbp) == SBD_DEVS_PRESENT(sbp)) + new_state = SBD_STATE_CONFIGURED; + else + new_state = SBD_STATE_PARTIAL; + if (SBD_BOARD_STATE(sbp) != new_state) { + SBD_BOARD_TRANSITION(sbp, new_state); + } + sbp->sb_ostate = SBD_STAT_CONFIGURED; + (void) drv_getparm(TIME, (void *)&sbp->sb_time); + } +} + +static void +sbd_get_ncm(sbd_handle_t *hp) +{ + sbd_devset_t devset; + sbd_priv_handle_t *shp = HD2MACHHD(hp); + sbd_cmd_t *cmdp = (sbd_cmd_t *)hp->h_iap; + int error; + + /* pre_op restricted the devices to those selected by the ioctl */ + devset = shp->sh_devset; + + cmdp->cmd_getncm.g_ncm = sbd_cpu_cnt(hp, devset) + + sbd_io_cnt(hp, devset) + sbd_mem_cnt(hp, devset); + + error = sbd_copyout_ioarg(hp->h_mode, hp->h_cmd, cmdp, + (sbd_ioctl_arg_t *)shp->sh_arg); + + if (error != 0) + SBD_SET_ERRNO(SBD_HD2ERR(hp), error); +} + +static void +sbd_status(sbd_handle_t *hp) +{ + int nstat, mode, ncm, sz, cksz; + sbd_priv_handle_t *shp = HD2MACHHD(hp); + sbd_devset_t devset; + sbd_board_t *sbp = SBDH2BD(hp->h_sbd); + sbd_stat_t *dstatp; + sbd_cmd_t *cmdp = (sbd_cmd_t *)hp->h_iap; + sbdp_handle_t *hdp; + sbd_dev_stat_t *devstatp; + +#ifdef _MULTI_DATAMODEL + int sz32; + sbd_stat32_t *dstat32p; +#endif /* _MULTI_DATAMODEL */ + + static fn_t f = "sbd_status"; + + mode = hp->h_mode; + devset = shp->sh_devset; + + devset &= SBD_DEVS_PRESENT(sbp); + + if (cmdp->cmd_cm.c_id.c_type == SBD_COMP_NONE) { + if (cmdp->cmd_cm.c_flags & SBD_FLAG_ALLCMP) { + /* + * Get the number of components "ncm" on the board. + * Calculate size of buffer required to store one + * sbd_stat_t structure plus ncm-1 sbd_dev_stat_t + * structures. Note that sbd_stat_t already contains + * one sbd_dev_stat_t, so only an additional ncm-1 + * sbd_dev_stat_t structures need to be accounted for + * in the calculation when more than one component + * is present. + */ + ncm = sbd_cpu_cnt(hp, devset) + sbd_io_cnt(hp, devset) + + sbd_mem_cnt(hp, devset); + + } else { + /* + * In the case of c_type == SBD_COMP_NONE, and + * SBD_FLAG_ALLCMP not specified, only the board + * info is to be returned, no components. + */ + ncm = 0; + devset = 0; + } + } else { + /* Confirm that only one component is selected. */ + ncm = sbd_cpu_cnt(hp, devset) + sbd_io_cnt(hp, devset) + + sbd_mem_cnt(hp, devset); + if (ncm != 1) { + PR_ALL("%s: expected ncm of 1, got %d, devset 0x%x\n", + f, ncm, devset); + SBD_SET_ERRNO(SBD_HD2ERR(hp), EINVAL); + return; + } + } + + sz = sizeof (sbd_stat_t); + if (ncm > 1) + sz += sizeof (sbd_dev_stat_t) * (ncm - 1); + + cksz = sz; + + /* + * s_nbytes describes the size of the preallocated user + * buffer into which the application is executing to + * receive the sbd_stat_t and sbd_dev_stat_t structures. + * This buffer must be at least the required (sz) size. + */ + +#ifdef _MULTI_DATAMODEL + + /* + * More buffer space is required for the 64bit to 32bit + * conversion of data structures. + */ + if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) { + sz32 = sizeof (sbd_stat32_t); + if (ncm > 1) + sz32 += sizeof (sbd_dev_stat32_t) * (ncm - 1); + cksz = sz32; + } else + sz32 = 0; +#endif + + if ((int)cmdp->cmd_stat.s_nbytes < cksz) { + PR_ALL("%s: ncm=%d s_nbytes = 0x%x\n", f, ncm, + cmdp->cmd_stat.s_nbytes); + PR_ALL("%s: expected size of 0x%x\n", f, cksz); + SBD_SET_ERRNO(SBD_HD2ERR(hp), EINVAL); + return; + } + + dstatp = kmem_zalloc(sz, KM_SLEEP); + devstatp = &dstatp->s_stat[0]; + +#ifdef _MULTI_DATAMODEL + if (sz32 != 0) + dstat32p = kmem_zalloc(sz32, KM_SLEEP); +#endif + + /* + * if connected or better, provide cached status if available, + * otherwise call sbdp for status + */ + mutex_enter(&sbp->sb_flags_mutex); + switch (sbp->sb_state) { + + case SBD_STATE_CONNECTED: + case SBD_STATE_PARTIAL: + case SBD_STATE_CONFIGURED: + if (sbp->sb_flags & SBD_BOARD_STATUS_CACHED) { + bcopy(&sbp->sb_stat, dstatp, sizeof (sbd_stat_t)); + dstatp->s_rstate = rstate_cvt(sbp->sb_state); + dstatp->s_ostate = ostate_cvt(sbp->sb_state); + dstatp->s_busy = sbp->sb_busy; + dstatp->s_time = sbp->sb_time; + dstatp->s_cond = sbp->sb_cond; + break; + } + /*FALLTHROUGH*/ + + default: + sbp->sb_flags &= ~SBD_BOARD_STATUS_CACHED; + dstatp->s_board = sbp->sb_num; + dstatp->s_ostate = ostate_cvt(sbp->sb_state); + dstatp->s_time = sbp->sb_time; + + hdp = sbd_get_sbdp_handle(sbp, hp); + + if (sbdp_get_board_status(hdp, dstatp) != 0) { + SBD_GET_PERR(hdp->h_err, SBD_HD2ERR(hp)); + sbd_release_sbdp_handle(hdp); +#ifdef _MULTI_DATAMODEL + if (sz32 != 0) + kmem_free(dstat32p, sz32); +#endif + kmem_free(dstatp, sz); + mutex_exit(&sbp->sb_flags_mutex); + return; + } + /* + * Do not cache status if the busy flag has + * been set by the call to sbdp_get_board_status(). + */ + if (!dstatp->s_busy) { + /* Can get board busy flag now */ + dstatp->s_busy = sbp->sb_busy; + sbp->sb_cond = (sbd_cond_t)dstatp->s_cond; + bcopy(dstatp, &sbp->sb_stat, + sizeof (sbd_stat_t)); + sbp->sb_flags |= SBD_BOARD_STATUS_CACHED; + } + sbd_release_sbdp_handle(hdp); + break; + } + mutex_exit(&sbp->sb_flags_mutex); + + if (DEVSET_IN_SET(devset, SBD_COMP_CPU, DEVSET_ANYUNIT)) + if ((nstat = sbd_cpu_flags(hp, devset, devstatp)) > 0) { + dstatp->s_nstat += nstat; + devstatp += nstat; + } + + if (DEVSET_IN_SET(devset, SBD_COMP_MEM, DEVSET_ANYUNIT)) + if ((nstat = sbd_mem_status(hp, devset, devstatp)) > 0) { + dstatp->s_nstat += nstat; + devstatp += nstat; + } + + if (DEVSET_IN_SET(devset, SBD_COMP_IO, DEVSET_ANYUNIT)) + if ((nstat = sbd_io_status(hp, devset, devstatp)) > 0) { + dstatp->s_nstat += nstat; + devstatp += nstat; + } + + /* paranoia: detect buffer overrun */ + if ((caddr_t)devstatp > ((caddr_t)dstatp) + sz) { + PR_ALL("%s: buffer overrun\n", f); +#ifdef _MULTI_DATAMODEL + if (sz32 != 0) + kmem_free(dstat32p, sz32); +#endif + kmem_free(dstatp, sz); + SBD_SET_ERRNO(SBD_HD2ERR(hp), EINVAL); + return; + } + +/* if necessary, move data into intermediate device status buffer */ +#ifdef _MULTI_DATAMODEL + if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) { + int i, j; + + ASSERT(sz32 != 0); + /* paranoia: detect buffer overrun */ + if ((caddr_t)&dstat32p->s_stat[dstatp->s_nstat] > + ((caddr_t)dstat32p) + sz32) { + cmn_err(CE_WARN, + "sbd:%s: buffer32 overrun", f); +#ifdef _MULTI_DATAMODEL + if (sz32 != 0) + kmem_free(dstat32p, sz32); +#endif + kmem_free(dstatp, sz); + SBD_SET_ERRNO(SBD_HD2ERR(hp), EINVAL); + return; + } + + /* + * initialize 32 bit sbd board status structure + */ + dstat32p->s_board = (int32_t)dstatp->s_board; + dstat32p->s_nstat = (int32_t)dstatp->s_nstat; + dstat32p->s_rstate = dstatp->s_rstate; + dstat32p->s_ostate = dstatp->s_ostate; + dstat32p->s_cond = dstatp->s_cond; + dstat32p->s_busy = dstatp->s_busy; + dstat32p->s_time = dstatp->s_time; + dstat32p->s_assigned = dstatp->s_assigned; + dstat32p->s_power = dstatp->s_power; + dstat32p->s_platopts = (int32_t)dstatp->s_platopts; + (void) strcpy(dstat32p->s_type, dstatp->s_type); + + for (i = 0; i < dstatp->s_nstat; i++) { + sbd_dev_stat_t *dsp = &dstatp->s_stat[i]; + sbd_dev_stat32_t *ds32p = &dstat32p->s_stat[i]; + + /* + * copy common data for the device + */ + ds32p->d_cm.ci_type = (int32_t)dsp->d_cm.ci_type; + ds32p->d_cm.ci_unit = (int32_t)dsp->d_cm.ci_unit; + ds32p->d_cm.c_ostate = (int32_t)dsp->d_cm.c_ostate; + ds32p->d_cm.c_cond = (int32_t)dsp->d_cm.c_cond; + ds32p->d_cm.c_busy = (int32_t)dsp->d_cm.c_busy; + ds32p->d_cm.c_time = (time32_t)dsp->d_cm.c_time; + ds32p->d_cm.c_sflags = (int32_t)dsp->d_cm.c_sflags; + (void) strcpy(ds32p->d_cm.ci_name, dsp->d_cm.ci_name); + + /* copy type specific data for the device */ + switch (dsp->d_cm.ci_type) { + + case SBD_COMP_CPU: + ds32p->d_cpu.cs_isbootproc = + (int32_t)dsp->d_cpu.cs_isbootproc; + ds32p->d_cpu.cs_cpuid = + (int32_t)dsp->d_cpu.cs_cpuid; + ds32p->d_cpu.cs_speed = + (int32_t)dsp->d_cpu.cs_speed; + ds32p->d_cpu.cs_ecache = + (int32_t)dsp->d_cpu.cs_ecache; + break; + + case SBD_COMP_MEM: + ds32p->d_mem.ms_type = + (int32_t)dsp->d_mem.ms_type; + ds32p->d_mem.ms_ostate = + (int32_t)dsp->d_mem.ms_ostate; + ds32p->d_mem.ms_cond = + (int32_t)dsp->d_mem.ms_cond; + ds32p->d_mem.ms_interleave = + (uint32_t)dsp->d_mem.ms_interleave; + ds32p->d_mem.ms_basepfn = + (uint32_t)dsp->d_mem.ms_basepfn; + ds32p->d_mem.ms_totpages = + (uint32_t)dsp->d_mem.ms_totpages; + ds32p->d_mem.ms_detpages = + (uint32_t)dsp->d_mem.ms_detpages; + ds32p->d_mem.ms_pageslost = + (int32_t)dsp->d_mem.ms_pageslost; + ds32p->d_mem.ms_managed_pages = + (int32_t)dsp->d_mem.ms_managed_pages; + ds32p->d_mem.ms_noreloc_pages = + (int32_t)dsp->d_mem.ms_noreloc_pages; + ds32p->d_mem.ms_noreloc_first = + (int32_t)dsp->d_mem.ms_noreloc_first; + ds32p->d_mem.ms_noreloc_last = + (int32_t)dsp->d_mem.ms_noreloc_last; + ds32p->d_mem.ms_cage_enabled = + (int32_t)dsp->d_mem.ms_cage_enabled; + ds32p->d_mem.ms_peer_is_target = + (int32_t)dsp->d_mem.ms_peer_is_target; + (void) strcpy(ds32p->d_mem.ms_peer_ap_id, + dsp->d_mem.ms_peer_ap_id); + break; + + + case SBD_COMP_IO: + + ds32p->d_io.is_type = + (int32_t)dsp->d_io.is_type; + ds32p->d_io.is_unsafe_count = + (int32_t)dsp->d_io.is_unsafe_count; + ds32p->d_io.is_referenced = + (int32_t)dsp->d_io.is_referenced; + for (j = 0; j < SBD_MAX_UNSAFE; j++) + ds32p->d_io.is_unsafe_list[j] = + (int32_t) + ds32p->d_io.is_unsafe_list[j]; + bcopy(dsp->d_io.is_pathname, + ds32p->d_io.is_pathname, MAXPATHLEN); + break; + + case SBD_COMP_CMP: + /* copy sbd_cmp_stat_t structure members */ + bcopy(&dsp->d_cmp.ps_cpuid[0], + &ds32p->d_cmp.ps_cpuid[0], + sizeof (ds32p->d_cmp.ps_cpuid)); + ds32p->d_cmp.ps_ncores = + (int32_t)dsp->d_cmp.ps_ncores; + ds32p->d_cmp.ps_speed = + (int32_t)dsp->d_cmp.ps_speed; + ds32p->d_cmp.ps_ecache = + (int32_t)dsp->d_cmp.ps_ecache; + break; + + default: + cmn_err(CE_WARN, + "sbd:%s: unknown dev type (%d)", f, + (int)dsp->d_cm.c_id.c_type); + break; + } + } + + if (ddi_copyout((void *)dstat32p, + cmdp->cmd_stat.s_statp, sz32, mode) != 0) { + cmn_err(CE_WARN, + "sbd:%s: failed to copyout status " + "for board %d", f, sbp->sb_num); + SBD_SET_ERRNO(SBD_HD2ERR(hp), EFAULT); + } + } else +#endif /* _MULTI_DATAMODEL */ + if (ddi_copyout((void *)dstatp, cmdp->cmd_stat.s_statp, + sz, mode) != 0) { + cmn_err(CE_WARN, + "sbd:%s: failed to copyout status for board %d", + f, sbp->sb_num); + SBD_SET_ERRNO(SBD_HD2ERR(hp), EFAULT); + } + +#ifdef _MULTI_DATAMODEL + if (sz32 != 0) + kmem_free(dstat32p, sz32); +#endif + kmem_free(dstatp, sz); +} + +/* + * Called at driver load time to determine the state and condition + * of an existing board in the system. + */ +static void +sbd_board_discovery(sbd_board_t *sbp) +{ + int i; + dev_info_t *dip; + sbd_devset_t devs_lost, devs_attached = 0; + extern kmutex_t cpu_lock; + sbdp_handle_t *hdp; + static fn_t f = "sbd_board_discovery"; + sbderror_t error, *ep; + sbd_handle_t *hp = MACHBD2HD(sbp); + + if (SBD_DEVS_PRESENT(sbp) == 0) { + PR_ALL("%s: board %d has no devices present\n", + f, sbp->sb_num); + return; + } + + ep = &error; + bzero(ep, sizeof (sbderror_t)); + + /* + * Check for existence of cpus. + */ + + hdp = sbd_get_sbdp_handle(sbp, hp); + + for (i = 0; i < MAX_CPU_UNITS_PER_BOARD; i++) { + processorid_t cpuid; + + if (!SBD_DEV_IS_PRESENT(sbp, SBD_COMP_CPU, i)) + continue; + + dip = sbp->sb_devlist[NIX(SBD_COMP_CPU)][i]; + + if (dip != NULL) { + cpuid = sbdp_get_cpuid(hdp, dip); + + if (cpuid < 0) { + SBD_GET_PERR(hdp->h_err, + ep); + continue; + } + + mutex_enter(&cpu_lock); /* needed to call cpu_get() */ + if (cpu_get(cpuid)) { + SBD_DEV_SET_ATTACHED(sbp, SBD_COMP_CPU, i); + DEVSET_ADD(devs_attached, SBD_COMP_CPU, i); + PR_ALL("%s: board %d, cpuid %d - attached\n", + f, sbp->sb_num, cpuid); + } + mutex_exit(&cpu_lock); + sbd_init_cpu_unit(sbp, i); + } + } + + /* + * Check for existence of memory. + */ + for (i = 0; i < MAX_MEM_UNITS_PER_BOARD; i++) { + uint64_t basepa, endpa; + struct memlist *ml; + extern struct memlist *phys_install; + + if (!SBD_DEV_IS_PRESENT(sbp, SBD_COMP_MEM, i)) + continue; + + dip = sbp->sb_devlist[NIX(SBD_COMP_MEM)][i]; + if (dip == NULL) + continue; + + if (sbdphw_get_base_physaddr(hdp, dip, &basepa)) { + /* omit phantom memory controllers on I/O boards */ + if (SBD_DEV_IS_PRESENT(sbp, SBD_COMP_MEM, i)) { + ASSERT(sbp->sb_ndev != 0); + SBD_DEV_CLR_PRESENT(sbp, SBD_COMP_MEM, i); + sbp->sb_ndev--; + } + sbp->sb_devlist[NIX(SBD_COMP_MEM)][i] = NULL; + continue; + } + + /* + * basepa may not be on a alignment boundary, make it so. + */ + if (sbdp_get_mem_alignment(hdp, dip, &endpa)) { + cmn_err(CE_WARN, "%s sbdp_get_mem_alignment fail", f); + continue; + } + + basepa &= ~(endpa - 1); + endpa += basepa; + + /* + * Check if base address is in phys_install. + */ + memlist_read_lock(); + for (ml = phys_install; ml; ml = ml->next) + if ((endpa <= ml->address) || + (basepa >= (ml->address + ml->size))) + continue; + else + break; + memlist_read_unlock(); + + if (ml) { + SBD_DEV_SET_ATTACHED(sbp, SBD_COMP_MEM, i); + DEVSET_ADD(devs_attached, SBD_COMP_MEM, i); + PR_ALL("%s: board %d, mem-unit %d - attached\n", + f, sbp->sb_num, i); + } + sbd_init_mem_unit(sbp, i, ep); + } + sbd_release_sbdp_handle(hdp); + + /* + * If so far we have found an error, we just log it but continue + */ + if (SBD_GET_ERRNO(ep) != 0) + cmn_err(CE_WARN, "%s errno has occurred: errno %d", f, + SBD_GET_ERRNO(ep)); + + /* + * Check for i/o state. + */ + for (i = 0; i < MAX_IO_UNITS_PER_BOARD; i++) { + + if (!SBD_DEV_IS_PRESENT(sbp, SBD_COMP_IO, i)) + continue; + + dip = sbp->sb_devlist[NIX(SBD_COMP_IO)][i]; + if (dip == NULL) + continue; + + ASSERT(e_ddi_branch_held(dip)); + + /* + * XXX Is the devstate check needed ? + */ + if (i_ddi_node_state(dip) >= DS_ATTACHED || + ddi_get_devstate(dip) == DDI_DEVSTATE_UP) { + + /* + * Found it! + */ + SBD_DEV_SET_ATTACHED(sbp, SBD_COMP_IO, i); + DEVSET_ADD(devs_attached, SBD_COMP_IO, i); + PR_ALL("%s: board %d, io-unit %d - attached\n", + f, sbp->sb_num, i); + } + sbd_init_io_unit(sbp, i); + } + + SBD_DEVS_CONFIGURE(sbp, devs_attached); + if (devs_attached && ((devs_lost = SBD_DEVS_UNATTACHED(sbp)) != 0)) { + int ut; + /* + * A prior comment stated that a partially configured + * board was not permitted. The Serengeti architecture + * makes this possible, so the SB_DEVS_DISCONNECT + * at the end of this block has been removed. + */ + + PR_ALL("%s: some devices not configured (0x%x)...\n", + f, devs_lost); + + for (ut = 0; ut < MAX_CPU_UNITS_PER_BOARD; ut++) + if (DEVSET_IN_SET(devs_lost, SBD_COMP_CPU, ut)) { + SBD_DEVICE_TRANSITION(sbp, SBD_COMP_CPU, + ut, SBD_STATE_UNCONFIGURED); + } + + for (ut = 0; ut < MAX_MEM_UNITS_PER_BOARD; ut++) + if (DEVSET_IN_SET(devs_lost, SBD_COMP_MEM, ut)) { + SBD_DEVICE_TRANSITION(sbp, SBD_COMP_MEM, + ut, SBD_STATE_UNCONFIGURED); + } + + for (ut = 0; ut < MAX_IO_UNITS_PER_BOARD; ut++) + if (DEVSET_IN_SET(devs_lost, SBD_COMP_IO, ut)) { + SBD_DEVICE_TRANSITION(sbp, SBD_COMP_IO, + ut, SBD_STATE_UNCONFIGURED); + } + } +} + +static int +hold_rele_branch(dev_info_t *rdip, void *arg) +{ + walk_tree_t *wp = (walk_tree_t *)arg; + + ASSERT(wp && (wp->hold == 0 || wp->hold == 1)); + + switch (get_node_type(wp->sbp, rdip, NULL)) { + case SBD_COMP_CMP: + case SBD_COMP_MEM: + case SBD_COMP_IO: + break; + case SBD_COMP_CPU: + + /* + * All CPU nodes under CMP nodes should have + * gotten pruned when the CMP node was first + * encountered. + */ + ASSERT(!sbd_is_cmp_child(rdip)); + + break; + + case SBD_COMP_UNKNOWN: + /* Not of interest to us */ + return (DDI_WALK_CONTINUE); + default: + ASSERT(0); + return (DDI_WALK_PRUNECHILD); + } + + if (wp->hold) { + ASSERT(!e_ddi_branch_held(rdip)); + e_ddi_branch_hold(rdip); + } else { + ASSERT(e_ddi_branch_held(rdip)); + e_ddi_branch_rele(rdip); + } + + return (DDI_WALK_PRUNECHILD); +} + +static void +sbd_board_init(sbd_board_t *sbp, sbd_softstate_t *softsp, + int bd, dev_info_t *top_dip, int wnode) +{ + int i; + dev_info_t *pdip; + int circ; + walk_tree_t walk = {0}; + + mutex_init(&sbp->sb_mutex, NULL, MUTEX_DRIVER, NULL); + mutex_init(&sbp->sb_flags_mutex, NULL, MUTEX_DRIVER, NULL); + mutex_init(&sbp->sb_slock, NULL, MUTEX_DRIVER, NULL); + + sbp->sb_ref = 0; + sbp->sb_num = bd; + sbp->sb_time = gethrestime_sec(); + /* + * For serengeti, top_dip doesn't need to be held because + * sbp i.e. sbd_board_t will be destroyed in sbd_teardown_instance() + * before top_dip detaches. For Daktari, top_dip is the + * root node which never has to be held. + */ + sbp->sb_topdip = top_dip; + sbp->sb_cpuid = -1; + sbp->sb_softsp = (void *) softsp; + sbp->sb_cond = SBD_COND_UNKNOWN; + sbp->sb_wnode = wnode; + sbp->sb_memaccess_ok = 1; + + ASSERT(MAX_IO_UNITS_PER_BOARD <= SBD_MAX_UNITS_PER_BOARD); + ASSERT(MAX_CPU_UNITS_PER_BOARD <= SBD_MAX_UNITS_PER_BOARD); + ASSERT(MAX_MEM_UNITS_PER_BOARD <= SBD_MAX_UNITS_PER_BOARD); + + /* + * Allocate the devlist for cpus. + */ + sbp->sb_devlist[NIX(SBD_COMP_CPU)] = GETSTRUCT(dev_info_t *, + MAX_CPU_UNITS_PER_BOARD); + + /* + * Allocate the devlist for mem. + */ + sbp->sb_devlist[NIX(SBD_COMP_MEM)] = GETSTRUCT(dev_info_t *, + MAX_MEM_UNITS_PER_BOARD); + + /* + * Allocate the devlist for io. + */ + sbp->sb_devlist[NIX(SBD_COMP_IO)] = GETSTRUCT(dev_info_t *, + MAX_IO_UNITS_PER_BOARD); + + + sbp->sb_dev[NIX(SBD_COMP_CPU)] = GETSTRUCT(sbd_dev_unit_t, + MAX_CPU_UNITS_PER_BOARD); + + sbp->sb_dev[NIX(SBD_COMP_MEM)] = GETSTRUCT(sbd_dev_unit_t, + MAX_MEM_UNITS_PER_BOARD); + + sbp->sb_dev[NIX(SBD_COMP_IO)] = GETSTRUCT(sbd_dev_unit_t, + MAX_IO_UNITS_PER_BOARD); + + for (i = 0; i < MAX_CPU_UNITS_PER_BOARD; i++) { + sbp->sb_cpupath[i] = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + } + + for (i = 0; i < MAX_MEM_UNITS_PER_BOARD; i++) { + sbp->sb_mempath[i] = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + } + + for (i = 0; i < MAX_IO_UNITS_PER_BOARD; i++) { + sbp->sb_iopath[i] = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + } + + /* + * Walk the device tree, find all top dips on this board and + * hold the branches rooted at them + */ + ASSERT(sbp->sb_topdip); + pdip = ddi_get_parent(sbp->sb_topdip); + if (pdip) + ndi_devi_enter(pdip, &circ); + walk.sbp = sbp; + walk.hold = 1; + ddi_walk_devs(sbp->sb_topdip, hold_rele_branch, (void *)&walk); + if (pdip) + ndi_devi_exit(pdip, circ); + + /* + * Initialize the devlists + */ + if (sbd_init_devlists(sbp) == 0) { + SBD_BOARD_TRANSITION(sbp, SBD_STATE_EMPTY); + } else { + /* + * Couldn't have made it down here without + * having found at least one device. + */ + ASSERT(SBD_DEVS_PRESENT(sbp) != 0); + /* + * Check the state of any possible devices on the + * board. + */ + sbd_board_discovery(sbp); + + if (SBD_DEVS_UNATTACHED(sbp) == 0) { + /* + * The board has no unattached devices, therefore + * by reason of insanity it must be configured! + */ + SBD_BOARD_TRANSITION(sbp, SBD_STATE_CONFIGURED); + sbp->sb_cond = SBD_COND_OK; + } else if (SBD_DEVS_ATTACHED(sbp)) { + SBD_BOARD_TRANSITION(sbp, SBD_STATE_PARTIAL); + } else { + SBD_BOARD_TRANSITION(sbp, SBD_STATE_CONNECTED); + } + } +} + +static void +sbd_board_destroy(sbd_board_t *sbp) +{ + int i; + dev_info_t *pdip; + int circ; + walk_tree_t walk = {0}; + + SBD_BOARD_TRANSITION(sbp, SBD_STATE_EMPTY); + +#ifdef DEBUG + for (i = 0; i < MAX_MEM_UNITS_PER_BOARD; i++) { + sbd_mem_unit_t *mp; + + mp = SBD_GET_BOARD_MEMUNIT(sbp, i); + ASSERT(mp->sbm_mlist == NULL); + } +#endif /* DEBUG */ + + /* + * Free up MEM unit structs. + */ + FREESTRUCT(sbp->sb_dev[NIX(SBD_COMP_MEM)], + sbd_dev_unit_t, MAX_MEM_UNITS_PER_BOARD); + sbp->sb_dev[NIX(SBD_COMP_MEM)] = NULL; + + /* + * Free up CPU unit structs. + */ + FREESTRUCT(sbp->sb_dev[NIX(SBD_COMP_CPU)], + sbd_dev_unit_t, MAX_CPU_UNITS_PER_BOARD); + sbp->sb_dev[NIX(SBD_COMP_CPU)] = NULL; + + /* + * Free up IO unit structs. + */ + FREESTRUCT(sbp->sb_dev[NIX(SBD_COMP_IO)], + sbd_dev_unit_t, MAX_IO_UNITS_PER_BOARD); + sbp->sb_dev[NIX(SBD_COMP_IO)] = NULL; + + /* + * free up CPU devlists. + */ + + for (i = 0; i < MAX_CPU_UNITS_PER_BOARD; i++) { + kmem_free((caddr_t)sbp->sb_cpupath[i], MAXPATHLEN); + } + FREESTRUCT(sbp->sb_devlist[NIX(SBD_COMP_CPU)], dev_info_t *, + MAX_CPU_UNITS_PER_BOARD); + sbp->sb_devlist[NIX(SBD_COMP_CPU)] = NULL; + + /* + * free up MEM devlists. + */ + for (i = 0; i < MAX_MEM_UNITS_PER_BOARD; i++) { + kmem_free((caddr_t)sbp->sb_mempath[i], MAXPATHLEN); + } + FREESTRUCT(sbp->sb_devlist[NIX(SBD_COMP_MEM)], dev_info_t *, + MAX_MEM_UNITS_PER_BOARD); + sbp->sb_devlist[NIX(SBD_COMP_MEM)] = NULL; + + /* + * free up IO devlists. + */ + for (i = 0; i < MAX_IO_UNITS_PER_BOARD; i++) { + kmem_free((caddr_t)sbp->sb_iopath[i], MAXPATHLEN); + } + FREESTRUCT(sbp->sb_devlist[NIX(SBD_COMP_IO)], dev_info_t *, + MAX_IO_UNITS_PER_BOARD); + sbp->sb_devlist[NIX(SBD_COMP_IO)] = NULL; + + /* + * Release all branches held earlier + */ + ASSERT(sbp->sb_topdip); + pdip = ddi_get_parent(sbp->sb_topdip); + if (pdip) + ndi_devi_enter(pdip, &circ); + walk.sbp = sbp; + walk.hold = 0; + ddi_walk_devs(sbp->sb_topdip, hold_rele_branch, (void *)&walk); + if (pdip) + ndi_devi_exit(pdip, circ); + + mutex_destroy(&sbp->sb_slock); + mutex_destroy(&sbp->sb_flags_mutex); + mutex_destroy(&sbp->sb_mutex); +} + +sbd_comp_type_t +sbd_cm_type(char *name) +{ + sbd_comp_type_t type = SBD_COMP_UNKNOWN; + int i; + + /* look up type in table */ + for (i = 0; SBD_COMP(i) != SBD_COMP_UNKNOWN; i++) { + if (strcmp(name, SBD_OTYPE(i)) == 0) { + type = SBD_COMP(i); + break; + } + } + + return (type); +} + +/* + * There are certain cases where obp marks components as failed + * If the status is ok the node won't have any status property. It + * is only there if the status is other than ok. + * + * The translation is as follows: + * If there is no status prop, the the cond is SBD_COND_OK + * If we find a status prop but can't get to it then cond is SBD_COND_UNKNOWN + * if we find a stat and it is failed the cond is SBD_COND_FAILED + * If the stat is disabled, the cond is SBD_COND_UNUSABLE + * Otherwise we return con as SBD_COND_OK + */ +sbd_cond_t +sbd_get_comp_cond(dev_info_t *dip) +{ + int len; + char *status_buf; + static const char *status = "status"; + static const char *failed = "fail"; + static const char *disabled = "disabled"; + + if (dip == NULL) { + PR_BYP("dip is NULL\n"); + return (SBD_COND_UNKNOWN); + } + + if (ddi_getproplen(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + (char *)status, &len) != DDI_PROP_SUCCESS) { + PR_CPU("status in sbd is ok\n"); + return (SBD_COND_OK); + } + + status_buf = kmem_zalloc(sizeof (char) * OBP_MAXPROPNAME, KM_SLEEP); + if (ddi_getlongprop_buf(DDI_DEV_T_NONE, dip, DDI_PROP_DONTPASS, + (char *)status, status_buf, &len) != DDI_PROP_SUCCESS) { + PR_CPU("status in sbd is unknown\n"); + return (SBD_COND_UNKNOWN); + } + + if (strncmp(status_buf, failed, strlen(failed)) == 0) { + PR_CPU("status in sbd is failed\n"); + kmem_free(status_buf, sizeof (char) * OBP_MAXPROPNAME); + return (SBD_COND_FAILED); + } + + if (strcmp(status_buf, disabled) == 0) { + PR_CPU("status in sbd is unusable\n"); + kmem_free(status_buf, sizeof (char) * OBP_MAXPROPNAME); + return (SBD_COND_UNUSABLE); + } + + kmem_free(status_buf, sizeof (char) * OBP_MAXPROPNAME); + return (SBD_COND_OK); +} + +#ifdef SBD_DEBUG_ERRS + +/* function to simulate errors throughout the sbd code */ +void +sbd_inject_err(int error, sbderror_t *ep, int Errno, int ecode, + char *rsc) +{ + static fn_t f = "sbd_inject_err"; + + if (sbd_err_debug == 0) + return; + + if (ep == NULL) { + cmn_err(CE_WARN, "%s ep is NULL", f); + return; + } + + if (SBD_GET_ERRNO(ep) != 0) { + cmn_err(CE_WARN, "%s errno already set to %d", f, + SBD_GET_ERRNO(ep)); + return; + } + + if (SBD_GET_ERR(ep) != 0) { + cmn_err(CE_WARN, "%s code already set to %d", f, + SBD_GET_ERR(ep)); + return; + } + + if ((sbd_err_debug & (1 << error)) != 0) { + ep->e_errno = Errno; + ep->e_code = ecode; + + if (rsc != NULL) + bcopy((caddr_t)rsc, + (caddr_t)ep->e_rsc, + sizeof (ep->e_rsc)); + + if (Errno != 0) + PR_ERR_ERRNO("%s set errno to %d", f, ep->e_errno); + + if (ecode != 0) + PR_ERR_ECODE("%s set ecode to %d", f, ep->e_code); + + if (rsc != NULL) + PR_ERR_RSC("%s set rsc to %s", f, ep->e_rsc); + } +} +#endif |
