root/usr/src/uts/common/io/cardbus/cardbus_hp.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright (c)  * Copyright (c) 2001 Tadpole Technology plc
 * All rights reserved.
 * From "@(#)pcicfg.c   1.31    99/06/18 SMI"
 */

/*
 * Copyright 2023 Oxide Computer Company
 */

/*
 * Cardbus hotplug module
 */

#include <sys/open.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/ddi.h>
#include <sys/sunndi.h>

#include <sys/note.h>

#include <sys/pci.h>

#include <sys/hotplug/hpcsvc.h>
#include <sys/hotplug/pci/pcicfg.h>
#include <sys/pcic_reg.h>

#include "cardbus.h"
#include "cardbus_hp.h"
#include "cardbus_cfg.h"

/*
 * ************************************************************************
 * *** Implementation specific data structures/definitions.             ***
 * ************************************************************************
 */

#ifndef HPC_MAX_OCCUPANTS
#define HPC_MAX_OCCUPANTS 8
typedef struct hpc_occupant_info {
        int     i;
        char    *id[HPC_MAX_OCCUPANTS];
} hpc_occupant_info_t;
#endif

#define PCICFG_FLAGS_CONTINUE   0x1

#define PCICFG_OP_ONLINE        0x1
#define PCICFG_OP_OFFLINE       0x0

#define CBHP_DEVCTL_MINOR       255

#define AP_MINOR_NUM_TO_CB_INSTANCE(x)  ((x) & 0xFF)
#define AP_MINOR_NUM(x)         (((uint_t)(3) << 8) | ((x) & 0xFF))
#define AP_IS_CB_MINOR(x)       (((x)>>8) == (3))

extern int cardbus_debug;
extern int number_of_cardbus_cards;

static int cardbus_autocfg_enabled = 1; /* auto config is enabled by default */

/* static functions */
static int cardbus_event_handler(caddr_t slot_arg, uint_t event_mask);
static int cardbus_pci_control(caddr_t ops_arg, hpc_slot_t slot_hdl,
                                int request, caddr_t arg);
static int cardbus_new_slot_state(dev_info_t *dip, hpc_slot_t hdl,
                                hpc_slot_info_t *slot_info, int slot_state);
static int cardbus_list_occupants(dev_info_t *dip, void *hdl);
static void create_occupant_props(dev_info_t *self, dev_t dev);
static void delete_occupant_props(dev_info_t *dip, dev_t dev);
static int cardbus_configure_ap(cbus_t *cbp);
static int cardbus_unconfigure_ap(cbus_t *cbp);
static int cbus_unconfigure(dev_info_t *devi, int prim_bus);
void cardbus_dump_pci_config(dev_info_t *dip);
void cardbus_dump_pci_node(dev_info_t *dip);

int
cardbus_init_hotplug(cbus_t *cbp)
{
        char tbuf[MAXNAMELEN];
        hpc_slot_info_t slot_info;
        hpc_slot_ops_t  *slot_ops;
        hpc_slot_t      slhandle;       /* HPS slot handle */

        /*
         *  register the bus instance with the HPS framework.
         */
        if (hpc_nexus_register_bus(cbp->cb_dip,
            cardbus_new_slot_state, 0) != 0) {
                cmn_err(CE_WARN, "%s%d: failed to register the bus with HPS\n",
                    ddi_driver_name(cbp->cb_dip), cbp->cb_instance);
                return (DDI_FAILURE);
        }

        (void) sprintf(cbp->ap_id, "slot%d", cbp->cb_instance);
        (void) ddi_pathname(cbp->cb_dip, tbuf);
        cbp->nexus_path = kmem_alloc(strlen(tbuf) + 1, KM_SLEEP);
        (void) strcpy(cbp->nexus_path, tbuf);
        cardbus_err(cbp->cb_dip, 8,
            "cardbus_init_hotplug: nexus_path set to %s", cbp->nexus_path);

        slot_ops = hpc_alloc_slot_ops(KM_SLEEP);
        cbp->slot_ops = slot_ops;

        /*
         * Fill in the slot information structure that
         * describes the slot.
         */
        slot_info.version = HPC_SLOT_INFO_VERSION;
        slot_info.slot_type = HPC_SLOT_TYPE_PCI;
        slot_info.slot.pci.device_number = 0;
        slot_info.slot.pci.slot_capabilities = 0;

        (void) strcpy(slot_info.slot.pci.slot_logical_name, cbp->ap_id);

        slot_ops->hpc_version = HPC_SLOT_OPS_VERSION;
        slot_ops->hpc_op_connect = NULL;
        slot_ops->hpc_op_disconnect = NULL;
        slot_ops->hpc_op_insert = NULL;
        slot_ops->hpc_op_remove = NULL;
        slot_ops->hpc_op_control = cardbus_pci_control;

        if (hpc_slot_register(cbp->cb_dip, cbp->nexus_path, &slot_info,
            &slhandle, slot_ops, (caddr_t)cbp, 0) != 0) {
                /*
                 * If the slot can not be registered,
                 * then the slot_ops need to be freed.
                 */
                cmn_err(CE_WARN,
                    "cbp%d Unable to Register Slot %s", cbp->cb_instance,
                    slot_info.slot.pci.slot_logical_name);

                (void) hpc_nexus_unregister_bus(cbp->cb_dip);
                hpc_free_slot_ops(slot_ops);
                cbp->slot_ops = NULL;
                return (DDI_FAILURE);
        }

        ASSERT(slhandle == cbp->slot_handle);

        cardbus_err(cbp->cb_dip, 8,
            "cardbus_init_hotplug: slot_handle 0x%p", cbp->slot_handle);
        return (DDI_SUCCESS);
}

static int
cardbus_event_handler(caddr_t slot_arg, uint_t event_mask)
{
        int ap_minor = (int)((uintptr_t)slot_arg);
        cbus_t *cbp;
        int cb_instance;
        int rv = HPC_EVENT_CLAIMED;

        cb_instance = AP_MINOR_NUM_TO_CB_INSTANCE(ap_minor);

        ASSERT(cb_instance >= 0);
        cbp = (cbus_t *)ddi_get_soft_state(cardbus_state, cb_instance);
        mutex_enter(&cbp->cb_mutex);

        switch (event_mask) {

        case HPC_EVENT_SLOT_INSERTION:
                /*
                 * A card is inserted in the slot. Just report this
                 * event and return.
                 */
                cardbus_err(cbp->cb_dip, 7,
                    "cardbus_event_handler(%s%d): card is inserted",
                    ddi_driver_name(cbp->cb_dip), cbp->cb_instance);

                break;

        case HPC_EVENT_SLOT_CONFIGURE:
                /*
                 * Configure the occupant that is just inserted in the slot.
                 * The receptacle may or may not be in the connected state. If
                 * the receptacle is not connected and the auto configuration
                 * is enabled on this slot then connect the slot. If auto
                 * configuration is enabled then configure the card.
                 */
                if (!(cbp->auto_config)) {
                        /*
                         * auto configuration is disabled.
                         */
                        cardbus_err(cbp->cb_dip, 7,
                            "cardbus_event_handler(%s%d): "
                            "SLOT_CONFIGURE event occured (slot %s)",
                            ddi_driver_name(cbp->cb_dip), cbp->cb_instance,
                            cbp->name);

                        break;
                }

                cardbus_err(cbp->cb_dip, 7,
                    "cardbus_event_handler(%s%d): configure event",
                    ddi_driver_name(cbp->cb_dip), cbp->cb_instance);

                if (cbp->ostate != AP_OSTATE_UNCONFIGURED) {
                        cmn_err(CE_WARN, "!slot%d already configured\n",
                            cbp->cb_instance);
                        break;
                }

                /*
                 * Auto configuration is enabled. First, make sure the
                 * receptacle is in the CONNECTED state.
                 */
                if ((rv = hpc_nexus_connect(cbp->slot_handle,
                    NULL, 0)) == HPC_SUCCESS) {
                        cbp->rstate = AP_RSTATE_CONNECTED; /* record rstate */
                }

                if (cardbus_configure_ap(cbp) == HPC_SUCCESS)
                        create_occupant_props(cbp->cb_dip, makedevice(
                            ddi_driver_major((cbp->cb_dip)), ap_minor));
                else
                        rv = HPC_ERR_FAILED;

                break;

        case HPC_EVENT_SLOT_UNCONFIGURE:
                /*
                 * Unconfigure the occupant in this slot.
                 */
                if (!(cbp->auto_config)) {
                        /*
                         * auto configuration is disabled.
                         */
                        cardbus_err(cbp->cb_dip, 7,
                            "cardbus_event_handler(%s%d): "
                            "SLOT_UNCONFIGURE event"
                            " occured - auto-conf disabled (slot %s)",
                            ddi_driver_name(cbp->cb_dip), cbp->cb_instance,
                            cbp->name);

                        break;
                }

                cardbus_err(cbp->cb_dip, 7,
                    "cardbus_event_handler(%s%d): SLOT_UNCONFIGURE event"
                    " occured (slot %s)",
                    ddi_driver_name(cbp->cb_dip), cbp->cb_instance,
                    cbp->name);

                if (cardbus_unconfigure_ap(cbp) != HPC_SUCCESS)
                        rv = HPC_ERR_FAILED;

                DEVI(cbp->cb_dip)->devi_ops->devo_bus_ops = cbp->orig_bopsp;
                --number_of_cardbus_cards;
                break;

        case HPC_EVENT_SLOT_REMOVAL:
                /*
                 * Card is removed from the slot. The card must have been
                 * unconfigured before this event.
                 */
                if (cbp->ostate != AP_OSTATE_UNCONFIGURED) {
                        cardbus_err(cbp->cb_dip, 1,
                            "cardbus_event_handler(%s%d): "
                            "card is removed from"
                            " the slot %s before doing unconfigure!!",
                            ddi_driver_name(cbp->cb_dip), cbp->cb_instance,
                            cbp->name);

                        break;
                }

                cardbus_err(cbp->cb_dip, 7,
                    "cardbus_event_handler(%s%d): "
                    "card is removed from the slot %s",
                    ddi_driver_name(cbp->cb_dip), cbp->cb_instance,
                    cbp->name);

                break;

        case HPC_EVENT_SLOT_POWER_ON:
                /*
                 * Slot is connected to the bus. i.e the card is powered
                 * on.
                 */
                cardbus_err(cbp->cb_dip, 7,
                    "cardbus_event_handler(%s%d): "
                    "card is powered on in the slot %s",
                    ddi_driver_name(cbp->cb_dip), cbp->cb_instance,
                    cbp->name);

                cbp->rstate = AP_RSTATE_CONNECTED; /* record rstate */

                break;

        case HPC_EVENT_SLOT_POWER_OFF:
                /*
                 * Slot is disconnected from the bus. i.e the card is powered
                 * off.
                 */
                cardbus_err(cbp->cb_dip, 7,
                    "cardbus_event_handler(%s%d): "
                    "card is powered off in the slot %s",
                    ddi_driver_name(cbp->cb_dip), cbp->cb_instance,
                    cbp->name);

                cbp->rstate = AP_RSTATE_DISCONNECTED; /* record rstate */

                break;

        default:
                cardbus_err(cbp->cb_dip, 4,
                    "cardbus_event_handler(%s%d): "
                    "unknown event %x for this slot %s",
                    ddi_driver_name(cbp->cb_dip), cbp->cb_instance,
                    event_mask, cbp->name);

                break;
        }

        mutex_exit(&cbp->cb_mutex);

        return (rv);
}

static int
cardbus_pci_control(caddr_t ops_arg, hpc_slot_t slot_hdl, int request,
    caddr_t arg)
{
        cbus_t *cbp;
        int rval = HPC_SUCCESS;
        hpc_led_info_t *hpc_led_info;

        _NOTE(ARGUNUSED(slot_hdl))

        cbp = (cbus_t *)ops_arg;
        ASSERT(mutex_owned(&cbp->cb_mutex));

        switch (request) {

        case HPC_CTRL_GET_SLOT_STATE: {
                hpc_slot_state_t        *hpc_slot_state;

                hpc_slot_state = (hpc_slot_state_t *)arg;

                cardbus_err(cbp->cb_dip, 7,
                    "cardbus_pci_control() - "
                    "HPC_CTRL_GET_SLOT_STATE hpc_slot_state=0x%p",
                    (void *) hpc_slot_state);

                if (cbp->card_present)
                        *hpc_slot_state = HPC_SLOT_CONNECTED;
                else
                        *hpc_slot_state = HPC_SLOT_EMPTY;

                break;
        }

        case HPC_CTRL_GET_BOARD_TYPE: {
                hpc_board_type_t        *hpc_board_type;

                hpc_board_type = (hpc_board_type_t *)arg;

                cardbus_err(cbp->cb_dip, 7,
                    "cardbus_pci_control() - HPC_CTRL_GET_BOARD_TYPE");

                /*
                 * The HPC driver does not know what board type
                 * is plugged in.
                 */
                *hpc_board_type = HPC_BOARD_PCI_HOTPLUG;

                break;
        }

        case HPC_CTRL_DEV_CONFIGURED:
        case HPC_CTRL_DEV_UNCONFIGURED:
                cardbus_err(cbp->cb_dip, 5,
                    "cardbus_pci_control() - HPC_CTRL_DEV_%sCONFIGURED",
                    request == HPC_CTRL_DEV_UNCONFIGURED ? "UN" : "");
                break;

        case HPC_CTRL_GET_LED_STATE:
                hpc_led_info = (hpc_led_info_t *)arg;
                cardbus_err(cbp->cb_dip, 5,
                    "cardbus_pci_control() - HPC_CTRL_GET_LED_STATE "
                    "led %d is %d",
                    hpc_led_info->led, cbp->leds[hpc_led_info->led]);

                hpc_led_info->state = cbp->leds[hpc_led_info->led];
                break;

        case HPC_CTRL_SET_LED_STATE:
                hpc_led_info = (hpc_led_info_t *)arg;

                cardbus_err(cbp->cb_dip, 4,
                    "cardbus_pci_control() - HPC_CTRL_SET_LED_STATE "
                    "led %d to %d",
                    hpc_led_info->led, hpc_led_info->state);

                cbp->leds[hpc_led_info->led] = hpc_led_info->state;
                break;

        case HPC_CTRL_ENABLE_AUTOCFG:
                cardbus_err(cbp->cb_dip, 5,
                    "cardbus_pci_control() - HPC_CTRL_ENABLE_AUTOCFG");

                /*
                 * Cardbus ALWAYS does auto config, from the slots point of
                 * view this is turning on the card and making sure it's ok.
                 * This is all done by the bridge driver before we see any
                 * indication.
                 */
                break;

        case HPC_CTRL_DISABLE_AUTOCFG:
                cardbus_err(cbp->cb_dip, 5,
                    "cardbus_pci_control() - HPC_CTRL_DISABLE_AUTOCFG");
                break;

        case HPC_CTRL_DISABLE_ENUM:
        case HPC_CTRL_ENABLE_ENUM:
        default:
                rval = HPC_ERR_NOTSUPPORTED;
                break;
        }

        return (rval);
}

/*
 * cardbus_new_slot_state()
 *
 * This function is called by the HPS when it finds a hot plug
 * slot is added or being removed from the hot plug framework.
 * It returns 0 for success and HPC_ERR_FAILED for errors.
 */
static int
cardbus_new_slot_state(dev_info_t *dip, hpc_slot_t hdl,
    hpc_slot_info_t *slot_info, int slot_state)
{
        int cb_instance;
        cbus_t *cbp;
        int ap_minor;
        int rv = 0;

        cardbus_err(dip, 8,
            "cardbus_new_slot_state: slot_handle 0x%p", hdl);

        /*
         * get the soft state structure for the bus instance.
         */
        cb_instance = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
            DDI_PROP_DONTPASS, "cbus-instance", -1);
        ASSERT(cb_instance >= 0);
        cbp = (cbus_t *)ddi_get_soft_state(cardbus_state, cb_instance);

        mutex_enter(&cbp->cb_mutex);

        switch (slot_state) {

        case HPC_SLOT_ONLINE:
                /*
                 * Make sure the slot is not already ONLINE
                 */
                if (cbp->slot_handle != NULL) {
                        cardbus_err(dip, 4,
                            "cardbus_new_slot_state: "
                            "cardbus already ONLINE!!");
                        rv = HPC_ERR_FAILED;
                        break;
                }

                /*
                 * Add the hot plug slot to the bus.
                 */

                /* create the AP minor node */
                ap_minor = AP_MINOR_NUM(cb_instance);
                if (ddi_create_minor_node(dip, slot_info->pci_slot_name,
                    S_IFCHR, ap_minor,
                    DDI_NT_PCI_ATTACHMENT_POINT,
                    0) == DDI_FAILURE) {
                        cardbus_err(dip, 4,
                            "cardbus_new_slot_state: "
                            "ddi_create_minor_node failed");
                        rv = HPC_ERR_FAILED;
                        break;
                }

                /* save the slot handle */
                cbp->slot_handle = hdl;

                /* setup event handler for all hardware events on the slot */
                if (hpc_install_event_handler(hdl, -1, cardbus_event_handler,
                    (caddr_t)((long)ap_minor)) != 0) {
                        cardbus_err(dip, 4,
                            "cardbus_new_slot_state: "
                            "install event handler failed");
                        rv = HPC_ERR_FAILED;
                        break;
                }
                cbp->event_mask = (uint32_t)0xFFFFFFFF;
                create_occupant_props(dip,
                    makedevice(ddi_name_to_major(ddi_get_name(dip)),
                    ap_minor));

                /* set default auto configuration enabled flag for this slot */
                cbp->auto_config = cardbus_autocfg_enabled;

                /* copy the slot information */
                cbp->name = (char *)kmem_alloc(strlen(slot_info->pci_slot_name)
                    + 1, KM_SLEEP);
                (void) strcpy(cbp->name, slot_info->pci_slot_name);
                cardbus_err(cbp->cb_dip, 10,
                    "cardbus_new_slot_state: cbp->name set to %s", cbp->name);

                cardbus_err(dip, 4,
                    "Cardbus slot \"%s\" ONLINE\n", slot_info->pci_slot_name);

                cbp->ostate = AP_OSTATE_UNCONFIGURED;
                cbp->rstate = AP_RSTATE_EMPTY;

                break;

        case HPC_SLOT_OFFLINE:
                /*
                 * A hot plug slot is being removed from the bus.
                 * Make sure there is no occupant configured on the
                 * slot before removing the AP minor node.
                 */
                if (cbp->ostate != AP_OSTATE_UNCONFIGURED) {
                        cmn_err(CE_WARN,
                            "cardbus: Card is still in configured state");
                        rv = HPC_ERR_FAILED;
                        break;
                }

                /*
                 * If the AP device is in open state then return
                 * error.
                 */
                if (cbp->soft_state != PCIHP_SOFT_STATE_CLOSED) {
                        rv = HPC_ERR_FAILED;
                        break;
                }

                /* remove the minor node */
                ddi_remove_minor_node(dip, cbp->name);
                /* free up the memory for the name string */
                kmem_free(cbp->name, strlen(cbp->name) + 1);

                /* update the slot info data */
                cbp->name = NULL;
                cbp->slot_handle = NULL;

                cardbus_err(dip, 6,
                    "cardbus_new_slot_state: Cardbus slot OFFLINE");
                break;

        default:
                cmn_err(CE_WARN,
                    "cardbus_new_slot_state: unknown slot_state %d\n",
                    slot_state);
                rv = HPC_ERR_FAILED;
        }

        mutex_exit(&cbp->cb_mutex);

        return (rv);
}

static int
cardbus_list_occupants(dev_info_t *dip, void *hdl)
{
        hpc_occupant_info_t *occupant = (hpc_occupant_info_t *)hdl;
        char pn[MAXPATHLEN];

        /*
         * Ignore the attachment point and pcs.
         */
        if (strcmp(ddi_binding_name(dip), "pcs") == 0) {
                return (DDI_WALK_CONTINUE);
        }

        (void) ddi_pathname(dip, pn);

        occupant->id[occupant->i] = kmem_alloc(strlen(pn) + 1, KM_SLEEP);
        (void) strcpy(occupant->id[occupant->i], pn);

        occupant->i++;

        /*
         * continue the walk to the next sibling to look for a match
         * or to find other nodes if this card is a multi-function card.
         */
        return (DDI_WALK_PRUNECHILD);
}

static void
create_occupant_props(dev_info_t *self, dev_t dev)
{
        hpc_occupant_info_t occupant;
        int i;

        occupant.i = 0;

        ndi_devi_enter(self);
        ddi_walk_devs(ddi_get_child(self), cardbus_list_occupants,
            (void *)&occupant);
        ndi_devi_exit(self);

        if (occupant.i == 0) {
                char *c[] = { "" };
                cardbus_err(self, 1, "create_occupant_props: no occupant\n");
                (void) ddi_prop_update_string_array(dev, self, "pci-occupant",
                    c, 1);
        } else {
                cardbus_err(self, 1,
                    "create_occupant_props: %d occupant\n", occupant.i);
                (void) ddi_prop_update_string_array(dev, self, "pci-occupant",
                    occupant.id, occupant.i);
        }

        for (i = 0; i < occupant.i; i++) {
                kmem_free(occupant.id[i], strlen(occupant.id[i]) + 1);
        }
}

static void
delete_occupant_props(dev_info_t *dip, dev_t dev)
{
        if (ddi_prop_remove(dev, dip, "pci-occupant")
            != DDI_PROP_SUCCESS)
                return; /* add error handling */

}

/*
 * **************************************
 * CONFIGURE the occupant in the slot.
 * **************************************
 */
static int
cardbus_configure_ap(cbus_t *cbp)
{
        dev_info_t *self = cbp->cb_dip;
        int rv = HPC_SUCCESS;
        hpc_slot_state_t rstate;
        struct cardbus_config_ctrl ctrl;

        /*
         * check for valid request:
         *  1. It is a hotplug slot.
         *  2. The receptacle is in the CONNECTED state.
         */
        if (cbp->slot_handle == NULL || cbp->disabled) {
                return (ENXIO);
        }

        /*
         * If the occupant is already in (partially) configured
         * state then call the ndi_devi_online() on the device
         * subtree(s) for this attachment point.
         */

        if (cbp->ostate == AP_OSTATE_CONFIGURED) {
                ctrl.flags = PCICFG_FLAGS_CONTINUE;
                ctrl.busno = cardbus_primary_busno(self);
                ctrl.rv = NDI_SUCCESS;
                ctrl.dip = NULL;
                ctrl.op = PCICFG_OP_ONLINE;

                ndi_devi_enter(self);
                ddi_walk_devs(ddi_get_child(self),
                    cbus_configure, (void *)&ctrl);
                ndi_devi_exit(self);

                if (cardbus_debug) {
                        cardbus_dump_pci_config(self);
                        cardbus_dump_pci_node(self);
                }

                if (ctrl.rv != NDI_SUCCESS) {
                        /*
                         * one or more of the devices are not
                         * onlined.
                         */
                        cmn_err(CE_WARN, "cardbus(%s%d): failed to attach "
                            "one or more drivers for the card in the slot %s",
                            ddi_driver_name(self), cbp->cb_instance,
                            cbp->name);
                }

                /* tell HPC driver that the occupant is configured */
                (void) hpc_nexus_control(cbp->slot_handle,
                    HPC_CTRL_DEV_CONFIGURED, NULL);
                return (rv);
        }

        /*
         * Occupant is in the UNCONFIGURED state.
         */

        /* Check if the receptacle is in the CONNECTED state. */
        if (hpc_nexus_control(cbp->slot_handle,
            HPC_CTRL_GET_SLOT_STATE, (caddr_t)&rstate) != 0) {
                return (ENXIO);
        }

        if (rstate != HPC_SLOT_CONNECTED) {
                /* error. either the slot is empty or connect failed */
                return (ENXIO);
        }

        cbp->rstate = AP_RSTATE_CONNECTED; /* record rstate */

        /*
         * Call the configurator to configure the card.
         */
        if (cardbus_configure(cbp) != PCICFG_SUCCESS) {
                return (EIO);
        }

        /* record the occupant state as CONFIGURED */
        cbp->ostate = AP_OSTATE_CONFIGURED;
        cbp->condition = AP_COND_OK;

        /* now, online all the devices in the AP */
        ctrl.flags = PCICFG_FLAGS_CONTINUE;
        ctrl.busno = cardbus_primary_busno(self);
        ctrl.rv = NDI_SUCCESS;
        ctrl.dip = NULL;
        ctrl.op = PCICFG_OP_ONLINE;

        ndi_devi_enter(self);
        ddi_walk_devs(ddi_get_child(self), cbus_configure, (void *)&ctrl);
        ndi_devi_exit(self);

        if (cardbus_debug) {
                cardbus_dump_pci_config(self);
                cardbus_dump_pci_node(self);
        }
        if (ctrl.rv != NDI_SUCCESS) {
                /*
                 * one or more of the devices are not
                 * ONLINE'd.
                 */
                cmn_err(CE_WARN, "cbhp (%s%d): failed to attach one or"
                    " more drivers for the card in the slot %s",
                    ddi_driver_name(cbp->cb_dip),
                    cbp->cb_instance, cbp->name);
                /* rv = EFAULT; */
        }

        /* tell HPC driver that the occupant is configured */
        (void) hpc_nexus_control(cbp->slot_handle,
            HPC_CTRL_DEV_CONFIGURED, NULL);

        return (rv);
}

/*
 * **************************************
 * UNCONFIGURE the occupant in the slot.
 * **************************************
 */
static int
cardbus_unconfigure_ap(cbus_t *cbp)
{
        dev_info_t *self = cbp->cb_dip;
        int rv = HPC_SUCCESS, nrv;

        /*
         * check for valid request:
         *  1. It is a hotplug slot.
         *  2. The occupant is in the CONFIGURED state.
         */

        if (cbp->slot_handle == NULL || cbp->disabled) {
                return (ENXIO);
        }

        /*
         * If the occupant is in the CONFIGURED state then
         * call the configurator to unconfigure the slot.
         */
        if (cbp->ostate == AP_OSTATE_CONFIGURED) {
                /*
                 * Detach all the drivers for the devices in the
                 * slot.
                 */
                nrv = cardbus_unconfigure_node(self,
                    cardbus_primary_busno(self),
                    B_TRUE);

                if (nrv != NDI_SUCCESS) {
                        /*
                         * Failed to detach one or more drivers.
                         * Restore the status for the drivers
                         * which are offlined during this step.
                         */
                        cmn_err(CE_WARN,
                            "cbhp (%s%d): Failed to offline all devices"
                            " (slot %s)", ddi_driver_name(cbp->cb_dip),
                            cbp->cb_instance, cbp->name);
                        rv = EBUSY;
                } else {

                        if (cardbus_unconfigure(cbp) == PCICFG_SUCCESS) {
                                /*
                                 * Now that resources are freed,
                                 * clear EXT and Turn LED ON.
                                 */
                                cbp->ostate = AP_OSTATE_UNCONFIGURED;
                                cbp->condition = AP_COND_UNKNOWN;
                                /*
                                 * send the notification of state change
                                 * to the HPC driver.
                                 */
                                (void) hpc_nexus_control(cbp->slot_handle,
                                    HPC_CTRL_DEV_UNCONFIGURED, NULL);
                        } else {
                                rv = EIO;
                        }
                }
        }

        return (rv);
}

int
cbus_configure(dev_info_t *dip, void *hdl)
{
        pci_regspec_t *pci_rp;
        int length, rc;
        struct cardbus_config_ctrl *ctrl = (struct cardbus_config_ctrl *)hdl;
        uint8_t bus, device, function;

        /*
         * Ignore the attachment point and pcs.
         */
        if (strcmp(ddi_binding_name(dip), "hp_attachment") == 0 ||
            strcmp(ddi_binding_name(dip), "pcs") == 0) {
                cardbus_err(dip, 8, "cbus_configure: Ignoring\n");
                return (DDI_WALK_CONTINUE);
        }

        cardbus_err(dip, 6, "cbus_configure\n");

        ASSERT(ctrl->op == PCICFG_OP_ONLINE);

        /*
         * Get the PCI device number information from the devinfo
         * node. Since the node may not have the address field
         * setup (this is done in the DDI_INITCHILD of the parent)
         * we look up the 'reg' property to decode that information.
         */
        if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip,
            DDI_PROP_DONTPASS, "reg", (int **)&pci_rp,
            (uint_t *)&length) != DDI_PROP_SUCCESS) {
                /* Porbably not a real device, like PCS for example */
                if (ddi_get_child(dip) == NULL)
                        return (DDI_WALK_PRUNECHILD);

                cardbus_err(dip, 1, "cubs_configure: Don't configure device\n");
                ctrl->rv = DDI_FAILURE;
                ctrl->dip = dip;
                return (DDI_WALK_TERMINATE);
        }

        if (pci_rp->pci_phys_hi == 0)
                return (DDI_WALK_CONTINUE);

        /* get the pci device id information */
        bus = PCI_REG_BUS_G(pci_rp->pci_phys_hi);
        device = PCI_REG_DEV_G(pci_rp->pci_phys_hi);
        function = PCI_REG_FUNC_G(pci_rp->pci_phys_hi);

        /*
         * free the memory allocated by ddi_prop_lookup_int_array
         */
        ddi_prop_free(pci_rp);

        if (bus <= ctrl->busno)
                return (DDI_WALK_CONTINUE);

        cardbus_err(dip, 8,
            "cbus_configure on-line device at: "
            "[0x%x][0x%x][0x%x]\n", bus, device, function);

        rc = ndi_devi_online(dip, NDI_ONLINE_ATTACH|NDI_CONFIG);

        cardbus_err(dip, 7,
            "cbus_configure %s\n",
            rc == NDI_SUCCESS ? "Success": "Failure");

        if (rc != NDI_SUCCESS)
                return (DDI_WALK_PRUNECHILD);

        return (DDI_WALK_CONTINUE);
}

int
cardbus_unconfigure_node(dev_info_t *dip, int prim_bus, boolean_t top_bridge)
{
        dev_info_t *child, *next;

        cardbus_err(dip, 6, "cardbus_unconfigure_node\n");

        /*
         * Ignore pcs.
         */
        if (strcmp(ddi_binding_name(dip), "pcs") == 0) {
                cardbus_err(dip, 8, "cardbus_unconfigure_node: Ignoring\n");
                return (NDI_SUCCESS);
        }

        /*
         * bottom up off-line
         */
        for (child = ddi_get_child(dip); child; child = next) {
                int rc;
                next = ddi_get_next_sibling(child);
                rc = cardbus_unconfigure_node(child, prim_bus, B_FALSE);
                if (rc != NDI_SUCCESS)
                        return (rc);
        }

        /*
         * Don't unconfigure the bridge itself.
         */
        if (top_bridge)
                return (NDI_SUCCESS);

        if (cbus_unconfigure(dip, prim_bus) != NDI_SUCCESS) {
                cardbus_err(dip, 1,
                    "cardbus_unconfigure_node: cardbus_unconfigure failed\n");
                return (NDI_FAILURE);
        }
        return (NDI_SUCCESS);
}

/*
 * This will turn  resources allocated by cbus_configure()
 * and remove the device tree from the attachment point
 * and below.  The routine assumes the devices have their
 * drivers detached.
 */
static int
cbus_unconfigure(dev_info_t *devi, int prim_bus)
{
        pci_regspec_t *pci_rp;
        uint_t bus, device, func, length;
        int ndi_flags = NDI_UNCONFIG;

        if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, devi,
            DDI_PROP_DONTPASS, "reg", (int **)&pci_rp,
            &length) != DDI_PROP_SUCCESS) {
                /*
                 * This cannot be one of our devices. If it's something like a
                 * SCSI device then the attempt to offline the HBA
                 * (which probably is one of our devices)
                 * will also do bottom up offlining. That
                 * will fail if this device is busy. So always
                 * return success here
                 * so that the walk will continue.
                 */
                return (NDI_SUCCESS);
        }

        if (pci_rp->pci_phys_hi == 0)
                return (NDI_FAILURE);

        bus = PCI_REG_BUS_G(pci_rp->pci_phys_hi);

        if (bus <= prim_bus)
                return (NDI_SUCCESS);

        device = PCI_REG_DEV_G(pci_rp->pci_phys_hi);
        func = PCI_REG_FUNC_G(pci_rp->pci_phys_hi);
        ddi_prop_free(pci_rp);

        cardbus_err(devi, 8,
            "cbus_unconfigure: "
            "offline bus [0x%x] device [0x%x] function [%x]\n",
            bus, device, func);
        if (ndi_devi_offline(devi, ndi_flags) != NDI_SUCCESS) {
                cardbus_err(devi, 1,
                    "Device [0x%x] function [%x] is busy\n", device, func);
                return (NDI_FAILURE);
        }

        cardbus_err(devi, 9,
            "Tearing down device [0x%x] function [0x%x]\n", device, func);

        if (cardbus_teardown_device(devi) != PCICFG_SUCCESS) {
                cardbus_err(devi, 1,
                    "Failed to tear down "
                    "device [0x%x] function [0x%x]\n", device, func);
                return (NDI_FAILURE);
        }

        return (NDI_SUCCESS);
}

boolean_t
cardbus_is_cb_minor(dev_t dev)
{
        return (AP_IS_CB_MINOR(getminor(dev)) ? B_TRUE : B_FALSE);
}

int
cardbus_open(dev_t *devp, int flags, int otyp, cred_t *credp)
{
        cbus_t *cbp;
        int minor;

        _NOTE(ARGUNUSED(credp))

        minor = getminor(*devp);

        /*
         * Make sure the open is for the right file type.
         */
        if (otyp != OTYP_CHR)
        return (EINVAL);

        /*
         * Get the soft state structure for the 'devctl' device.
         */
        cbp = (cbus_t *)ddi_get_soft_state(cardbus_state,
            AP_MINOR_NUM_TO_CB_INSTANCE(minor));
        if (cbp == NULL)
                return (ENXIO);

        mutex_enter(&cbp->cb_mutex);

        /*
         * Handle the open by tracking the device state.
         *
         * Note: Needs review w.r.t exclusive access to AP or the bus.
         * Currently in the pci plug-in we don't use EXCL open at all
         * so the code below implements EXCL access on the bus.
         */

        /* enforce exclusive access to the bus */
        if ((cbp->soft_state == PCIHP_SOFT_STATE_OPEN_EXCL) ||
            ((flags & FEXCL) &&
            (cbp->soft_state != PCIHP_SOFT_STATE_CLOSED))) {
                mutex_exit(&cbp->cb_mutex);
                return (EBUSY);
        }

        if (flags & FEXCL)
                cbp->soft_state = PCIHP_SOFT_STATE_OPEN_EXCL;
        else
                cbp->soft_state = PCIHP_SOFT_STATE_OPEN;

        mutex_exit(&cbp->cb_mutex);
        return (0);
}

/*ARGSUSED*/
int
cardbus_close(dev_t dev, int flags, int otyp, cred_t *credp)
{
        cbus_t *cbp;
        int minor;

        _NOTE(ARGUNUSED(credp))

        minor = getminor(dev);

        if (otyp != OTYP_CHR)
                return (EINVAL);

        cbp = (cbus_t *)ddi_get_soft_state(cardbus_state,
            AP_MINOR_NUM_TO_CB_INSTANCE(minor));
        if (cbp == NULL)
                return (ENXIO);

        mutex_enter(&cbp->cb_mutex);
        cbp->soft_state = PCIHP_SOFT_STATE_CLOSED;
        mutex_exit(&cbp->cb_mutex);
        return (0);
}

/*
 * cardbus_ioctl: devctl hotplug controls
 */
/*ARGSUSED*/
int
cardbus_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
    int *rvalp)
{
        cbus_t *cbp;
        dev_info_t *self;
        dev_info_t *child_dip = NULL;
        struct devctl_iocdata *dcp;
        uint_t bus_state;
        int rv = 0;
        int nrv = 0;
        int ap_minor;
        hpc_slot_state_t rstate;
        devctl_ap_state_t ap_state;
        struct hpc_control_data hpc_ctrldata;
        struct hpc_led_info led_info;

        _NOTE(ARGUNUSED(credp))

        ap_minor = getminor(dev);
        cbp = (cbus_t *)ddi_get_soft_state(cardbus_state,
            AP_MINOR_NUM_TO_CB_INSTANCE(ap_minor));
        if (cbp == NULL)
                return (ENXIO);

        self = cbp->cb_dip;
        /*
         * read devctl ioctl data
         */
        if ((cmd != DEVCTL_AP_CONTROL) && ndi_dc_allochdl((void *)arg,
            &dcp) != NDI_SUCCESS)
                return (EFAULT);

#ifdef CARDBUS_DEBUG
{
        char *cmd_name;

        switch (cmd) {
        case DEVCTL_DEVICE_GETSTATE: cmd_name = "DEVCTL_DEVICE_GETSTATE"; break;
        case DEVCTL_DEVICE_ONLINE: cmd_name = "DEVCTL_DEVICE_ONLINE"; break;
        case DEVCTL_DEVICE_OFFLINE: cmd_name = "DEVCTL_DEVICE_OFFLINE"; break;
        case DEVCTL_DEVICE_RESET: cmd_name = "DEVCTL_DEVICE_RESET"; break;
        case DEVCTL_BUS_QUIESCE: cmd_name = "DEVCTL_BUS_QUIESCE"; break;
        case DEVCTL_BUS_UNQUIESCE: cmd_name = "DEVCTL_BUS_UNQUIESCE"; break;
        case DEVCTL_BUS_RESET: cmd_name = "DEVCTL_BUS_RESET"; break;
        case DEVCTL_BUS_RESETALL: cmd_name = "DEVCTL_BUS_RESETALL"; break;
        case DEVCTL_BUS_GETSTATE: cmd_name = "DEVCTL_BUS_GETSTATE"; break;
        case DEVCTL_AP_CONNECT: cmd_name = "DEVCTL_AP_CONNECT"; break;
        case DEVCTL_AP_DISCONNECT: cmd_name = "DEVCTL_AP_DISCONNECT"; break;
        case DEVCTL_AP_INSERT: cmd_name = "DEVCTL_AP_INSERT"; break;
        case DEVCTL_AP_REMOVE: cmd_name = "DEVCTL_AP_REMOVE"; break;
        case DEVCTL_AP_CONFIGURE: cmd_name = "DEVCTL_AP_CONFIGURE"; break;
        case DEVCTL_AP_UNCONFIGURE: cmd_name = "DEVCTL_AP_UNCONFIGURE"; break;
        case DEVCTL_AP_GETSTATE: cmd_name = "DEVCTL_AP_GETSTATE"; break;
        case DEVCTL_AP_CONTROL: cmd_name = "DEVCTL_AP_CONTROL"; break;
        default: cmd_name = "Unknown"; break;
        }
        cardbus_err(cbp->cb_dip, 7,
            "cardbus_ioctl: cmd = 0x%x, \"%s\"", cmd, cmd_name);
}
#endif

        switch (cmd) {
        case DEVCTL_DEVICE_GETSTATE:
        case DEVCTL_DEVICE_ONLINE:
        case DEVCTL_DEVICE_OFFLINE:
        case DEVCTL_BUS_GETSTATE:
                rv = ndi_devctl_ioctl(self, cmd, arg, mode, 0);
                ndi_dc_freehdl(dcp);
                return (rv);
        default:
                break;
        }

        switch (cmd) {
        case DEVCTL_DEVICE_RESET:
                rv = ENOTSUP;
                break;

        case DEVCTL_BUS_QUIESCE:
                if (ndi_get_bus_state(self, &bus_state) == NDI_SUCCESS)
                        if (bus_state == BUS_QUIESCED)
                                break;
                (void) ndi_set_bus_state(self, BUS_QUIESCED);
                break;

        case DEVCTL_BUS_UNQUIESCE:
                if (ndi_get_bus_state(self, &bus_state) == NDI_SUCCESS)
                        if (bus_state == BUS_ACTIVE)
                                break;
                (void) ndi_set_bus_state(self, BUS_ACTIVE);
                break;

        case DEVCTL_BUS_RESET:
                rv = ENOTSUP;
                break;

        case DEVCTL_BUS_RESETALL:
                rv = ENOTSUP;
                break;

        case DEVCTL_AP_CONNECT:
        case DEVCTL_AP_DISCONNECT:
                /*
                 * CONNECT(DISCONNECT) the hot plug slot to(from) the bus.
                 */
        case DEVCTL_AP_INSERT:
        case DEVCTL_AP_REMOVE:
                /*
                 * Prepare the slot for INSERT/REMOVE operation.
                 */

                /*
                 * check for valid request:
                 *      1. It is a hotplug slot.
                 *      2. The slot has no occupant that is in
                 *      the 'configured' state.
                 *
                 * The lower 8 bits of the minor number is the PCI
                 * device number for the slot.
                 */
                if ((cbp->slot_handle == NULL) || cbp->disabled) {
                        rv = ENXIO;
                        break;
                }

                /* the slot occupant must be in the UNCONFIGURED state */
                if (cbp->ostate != AP_OSTATE_UNCONFIGURED) {
                        rv = EINVAL;
                        break;
                }

                /*
                 * Call the HPC driver to perform the operation on the slot.
                 */
                mutex_enter(&cbp->cb_mutex);
                switch (cmd) {
                case DEVCTL_AP_INSERT:
                        rv = hpc_nexus_insert(cbp->slot_handle, NULL, 0);
                        break;
                case DEVCTL_AP_REMOVE:
                        rv = hpc_nexus_remove(cbp->slot_handle, NULL, 0);
                        break;
                case DEVCTL_AP_CONNECT:
                        if ((rv = hpc_nexus_connect(cbp->slot_handle,
                            NULL, 0)) == 0)
                                cbp->rstate = AP_RSTATE_CONNECTED;
                        break;
                case DEVCTL_AP_DISCONNECT:
                        if ((rv = hpc_nexus_disconnect(cbp->slot_handle,
                            NULL, 0)) == 0)
                                cbp->rstate = AP_RSTATE_DISCONNECTED;
                        break;
                }
                mutex_exit(&cbp->cb_mutex);

                switch (rv) {
                case HPC_ERR_INVALID:
                        rv = ENXIO;
                        break;
                case HPC_ERR_NOTSUPPORTED:
                        rv = ENOTSUP;
                        break;
                case HPC_ERR_FAILED:
                        rv = EIO;
                        break;
                }

                break;

        case DEVCTL_AP_CONFIGURE:
                /*
                 * **************************************
                 * CONFIGURE the occupant in the slot.
                 * **************************************
                 */

                mutex_enter(&cbp->cb_mutex);
                if ((nrv = cardbus_configure_ap(cbp)) == HPC_SUCCESS) {
                        create_occupant_props(cbp->cb_dip, dev);
                } else
                        rv = nrv;
                mutex_exit(&cbp->cb_mutex);
                break;

        case DEVCTL_AP_UNCONFIGURE:
                /*
                 * **************************************
                 * UNCONFIGURE the occupant in the slot.
                 * **************************************
                 */

                mutex_enter(&cbp->cb_mutex);
                if ((nrv = cardbus_unconfigure_ap(cbp)) == HPC_SUCCESS) {
                        delete_occupant_props(cbp->cb_dip, dev);
                } else
                        rv = nrv;
                mutex_exit(&cbp->cb_mutex);
                break;

        case DEVCTL_AP_GETSTATE:
            {
                int mutex_held;

                /*
                 * return the state of Attachment Point.
                 *
                 * If the occupant is in UNCONFIGURED state then
                 * we should get the receptacle state from the
                 * HPC driver because the receptacle state
                 * maintained in the nexus may not be accurate.
                 */

                /*
                 * check for valid request:
                 *      1. It is a hotplug slot.
                 */
                if (cbp->slot_handle == NULL) {
                        rv = ENXIO;
                        break;
                }

                /* try to acquire the slot mutex */
                mutex_held = mutex_tryenter(&cbp->cb_mutex);

                if (cbp->ostate == AP_OSTATE_UNCONFIGURED) {
                        if (hpc_nexus_control(cbp->slot_handle,
                            HPC_CTRL_GET_SLOT_STATE,
                            (caddr_t)&rstate) != 0) {
                                rv = ENXIO;
                                if (mutex_held)
                                        mutex_exit(&cbp->cb_mutex);
                                break;
                        }
                        cbp->rstate = (ap_rstate_t)rstate;
                }

                ap_state.ap_rstate = cbp->rstate;
                ap_state.ap_ostate = cbp->ostate;
                ap_state.ap_condition = cbp->condition;
                ap_state.ap_last_change = 0;
                ap_state.ap_error_code = 0;
                if (mutex_held)
                        ap_state.ap_in_transition = 0; /* AP is not busy */
                else
                        ap_state.ap_in_transition = 1; /* AP is busy */

                if (mutex_held)
                        mutex_exit(&cbp->cb_mutex);

                /* copy the return-AP-state information to the user space */
                if (ndi_dc_return_ap_state(&ap_state, dcp) != NDI_SUCCESS)
                        rv = ENXIO;

                break;

            }

        case DEVCTL_AP_CONTROL:
                /*
                 * HPC control functions:
                 *      HPC_CTRL_ENABLE_SLOT/HPC_CTRL_DISABLE_SLOT
                 *              Changes the state of the slot and preserves
                 *              the state across the reboot.
                 *      HPC_CTRL_ENABLE_AUTOCFG/HPC_CTRL_DISABLE_AUTOCFG
                 *              Enables or disables the auto configuration
                 *              of hot plugged occupant if the hardware
                 *              supports notification of the hot plug
                 *              events.
                 *      HPC_CTRL_GET_LED_STATE/HPC_CTRL_SET_LED_STATE
                 *              Controls the state of an LED.
                 *      HPC_CTRL_GET_SLOT_INFO
                 *              Get slot information data structure
                 *              (hpc_slot_info_t).
                 *      HPC_CTRL_GET_BOARD_TYPE
                 *              Get board type information (hpc_board_type_t).
                 *      HPC_CTRL_GET_CARD_INFO
                 *              Get card information (hpc_card_info_t).
                 *
                 * These control functions are used by the cfgadm plug-in
                 * to implement "-x" and "-v" options.
                 */

                /* copy user ioctl data first */
#ifdef _MULTI_DATAMODEL
                if (ddi_model_convert_from(mode & FMODELS) == DDI_MODEL_ILP32) {
                        struct hpc_control32_data hpc_ctrldata32;

                        if (copyin((void *)arg, (void *)&hpc_ctrldata32,
                            sizeof (struct hpc_control32_data)) != 0) {
                                rv = EFAULT;
                                break;
                        }
                        hpc_ctrldata.cmd = hpc_ctrldata32.cmd;
                        hpc_ctrldata.data =
                            (void *)(intptr_t)hpc_ctrldata32.data;
                }
#else
                if (copyin((void *)arg, (void *)&hpc_ctrldata,
                    sizeof (struct hpc_control_data)) != 0) {
                        rv = EFAULT;
                        break;
                }
#endif

#ifdef CARDBUS_DEBUG
{
                char *hpc_name;
                switch (hpc_ctrldata.cmd) {
                case HPC_CTRL_GET_LED_STATE:
                        hpc_name = "HPC_CTRL_GET_LED_STATE";
                        break;
                case HPC_CTRL_SET_LED_STATE:
                        hpc_name = "HPC_CTRL_SET_LED_STATE";
                        break;
                case HPC_CTRL_ENABLE_SLOT:
                        hpc_name = "HPC_CTRL_ENABLE_SLOT";
                        break;
                case HPC_CTRL_DISABLE_SLOT:
                        hpc_name = "HPC_CTRL_DISABLE_SLOT";
                        break;
                case HPC_CTRL_ENABLE_AUTOCFG:
                        hpc_name = "HPC_CTRL_ENABLE_AUTOCFG";
                        break;
                case HPC_CTRL_DISABLE_AUTOCFG:
                        hpc_name = "HPC_CTRL_DISABLE_AUTOCFG";
                        break;
                case HPC_CTRL_GET_BOARD_TYPE:
                        hpc_name = "HPC_CTRL_GET_BOARD_TYPE";
                        break;
                case HPC_CTRL_GET_SLOT_INFO:
                        hpc_name = "HPC_CTRL_GET_SLOT_INFO";
                        break;
                case HPC_CTRL_GET_CARD_INFO:
                        hpc_name = "HPC_CTRL_GET_CARD_INFO";
                        break;
                default: hpc_name = "Unknown"; break;
                }
                cardbus_err(cbp->cb_dip, 7,
                    "cardbus_ioctl: HP Control cmd 0x%x - \"%s\"",
                    hpc_ctrldata.cmd, hpc_name);
}
#endif
                /*
                 * check for valid request:
                 *      1. It is a hotplug slot.
                 */
                if (cbp->slot_handle == NULL) {
                        rv = ENXIO;
                        break;
                }

                mutex_enter(&cbp->cb_mutex);
                switch (hpc_ctrldata.cmd) {
                case HPC_CTRL_GET_LED_STATE:
                        /* copy the led info from the user space */
                        if (copyin(hpc_ctrldata.data, (void *)&led_info,
                            sizeof (hpc_led_info_t)) != 0) {
                                rv = ENXIO;
                                break;
                        }

                        /* get the state of LED information */
                        if (hpc_nexus_control(cbp->slot_handle,
                            HPC_CTRL_GET_LED_STATE,
                            (caddr_t)&led_info) != 0) {
                                rv = ENXIO;
                                break;
                        }

                        /* copy the led info to the user space */
                        if (copyout((void *)&led_info, hpc_ctrldata.data,
                            sizeof (hpc_led_info_t)) != 0) {
                                rv = ENXIO;
                                break;
                        }
                        break;

                case HPC_CTRL_SET_LED_STATE:
                        /* copy the led info from the user space */
                        if (copyin(hpc_ctrldata.data, (void *)&led_info,
                            sizeof (hpc_led_info_t)) != 0) {
                                rv = ENXIO;
                                break;
                        }

                        /* set the state of an LED */
                        if (hpc_nexus_control(cbp->slot_handle,
                            HPC_CTRL_SET_LED_STATE,
                            (caddr_t)&led_info) != 0) {
                                rv = ENXIO;
                                break;
                        }

                        break;

                case HPC_CTRL_ENABLE_SLOT:
                        /*
                         * Enable the slot for hotplug operations.
                         */
                        cbp->disabled = B_FALSE;

                        /* tell the HPC driver also */
                        (void) hpc_nexus_control(cbp->slot_handle,
                                HPC_CTRL_ENABLE_SLOT, NULL);

                        break;

                case HPC_CTRL_DISABLE_SLOT:
                        /*
                         * Disable the slot for hotplug operations.
                         */
                        cbp->disabled = B_TRUE;

                        /* tell the HPC driver also */
                        (void) hpc_nexus_control(cbp->slot_handle,
                                HPC_CTRL_DISABLE_SLOT, NULL);

                        break;

                case HPC_CTRL_ENABLE_AUTOCFG:
                        /*
                         * Enable auto configuration on this slot.
                         */
                        cbp->auto_config = B_TRUE;

                        /* tell the HPC driver also */
                        (void) hpc_nexus_control(cbp->slot_handle,
                                HPC_CTRL_ENABLE_AUTOCFG, NULL);
                        break;

                case HPC_CTRL_DISABLE_AUTOCFG:
                        /*
                         * Disable auto configuration on this slot.
                         */
                        cbp->auto_config = B_FALSE;

                        /* tell the HPC driver also */
                        (void) hpc_nexus_control(cbp->slot_handle,
                                HPC_CTRL_DISABLE_AUTOCFG, NULL);

                        break;

                case HPC_CTRL_GET_BOARD_TYPE:
                    {
                        hpc_board_type_t board_type;

                        /*
                         * Get board type data structure, hpc_board_type_t.
                         */
                        if (hpc_nexus_control(cbp->slot_handle,
                            HPC_CTRL_GET_BOARD_TYPE,
                            (caddr_t)&board_type) != 0) {
                                rv = ENXIO;
                                break;
                        }

                        /* copy the board type info to the user space */
                        if (copyout((void *)&board_type, hpc_ctrldata.data,
                            sizeof (hpc_board_type_t)) != 0) {
                                rv = ENXIO;
                                break;
                        }

                        break;
                    }

                case HPC_CTRL_GET_SLOT_INFO:
                    {
                        hpc_slot_info_t slot_info;

                        /*
                         * Get slot information structure, hpc_slot_info_t.
                         */
                        slot_info.version = HPC_SLOT_INFO_VERSION;
                        slot_info.slot_type = 0;
                        slot_info.pci_slot_capabilities = 0;
                        slot_info.pci_dev_num =
                                (uint16_t)AP_MINOR_NUM_TO_CB_INSTANCE(ap_minor);
                        (void) strcpy(slot_info.pci_slot_name, cbp->name);

                        /* copy the slot info structure to the user space */
                        if (copyout((void *)&slot_info, hpc_ctrldata.data,
                            sizeof (hpc_slot_info_t)) != 0) {
                                rv = ENXIO;
                                break;
                        }

                        break;
                    }

                case HPC_CTRL_GET_CARD_INFO:
                    {
                        hpc_card_info_t card_info;
                        ddi_acc_handle_t handle;

                        /*
                         * Get card information structure, hpc_card_info_t.
                         */

                        if (cbp->card_present == B_FALSE) {
                                rv = ENXIO;
                                break;
                        }
                        /* verify that the card is configured */
                        if (cbp->ostate != AP_OSTATE_CONFIGURED) {
                                /* either the card is not present or */
                                /* it is not configured. */
                                rv = ENXIO;
                                break;
                        }

                        /* get the information from the PCI config header */
                        /* for the function 0. */
                        for (child_dip = ddi_get_child(cbp->cb_dip); child_dip;
                            child_dip = ddi_get_next_sibling(child_dip))
                                if (strcmp("pcs", ddi_get_name(child_dip)))
                                        break;

                        if (!child_dip) {
                                rv = ENXIO;
                                break;
                        }

                        if (pci_config_setup(child_dip, &handle)
                            != DDI_SUCCESS) {
                                rv = EIO;
                                break;
                        }
                        card_info.prog_class = pci_config_get8(handle,
                                                        PCI_CONF_PROGCLASS);
                        card_info.base_class = pci_config_get8(handle,
                                                        PCI_CONF_BASCLASS);
                        card_info.sub_class = pci_config_get8(handle,
                                                        PCI_CONF_SUBCLASS);
                        card_info.header_type = pci_config_get8(handle,
                                                        PCI_CONF_HEADER);
                        pci_config_teardown(&handle);

                        /* copy the card info structure to the user space */
                        if (copyout((void *)&card_info, hpc_ctrldata.data,
                            sizeof (hpc_card_info_t)) != 0) {
                                rv = ENXIO;
                                break;
                        }

                        break;
                    }

                default:
                        rv = EINVAL;
                        break;
                }

                mutex_exit(&cbp->cb_mutex);
                break;

        default:
                rv = ENOTTY;
        }

        if (cmd != DEVCTL_AP_CONTROL)
                ndi_dc_freehdl(dcp);

        cardbus_err(cbp->cb_dip, 7,
            "cardbus_ioctl: rv = 0x%x", rv);

        return (rv);
}

struct cardbus_pci_desc {
        char    *name;
        ushort_t        offset;
        int     (*cfg_get_func)();
        char    *fmt;
};

#define CFG_GET(f)      ((int(*)())(uintptr_t)f)

static struct cardbus_pci_desc generic_pci_cfg[] = {
            { "VendorId    =", 0, CFG_GET(pci_config_get16), "%s 0x%04x" },
            { "DeviceId    =", 2, CFG_GET(pci_config_get16), "%s 0x%04x" },
            { "Command     =", 4, CFG_GET(pci_config_get16), "%s 0x%04x" },
            { "Status      =", 6, CFG_GET(pci_config_get16), "%s 0x%04x" },
            { "Latency     =", 0xd, CFG_GET(pci_config_get8), "%s 0x%02x" },
            { "BASE0       =", 0x10, CFG_GET(pci_config_get32), "%s 0x%08x" },
            { "BASE1       =", 0x14, CFG_GET(pci_config_get32), "%s 0x%08x" },
            { "BASE2       =", 0x18, CFG_GET(pci_config_get32), "%s 0x%08x" },
            { "BASE3       =", 0x1c, CFG_GET(pci_config_get32), "%s 0x%08x" },
            { "BASE4       =", 0x20, CFG_GET(pci_config_get32), "%s 0x%08x" },
            { "CIS Pointer =", 0x28, CFG_GET(pci_config_get32), "%s 0x%08x" },
            { "ILINE       =", 0x3c, CFG_GET(pci_config_get8), "%s 0x%02x" },
            { "IPIN        =", 0x3d, CFG_GET(pci_config_get8), "%s 0x%02x" },
            { NULL, 0, NULL, NULL }
};

static struct cardbus_pci_desc cardbus_pci_cfg[] = {
            { "VendorId    =", 0, CFG_GET(pci_config_get16), "%s 0x%04x" },
            { "DeviceId    =", 2, CFG_GET(pci_config_get16), "%s 0x%04x" },
            { "Command     =", 4, CFG_GET(pci_config_get16), "%s 0x%04x" },
            { "Status      =", 6, CFG_GET(pci_config_get16), "%s 0x%04x" },
            { "CacheLineSz =", 0xc, CFG_GET(pci_config_get8), "%s 0x%02x" },
            { "Latency     =", 0xd, CFG_GET(pci_config_get8), "%s 0x%02x" },
            { "MemBase Addr=", 0x10, CFG_GET(pci_config_get32), "%s 0x%08x" },
            { "Pri Bus     =", 0x18, CFG_GET(pci_config_get8), "%s 0x%02x" },
            { "Sec Bus     =", 0x19, CFG_GET(pci_config_get8), "%s 0x%02x" },
            { "Sub Bus     =", 0x1a, CFG_GET(pci_config_get8), "%s 0x%02x" },
            { "CBus Latency=", 0x1b, CFG_GET(pci_config_get8), "%s 0x%02x" },
            { "Mem0 Base   =", 0x1c, CFG_GET(pci_config_get32), "%s 0x%08x" },
            { "Mem0 Limit  =", 0x20, CFG_GET(pci_config_get32), "%s 0x%08x" },
            { "Mem1 Base   =", 0x24, CFG_GET(pci_config_get32), "%s 0x%08x" },
            { "Mem1 Limit  =", 0x28, CFG_GET(pci_config_get32), "%s 0x%08x" },
            { "I/O0 Base   =", 0x2c, CFG_GET(pci_config_get32), "%s 0x%08x" },
            { "I/O0 Limit  =", 0x30, CFG_GET(pci_config_get32), "%s 0x%08x" },
            { "I/O1 Base   =", 0x34, CFG_GET(pci_config_get32), "%s 0x%08x" },
            { "I/O1 Limit  =", 0x38, CFG_GET(pci_config_get32), "%s 0x%08x" },
            { "ILINE       =", 0x3c, CFG_GET(pci_config_get8), "%s 0x%02x" },
            { "IPIN        =", 0x3d, CFG_GET(pci_config_get8), "%s 0x%02x" },
            { "Bridge Ctrl =", 0x3e, CFG_GET(pci_config_get16), "%s 0x%04x" },
            { "Legacy Addr =", 0x44, CFG_GET(pci_config_get32), "%s 0x%08x" },
            { NULL, 0, NULL, NULL }
};

static void
cardbus_dump(struct cardbus_pci_desc *spcfg, ddi_acc_handle_t handle)
{
        int     i;
        for (i = 0; spcfg[i].name; i++) {

                cmn_err(CE_NOTE, spcfg[i].fmt, spcfg[i].name,
                    spcfg[i].cfg_get_func(handle, spcfg[i].offset));
        }

}

void
cardbus_dump_pci_node(dev_info_t *dip)
{
        dev_info_t *next;
        struct cardbus_pci_desc *spcfg;
        ddi_acc_handle_t config_handle;
        uint32_t VendorId;

        cmn_err(CE_NOTE, "\nPCI leaf node of dip 0x%p:\n", (void *)dip);
        for (next = ddi_get_child(dip); next;
            next = ddi_get_next_sibling(next)) {

                VendorId = ddi_getprop(DDI_DEV_T_ANY, next,
                    DDI_PROP_CANSLEEP|DDI_PROP_DONTPASS,
                    "vendor-id", -1);
                if (VendorId == -1) {
                        /* not a pci device */
                        continue;
                }

                if (pci_config_setup(next, &config_handle) != DDI_SUCCESS) {
                        cmn_err(CE_WARN, "!pcic child: non pci device\n");
                        continue;
                }

                spcfg = generic_pci_cfg;
                cardbus_dump(spcfg, config_handle);
                pci_config_teardown(&config_handle);

        }

}

void
cardbus_dump_pci_config(dev_info_t *dip)
{
        struct cardbus_pci_desc *spcfg;
        ddi_acc_handle_t config_handle;

        if (pci_config_setup(dip, &config_handle) != DDI_SUCCESS) {
                cmn_err(CE_WARN,
                    "!pci_config_setup() failed on 0x%p", (void *)dip);
                return;
        }

        spcfg = cardbus_pci_cfg;
        cardbus_dump(spcfg, config_handle);

        pci_config_teardown(&config_handle);
}

void
cardbus_dump_socket(dev_info_t *dip)
{
        ddi_acc_handle_t        iohandle;
        caddr_t         ioaddr;
        ddi_device_acc_attr_t attr;
        attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
        attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC;
        attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
        if (ddi_regs_map_setup(dip, 1,
            (caddr_t *)&ioaddr,
            0,
            4096,
            &attr, &iohandle) != DDI_SUCCESS) {
                cmn_err(CE_WARN, "Failed to map address for 0x%p", (void *)dip);
                return;
        }

        cmn_err(CE_NOTE, "////////////////////////////////////////");
        cmn_err(CE_NOTE, "SOCKET_EVENT  = [0x%x]",
            ddi_get32(iohandle, (uint32_t *)(ioaddr+CB_STATUS_EVENT)));
        cmn_err(CE_NOTE, "SOCKET_MASK   = [0x%x]",
            ddi_get32(iohandle, (uint32_t *)(ioaddr+CB_STATUS_MASK)));
        cmn_err(CE_NOTE, "SOCKET_STATE  = [0x%x]",
            ddi_get32(iohandle, (uint32_t *)(ioaddr+CB_PRESENT_STATE)));
        cmn_err(CE_NOTE, "////////////////////////////////////////");

        ddi_regs_map_free(&iohandle);

}