root/usr/src/uts/common/io/pciex/hotplug/pciehpc.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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2019 Joyent, Inc.
 * Copyright 2024 Oxide Computer Company
 */

/*
 * This file contains Standard PCI Express HotPlug functionality that is
 * compatible with the PCI Express ver 1.1 specification.
 *
 * NOTE: This file is compiled and delivered through misc/pcie module.
 *
 * The main purpose of this is to take the PCIe slot logic, which is found on a
 * PCIe bridge that indicates it is hotplug capable, and map the DDI hotplug
 * controller states to this. This is an imperfect mapping as the definition of
 * the pciehpc_slot_power_t shows. This file assumes that a device can be
 * removed at any time without notice. This is what the spec calls 'surprise
 * removal'.
 *
 * Not all PCIe slots are the same. In particular this can handle the following
 * features which may or may not exist on the slot:
 *
 *  o Power Controllers: With the rise of NVMe based hotplug and the Enterprise
 *    SSD specification, you can have hotplug, but not specific power control
 *    over the device.
 *  o MRL sensor: Manually-operated Retention latches are an optional item and
 *    less common with U.2, E.1, and E.3 based form factors, but there is the
 *    ability to see their state.
 *  o EMI: Electromechanical Interlock. This is used to lock the device in place
 *    and is often paired with an MRL. This similarly isn't as common.
 *  o Attention Button: A button which can be pressed to say please do
 *    something. This is more of a holdover from the world of coordinated
 *    removal from the PCI Standard Hot-Plug Controller (SHPC).
 *  o Power Fault: The ability to tell whether or not a power fault has
 *    occurred.
 *  o Power and Attention Indicators: These are LEDs that are supposed to be
 *    enabled in response to specific actions in the spec, but some of that is
 *    ultimately policy. It's worth noting that not all controllers support both
 *    LEDs and so platforms may want to munge the logical states here a bit
 *    more.
 *
 * There are four primary states that a slot is considered to exist in that
 * roughly have the following state transition diagram:
 *
 *      +-------+
 *      | Empty |<---------------<------------+
 *      +-------+                             |
 *          |                                 |
 * Device   |                                 |
 * Inserted .                                 ^
 *          |                                 |
 *          |                                 |
 *          v                                 |
 *     +---------+                            . . . Device
 *     | Present |<------+                    |     Removed
 *     +---------+       |                    |
 *          |            |                    |
 * Slot     |            |                    |
 * Power  . .            . . Slot Power       |
 * Enabled  |            |   Disabled,        |
 *          |            |   Power Fault,     |
 *          v            |   or specific      |
 *     +---------+       |   request          |
 *     | Powered |-->----+                    |
 *     +---------+       |                    |
 *          |            |                    |
 *          |            |                    |
 * Request  |            ^                    ^
 * or auto- |            |                    |
 * online . *            |                    |
 *          |            |                    |
 *          v            |                    |
 *     +---------+       |                    |
 *     | Enabled |-->----+--------->----------+
 *     +---------+
 *
 * These four states are all related to the DDI_HP_CN_STATE_* values. For
 * example, the empty state above is DDI_HP_CN_STATE_EMPTY and enabled is
 * DDI_HP_CN_STATE_ENABLED. These changes can happen initially because of
 * outside action that is taken or because an explicit state change has been
 * requested via cfgadm/libhotplug. Note that one cannot enter or leave empty
 * without removing or inserting a device.
 *
 * A device node is created in the devinfo tree as a side effect of
 * transitioning to the enabled state and removed when transitioning away from
 * enabled. This is initiated by the DDI hotplug framework making a probe
 * (DDI_HPOP_CN_PROBE) and unprobe (DDI_HPOP_CN_UNPROBE) request which will
 * ultimately get us to pcicfg_configure() which dynamically sets up child
 * nodes.
 *
 * State Initialization
 * --------------------
 *
 * Initializing the state of the world is a bit tricky here. In particular,
 * there are a few things that we need to understand and deal with:
 *
 * 1. A PCIe slot may or may not have been powered prior to us initializing this
 * module. In particular, the PCIe firmware specification generally expects
 * occupied slots to have both their MRL and power indicator match the slot
 * occupancy state (3.5 Device State at Firmware/Operating System Handoff). Of
 * course, we must not assume that firmware has done this or not.
 *
 * This is further complicated by the fact that while the PCIe default is that
 * power is enabled at reset, some controllers require an explicit first write
 * to enact the reset behavior. You cannot do things like enable or disable
 * interrupts without doing a write to the PCIe Slot Control register and
 * turning power on. Those are just the breaks from the spec. The spec also
 * doesn't have a way to tell us if power is actually on or not, we just have to
 * hope. All we can see is if we've commanded power on and if a power fault was
 * detected at some point.
 *
 * 2. Because of (1), the normal platform-specific startup logic for PCIe may or
 * may not have found any devices and initialized them depending on at what
 * state in the initialization point it was at.
 *
 * 3. To actually enumerate a hotplug device, our DDI_HPOP_CN_PROBE entry point
 * needs to be called, which is pciehpc_slot_probe(). This will ultimately call
 * pcicfg_configure(). There is a wrinkle here. If we have done (2), we don't
 * want to call the probe entry point. However, if we haven't, then at start up,
 * the broader hotplug unfortunately, won't assume that there is anything to do
 * here to make this happen. The kernel framework won't call this unless it sees
 * a transition from a lesser state to DDI_HP_CN_STATE_ENABLED.
 *
 * The cases described above are not our only problem. In particular, there are
 * some other complications that happen here. In particular, it's worth
 * understanding how we actually keep all of our state in sync. The core idea is
 * that changes are coming from one of two places: either a user has explicitly
 * requested a state change or we have had external activity that has injected a
 * hotplug interrupt. This is due to something such as a surprise insertion,
 * removal, power fault, or similar activity.
 *
 * The general construction and assumption is that we know the valid state at
 * the moment before an interrupt occurs, so then the slot status register will
 * indicate to us what has changed. Once we know what we should transition to,
 * then we will go ahead and ask the system to make a state change request to
 * change our state to a given target. While this is similar in spirit to what a
 * user could request, they could not imitate a state transition to EMPTY. The
 * transition to EMPTY or to ENABLED is what kicks off the probe and unprobe
 * operations.
 *
 * This system is all well and good, but it is dependent on the fact that we
 * have an interrupt enabled for changes and that the various interrupt cause
 * bits in the slot status register have been cleared as they are generally RW1C
 * (read, write 1 to clear). This means that in addition to the issues with case
 * (1) and what firmware has or hasn't done, it is also possible that additional
 * changes may occur without us recording them or noticing them in an interrupt.
 *
 * This steady state is a great place to be, but because of the races we
 * discussed above, we need to do a bit of additional work here to ensure that
 * we can reliably enter it. As such, we're going to address the three
 * complications above in reverse order. If we start with (3), while in the
 * steady state, we basically treat the DDI states as the main states we can
 * transition to and from (again see the pciehpc_slot_power_t comment for the
 * fact that this is somewhat factious). This means that if we are ENABLED, a
 * probe must have happened (or the semi-equivalent in (2)).
 *
 * Normally, we assume that if we got all the way up and have a powered device
 * that the state we should return to the system is ENABLED. However, that only
 * works if we can ensure that the state transition from less than ENABLED to
 * ENABLED occurs so a probe can occur.
 *
 * This window is made larger because of (1) and (2). However, this is not
 * unique to the ENABLED state and these cases can happen by having a device
 * that was probed at initial time be removed prior to the interrupt being
 * enabled. While this is honestly a very tight window and something that may
 * not happen in practice, it highlights many of the things that can occur and
 * that we need to handle.
 *
 * To deal with this we are a little more careful with our startup state. When
 * we reach our module's main initialization entry point for a given controller,
 * pciehpc_init(), we know that at that point (2) has completed. We also know
 * that the interrupt shouldn't be initiated at that point, but that isn't
 * guaranteed until we finish calling the pciehpc_hpc_init() entry point. We
 * subsequently will enable the interrupt via the enable_phc_intr() function
 * pointer, which is called from pcie_hpintr_enable(). This gap is to allow the
 * overall driver (say pcieb) to ensure that it has allocated and attached
 * interrupts prior to us enabling it.
 *
 * At the point that we are initialized, we can look and see if we have any
 * children. If we do, then we know that (2) performed initialization and it's
 * safe for us to set our initial state to ENABLED and allow that to be the
 * first thing the kernel hotplug framework sees, assuming our state would
 * otherwise suggest we'd be here. If we do not see a child device and we have
 * enabled power, then we basically need to mimic the normal act of having
 * transitioned to an ENABLED state. This needs to happen ahead of us first
 * communicating our state to the DDI.
 *
 * The next set of things we need to do happen when we go to enable interrupts.
 * It's worth keeping in mind that at this point the rest of the system is fully
 * operational. One of three events can be injected at this point, a removal,
 * insertion, or a power fault. We also need to consider that a removal or
 * insertion happened in an intervening point. To make this all happen, let's
 * discuss the different pieces that are involved in tracking what's going on:
 *
 * 1) During pciehpc_slotinfo_init() we check to see if we have a child devinfo
 * node or not. We only mark a node as ENABLED if we have a child and it is
 * already POWERED. This ensures that we aren't ahead of ourselves. The normal
 * state determination logic will not put us at enabled prior to being there.
 *
 * 2) We have added a pair of flags to the pcie_hp_ctrl_t called
 * PCIE_HP_SYNC_PENDING and PCIE_HP_SYNC_RUNNING. The former indicates that we
 * have identified that we need to perform a state correction and have
 * dispatched a task to the system taskq to deal with it. The
 * PCIE_HP_SYNC_RUNNING flag is used to indicate that a state transition request
 * is actually being made due to this right now. This is used to tweak a bit of
 * the slot upgrade path, discussed below.
 *
 * 3) Immediately after enabling interrupts, while still holding the hotplug
 * controller mutex, we investigate what our current state is and what we have
 * previously set it to. Depending on the transition that needs to occur and if
 * it has a side effect of needing to probe or unprobe a connection, then we'll
 * end up scheduling a task in the system taskq to perform that transition.
 * Otherwise, we will simply fix up the LED state as we have no reason to
 * believe that it is currently correct for our state.
 *
 * Using the taskq has a major benefit for us in that it allows us to leverage
 * the existing code paths for state transitions. This means that if things are
 * already powered on and the data link layer is active, there won't be any
 * extra delay and if not, it will honor the same 1s timeout, take advantage of
 * the datalink layer active bit if supported, and on failure it will turn off
 * the controller.
 *
 * 4) We are reliant on an important property of pciehpc_get_slot_state(): if it
 * finds itself in the POWERED state, it will not change from that. This is half
 * of the reason that we opt to go to the POWERED state when this occurs. The
 * other half is that it is factually accurate and doing otherwise would get in
 * the way of our logic which attempts to correct the state in
 * pciehpc_change_slot_state() which corrects for the state being incorrect.
 * While it is tempting to use the PRESENT state and try to avoid a special case
 * in pciehpc_upgrade_slot_state(), that ends up breaking more invariants than
 * the logic described below.
 *
 * 5) Finally, when the PCIE_HP_SYNC_RUNNING bit is set, that tells us when
 * we're doing a power on exercise that we need to do so again regardless of
 * what we think we've done. Because of our attempts to try to have things be
 * idempotent, this ends up being a relatively safe operation to perform again
 * and being able to reuse this helps a lot.
 *
 * It is our hope that after this point everything will be in line such that we
 * can enter the steady state. If devices have come or gone, the use of the
 * normal state machine transitions should allow us to get them to be attached
 * or not.
 *
 * LED Management
 * --------------
 *
 * The PCIe specifications define two LEDs that may optionally exist on a slot:
 * a power LED and an attention LED. In the earliest version of the PCIe
 * specification, these were maintained as device capabilities, but by PCIe 1.1
 * they are solely on the slot. The PCIe specification provides very specific
 * meanings for what these LEDs indicate. However, different systems implement a
 * subset of these LEDs and may not want to use them for the same purposes. The
 * PCIe Native HotPlug defined in S6.7 describes the following roles for each
 * LED (and also defines the colors):
 *
 * POWER        Off:    Device not powered
 *              On:     Device powered
 *              Blink:  Power transition occurring
 *
 * ATTENTION    Off:    Everything's fine, how are you?
 *              On:     There's a problem here.
 *              Blink:  Attention
 *
 * While this is the standard, it's not always the case that everything actually
 * has implemented these, that all platforms want to use the same definitions,
 * or even have both LEDs. The above definitions are strictly meant for hotplug,
 * but one could have LEDs without hotplug (though it's rare). In addition,
 * there are cases that the operating system adds additional semantics as to
 * what the LEDs should mean. For example, while holding the attention button,
 * the power LED blinks, or if probe fails, then we assume that attention should
 * be asserted. Finally, there is the unfortunate reality that many systems
 * which implement a more limited form of Enterprise SSD take away LED
 * management from the OS entirely and it is left entirely up to firmware's
 * druthers (possibly allowing for interaction via IPMI or something similar).
 *
 * The next consideration for LED management is that there is an ability for
 * this to be overridden by userland. For example, there is no first class blink
 * or locate ioctl and instead that is something that is created by an operator
 * using libhotplug or say an FMA topology. This means that the state that we
 * have displayed can be overridden at any time. As such, while libhotplug and
 * other consumers may chance the LED state, we instead think of this as an
 * override. When they are done, they can restore the default behavior and we
 * will figure out what that means.
 *
 * For the most part our LED state can be determined based upon the slot's
 * state. However, there are a few specific transitions that can occur that need
 * to be captured like while we are powering, attention button activity, or
 * probe failures. A particular part of this is what are the lifetimes of these
 * and when should they be cleared out. There is a bunch of complexity here that
 * suggests this subsystem should perhaps be driven more by userland (where it's
 * a bit easier to inject what someone wants). For now, here is how we scope
 * those special cases:
 *
 * POWER FAULT          When a power fault occurs, this normally automatically
 *                      causes us to power off the device (if there is a power
 *                      controller). As such the only things that clear out a
 *                      power fault is another attempt to power on the device
 *                      (because we have no way of knowing whether or not it's
 *                      still faulted, thanks specs) or until the device is
 *                      removed.
 *
 * ATTENTION BUTTON     Pushing the attention button is supposed to cause a five
 *                      second timer to begin. This state persists across its
 *                      entire lifetime. Once that is done, then it will be
 *                      turned off. Unlike other things it is not bound to a
 *                      power transition.
 *
 * PROBE FAILURE        This is not a PCIe concept, but something that we do.
 *                      This is meant to last until a subsequent attempt to
 *                      probe the device occurs, the device is powered off, or
 *                      the device is removed. This is different from a power
 *                      fault in so far as we honor power off as a transition
 *                      point here. This was chosen because powering off
 *                      represents an active action to try to and change the
 *                      state away from probing, so it no longer serves to blink
 *                      the LED because of a probe failure.
 *
 * To deal with this, we have a series of logical LED states that can be enabled
 * which are then evaluated in a preferential ordering that describe what to do
 * for each logical item we want to do. These include things like power
 * transitions, the device being powered, or things like the power fault or
 * probe failing. This is tracked in the slot's hs_led_plat_en[] data structure.
 * Higher states are preferential which is why we work through the states
 * backwards. Each state can opt to take an explicit state or punt to the next.
 *
 * We map the logical state to LED actions through a platform-specific set of
 * definitions. Pretty much everything will use the standard PCIe LED behavior
 * (pciehpc_pcie_std_leds[]), but this can be overridden for particular
 * platforms.
 *
 * Because of our startup races, we defer actually setting the external LED
 * state until we first get through pciehpc_enable_state_sync() or a
 * corresponding state transition that it forces to happen.
 */

#include <sys/types.h>
#include <sys/note.h>
#include <sys/conf.h>
#include <sys/kmem.h>
#include <sys/debug.h>
#include <sys/vtrace.h>
#include <sys/autoconf.h>
#include <sys/varargs.h>
#include <sys/ddi_impldefs.h>
#include <sys/time.h>
#include <sys/callb.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/sysmacros.h>
#include <sys/sysevent/dr.h>
#include <sys/pci_impl.h>
#include <sys/kstat.h>
#include <sys/zone.h>
#include <sys/hotplug/pci/pcie_hp.h>
#include <sys/hotplug/pci/pciehpc.h>

/* XXX /etc/system is NOT a policy interface */
int pcie_auto_online = 1;

/*
 * Ideally, it would be possible to represent the state of a slot with a single
 * ddi_hp_cn_state_t; after all, that's the purpose of that data type.
 * Unfortunately it wasn't designed very well and cannot even represent the
 * range of possible power states of a PCIe slot.  It is possible for a slot to
 * be powered on or off with or without a device present, and it is possible for
 * a slot not to have a power controller at all.  Finally, it's possible for a
 * power fault to be detected regardless of whether power is on or off or a
 * device is present or not.  This state attempts to represent all possible
 * power states that a slot can have, which is important for implementing our
 * state machine that has to expose only the very limited DDI states.
 *
 * These are bits that may be ORed together.  Not all combinations comply with
 * the standards, but these definitions were chosen to make it harder to
 * construct invalid combinations.  In particular, if there is no controller,
 * there is also no possibility of the slot being turned off, nor is it possible
 * for there to be a fault.
 */
typedef enum pciehpc_slot_power {
        PSP_NO_CONTROLLER = 0,
        PSP_HAS_CONTROLLER = (1U << 0),
        PSP_OFF = (1U << 1),
        PSP_FAULT = (1U << 2)
} pciehpc_slot_power_t;

typedef struct {
        pcie_hp_ctrl_t *pst_ctrl;
        ddi_hp_cn_state_t pst_targ;
        ddi_hp_cn_state_t pst_cur;
} pciehpc_sync_task_t;

static const pciehpc_led_plat_state_t pciehpc_std_leds[PCIEHPC_LED_NSTATES] = {
        [PCIE_LL_BASE] = { PCIE_HLA_OFF, PCIE_HLA_OFF },
        [PCIE_LL_POWER_TRANSITION] = { PCIE_HLA_BLINK, PCIE_HLA_PASS },
        [PCIE_LL_POWERED] = { PCIE_HLA_ON, PCIE_HLA_OFF },
        [PCIE_LL_PROBE_FAILED] = { PCIE_HLA_PASS, PCIE_HLA_ON },
        [PCIE_LL_POWER_FAULT] = { PCIE_HLA_BLINK, PCIE_HLA_PASS },
        [PCIE_LL_ATTENTION_BUTTON] = { PCIE_HLA_BLINK, PCIE_HLA_PASS }
};

/*
 * The Gimlet hardware platform only has a single attention LED that is used for
 * multiple purposes.
 */
static const pciehpc_led_plat_state_t pciehpc_gimlet_leds[PCIEHPC_LED_NSTATES] =
{
        [PCIE_LL_BASE] = { PCIE_HLA_OFF, PCIE_HLA_OFF },
        [PCIE_LL_POWER_TRANSITION] = { PCIE_HLA_PASS, PCIE_HLA_PASS },
        [PCIE_LL_POWERED] = { PCIE_HLA_PASS, PCIE_HLA_ON },
        [PCIE_LL_PROBE_FAILED] = { PCIE_HLA_PASS, PCIE_HLA_BLINK },
        [PCIE_LL_POWER_FAULT] = { PCIE_HLA_PASS, PCIE_HLA_BLINK },
        [PCIE_LL_ATTENTION_BUTTON] = { PCIE_HLA_PASS, PCIE_HLA_PASS }
};

typedef struct pciehpc_led_plat_map {
        uint16_t plpm_vid;
        uint16_t plpm_did;
        uint16_t plpm_svid;
        uint16_t plpm_ssys;
        const pciehpc_led_plat_state_t *plpm_map;
} pciehpc_led_plat_map_t;

/*
 * This table represents platforms that have different values than the default
 * PCIe LED usage that should be applied.
 */
static const pciehpc_led_plat_map_t pciehpc_led_plat_map[] = {
        { .plpm_vid = 0x1022, .plpm_did = 0x1483, .plpm_svid = 0x1de,
            .plpm_ssys = 0xfff9, .plpm_map = pciehpc_gimlet_leds }
};

/* Local functions prototype */
static int pciehpc_hpc_init(pcie_hp_ctrl_t *ctrl_p);
static int pciehpc_hpc_uninit(pcie_hp_ctrl_t *ctrl_p);
static int pciehpc_slotinfo_init(pcie_hp_ctrl_t *ctrl_p);
static int pciehpc_slotinfo_uninit(pcie_hp_ctrl_t *ctrl_p);
static int pciehpc_enable_intr(pcie_hp_ctrl_t *ctrl_p);
static int pciehpc_disable_intr(pcie_hp_ctrl_t *ctrl_p);
static pcie_hp_ctrl_t *pciehpc_create_controller(dev_info_t *dip);
static void pciehpc_destroy_controller(dev_info_t *dip);
static int pciehpc_register_slot(pcie_hp_ctrl_t *ctrl_p);
static int pciehpc_unregister_slot(pcie_hp_ctrl_t *ctrl_p);
static int pciehpc_slot_get_property(pcie_hp_slot_t *slot_p,
    uintptr_t arg, uintptr_t rval);
static int pciehpc_slot_set_property(pcie_hp_slot_t *slot_p,
    uintptr_t arg, uintptr_t rval);
static void pciehpc_issue_hpc_command(pcie_hp_ctrl_t *ctrl_p, uint16_t control);
static void pciehpc_attn_btn_handler(pcie_hp_ctrl_t *ctrl_p);
static pcie_hp_led_state_t pciehpc_led_state_to_hpc(uint16_t state);

static int pciehpc_upgrade_slot_state(pcie_hp_slot_t *slot_p,
    ddi_hp_cn_state_t target_state);
static int pciehpc_downgrade_slot_state(pcie_hp_slot_t *slot_p,
    ddi_hp_cn_state_t target_state);
static int pciehpc_change_slot_state(pcie_hp_slot_t *slot_p,
    ddi_hp_cn_state_t target_state);
static int
    pciehpc_slot_poweron(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result);
static int
    pciehpc_slot_poweroff(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result);
static int pciehpc_slot_probe(pcie_hp_slot_t *slot_p);
static int pciehpc_slot_unprobe(pcie_hp_slot_t *slot_p);
static void pciehpc_handle_power_fault(dev_info_t *dip);
static void pciehpc_power_fault_handler(void *arg);

#ifdef  DEBUG
static void pciehpc_dump_hpregs(pcie_hp_ctrl_t *ctrl_p);
#endif  /* DEBUG */

typedef struct pciehpc_stat_data {
        kstat_named_t psd_attnsw_count;
        kstat_named_t psd_attnsw_ts;
        kstat_named_t psd_pwrflt_count;
        kstat_named_t psd_pwrflt_ts;
        kstat_named_t psd_mrl_chg_count;
        kstat_named_t psd_mrl_chg_ts;
        kstat_named_t psd_pres_chg_count;
        kstat_named_t psd_pres_chg_ts;
        kstat_named_t psd_cmd_cpl_count;
        kstat_named_t psd_cmd_cpl_ts;
        kstat_named_t psd_dll_chg_count;
        kstat_named_t psd_dll_chg_ts;
        kstat_named_t psd_intr_count;
        kstat_named_t psd_intr_ts;
} pciehpc_stat_data_t;

static inline void
pciehpc_kstat_event(kstat_named_t *countp, kstat_named_t *tsp, hrtime_t now)
{
        atomic_inc_64(&countp->value.ui64);
        tsp->value.ui64 = now;
}

#define KSTAT_EVENT(_slotp, _evname, _now)      \
    pciehpc_kstat_event((&(_slotp)->hs_stat_data->psd_ ## _evname ## _count), \
        (&(_slotp)->hs_stat_data->psd_ ## _evname ## _ts), (_now))

/*
 * Global functions (called by other drivers/modules)
 */

/*
 * Initialize Hot Plug Controller if present. The arguments are:
 *      dip     - devinfo node pointer to the hot plug bus node
 *      regops  - register ops to access HPC registers for non-standard
 *                HPC hw implementations (e.g: HPC in host PCI-E bridges)
 *                This is NULL for standard HPC in PCIe bridges.
 * Returns:
 *      DDI_SUCCESS for successful HPC initialization
 *      DDI_FAILURE for errors or if HPC hw not found
 */
int
pciehpc_init(dev_info_t *dip, caddr_t arg)
{
        pcie_hp_regops_t        *regops = (pcie_hp_regops_t *)(void *)arg;
        pcie_hp_ctrl_t          *ctrl_p;

        PCIE_DBG("pciehpc_init() called (dip=%p)\n", (void *)dip);

        /* Make sure that it is not already initialized */
        if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) != NULL) {
                PCIE_DBG("%s%d: pciehpc instance already initialized!\n",
                    ddi_driver_name(dip), ddi_get_instance(dip));
                return (DDI_SUCCESS);
        }

        /* Allocate a new hotplug controller and slot structures */
        ctrl_p = pciehpc_create_controller(dip);

        /* setup access handle for HPC regs */
        if (regops != NULL) {
                /* HPC access is non-standard; use the supplied reg ops */
                ctrl_p->hc_regops = *regops;
        }

        /*
         * Setup resource maps for this bus node.
         */
        (void) pci_resource_setup(dip);

        PCIE_DISABLE_ERRORS(dip);

        /*
         * Set the platform specific hot plug mode.
         */
        ctrl_p->hc_ops.init_hpc_hw = pciehpc_hpc_init;
        ctrl_p->hc_ops.uninit_hpc_hw = pciehpc_hpc_uninit;
        ctrl_p->hc_ops.init_hpc_slotinfo = pciehpc_slotinfo_init;
        ctrl_p->hc_ops.uninit_hpc_slotinfo = pciehpc_slotinfo_uninit;
        ctrl_p->hc_ops.poweron_hpc_slot = pciehpc_slot_poweron;
        ctrl_p->hc_ops.poweroff_hpc_slot = pciehpc_slot_poweroff;

        ctrl_p->hc_ops.enable_hpc_intr = pciehpc_enable_intr;
        ctrl_p->hc_ops.disable_hpc_intr = pciehpc_disable_intr;

#if     defined(__x86)
        pciehpc_update_ops(ctrl_p);
#endif

        /* initialize hot plug controller hw */
        if ((ctrl_p->hc_ops.init_hpc_hw)(ctrl_p) != DDI_SUCCESS)
                goto cleanup1;

        /* initialize slot information soft state structure */
        if ((ctrl_p->hc_ops.init_hpc_slotinfo)(ctrl_p) != DDI_SUCCESS)
                goto cleanup2;

        /* register the hot plug slot with DDI HP framework */
        if (pciehpc_register_slot(ctrl_p) != DDI_SUCCESS)
                goto cleanup3;

        /* create minor node for this slot */
        if (pcie_create_minor_node(ctrl_p, 0) != DDI_SUCCESS)
                goto cleanup4;

        /*
         * While we disabled errors upon entry, if we were initialized and
         * entered the ENABLED state that indicates we have children and
         * therefore we should go back and enable errors.
         */
        if (ctrl_p->hc_slots[0]->hs_info.cn_state == DDI_HP_CN_STATE_ENABLED) {
                PCIE_ENABLE_ERRORS(dip);
        }

        /* HPC initialization is complete now */
        ctrl_p->hc_flags |= PCIE_HP_INITIALIZED_FLAG;

#ifdef  DEBUG
        /* For debug, dump the HPC registers */
        pciehpc_dump_hpregs(ctrl_p);
#endif  /* DEBUG */

        return (DDI_SUCCESS);
cleanup4:
        (void) pciehpc_unregister_slot(ctrl_p);
cleanup3:
        (void) (ctrl_p->hc_ops.uninit_hpc_slotinfo)(ctrl_p);

cleanup2:
        (void) (ctrl_p->hc_ops.uninit_hpc_hw)(ctrl_p);

cleanup1:
        PCIE_ENABLE_ERRORS(dip);
        (void) pci_resource_destroy(dip);

        pciehpc_destroy_controller(dip);
        return (DDI_FAILURE);
}

/*
 * Uninitialize HPC soft state structure and free up any resources
 * used for the HPC instance.
 */
int
pciehpc_uninit(dev_info_t *dip)
{
        pcie_hp_ctrl_t *ctrl_p;
        taskqid_t id;

        PCIE_DBG("pciehpc_uninit() called (dip=%p)\n", (void *)dip);

        /* get the soft state structure for this dip */
        if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) == NULL) {
                return (DDI_FAILURE);
        }

        /*
         * Prior to taking any action, we want to remove the initialized flag.
         * Any interrupts should have already been quiesced prior to this. There
         * may be an outstanding startup synchronization timeout(9F) call.
         */
        mutex_enter(&ctrl_p->hc_mutex);
        ctrl_p->hc_flags &= ~PCIE_HP_INITIALIZED_FLAG;
        id = ctrl_p->hc_startup_sync;
        ctrl_p->hc_startup_sync = TASKQID_INVALID;
        mutex_exit(&ctrl_p->hc_mutex);

        if (id != TASKQID_INVALID)
                taskq_wait_id(system_taskq, id);

        pcie_remove_minor_node(ctrl_p, 0);

        /* unregister the slot */
        (void) pciehpc_unregister_slot(ctrl_p);

        /* uninit any slot info data structures */
        (void) (ctrl_p->hc_ops.uninit_hpc_slotinfo)(ctrl_p);

        /* uninitialize hpc, remove interrupt handler, etc. */
        (void) (ctrl_p->hc_ops.uninit_hpc_hw)(ctrl_p);

        PCIE_ENABLE_ERRORS(dip);

        /*
         * Destroy resource maps for this bus node.
         */
        (void) pci_resource_destroy(dip);

        /* destroy the soft state structure */
        pciehpc_destroy_controller(dip);

        return (DDI_SUCCESS);
}

/*
 * pciehpc_intr()
 *
 * Interrupt handler for PCI-E Hot plug controller interrupts.
 *
 * Note: This is only for native mode hot plug. This is called
 * by the nexus driver at interrupt context. Interrupt Service Routine
 * registration is done by the nexus driver for both hot plug and
 * non-hot plug interrupts. This function is called from the ISR
 * of the nexus driver to handle hot-plug interrupts.
 *
 * We must check whether or not we have a pending synchronization event and if
 * so, cancel it. In particular, there are several cases that may cause us to
 * request an asynchronous state transition (e.g. a drive was removed or
 * inserted). When that occurs, we effectively cancel the pending
 * synchronization taskq activity. It will still execute, but do nothing. If it
 * has already started executing, then its state change request will already
 * have been dispatched and we let things shake out with the additional logic we
 * have present in pciehpc_change_slot_state().
 */
int
pciehpc_intr(dev_info_t *dip)
{
        pcie_hp_ctrl_t  *ctrl_p;
        pcie_hp_slot_t  *slot_p;
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(dip);
        uint16_t        status, control;
        boolean_t       clear_pend = B_FALSE;
        hrtime_t        now;

        /* get the soft state structure for this dip */
        if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) == NULL)
                return (DDI_INTR_UNCLAIMED);

        now = gethrtime();

        mutex_enter(&ctrl_p->hc_mutex);

        /* make sure the controller soft state is initialized */
        if (!(ctrl_p->hc_flags & PCIE_HP_INITIALIZED_FLAG)) {
                mutex_exit(&ctrl_p->hc_mutex);
                return (DDI_INTR_UNCLAIMED);
        }

        /* if it is not NATIVE hot plug mode then return */
        if (bus_p->bus_hp_curr_mode != PCIE_NATIVE_HP_MODE) {
                mutex_exit(&ctrl_p->hc_mutex);
                return (DDI_INTR_UNCLAIMED);
        }

        slot_p = ctrl_p->hc_slots[0];

        /* read the current slot status register */
        status = pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTSTS);

        /* check if there are any hot plug interrupts occurred */
        if (!(status & PCIE_SLOTSTS_STATUS_EVENTS)) {
                /* no hot plug events occurred */
                mutex_exit(&ctrl_p->hc_mutex);
                return (DDI_INTR_UNCLAIMED);
        }

        /* We are now committed to claiming the interrupt; record it. */
        KSTAT_EVENT(slot_p, intr, now);

        /* clear the interrupt status bits */
        pciehpc_reg_put16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTSTS, status);

        /* check for CMD COMPLETE interrupt */
        if (status & PCIE_SLOTSTS_COMMAND_COMPLETED) {
                KSTAT_EVENT(slot_p, cmd_cpl, now);
                PCIE_DBG("pciehpc_intr(): CMD COMPLETED interrupt received\n");
                /* wake up any one waiting for Command Completion event */
                cv_signal(&ctrl_p->hc_cmd_comp_cv);
        }

        /* check for ATTN button interrupt */
        if (status & PCIE_SLOTSTS_ATTN_BTN_PRESSED) {
                KSTAT_EVENT(slot_p, attnsw, now);
                PCIE_DBG("pciehpc_intr(): ATTN BUTTON interrupt received\n");

                /* if ATTN button event is still pending then cancel it */
                if (slot_p->hs_attn_btn_pending == B_TRUE)
                        slot_p->hs_attn_btn_pending = B_FALSE;
                else
                        slot_p->hs_attn_btn_pending = B_TRUE;

                /* wake up the ATTN event handler */
                cv_signal(&slot_p->hs_attn_btn_cv);
        }

        /* check for power fault interrupt */
        if (status & PCIE_SLOTSTS_PWR_FAULT_DETECTED) {
                KSTAT_EVENT(slot_p, pwrflt, now);
                PCIE_DBG("pciehpc_intr(): POWER FAULT interrupt received"
                    " on slot %d\n", slot_p->hs_phy_slot_num);
                control =  pciehpc_reg_get16(ctrl_p,
                    bus_p->bus_pcie_off + PCIE_SLOTCTL);

                if (control & PCIE_SLOTCTL_PWR_FAULT_EN) {
                        slot_p->hs_condition = AP_COND_FAILED;

                        /* disable power fault detection interrupt */
                        pciehpc_reg_put16(ctrl_p, bus_p->bus_pcie_off +
                            PCIE_SLOTCTL, control & ~PCIE_SLOTCTL_PWR_FAULT_EN);

                        pciehpc_handle_power_fault(dip);
                        clear_pend = B_TRUE;
                }
        }

        /* check for MRL SENSOR CHANGED interrupt */
        if (status & PCIE_SLOTSTS_MRL_SENSOR_CHANGED) {
                KSTAT_EVENT(slot_p, mrl_chg, now);
                /* For now (phase-I), no action is taken on this event */
                PCIE_DBG("pciehpc_intr(): MRL SENSOR CHANGED interrupt received"
                    " on slot %d\n", slot_p->hs_phy_slot_num);
        }

        /* check for PRESENCE CHANGED interrupt */
        if (status & PCIE_SLOTSTS_PRESENCE_CHANGED) {
                KSTAT_EVENT(slot_p, pres_chg, now);
                PCIE_DBG("pciehpc_intr(): PRESENCE CHANGED interrupt received"
                    " on slot %d\n", slot_p->hs_phy_slot_num);

                if (status & PCIE_SLOTSTS_PRESENCE_DETECTED) {
                        ddi_hp_cn_state_t tgt_state = (pcie_auto_online != 0) ?
                            DDI_HP_CN_STATE_ENABLED : DDI_HP_CN_STATE_PRESENT;
                        /*
                         * card is inserted into the slot, ask DDI Hotplug
                         * framework to change state to Present.
                         */
                        cmn_err(CE_NOTE, "pciehpc (%s%d): card is inserted"
                            " in the slot %s",
                            ddi_driver_name(dip),
                            ddi_get_instance(dip),
                            slot_p->hs_info.cn_name);

                        (void) ndi_hp_state_change_req(dip,
                            slot_p->hs_info.cn_name,
                            tgt_state, DDI_HP_REQ_ASYNC);
                } else { /* card is removed from the slot */
                        cmn_err(CE_NOTE, "pciehpc (%s%d): card is removed"
                            " from the slot %s",
                            ddi_driver_name(dip),
                            ddi_get_instance(dip),
                            slot_p->hs_info.cn_name);

                        if (slot_p->hs_info.cn_state ==
                            DDI_HP_CN_STATE_ENABLED) {
                                /* Card is removed when slot is enabled */
                                slot_p->hs_condition = AP_COND_FAILED;
                        } else {
                                slot_p->hs_condition = AP_COND_UNKNOWN;
                        }
                        /* make sure to disable power fault detction intr */
                        control =  pciehpc_reg_get16(ctrl_p,
                            bus_p->bus_pcie_off + PCIE_SLOTCTL);

                        if (control & PCIE_SLOTCTL_PWR_FAULT_EN)
                                pciehpc_reg_put16(ctrl_p, bus_p->bus_pcie_off +
                                    PCIE_SLOTCTL,
                                    control & ~PCIE_SLOTCTL_PWR_FAULT_EN);

                        /*
                         * If supported, notify the child device driver that the
                         * device is being removed.
                         */
                        dev_info_t *cdip = ddi_get_child(dip);
                        if (cdip != NULL) {
                                ddi_eventcookie_t rm_cookie;
                                if (ddi_get_eventcookie(cdip,
                                    DDI_DEVI_REMOVE_EVENT,
                                    &rm_cookie) == DDI_SUCCESS) {
                                        ndi_post_event(dip, cdip, rm_cookie,
                                            NULL);
                                }
                        }

                        /*
                         * Ask DDI Hotplug framework to change state to Empty
                         */
                        (void) ndi_hp_state_change_req(dip,
                            slot_p->hs_info.cn_name,
                            DDI_HP_CN_STATE_EMPTY,
                            DDI_HP_REQ_ASYNC);
                }

                clear_pend = B_TRUE;
        }

        /* check for DLL state changed interrupt */
        if (ctrl_p->hc_dll_active_rep &&
            (status & PCIE_SLOTSTS_DLL_STATE_CHANGED)) {
                KSTAT_EVENT(slot_p, dll_chg, now);
                PCIE_DBG("pciehpc_intr(): DLL STATE CHANGED interrupt received"
                    " on slot %d\n", slot_p->hs_phy_slot_num);

                cv_signal(&slot_p->hs_dll_active_cv);
        }

        if (clear_pend) {
                ctrl_p->hc_flags &= ~PCIE_HP_SYNC_PENDING;
        }
        mutex_exit(&ctrl_p->hc_mutex);

        return (DDI_INTR_CLAIMED);
}

/*
 * Handle hotplug commands
 *
 * Note: This function is called by DDI HP framework at kernel context only
 */
int
pciehpc_hp_ops(dev_info_t *dip, char *cn_name, ddi_hp_op_t op,
    void *arg, void *result)
{
        pcie_hp_ctrl_t  *ctrl_p;
        pcie_hp_slot_t  *slot_p;
        int             ret = DDI_SUCCESS;

        PCIE_DBG("pciehpc_hp_ops: dip=%p cn_name=%s op=%x arg=%p\n",
            dip, cn_name, op, arg);

        if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) == NULL)
                return (DDI_FAILURE);

        slot_p = ctrl_p->hc_slots[0];

        if (strcmp(cn_name, slot_p->hs_info.cn_name) != 0)
                return (DDI_EINVAL);

        switch (op) {
        case DDI_HPOP_CN_GET_STATE:
        {
                mutex_enter(&slot_p->hs_ctrl->hc_mutex);

                /* get the current slot state */
                pciehpc_get_slot_state(slot_p);

                *((ddi_hp_cn_state_t *)result) = slot_p->hs_info.cn_state;

                mutex_exit(&slot_p->hs_ctrl->hc_mutex);
                break;
        }
        case DDI_HPOP_CN_CHANGE_STATE:
        {
                ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg;

                mutex_enter(&slot_p->hs_ctrl->hc_mutex);

                ret = pciehpc_change_slot_state(slot_p, target_state);
                *(ddi_hp_cn_state_t *)result = slot_p->hs_info.cn_state;

                mutex_exit(&slot_p->hs_ctrl->hc_mutex);
                break;
        }
        case DDI_HPOP_CN_PROBE:

                ret = pciehpc_slot_probe(slot_p);

                break;
        case DDI_HPOP_CN_UNPROBE:
                ret = pciehpc_slot_unprobe(slot_p);

                break;
        case DDI_HPOP_CN_GET_PROPERTY:
                ret = pciehpc_slot_get_property(slot_p, (uintptr_t)arg,
                    (uintptr_t)result);
                break;
        case DDI_HPOP_CN_SET_PROPERTY:
                ret = pciehpc_slot_set_property(slot_p, (uintptr_t)arg,
                    (uintptr_t)result);
                break;
        default:
                ret = DDI_ENOTSUP;
                break;
        }

        return (ret);
}

/*
 * Get the current state of the slot from the hw.
 *
 * The slot state should have been initialized before this function gets called.
 */
void
pciehpc_get_slot_state(pcie_hp_slot_t *slot_p)
{
        pcie_hp_ctrl_t  *ctrl_p = slot_p->hs_ctrl;
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);
        uint16_t        control, status;
        ddi_hp_cn_state_t curr_state = slot_p->hs_info.cn_state;

        ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex));

        /* read the Slot Control Register */
        control = pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCTL);

        /*
         * These LEDs are always set to off because they don't exist. They are
         * only here because the data structure is shared with the PCI SHPC
         * logic. We do not pretend to implement it.
         */
        slot_p->hs_fault_led_state = PCIE_HP_LED_OFF;
        slot_p->hs_active_led_state = PCIE_HP_LED_OFF;

        /* read the current Slot Status Register */
        status = pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTSTS);

        /* get POWER led state */
        slot_p->hs_power_led_state =
            pciehpc_led_state_to_hpc(pcie_slotctl_pwr_indicator_get(control));

        /* get ATTN led state */
        slot_p->hs_attn_led_state =
            pciehpc_led_state_to_hpc(pcie_slotctl_attn_indicator_get(control));

        if (!(status & PCIE_SLOTSTS_PRESENCE_DETECTED)) {
                /* no device present; slot is empty */
                slot_p->hs_info.cn_state = DDI_HP_CN_STATE_EMPTY;
                return;
        }

        /* device is present */
        slot_p->hs_info.cn_state = DDI_HP_CN_STATE_PRESENT;

        /*
         * If we have power control and power control is disabled, then we are
         * merely present. We cannot be POWERED or ENABLED without this being
         * active.
         */
        if (ctrl_p->hc_has_pwr && (control & PCIE_SLOTCTL_PWR_CONTROL) != 0) {
                return;
        }

        /*
         * To be in the ENABLED state that means that we have verified that the
         * device is ready to be used. This happens at different points in time
         * right now depending on whether or not we have a power controller and
         * should be consolidated in the future. Our main constraint is that the
         * kernel expects that when something is in the ENABLED state that probe
         * should succeed.
         *
         * For devices with a power controller, this is guaranteed as part of
         * the PRESENT->POWERED transition. For devices without a power
         * controller, we must assume that power is always applied (the slot
         * control register bit for power status is undefined). This means that
         * the POWERED->ENABLED transition point is where this occurs.
         *
         * This is all a long way of justifying the logic below. If our current
         * state is enabled then we will stay in enabled; however, if it is
         * anything else we will go to powered and allow the normal state
         * transition to take effect.
         */
        if (curr_state == DDI_HP_CN_STATE_ENABLED) {
                slot_p->hs_info.cn_state = curr_state;
        } else {
                slot_p->hs_info.cn_state = DDI_HP_CN_STATE_POWERED;
        }
}

/*
 * setup slot name/slot-number info.
 */
void
pciehpc_set_slot_name(pcie_hp_ctrl_t *ctrl_p)
{
        pcie_hp_slot_t  *slot_p = ctrl_p->hc_slots[0];
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);
        uchar_t         *slotname_data;
        int             *slotnum;
        uint_t          count;
        int             len;
        int             invalid_slotnum = 0;
        uint32_t        slot_capabilities;

        if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, ctrl_p->hc_dip,
            DDI_PROP_DONTPASS, "physical-slot#", &slotnum, &count) ==
            DDI_PROP_SUCCESS) {
                slot_p->hs_phy_slot_num = slotnum[0];
                ddi_prop_free(slotnum);
        } else {
                slot_capabilities = pciehpc_reg_get32(ctrl_p,
                    bus_p->bus_pcie_off + PCIE_SLOTCAP);
                slot_p->hs_phy_slot_num =
                    PCIE_SLOTCAP_PHY_SLOT_NUM(slot_capabilities);
        }

        /* platform may not have initialized it */
        if (!slot_p->hs_phy_slot_num) {
                PCIE_DBG("%s#%d: Invalid slot number!\n",
                    ddi_driver_name(ctrl_p->hc_dip),
                    ddi_get_instance(ctrl_p->hc_dip));
                slot_p->hs_phy_slot_num = pciehpc_reg_get8(ctrl_p,
                    PCI_BCNF_SECBUS);
                invalid_slotnum = 1;
        }
        slot_p->hs_info.cn_num = slot_p->hs_phy_slot_num;
        slot_p->hs_info.cn_num_dpd_on = DDI_HP_CN_NUM_NONE;

        /*
         * construct the slot_name:
         *      if "slot-names" property exists then use that name
         *      else if valid slot number exists then it is "pcie<slot-num>".
         *      else it will be "pcie<sec-bus-number>dev0"
         */
        if (ddi_getlongprop(DDI_DEV_T_ANY, ctrl_p->hc_dip, DDI_PROP_DONTPASS,
            "slot-names", (caddr_t)&slotname_data, &len) == DDI_PROP_SUCCESS) {
                char tmp_name[256];

                /*
                 * Note: for PCI-E slots, the device number is always 0 so the
                 * first (and only) string is the slot name for this slot.
                 */
                (void) snprintf(tmp_name, sizeof (tmp_name),
                    (char *)slotname_data + 4);
                slot_p->hs_info.cn_name = ddi_strdup(tmp_name, KM_SLEEP);
                kmem_free(slotname_data, len);
        } else {
                if (invalid_slotnum) {
                        /* use device number ie. 0 */
                        slot_p->hs_info.cn_name = ddi_strdup("pcie0",
                            KM_SLEEP);
                } else {
                        char tmp_name[256];

                        (void) snprintf(tmp_name, sizeof (tmp_name), "pcie%d",
                            slot_p->hs_phy_slot_num);
                        slot_p->hs_info.cn_name = ddi_strdup(tmp_name,
                            KM_SLEEP);
                }
        }
}

/*
 * Read/Write access to HPC registers. If platform nexus has non-standard
 * HPC access mechanism then regops functions are used to do reads/writes.
 */
uint8_t
pciehpc_reg_get8(pcie_hp_ctrl_t *ctrl_p, uint_t off)
{
        if (ctrl_p->hc_regops.get != NULL) {
                return ((uint8_t)ctrl_p->hc_regops.get(
                    ctrl_p->hc_regops.cookie, (off_t)off));
        } else {
                pcie_bus_t      *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);

                return (pci_config_get8(bus_p->bus_cfg_hdl, off));
        }
}

uint16_t
pciehpc_reg_get16(pcie_hp_ctrl_t *ctrl_p, uint_t off)
{
        if (ctrl_p->hc_regops.get != NULL) {
                return ((uint16_t)ctrl_p->hc_regops.get(
                    ctrl_p->hc_regops.cookie, (off_t)off));
        } else {
                pcie_bus_t      *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);

                return (pci_config_get16(bus_p->bus_cfg_hdl, off));
        }
}

uint32_t
pciehpc_reg_get32(pcie_hp_ctrl_t *ctrl_p, uint_t off)
{
        if (ctrl_p->hc_regops.get != NULL) {
                return ((uint32_t)ctrl_p->hc_regops.get(
                    ctrl_p->hc_regops.cookie, (off_t)off));
        } else {
                pcie_bus_t      *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);

                return (pci_config_get32(bus_p->bus_cfg_hdl, off));
        }
}

void
pciehpc_reg_put8(pcie_hp_ctrl_t *ctrl_p, uint_t off, uint8_t val)
{
        if (ctrl_p->hc_regops.put != NULL) {
                ctrl_p->hc_regops.put(ctrl_p->hc_regops.cookie,
                    (off_t)off, (uint_t)val);
        } else {
                pcie_bus_t      *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);

                pci_config_put8(bus_p->bus_cfg_hdl, off, val);
        }
}

void
pciehpc_reg_put16(pcie_hp_ctrl_t *ctrl_p, uint_t off, uint16_t val)
{
        if (ctrl_p->hc_regops.put != NULL) {
                ctrl_p->hc_regops.put(ctrl_p->hc_regops.cookie,
                    (off_t)off, (uint_t)val);
        } else {
                pcie_bus_t      *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);

                pci_config_put16(bus_p->bus_cfg_hdl, off, val);
        }
}

void
pciehpc_reg_put32(pcie_hp_ctrl_t *ctrl_p, uint_t off, uint32_t val)
{
        if (ctrl_p->hc_regops.put != NULL) {
                ctrl_p->hc_regops.put(ctrl_p->hc_regops.cookie,
                    (off_t)off, (uint_t)val);
        } else {
                pcie_bus_t      *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);

                pci_config_put32(bus_p->bus_cfg_hdl, off, val);
        }
}

/*
 * ************************************************************************
 * ***  Local functions (called within this file)
 * ***  PCIe Native Hotplug mode specific functions
 * ************************************************************************
 */

static uint8_t
pciehpc_led_state_to_pcie(pcie_hp_led_state_t state)
{
        switch (state) {
        case PCIE_HP_LED_ON:
                return (PCIE_SLOTCTL_INDICATOR_STATE_ON);
        case PCIE_HP_LED_BLINK:
                return (PCIE_SLOTCTL_INDICATOR_STATE_BLINK);
        case PCIE_HP_LED_OFF:
        default:
                return (PCIE_SLOTCTL_INDICATOR_STATE_OFF);
        }
}

/*
 * This iterates over the logical led states that we have (in descending order)
 * and computes the resulting state that a given LED should have. User overrides
 * are applied elsewhere.
 */
static pcie_hp_led_state_t
pciehpc_calc_logical_led(pcie_hp_slot_t *slot_p, pciehpc_led_plat_id_t led)
{
        pcie_hp_led_state_t ret = PCIE_HP_LED_OFF;

        for (uint_t i = PCIEHPC_LED_NSTATES; i > 0; i--) {
                const uint_t idx = i - 1;
                if (!slot_p->hs_led_plat_en[idx])
                        continue;

                switch (slot_p->hs_led_plat_conf[idx].plps_acts[led]) {
                case PCIE_HLA_PASS:
                        continue;
                case PCIE_HLA_OFF:
                        return (PCIE_HP_LED_OFF);
                case PCIE_HLA_ON:
                        return (PCIE_HP_LED_ON);
                case PCIE_HLA_BLINK:
                        return (PCIE_HP_LED_BLINK);
                }
        }

        return (ret);
}

static void
pciehpc_sync_leds_to_hw(pcie_hp_slot_t *slot_p)
{
        pcie_hp_led_state_t     power, attn;
        uint16_t                orig_ctrl, ctrl;
        pcie_hp_ctrl_t          *ctrl_p = slot_p->hs_ctrl;
        pcie_bus_t              *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);

        ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex));


        if (slot_p->hs_power_usr_ovr) {
                power = slot_p->hs_power_usr_ovr_state;
        } else {
                power = pciehpc_calc_logical_led(slot_p, PCIEHPC_PLAT_ID_POWER);
        }

        if (slot_p->hs_attn_usr_ovr) {
                attn = slot_p->hs_attn_usr_ovr_state;
        } else {
                attn = pciehpc_calc_logical_led(slot_p, PCIEHPC_PLAT_ID_ATTN);
        }

        slot_p->hs_power_led_state = power;
        slot_p->hs_attn_led_state = attn;
        orig_ctrl = ctrl = pciehpc_reg_get16(ctrl_p, bus_p->bus_pcie_off +
            PCIE_SLOTCTL);

        ctrl = pcie_slotctl_attn_indicator_set(ctrl,
            pciehpc_led_state_to_pcie(attn));
        ctrl = pcie_slotctl_pwr_indicator_set(ctrl,
            pciehpc_led_state_to_pcie(power));
        if (orig_ctrl != ctrl) {
                pciehpc_issue_hpc_command(ctrl_p, ctrl);
        }
}

/*
 * Initialize HPC hardware, install interrupt handler, etc. It doesn't
 * enable hot plug interrupts.
 *
 * (Note: It is called only from pciehpc_init().)
 */
static int
pciehpc_hpc_init(pcie_hp_ctrl_t *ctrl_p)
{
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);
        uint16_t        reg;

        /* read the Slot Control Register */
        reg = pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCTL);

        /* disable all interrupts */
        reg &= ~(PCIE_SLOTCTL_INTR_MASK);
        pciehpc_reg_put16(ctrl_p, bus_p->bus_pcie_off +
            PCIE_SLOTCTL, reg);

        /* clear any interrupt status bits */
        reg = pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTSTS);
        pciehpc_reg_put16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTSTS, reg);

        return (DDI_SUCCESS);
}

/*
 * Uninitialize HPC hardware, uninstall interrupt handler, etc.
 *
 * (Note: It is called only from pciehpc_uninit().)
 */
static int
pciehpc_hpc_uninit(pcie_hp_ctrl_t *ctrl_p)
{
        /* disable interrupts */
        (void) pciehpc_disable_intr(ctrl_p);

        return (DDI_SUCCESS);
}

/*
 * Initialize the PCIe LED tracking and policy.
 */
void
pciehpc_led_init(pcie_hp_slot_t *slot_p)
{
        int vid, did, subvid, subsys;
        pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl;

        slot_p->hs_power_usr_ovr = false;
        slot_p->hs_attn_usr_ovr = false;
        slot_p->hs_power_usr_ovr_state = PCIE_HP_LED_OFF;
        slot_p->hs_attn_usr_ovr_state = PCIE_HP_LED_OFF;
        bzero(slot_p->hs_led_plat_en, sizeof (slot_p->hs_led_plat_en));

        slot_p->hs_led_plat_conf = pciehpc_std_leds;

        vid = ddi_prop_get_int(DDI_DEV_T_ANY, ctrl_p->hc_dip, DDI_PROP_DONTPASS,
            "vendor-id", PCI_EINVAL16);
        did = ddi_prop_get_int(DDI_DEV_T_ANY, ctrl_p->hc_dip, DDI_PROP_DONTPASS,
            "device-id", PCI_EINVAL16);
        subvid = ddi_prop_get_int(DDI_DEV_T_ANY, ctrl_p->hc_dip,
            DDI_PROP_DONTPASS, "subsystem-vendor-id", PCI_EINVAL16);
        subsys = ddi_prop_get_int(DDI_DEV_T_ANY, ctrl_p->hc_dip,
            DDI_PROP_DONTPASS, "subsystem-id", PCI_EINVAL16);
        for (size_t i = 0; i < ARRAY_SIZE(pciehpc_led_plat_map); i++) {
                if (vid == pciehpc_led_plat_map[i].plpm_vid &&
                    did == pciehpc_led_plat_map[i].plpm_did &&
                    subvid == pciehpc_led_plat_map[i].plpm_svid &&
                    subsys == pciehpc_led_plat_map[i].plpm_ssys) {
                        slot_p->hs_led_plat_conf =
                            pciehpc_led_plat_map[i].plpm_map;
                        break;
                }
        }

        /*
         * Finally, enable the base state for the slot which is meant to always
         * be true. We'll catch up the rest of the states when we get the slot
         * state there.
         */
        slot_p->hs_led_plat_en[PCIE_LL_BASE] = true;
}

bool
pciehpc_slot_kstat_init(pcie_hp_slot_t *slot_p)
{
        /*
         * Many if not most PCs have firmware bugs that cause multiple slots to
         * end up with the same physical slot number in the slot cap register.
         * This is a clear and direct violation of PCIe4 7.5.3.9, but that
         * doesn't seem to matter to the people who write firmware.  This is
         * going to end up confusing the hotplug subsystem and the time to deal
         * with it is probably before we arrive here, but in order to avoid
         * additional warning messages during boot on such machines, we will use
         * the hotplug-capable bridge's instance number when creating event
         * counter kstats instead of hs_phy_slot_num (or hs_info.cn_num, which
         * is usually the same).  In the unlikely event that the broken firmware
         * has given us unique names (even if not slot numbers) for a slot,
         * we'll still have a unique name for each kstat, just as we will for
         * each AP.  Otherwise the kstat names will at least match the still
         * possibly duplicated AP names.
         */
        int inst = ddi_get_instance(slot_p->hs_ctrl->hc_dip);

        /*
         * Although the interrupts we are counting are generated by the hotplug
         * controller, they are conceptually associated with the slot so we
         * store them with it.  The PCIe hotplug controller as currently defined
         * supports only a single slot (0), but in principle a future controller
         * might support multiple slots and we would have some way of
         * determining the set of events that have been detected for each slot
         * even the state changes for all slots share a single interrupt.
         *
         * The statistics are persistent because we want to keep counting them
         * over the entire lifetime of this kernel; if the slot's occupant is
         * removed and that leads to pciehpc_uninit() being called, we don't
         * want to lose the previous statistics.  Indeed, observing what led up
         * to such an event is a key purpose of this infrastructure.
         */
        slot_p->hs_kstat = kstat_create_zone("pcie", inst,
            slot_p->hs_info.cn_name, "controller", KSTAT_TYPE_NAMED,
            sizeof (pciehpc_stat_data_t) / sizeof (kstat_named_t),
            KSTAT_FLAG_PERSISTENT, GLOBAL_ZONEID);

        if (slot_p->hs_kstat == NULL) {
                PCIE_DBG("pciehpc_slot_kstat_init: %d:%s kstat_create failed",
                    inst, slot_p->hs_info.cn_name);
                return (false);
        }

        slot_p->hs_stat_data = (pciehpc_stat_data_t *)slot_p->hs_kstat->ks_data;

        /*
         * We would like to report only the set of kstats that correspond to the
         * events for which the controller advertises support.  However, it is
         * possible for a buggy controller to set status bits that correspond to
         * bits that aren't set in the capabilities register.  If this were to
         * occur, we'll still increment the counters and the kstat framework
         * doesn't really guarantee that we're allowed to do so.  Therefore we
         * will initialise all the counters, even those that should not or
         * cannot have meaningful values for this slot.
         */
        kstat_named_init(&slot_p->hs_stat_data->psd_attnsw_count,
            "attention_switch_count", KSTAT_DATA_UINT64);
        kstat_named_init(&slot_p->hs_stat_data->psd_attnsw_ts,
            "attention_switch_ts", KSTAT_DATA_UINT64);
        kstat_named_init(&slot_p->hs_stat_data->psd_pwrflt_count,
            "power_fault_count", KSTAT_DATA_UINT64);
        kstat_named_init(&slot_p->hs_stat_data->psd_pwrflt_ts,
            "power_fault_ts", KSTAT_DATA_UINT64);
        kstat_named_init(&slot_p->hs_stat_data->psd_mrl_chg_count,
            "mrl_change_count", KSTAT_DATA_UINT64);
        kstat_named_init(&slot_p->hs_stat_data->psd_mrl_chg_ts,
            "mrl_change_ts", KSTAT_DATA_UINT64);
        kstat_named_init(&slot_p->hs_stat_data->psd_pres_chg_count,
            "presence_change_count", KSTAT_DATA_UINT64);
        kstat_named_init(&slot_p->hs_stat_data->psd_pres_chg_ts,
            "presence_change_ts", KSTAT_DATA_UINT64);
        kstat_named_init(&slot_p->hs_stat_data->psd_cmd_cpl_count,
            "command_completion_count", KSTAT_DATA_UINT64);
        kstat_named_init(&slot_p->hs_stat_data->psd_cmd_cpl_ts,
            "command_completion_ts", KSTAT_DATA_UINT64);
        kstat_named_init(&slot_p->hs_stat_data->psd_dll_chg_count,
            "dll_active_change_count", KSTAT_DATA_UINT64);
        kstat_named_init(&slot_p->hs_stat_data->psd_dll_chg_ts,
            "dll_active_change_ts", KSTAT_DATA_UINT64);
        kstat_named_init(&slot_p->hs_stat_data->psd_intr_count,
            "interrupt_count", KSTAT_DATA_UINT64);
        kstat_named_init(&slot_p->hs_stat_data->psd_intr_ts,
            "interrupt_ts", KSTAT_DATA_UINT64);

        kstat_install(slot_p->hs_kstat);

        return (true);
}

/*
 * Setup slot information for use with DDI HP framework. Per the theory
 * statement, this is where we need to go through and look at whether or not we
 * have a child and whether or not we want the 1s later timeout to get things
 * into a reasonable state.
 */
static int
pciehpc_slotinfo_init(pcie_hp_ctrl_t *ctrl_p)
{
        uint32_t        slot_capabilities, link_capabilities;
        pcie_hp_slot_t  *slot_p = ctrl_p->hc_slots[0];
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);
        boolean_t       have_child;

        /*
         * First we look to see if we have any children at all. If we do, then
         * we assume that things were initialized prior to our existence as
         * discussed by state initialization (2).
         */
        ndi_devi_enter(ctrl_p->hc_dip);
        have_child = ddi_get_child(ctrl_p->hc_dip) != NULL;
        ndi_devi_exit(ctrl_p->hc_dip);

        /*
         * There's no expectation for two threads to be here at the same time:
         * we arrive here through the attach path and the DDI guarantees
         * exclusion.  We take the mutex because some of the functions we call
         * assert that we have it.
         */
        mutex_enter(&ctrl_p->hc_mutex);
        /*
         * setup DDI HP framework slot information structure
         */
        slot_p->hs_device_num = 0;

        slot_p->hs_info.cn_type = DDI_HP_CN_TYPE_PCIE;
        slot_p->hs_info.cn_type_str = (ctrl_p->hc_regops.get == NULL) ?
            PCIE_NATIVE_HP_TYPE : PCIE_PROP_HP_TYPE;
        slot_p->hs_info.cn_child = NULL;

        slot_p->hs_minor =
            PCI_MINOR_NUM(ddi_get_instance(ctrl_p->hc_dip),
            slot_p->hs_device_num);
        slot_p->hs_condition = AP_COND_UNKNOWN;

        /* read Slot Capabilities Register */
        slot_capabilities = pciehpc_reg_get32(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCAP);

        /* set slot-name/slot-number info */
        pciehpc_set_slot_name(ctrl_p);

        /* check if Attn Button present */
        ctrl_p->hc_has_attn = (slot_capabilities & PCIE_SLOTCAP_ATTN_BUTTON) ?
            B_TRUE : B_FALSE;

        /* check if Manual Retention Latch sensor present */
        ctrl_p->hc_has_mrl = (slot_capabilities & PCIE_SLOTCAP_MRL_SENSOR) ?
            B_TRUE : B_FALSE;

        /*
         * Contrary to what one might expect, not all systems actually have
         * power control despite having hot-swap capabilities. This is most
         * commonly due to the Enterprise SSD specification which doesn't call
         * for power-control in the PCIe native hotplug implementation.
         */
        ctrl_p->hc_has_pwr = (slot_capabilities &
            PCIE_SLOTCAP_POWER_CONTROLLER) ? B_TRUE: B_FALSE;

        /*
         * PCI-E version 1.1 defines EMI Lock Present bit
         * in Slot Capabilities register. Check for it.
         */
        ctrl_p->hc_has_emi_lock = (slot_capabilities &
            PCIE_SLOTCAP_EMI_LOCK_PRESENT) ? B_TRUE : B_FALSE;

        link_capabilities = pciehpc_reg_get32(ctrl_p,
            bus_p->bus_pcie_off + PCIE_LINKCAP);
        ctrl_p->hc_dll_active_rep = (link_capabilities &
            PCIE_LINKCAP_DLL_ACTIVE_REP_CAPABLE) ? B_TRUE : B_FALSE;
        if (ctrl_p->hc_dll_active_rep)
                cv_init(&slot_p->hs_dll_active_cv, NULL, CV_DRIVER, NULL);

        /* setup thread for handling ATTN button events */
        if (ctrl_p->hc_has_attn) {
                PCIE_DBG("pciehpc_slotinfo_init: setting up ATTN button event "
                    "handler thread for slot %d\n", slot_p->hs_phy_slot_num);

                cv_init(&slot_p->hs_attn_btn_cv, NULL, CV_DRIVER, NULL);
                slot_p->hs_attn_btn_pending = B_FALSE;
                slot_p->hs_attn_btn_threadp = thread_create(NULL, 0,
                    pciehpc_attn_btn_handler,
                    (void *)ctrl_p, 0, &p0, TS_RUN, minclsyspri);
                slot_p->hs_attn_btn_thread_exit = B_FALSE;
        }

        /*
         * Initialize our LED state tracking.
         */
        pciehpc_led_init(slot_p);

        /* get current slot state from the hw */
        slot_p->hs_info.cn_state = DDI_HP_CN_STATE_EMPTY;
        pciehpc_get_slot_state(slot_p);

        /*
         * If the kernel has enumerated a device, note that we have performed
         * the enabled transition.
         */
        if (slot_p->hs_info.cn_state == DDI_HP_CN_STATE_POWERED &&
            have_child) {
                slot_p->hs_info.cn_state = DDI_HP_CN_STATE_ENABLED;
        }

        if (slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_POWERED)
                slot_p->hs_led_plat_en[PCIE_LL_POWERED] = true;

        if (slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_ENABLED)
                slot_p->hs_condition = AP_COND_OK;
        mutex_exit(&ctrl_p->hc_mutex);

        if (!pciehpc_slot_kstat_init(slot_p)) {
                (void) pciehpc_slotinfo_uninit(ctrl_p);
                return (DDI_FAILURE);
        }

        return (DDI_SUCCESS);
}

void
pciehpc_slot_kstat_fini(pcie_hp_slot_t *slot_p)
{
        if (slot_p->hs_kstat != NULL) {
                kstat_delete(slot_p->hs_kstat);
                slot_p->hs_kstat = NULL;
                slot_p->hs_stat_data = NULL;
        }
}

static int
pciehpc_slotinfo_uninit(pcie_hp_ctrl_t *ctrl_p)
{
        pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0];

        if (slot_p->hs_attn_btn_threadp != NULL) {
                mutex_enter(&ctrl_p->hc_mutex);
                slot_p->hs_attn_btn_thread_exit = B_TRUE;
                cv_signal(&slot_p->hs_attn_btn_cv);
                PCIE_DBG("pciehpc_slotinfo_uninit: "
                    "waiting for ATTN thread exit\n");
                cv_wait(&slot_p->hs_attn_btn_cv, &ctrl_p->hc_mutex);
                PCIE_DBG("pciehpc_slotinfo_uninit: ATTN thread exit\n");
                cv_destroy(&slot_p->hs_attn_btn_cv);
                slot_p->hs_attn_btn_threadp = NULL;
                mutex_exit(&ctrl_p->hc_mutex);
        }

        if (ctrl_p->hc_dll_active_rep)
                cv_destroy(&slot_p->hs_dll_active_cv);
        if (slot_p->hs_info.cn_name)
                kmem_free(slot_p->hs_info.cn_name,
                    strlen(slot_p->hs_info.cn_name) + 1);

        pciehpc_slot_kstat_fini(slot_p);

        return (DDI_SUCCESS);
}

/*
 * This is the synchronization function that is discussed in the 'State
 * Initialization' portion of the theory statement in this file. It is
 * responsible for trying to make sure that devices are in a usable state during
 * a potentially turbulent start up sequence.
 */
static void
pciehpc_state_sync(void *arg)
{
        pciehpc_sync_task_t *sync = arg;
        pcie_hp_ctrl_t *ctrl_p = sync->pst_ctrl;
        dev_info_t *dip = ctrl_p->hc_dip;
        pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0];

        mutex_enter(&ctrl_p->hc_mutex);
        if (ctrl_p->hc_startup_sync == TASKQID_INVALID) {
                mutex_exit(&ctrl_p->hc_mutex);
                kmem_free(sync, sizeof (pciehpc_sync_task_t));
                return;
        }

        if ((ctrl_p->hc_flags & PCIE_HP_SYNC_PENDING) == 0) {
                goto done;
        }

        cmn_err(CE_NOTE, "pciehpc (%s%d): synchronizing state in slot %s to "
            "0x%x", ddi_driver_name(dip), ddi_get_instance(dip),
            slot_p->hs_info.cn_name, sync->pst_targ);

        ASSERT3U(slot_p->hs_info.cn_state, ==, sync->pst_cur);

        ctrl_p->hc_flags &= ~PCIE_HP_SYNC_PENDING;
        ctrl_p->hc_flags |= PCIE_HP_SYNC_RUNNING;
        mutex_exit(&ctrl_p->hc_mutex);

        (void) ndi_hp_state_change_req(dip, slot_p->hs_info.cn_name,
            sync->pst_targ, DDI_HP_REQ_SYNC);

        /*
         * Now that we're done with operating this way, go ahead and clear
         * things up.
         */
        mutex_enter(&ctrl_p->hc_mutex);
done:
        ctrl_p->hc_flags &= ~PCIE_HP_SYNC_RUNNING;
        ctrl_p->hc_startup_sync = TASKQID_INVALID;
        mutex_exit(&ctrl_p->hc_mutex);
        kmem_free(sync, sizeof (pciehpc_sync_task_t));
}

static void
pciehpc_dispatch_state_sync(pcie_hp_ctrl_t *ctrl_p, ddi_hp_cn_state_t targ)
{
        pciehpc_sync_task_t *sync;
        pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0];

        ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex));
        sync = kmem_alloc(sizeof (pciehpc_sync_task_t), KM_SLEEP);
        sync->pst_ctrl = ctrl_p;
        sync->pst_targ = targ;
        sync->pst_cur = slot_p->hs_info.cn_state;

        ctrl_p->hc_flags |= PCIE_HP_SYNC_PENDING;
        ctrl_p->hc_startup_sync = taskq_dispatch(system_taskq,
            pciehpc_state_sync, sync, TQ_SLEEP);
}

/*
 * The point of this is to forcibly set the logical LED states to the
 * corresponding ones that make sense for the corresponding hotplug state.
 */
static void
pciehpc_enable_state_sync_leds(pcie_hp_ctrl_t *ctrl_p)
{
        pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0];
        ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex));

        bzero(slot_p->hs_led_plat_en, sizeof (slot_p->hs_led_plat_en));

        switch (slot_p->hs_info.cn_state) {
        case DDI_HP_CN_STATE_ENABLED:
        case DDI_HP_CN_STATE_POWERED:
                slot_p->hs_led_plat_en[PCIE_LL_POWERED] = true;
                slot_p->hs_led_plat_en[PCIE_LL_BASE] = true;
                break;
        case DDI_HP_CN_STATE_PRESENT:
        case DDI_HP_CN_STATE_EMPTY:
                slot_p->hs_led_plat_en[PCIE_LL_BASE] = true;
                break;
        default:
                dev_err(ctrl_p->hc_dip, CE_PANIC, "encountered invalid "
                    "connector state: 0x%x", slot_p->hs_info.cn_state);
                break;
        }

        pciehpc_sync_leds_to_hw(slot_p);
}

/*
 * We have just enabled interrupts and cleared any changes that may or may not
 * have been valid from the hardware perspective. There are a few key
 * assumptions that we're making right now as discussed in the theory statement:
 *
 *  o If we are currently enabled, then we know that we have children and
 *    nothing has changed from our init.
 *  o Because we have just enabled interrupts, but have not relinquished our
 *    exclusion on the controller hardware, nothing else could have come in and
 *    started reacting to an actual change.
 *  o Even though someone could come and call DDI_HPOP_CN_GET_STATE, that could
 *    not transition us to enabled yet.
 *  o Because interrupt enable is still called in attach context, we cannot have
 *    a user accessing the node and requesting a state change.
 *
 * Finally there are a few things that we need to be mindful of. We must set any
 * updates to the state prior to calling into any request to update the LED
 * state as that may rely on getting an async callback.
 */
static void
pciehpc_enable_state_sync(pcie_hp_ctrl_t *ctrl_p)
{
        pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);
        pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0];
        uint16_t control, status;
        ddi_hp_cn_state_t curr_state, online_targ;

        online_targ = (pcie_auto_online != 0) ?  DDI_HP_CN_STATE_ENABLED :
            DDI_HP_CN_STATE_PRESENT;
        ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex));

        /*
         * We manually compute the status from a single read of things rather
         * than go through and use pciehpc_get_slot_state(). This is important
         * to make sure that we can get hardware in sync with the kernel.
         */
        curr_state = slot_p->hs_info.cn_state;
        control = pciehpc_reg_get16(ctrl_p, bus_p->bus_pcie_off + PCIE_SLOTCTL);
        status = pciehpc_reg_get16(ctrl_p, bus_p->bus_pcie_off + PCIE_SLOTSTS);

        if (!(status & PCIE_SLOTSTS_PRESENCE_DETECTED)) {
                switch (curr_state) {
                case DDI_HP_CN_STATE_ENABLED:
                        pciehpc_dispatch_state_sync(ctrl_p,
                            DDI_HP_CN_STATE_EMPTY);
                        break;
                case DDI_HP_CN_STATE_EMPTY:
                case DDI_HP_CN_STATE_PRESENT:
                case DDI_HP_CN_STATE_POWERED:
                        if (ctrl_p->hc_has_pwr &&
                            (control & PCIE_SLOTCTL_PWR_CONTROL) == 0) {
                                slot_p->hs_info.cn_state =
                                    DDI_HP_CN_STATE_POWERED;
                                pciehpc_dispatch_state_sync(ctrl_p,
                                    DDI_HP_CN_STATE_EMPTY);
                        } else {
                                slot_p->hs_info.cn_state =
                                    DDI_HP_CN_STATE_EMPTY;
                                pciehpc_enable_state_sync_leds(ctrl_p);
                        }
                        break;
                default:
                        dev_err(ctrl_p->hc_dip, CE_PANIC, "encountered invalid "
                            "connector state: 0x%x", curr_state);
                        break;
                }

                return;
        }

        /*
         * If we don't have a power controller, don't bother looking at this.
         * There's nothing we can really do and we'll let the main case attempt
         * to online this.
         */
        if (ctrl_p->hc_has_pwr && (control & PCIE_SLOTCTL_PWR_CONTROL) != 0) {
                switch (curr_state) {
                case DDI_HP_CN_STATE_EMPTY:
                        pciehpc_dispatch_state_sync(ctrl_p, online_targ);
                        break;
                case DDI_HP_CN_STATE_PRESENT:
                        if (curr_state == online_targ) {
                                pciehpc_enable_state_sync_leds(ctrl_p);
                                break;
                        }
                        pciehpc_dispatch_state_sync(ctrl_p, online_targ);
                        break;
                case DDI_HP_CN_STATE_POWERED:
                        dev_err(ctrl_p->hc_dip, CE_WARN, "device powered off "
                            "somehow from prior powered state, attempting "
                            "recovery");
                        slot_p->hs_info.cn_state = DDI_HP_CN_STATE_PRESENT;
                        if (online_targ > DDI_HP_CN_STATE_PRESENT) {
                                pciehpc_dispatch_state_sync(ctrl_p,
                                    online_targ);
                        } else {
                                pciehpc_enable_state_sync_leds(ctrl_p);
                        }
                        break;
                case DDI_HP_CN_STATE_ENABLED:
                        /*
                         * This case seems very strange. We had a device that we
                         * enumerated and was online and something that wasn't
                         * us powerd off the slot. This is possibly a
                         * recoverable state, but it seems hard to understand
                         * what the proper path to go here is. While we could
                         * try to unprobe it, it's a real mystery how that
                         * happened and even that path might not be safe. If
                         * this kind of state is actually encountered in the
                         * wild and during this startup window of the device,
                         * then we'll need to figure out how to handle it there.
                         * Odds are it's either a software bug in this driver or
                         * something is going very wrong with hardware and as
                         * such, it's hard to predict what the solution is.
                         */
                        dev_err(ctrl_p->hc_dip, CE_PANIC, "device powered off "
                            "somehow from prior enabled state unable to "
                            "recover");
                        break;
                default:
                        dev_err(ctrl_p->hc_dip, CE_PANIC, "encountered invalid "
                            "connector state: 0x%x", curr_state);
                }
                return;
        }

        /*
         * While we should consider checking for a power fault here, if it was
         * injected just after we cleared everythign as part of interrupt
         * enable, then we'll get that injected normally and allow that to
         * happen naturally.
         */

        switch (curr_state) {
        case DDI_HP_CN_STATE_ENABLED:
                pciehpc_enable_state_sync_leds(ctrl_p);
                break;
        case DDI_HP_CN_STATE_POWERED:
        case DDI_HP_CN_STATE_EMPTY:
        case DDI_HP_CN_STATE_PRESENT:
                if (curr_state == online_targ) {
                        pciehpc_enable_state_sync_leds(ctrl_p);
                } else {
                        pciehpc_dispatch_state_sync(ctrl_p, online_targ);
                }
                break;
        default:
                dev_err(ctrl_p->hc_dip, CE_PANIC, "encountered invalid "
                    "connector state: 0x%x", curr_state);
        }
}

/*
 * Enable hot plug interrupts.
 * Note: this is only for Native hot plug mode.
 */
static int
pciehpc_enable_intr(pcie_hp_ctrl_t *ctrl_p)
{
        pcie_hp_slot_t  *slot_p = ctrl_p->hc_slots[0];
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);
        uint16_t        reg;
        uint16_t        intr_mask = PCIE_SLOTCTL_INTR_MASK;

        mutex_enter(&ctrl_p->hc_mutex);

        /*
         * power fault detection interrupt is enabled only
         * when the slot is powered ON
         */
        if (slot_p->hs_info.cn_state < DDI_HP_CN_STATE_POWERED)
                intr_mask &= ~PCIE_SLOTCTL_PWR_FAULT_EN;

        /*
         * enable interrupt sources but leave the top-level
         * interrupt disabled. some sources may generate a
         * spurrious event when they are first enabled.
         * by leaving the top-level interrupt disabled, those
         * can be cleared first.
         */
        reg = pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCTL);
        pciehpc_reg_put16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCTL,
            reg | (intr_mask & ~PCIE_SLOTCTL_HP_INTR_EN));

        /* clear any interrupt status bits */
        reg = pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTSTS);
        pciehpc_reg_put16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTSTS, reg);

        /* enable top-level interrupt */
        reg = pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCTL);
        pciehpc_reg_put16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCTL,
            reg | intr_mask);

        /*
         * Now, and only now that interrupts are enabled can we go back and
         * perform state synchronization that is required of the system. This
         * happens in a few steps. We have previously checked to see if we
         * should be in the ENABLED or POWERED state. However, it is quite
         * possible that hardware was left at its PCIe default of power being
         * enabled, even if no device is present. Because we have interrupts
         * enabled, if there is a change after this point, then it will be
         * caught. See the theory statement for more information.
         */
        pciehpc_enable_state_sync(ctrl_p);
        mutex_exit(&ctrl_p->hc_mutex);

        return (DDI_SUCCESS);
}

/*
 * Disable hot plug interrupts.
 * Note: this is only for Native hot plug mode.
 */
static int
pciehpc_disable_intr(pcie_hp_ctrl_t *ctrl_p)
{
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);
        uint16_t        reg;

        /* read the Slot Control Register */
        reg = pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCTL);

        /* disable all interrupts */
        reg &= ~(PCIE_SLOTCTL_INTR_MASK);
        pciehpc_reg_put16(ctrl_p, bus_p->bus_pcie_off + PCIE_SLOTCTL, reg);

        /* clear any interrupt status bits */
        reg = pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTSTS);
        pciehpc_reg_put16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTSTS, reg);

        return (DDI_SUCCESS);
}

/*
 * Allocate a new hotplug controller and slot structures for HPC
 * associated with this dip.
 */
static pcie_hp_ctrl_t *
pciehpc_create_controller(dev_info_t *dip)
{
        pcie_hp_ctrl_t  *ctrl_p;
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(dip);

        ctrl_p = kmem_zalloc(sizeof (pcie_hp_ctrl_t), KM_SLEEP);
        ctrl_p->hc_dip = dip;

        /* Allocate a new slot structure. */
        ctrl_p->hc_slots[0] = kmem_zalloc(sizeof (pcie_hp_slot_t), KM_SLEEP);
        ctrl_p->hc_slots[0]->hs_num = 0;
        ctrl_p->hc_slots[0]->hs_ctrl = ctrl_p;

        /* Initialize the interrupt mutex */
        mutex_init(&ctrl_p->hc_mutex, NULL, MUTEX_DRIVER,
            (void *)PCIE_INTR_PRI);

        /* Initialize synchronization conditional variable */
        cv_init(&ctrl_p->hc_cmd_comp_cv, NULL, CV_DRIVER, NULL);
        ctrl_p->hc_cmd_pending = B_FALSE;

        bus_p->bus_hp_curr_mode = PCIE_NATIVE_HP_MODE;
        PCIE_SET_HP_CTRL(dip, ctrl_p);

        return (ctrl_p);
}

/*
 * Remove the HPC controller and slot structures
 */
static void
pciehpc_destroy_controller(dev_info_t *dip)
{
        pcie_hp_ctrl_t  *ctrl_p;
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(dip);

        /* get the soft state structure for this dip */
        if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) == NULL)
                return;

        PCIE_SET_HP_CTRL(dip, NULL);
        bus_p->bus_hp_curr_mode = PCIE_NONE_HP_MODE;

        mutex_destroy(&ctrl_p->hc_mutex);
        cv_destroy(&ctrl_p->hc_cmd_comp_cv);
        kmem_free(ctrl_p->hc_slots[0], sizeof (pcie_hp_slot_t));
        kmem_free(ctrl_p, sizeof (pcie_hp_ctrl_t));
}

/*
 * Register the PCI-E hot plug slot with DDI HP framework.
 */
static int
pciehpc_register_slot(pcie_hp_ctrl_t *ctrl_p)
{
        pcie_hp_slot_t  *slot_p = ctrl_p->hc_slots[0];
        dev_info_t      *dip = ctrl_p->hc_dip;

        /* register the slot with DDI HP framework */
        if (ndi_hp_register(dip, &slot_p->hs_info) != NDI_SUCCESS) {
                PCIE_DBG("pciehpc_register_slot() failed to register slot %d\n",
                    slot_p->hs_phy_slot_num);
                return (DDI_FAILURE);
        }

        pcie_hp_create_occupant_props(dip, makedevice(ddi_driver_major(dip),
            slot_p->hs_minor), slot_p->hs_device_num);

        PCIE_DBG("pciehpc_register_slot(): registered slot %d\n",
            slot_p->hs_phy_slot_num);

        return (DDI_SUCCESS);
}

/*
 * Unregister the PCI-E hot plug slot from DDI HP framework.
 */
static int
pciehpc_unregister_slot(pcie_hp_ctrl_t *ctrl_p)
{
        pcie_hp_slot_t *slot_p = ctrl_p->hc_slots[0];
        dev_info_t      *dip = ctrl_p->hc_dip;

        pcie_hp_delete_occupant_props(dip, makedevice(ddi_driver_major(dip),
            slot_p->hs_minor));

        /* unregister the slot with DDI HP framework */
        if (ndi_hp_unregister(dip, slot_p->hs_info.cn_name) != NDI_SUCCESS) {
                PCIE_DBG("pciehpc_unregister_slot() "
                    "failed to unregister slot %d\n", slot_p->hs_phy_slot_num);
                return (DDI_FAILURE);
        }

        PCIE_DBG("pciehpc_unregister_slot(): unregistered slot %d\n",
            slot_p->hs_phy_slot_num);

        return (DDI_SUCCESS);
}

static pciehpc_slot_power_t
pciehpc_slot_power_state(pcie_hp_slot_t *slot_p)
{
        pcie_hp_ctrl_t  *ctrl_p = slot_p->hs_ctrl;
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);
        uint16_t control, status;
        pciehpc_slot_power_t state = 0;

        ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex));

        if (!ctrl_p->hc_has_pwr) {
                return (PSP_NO_CONTROLLER);
        } else {
                state |= PSP_HAS_CONTROLLER;
        }

        control = pciehpc_reg_get16(ctrl_p, bus_p->bus_pcie_off + PCIE_SLOTCTL);
        status = pciehpc_reg_get16(ctrl_p, bus_p->bus_pcie_off + PCIE_SLOTSTS);

        if ((control & PCIE_SLOTCTL_PWR_CONTROL) != 0)
                state |= PSP_OFF;

        if ((status & PCIE_SLOTSTS_PWR_FAULT_DETECTED) != 0)
                state |= PSP_FAULT;

        return (state);
}

/*
 * Wait for a PCIe slot to be considered active per the PCIe hotplug rules. If
 * there is no DLL active reporting capability then we wait up to 1 second and
 * just assume it was successful. Regardless of whether or not we have explicit
 * power control, the device is still powering on and may not be ready to work.
 */
static boolean_t
pciehpc_slot_wait_for_active(pcie_hp_slot_t *slot_p)
{
        pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl;
        pcie_bus_t *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);

        ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex));

        if (ctrl_p->hc_dll_active_rep) {
                clock_t deadline;
                uint16_t status;

                /* wait 1 sec for the DLL State Changed event */
                status = pciehpc_reg_get16(ctrl_p,
                    bus_p->bus_pcie_off + PCIE_LINKSTS);

                deadline = ddi_get_lbolt() +
                    SEC_TO_TICK(PCIE_HP_DLL_STATE_CHANGE_TIMEOUT);

                while ((status & PCIE_LINKSTS_DLL_LINK_ACTIVE) == 0 &&
                    ddi_get_lbolt() < deadline) {
                        (void) cv_timedwait(&slot_p->hs_dll_active_cv,
                            &ctrl_p->hc_mutex, deadline);

                        /* check Link status */
                        status =  pciehpc_reg_get16(ctrl_p,
                            bus_p->bus_pcie_off +
                            PCIE_LINKSTS);
                }

                if ((status & PCIE_LINKSTS_DLL_LINK_ACTIVE) == 0) {
                        return (B_FALSE);
                }
        } else {
                /* wait 1 sec for link to come up */
                delay(drv_usectohz(1000000));
        }

        return (B_TRUE);
}

/*
 * This takes care of all the logic for trying to verify a slot's state that
 * does not have an explicit power controller. If this is a surprise insertion,
 * we still need to wait for the data link layer to become active even if we
 * don't explicitly control power. We do this in three steps:
 *
 * 1) Verify the slot is powered at least.
 * 2) Wait for the slot to be active.
 * 3) Verify the slot is still powered after that.
 */
static int
pciehpc_slot_noctrl_active(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result)
{
        pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl;

        VERIFY3U(ctrl_p->hc_has_pwr, ==, B_FALSE);
        ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex));

        pciehpc_get_slot_state(slot_p);
        if (slot_p->hs_info.cn_state < DDI_HP_CN_STATE_POWERED) {
                return (DDI_FAILURE);
        }

        /*
         * Regardless of whether this worked or failed we must check the slot
         * state again.
         */
        if (!pciehpc_slot_wait_for_active(slot_p)) {
                cmn_err(CE_WARN, "pciehpc_slot_poweron_noctrl (slot %d): "
                    "device failed to become active", slot_p->hs_phy_slot_num);
                return (DDI_FAILURE);
        }
        pciehpc_get_slot_state(slot_p);
        *result = slot_p->hs_info.cn_state;
        if (slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_POWERED) {
                slot_p->hs_led_plat_en[PCIE_LL_POWERED] = true;
                slot_p->hs_led_plat_en[PCIE_LL_POWER_FAULT] = false;
                slot_p->hs_led_plat_en[PCIE_LL_PROBE_FAILED] = false;
                pciehpc_sync_leds_to_hw(slot_p);
                return (DDI_SUCCESS);
        } else {
                return (DDI_FAILURE);
        }
}

/*
 * Poweron/Enable the slot.
 *
 * Note: This function is called by DDI HP framework at kernel context only
 *
 * We intend for this function to be idempotent.  That is, when we return, if
 * the slot we've been asked to turn on has a device present, and has a power
 * controller, then a successful return guarantees all of the following,
 * regardless of the hardware or software state that existed when called:
 *
 * 1. The power controller enable bit is clear (asserted).
 * 2. If DLL State Change is supported by the bridge, we waited until DLL Active
 *    was asserted; otherwise we waited at least one second after the first
 *    moment we knew for certain that the power controller was enabled.
 * 3. Any power fault that was previously asserted in the status register has
 *    been acknowledged and cleared, allowing detection of subsequent faults if
 *    supported by hardware.
 * 4. The power indicator is on (if it exists).
 * 5. The MRL, if it exists, is latched.
 *
 * If we fail, either this slot has no power control capability or the following
 * guarantees are made:
 *
 * 1. We have attempted to disable the power controller for this slot.
 * 2. We have attempted to disable the power indicator for this slot.
 *
 * In the failure case, *result has undefined contents.  This function does not
 * change the contents of slot_p->hs_info.cn_state.  This allows callers to act
 * upon the previous software state (preserved by this function), the new
 * software state (in *result if successful), and the current hardware state
 * which can be obtained via pciehpc_get_slot_state().
 */
static int
pciehpc_slot_poweron(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result)
{
        pcie_hp_ctrl_t  *ctrl_p = slot_p->hs_ctrl;
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);
        uint16_t        status, control;

        ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex));

        /*
         * If the hardware doesn't have support for a power controller, then
         * that generally means that power is already on or at the least there
         * isn't very much else we can do and the PCIe spec says it's the
         * responsibility of the controller to have turned it on if a device is
         * present.  We don't care whether a device is present in this case,
         * though, because we've been asked to turn on power and we know that we
         * cannot.  Either a device is present and power is already on, in which
         * case calling code can figure that out, or no device is present and
         * we'd fail even if we had a controller.  Either way, we still indicate
         * that is a failure since we can't change it and instead rely on code
         * executing the actual state machine to figure out how to handle this.
         */
        if (!ctrl_p->hc_has_pwr) {
                PCIE_DBG("pciehpc_slot_poweron (slot %d): no power control "
                    "capability, but was asked to power on\n",
                    slot_p->hs_phy_slot_num);
                return (DDI_FAILURE);
        }

        /*
         * We need the current state of the slot control register to figure out
         * whether the power controller is enabled already.  Note that this is
         * not a status bit: it can't tell us whether power is actually on or
         * off, only what the last control input was.  We also grab the status
         * register here as we need several bits from it.
         */
        control = pciehpc_reg_get16(ctrl_p, bus_p->bus_pcie_off + PCIE_SLOTCTL);
        status = pciehpc_reg_get16(ctrl_p, bus_p->bus_pcie_off + PCIE_SLOTSTS);

        /*
         * If there's no device present, we need to fail.
         */
        if (!(status & PCIE_SLOTSTS_PRESENCE_DETECTED)) {
                /* slot is empty */
                PCIE_DBG("pciehpc_slot_poweron (slot %d): slot is empty\n",
                    slot_p->hs_phy_slot_num);
                goto cleanup;
        }

        /*
         * If there's an MRL and it's open, we need to fail.
         */
        if ((ctrl_p->hc_has_mrl) && (status & PCIE_SLOTSTS_MRL_SENSOR_OPEN)) {
                cmn_err(CE_WARN, "pciehpc_slot_poweron (slot %d): MRL switch "
                    "is open", slot_p->hs_phy_slot_num);
                goto cleanup;
        }

        /*
         * The power controller is already on, but we're in a state below
         * POWERED.  This shouldn't happen, but there are any number of ways
         * that it can; we simply note this if debugging and move on.
         */
        if ((control & PCIE_SLOTCTL_PWR_CONTROL) == 0 &&
            slot_p->hs_info.cn_state < DDI_HP_CN_STATE_POWERED) {
                PCIE_DBG("pciehpc_slot_poweron (slot %d): controller is "
                    "already enabled in SW state %d; continuing\n",
                    slot_p->hs_phy_slot_num, slot_p->hs_info.cn_state);
                goto alreadyon;
        }

        /*
         * The power controller has been turned off (which doesn't mean it *is*
         * off), but software thinks it's on.  This is pretty bad, and we
         * probably need to consider doing something here to reset the state
         * machine because upper layers are likely to be confused.  We will
         * nevertheless turn on the controller and hope the right things happen
         * above us.
         */
        if ((control & PCIE_SLOTCTL_PWR_CONTROL) != 0 &&
            slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_POWERED) {
                cmn_err(CE_WARN, "pciehpc_slot_poweron (slot %d): SW state is "
                    "already %d but power controller is disabled; continuing",
                    slot_p->hs_phy_slot_num, slot_p->hs_info.cn_state);
        }

        /*
         * Enabling power to the slot involves:
         *      1. Updating our LED state to indicate the transition is
         *         beginning and clearing out other aspects.
         *      2. Set power control ON in Slot Control Reigster and
         *         wait for Command Completed Interrupt or 1 sec timeout.
         *      3. If Data Link Layer State Changed events are supported
         *         then wait for the event to indicate Data Layer Link
         *         is active. The time out value for this event is 1 second.
         *         This is specified in PCI-E version 1.1.
         *      4. Set power LED to be ON.
         */

        /*
         * If power is already on, we skip indicating a power transition is
         * going on. However, we always try to clear out the error state LEDs at
         * this point.
         */
        slot_p->hs_led_plat_en[PCIE_LL_POWER_TRANSITION] = true;
alreadyon:
        slot_p->hs_led_plat_en[PCIE_LL_PROBE_FAILED] = false;
        slot_p->hs_led_plat_en[PCIE_LL_POWER_FAULT] = false;
        pciehpc_sync_leds_to_hw(slot_p);

        /* 2. set power control to ON */
        control =  pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCTL);
        control &= ~PCIE_SLOTCTL_PWR_CONTROL;
        pciehpc_issue_hpc_command(ctrl_p, control);

        /* 3. wait for DLL State Change event, if it's supported */
        if (!pciehpc_slot_wait_for_active(slot_p))
                goto cleanup;

        /* check power is really turned ON */
        control = pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCTL);

        if (control & PCIE_SLOTCTL_PWR_CONTROL) {
                PCIE_DBG("pciehpc_slot_poweron (slot %d): power controller "
                    "enable was disabled autonomously after SW enable",
                    slot_p->hs_phy_slot_num);

                goto cleanup;
        }

        /* clear power fault status */
        status = pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTSTS);
        status |= PCIE_SLOTSTS_PWR_FAULT_DETECTED;
        pciehpc_reg_put16(ctrl_p, bus_p->bus_pcie_off + PCIE_SLOTSTS,
            status);

        /* enable power fault detection interrupt */
        control |= PCIE_SLOTCTL_PWR_FAULT_EN;
        pciehpc_issue_hpc_command(ctrl_p, control);

        /* 4. Set power LED to be ON */
        slot_p->hs_led_plat_en[PCIE_LL_POWER_TRANSITION] = false;
        slot_p->hs_led_plat_en[PCIE_LL_POWERED] = true;
        pciehpc_sync_leds_to_hw(slot_p);

        /* if EMI is present, turn it ON */
        if (ctrl_p->hc_has_emi_lock) {
                status = pciehpc_reg_get16(ctrl_p,
                    bus_p->bus_pcie_off + PCIE_SLOTSTS);

                if (!(status & PCIE_SLOTSTS_EMI_LOCK_SET)) {
                        control = pciehpc_reg_get16(ctrl_p,
                            bus_p->bus_pcie_off + PCIE_SLOTCTL);
                        control |= PCIE_SLOTCTL_EMI_LOCK_CONTROL;
                        pciehpc_issue_hpc_command(ctrl_p, control);

                        /* wait 1 sec after toggling the state of EMI lock */
                        delay(drv_usectohz(1000000));
                }
        }

        *result = slot_p->hs_info.cn_state = DDI_HP_CN_STATE_POWERED;

        return (DDI_SUCCESS);

cleanup:
        control = pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCTL);

        /* if power is ON, set power control to OFF */
        if ((control & PCIE_SLOTCTL_PWR_CONTROL) == 0) {
                control |= PCIE_SLOTCTL_PWR_CONTROL;
                pciehpc_issue_hpc_command(ctrl_p, control);
        }

        /* set power led to OFF XXX what if HW/FW refused to turn off? */
        slot_p->hs_led_plat_en[PCIE_LL_POWER_TRANSITION] = false;
        slot_p->hs_led_plat_en[PCIE_LL_POWERED] = false;
        pciehpc_sync_leds_to_hw(slot_p);

        return (DDI_FAILURE);
}

/*
 * All the same considerations apply to poweroff; see notes above.
 */
static int
pciehpc_slot_poweroff(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t *result)
{
        pcie_hp_ctrl_t  *ctrl_p = slot_p->hs_ctrl;
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);
        uint16_t        status, control;

        ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex));

        /*
         * Some devices do not have a power controller. In such cases we need to
         * fail any request to power it off. If a device is being pulled, the
         * state will generally have automatically been updated; however, if
         * someone is asking for us to do something via an explicit request,
         * then this will fail.
         */
        if (!ctrl_p->hc_has_pwr) {
                PCIE_DBG("pciehpc_slot_poweroff (slot %d): no power control "
                    "capability, but was asked to power off\n",
                    slot_p->hs_phy_slot_num);
                return (DDI_ENOTSUP);
        }

        /*
         * SW thinks the slot is already powered off.  Note this unexpected
         * condition and continue.
         */
        if (slot_p->hs_info.cn_state < DDI_HP_CN_STATE_POWERED) {
                PCIE_DBG("pciehpc_slot_poweroff (slot %d): SW state is "
                    "already %d; continuing\n",
                    slot_p->hs_phy_slot_num, slot_p->hs_info.cn_state);
        }

        control = pciehpc_reg_get16(ctrl_p, bus_p->bus_pcie_off + PCIE_SLOTCTL);
        status = pciehpc_reg_get16(ctrl_p, bus_p->bus_pcie_off + PCIE_SLOTSTS);

        /*
         * The power controller has been turned off (which doesn't mean it *is*
         * off), but software thinks it's on.  Note this unexpected condition
         * for debugging and continue; we'll do what we can to get the state
         * machines back in sync.
         */
        if ((control & PCIE_SLOTCTL_PWR_CONTROL) != 0 &&
            slot_p->hs_info.cn_state >= DDI_HP_CN_STATE_POWERED) {
                cmn_err(CE_WARN, "pciehpc_slot_poweroff (slot %d): SW state is "
                    "%d but power controller is already disabled; continuing",
                    slot_p->hs_phy_slot_num, slot_p->hs_info.cn_state);
                goto alreadyoff;
        }

        if (!(status & PCIE_SLOTSTS_PRESENCE_DETECTED)) {
                PCIE_DBG("pciehpc_slot_poweroff (slot %d): powering off "
                    "empty slot\n", slot_p->hs_phy_slot_num);
        }

        /*
         * Disable power to the slot involves:
         *      1. Set power LED to blink.
         *      2. Set power control OFF in Slot Control Reigster and
         *         wait for Command Completed Interrupt or 1 sec timeout.
         *      3. Set POWER led and ATTN led to be OFF.
         */

        /*
         * We don't bother indicating a power transition if the device is
         * already off. The act of turning it off does clear out a probe
         * failure, but it doesn't clear out a power fault. That is only
         * reserved for a removal.
         */
        slot_p->hs_led_plat_en[PCIE_LL_POWER_TRANSITION] = true;
alreadyoff:
        slot_p->hs_led_plat_en[PCIE_LL_PROBE_FAILED] = false;
        pciehpc_sync_leds_to_hw(slot_p);

        /* disable power fault detection interrupt */
        control = pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCTL);
        control &= ~PCIE_SLOTCTL_PWR_FAULT_EN;
        pciehpc_issue_hpc_command(ctrl_p, control);

        /* 2. set power control to OFF */
        control = pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCTL);
        control |= PCIE_SLOTCTL_PWR_CONTROL;
        pciehpc_issue_hpc_command(ctrl_p, control);

        /*
         * Make sure our control input has been acknowledged.  Some
         * implementations may clear the control bit if the power controller
         * couldn't be disabled for some reasons, or if firmware decided to
         * disallow our command.
         */
        control = pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCTL);
        if ((control & PCIE_SLOTCTL_PWR_CONTROL) == 0) {
                /*
                 * Well, this is unfortunate: we couldn't turn power off.
                 * XXX Should we turn on the ATTN indicator?  For now we just
                 * log a warning and fail.
                 */
                cmn_err(CE_WARN, "pciehpc_slot_poweroff (slot %d): power "
                    "controller completed our disable command but is still "
                    "enabled", slot_p->hs_phy_slot_num);
                slot_p->hs_led_plat_en[PCIE_LL_POWER_TRANSITION] = false;
                slot_p->hs_led_plat_en[PCIE_LL_POWERED] = true;
                pciehpc_sync_leds_to_hw(slot_p);

                return (DDI_FAILURE);
        }

        /* 3. Set power LED to be OFF */
        slot_p->hs_led_plat_en[PCIE_LL_POWER_TRANSITION] = false;
        slot_p->hs_led_plat_en[PCIE_LL_POWERED] = false;
        pciehpc_sync_leds_to_hw(slot_p);

        /* if EMI is present, turn it OFF */
        if (ctrl_p->hc_has_emi_lock) {
                status =  pciehpc_reg_get16(ctrl_p,
                    bus_p->bus_pcie_off + PCIE_SLOTSTS);

                if (status & PCIE_SLOTSTS_EMI_LOCK_SET) {
                        control =  pciehpc_reg_get16(ctrl_p,
                            bus_p->bus_pcie_off + PCIE_SLOTCTL);
                        control |= PCIE_SLOTCTL_EMI_LOCK_CONTROL;
                        pciehpc_issue_hpc_command(ctrl_p, control);

                        /* wait 1 sec after toggling the state of EMI lock */
                        delay(drv_usectohz(1000000));
                }
        }

        /* get the current state of the slot */
        pciehpc_get_slot_state(slot_p);

        *result = slot_p->hs_info.cn_state;

        return (DDI_SUCCESS);
}

/*
 * pciehpc_slot_probe()
 *
 * Probe the slot.
 *
 * Note: This function is called by DDI HP framework at kernel context only
 */
static int
pciehpc_slot_probe(pcie_hp_slot_t *slot_p)
{
        pcie_hp_ctrl_t  *ctrl_p = slot_p->hs_ctrl;
        int             ret = DDI_SUCCESS;

        mutex_enter(&ctrl_p->hc_mutex);

        /*
         * Because we've been asked to probe again, make sure we aren't actively
         * suggesting probe failed from an LED perspective.
         */
        slot_p->hs_led_plat_en[PCIE_LL_PROBE_FAILED] = false;
        pciehpc_sync_leds_to_hw(slot_p);

        /* get the current state of the slot */
        pciehpc_get_slot_state(slot_p);

        /*
         * Probe a given PCIe Hotplug Connection (CN).
         */
        PCIE_DISABLE_ERRORS(ctrl_p->hc_dip);
        ret = pcie_hp_probe(slot_p);

        if (ret != DDI_SUCCESS) {
                PCIE_DBG("pciehpc_slot_probe() failed\n");

                /* turn the ATTN led ON for configure failure */
                slot_p->hs_led_plat_en[PCIE_LL_PROBE_FAILED] = true;
                pciehpc_sync_leds_to_hw(slot_p);
                mutex_exit(&ctrl_p->hc_mutex);
                return (DDI_FAILURE);
        }

        PCIE_ENABLE_ERRORS(ctrl_p->hc_dip);

        /* get the current state of the slot */
        pciehpc_get_slot_state(slot_p);

        mutex_exit(&ctrl_p->hc_mutex);
        return (DDI_SUCCESS);
}

/*
 * pciehpc_slot_unprobe()
 *
 * Unprobe the slot.
 *
 * Note: This function is called by DDI HP framework at kernel context only
 */
static int
pciehpc_slot_unprobe(pcie_hp_slot_t *slot_p)
{
        pcie_hp_ctrl_t  *ctrl_p = slot_p->hs_ctrl;
        int             ret;

        mutex_enter(&ctrl_p->hc_mutex);

        /* get the current state of the slot */
        pciehpc_get_slot_state(slot_p);

        /*
         * Unprobe a given PCIe Hotplug Connection (CN).
         */
        PCIE_DISABLE_ERRORS(ctrl_p->hc_dip);
        ret = pcie_hp_unprobe(slot_p);

        if (ret != DDI_SUCCESS) {
                PCIE_DBG("pciehpc_slot_unprobe() failed\n");
                PCIE_ENABLE_ERRORS(ctrl_p->hc_dip);
                mutex_exit(&ctrl_p->hc_mutex);
                return (DDI_FAILURE);
        }

        /* get the current state of the slot */
        pciehpc_get_slot_state(slot_p);

        mutex_exit(&ctrl_p->hc_mutex);
        return (DDI_SUCCESS);
}

static int
pciehpc_upgrade_slot_state(pcie_hp_slot_t *slot_p,
    ddi_hp_cn_state_t target_state)
{
        ddi_hp_cn_state_t curr_state;
        int rv = DDI_SUCCESS;
        pcie_hp_ctrl_t *ctrl_p = slot_p->hs_ctrl;

        if (target_state > DDI_HP_CN_STATE_ENABLED) {
                return (DDI_EINVAL);
        }

        curr_state = slot_p->hs_info.cn_state;
        while ((curr_state < target_state) && (rv == DDI_SUCCESS)) {

                switch (curr_state) {
                case DDI_HP_CN_STATE_EMPTY:
                        /*
                         * From EMPTY to PRESENT, just check the hardware
                         * slot state.
                         */
                        pciehpc_get_slot_state(slot_p);
                        curr_state = slot_p->hs_info.cn_state;
                        if (curr_state < DDI_HP_CN_STATE_PRESENT)
                                rv = DDI_FAILURE;
                        break;
                case DDI_HP_CN_STATE_PRESENT:
                        if (!ctrl_p->hc_has_pwr) {
                                pciehpc_get_slot_state(slot_p);
                                curr_state = slot_p->hs_info.cn_state;
                                if (curr_state < DDI_HP_CN_STATE_POWERED)
                                        rv = DDI_FAILURE;
                                break;
                        }

                        rv = (ctrl_p->hc_ops.poweron_hpc_slot)(slot_p,
                            &curr_state);

                        break;
                case DDI_HP_CN_STATE_POWERED:
                        /*
                         * If we're performing a synchronization, then the
                         * POWERED state isn't quite accurate. Power is enabled,
                         * but we haven't really done all the actual steps that
                         * are expected. As such, we will do another call to
                         * power on and if successful, then do the change to
                         * ENABLED. If the call to power on did not work, then
                         * we must transition back to PRESENT. If there is no
                         * power controller, then this is a no-op.
                         */
                        if ((ctrl_p->hc_flags & PCIE_HP_SYNC_RUNNING) != 0 &&
                            ctrl_p->hc_has_pwr) {
                                rv = (ctrl_p->hc_ops.poweron_hpc_slot)(slot_p,
                                    &curr_state);
                                if (rv != DDI_SUCCESS) {
                                        slot_p->hs_info.cn_state =
                                            DDI_HP_CN_STATE_PRESENT;
                                        break;
                                }
                        } else if (!ctrl_p->hc_has_pwr) {
                                rv = pciehpc_slot_noctrl_active(slot_p,
                                    &curr_state);
                                if (rv != DDI_SUCCESS)
                                        break;
                        }

                        curr_state = slot_p->hs_info.cn_state =
                            DDI_HP_CN_STATE_ENABLED;
                        break;
                default:
                        /* should never reach here */
                        ASSERT("unknown devinfo state");
                }
        }

        return (rv);
}

/*
 * Remove and clear out LEDs given a slot state downgrade. In particular, we
 * have three states that we need to worry about: whether the device is powered,
 * a power fault, and a probe failure.
 *
 * Removing power removes the powered state and a probe failure, but retains a
 * power fault. Removing a device ensures that all three are off.
 */
static void
pciehpc_downgrade_slot_leds(pcie_hp_slot_t *slot_p, ddi_hp_cn_state_t state)
{
        switch (state) {
        case DDI_HP_CN_STATE_EMPTY:
                slot_p->hs_led_plat_en[PCIE_LL_POWERED] = false;
                slot_p->hs_led_plat_en[PCIE_LL_PROBE_FAILED] = false;
                slot_p->hs_led_plat_en[PCIE_LL_POWER_FAULT] = false;
                break;
        case DDI_HP_CN_STATE_PRESENT:
                slot_p->hs_led_plat_en[PCIE_LL_POWERED] = false;
                slot_p->hs_led_plat_en[PCIE_LL_PROBE_FAILED] = false;
                break;
        default:
                break;
        }

        pciehpc_sync_leds_to_hw(slot_p);
}

static int
pciehpc_downgrade_slot_state(pcie_hp_slot_t *slot_p,
    ddi_hp_cn_state_t target_state)
{
        ddi_hp_cn_state_t curr_state;
        int rv = DDI_SUCCESS;


        curr_state = slot_p->hs_info.cn_state;
        while ((curr_state > target_state) && (rv == DDI_SUCCESS)) {

                switch (curr_state) {
                case DDI_HP_CN_STATE_PRESENT:
                        /*
                         * From PRESENT to EMPTY, just check hardware slot
                         * state. Removal of a device means we should make sure
                         * that the LEDs are cleaned up.
                         */
                        pciehpc_get_slot_state(slot_p);
                        curr_state = slot_p->hs_info.cn_state;
                        if (curr_state >= DDI_HP_CN_STATE_PRESENT) {
                                rv = DDI_FAILURE;
                        } else {
                                pciehpc_downgrade_slot_leds(slot_p, curr_state);
                        }
                        break;
                case DDI_HP_CN_STATE_POWERED:
                        /*
                         * If the device doesn't have power control then we
                         * cannot ask it to power off the slot. However, a
                         * device may have been removed and therefore we need to
                         * manually check if the device was removed by getting
                         * the state. Otherwise we let power control do
                         * everything.
                         */
                        if (!slot_p->hs_ctrl->hc_has_pwr) {
                                pciehpc_get_slot_state(slot_p);
                                curr_state = slot_p->hs_info.cn_state;
                                if (curr_state >= DDI_HP_CN_STATE_POWERED) {
                                        rv = DDI_FAILURE;
                                } else {
                                        pciehpc_downgrade_slot_leds(slot_p,
                                            curr_state);
                                }
                                break;
                        }

                        rv = (slot_p->hs_ctrl->hc_ops.poweroff_hpc_slot)(
                            slot_p, &curr_state);

                        break;
                case DDI_HP_CN_STATE_ENABLED:
                        curr_state = slot_p->hs_info.cn_state =
                            DDI_HP_CN_STATE_POWERED;

                        break;
                default:
                        /* should never reach here */
                        ASSERT("unknown devinfo state");
                }
        }

        return (rv);
}

/* Change slot state to a target state */
static int
pciehpc_change_slot_state(pcie_hp_slot_t *slot_p,
    ddi_hp_cn_state_t target_state)
{
        ddi_hp_cn_state_t curr_state;
        pciehpc_slot_power_t pwr_state;
        boolean_t sync = B_FALSE;
        int rv = 0;

        ASSERT(MUTEX_HELD(&slot_p->hs_ctrl->hc_mutex));

        pciehpc_get_slot_state(slot_p);
        curr_state = slot_p->hs_info.cn_state;
        pwr_state = pciehpc_slot_power_state(slot_p);

        /*
         * We've been asked to change the slot state. If we still had an
         * outstanding synchronization task, then we should remove that because
         * we've had an explicit state change. In essence we take over that sync
         * and note that it's running.
         */
        if ((slot_p->hs_ctrl->hc_flags & PCIE_HP_SYNC_PENDING) != 0 &&
            slot_p->hs_info.cn_state == DDI_HP_CN_STATE_POWERED) {
                sync = B_TRUE;
                slot_p->hs_ctrl->hc_flags |= PCIE_HP_SYNC_RUNNING;
        }
        slot_p->hs_ctrl->hc_flags &= ~PCIE_HP_SYNC_PENDING;

        /*
         * We need to see whether the power controller state (if there is one)
         * matches the DDI slot state.  If not, it may be necessary to perform
         * the upgrade or downgrade procedure even if the DDI slot state matches
         * the target already.  We'll make sure that curr_state reflects the
         * state of the power controller with respect to our desired target
         * state, even if the slot is empty.
         */
        if (pwr_state == PSP_NO_CONTROLLER)
                goto skip_sync;

        switch (target_state) {
        case DDI_HP_CN_STATE_EMPTY:
        case DDI_HP_CN_STATE_PRESENT:
                /*
                 * Power controller is on but software doesn't know that, and
                 * wants to enter a state in which power should be off.
                 */
                if ((pwr_state & PSP_OFF) == 0 &&
                    curr_state < DDI_HP_CN_STATE_POWERED) {
                        curr_state = DDI_HP_CN_STATE_POWERED;
                }
                break;
        case DDI_HP_CN_STATE_POWERED:
        case DDI_HP_CN_STATE_ENABLED:
                /*
                 * Power controller is off but software doesn't know that, and
                 * wants to enter a state in which power should be on.
                 */
                if ((pwr_state & PSP_OFF) != 0 &&
                    curr_state >= DDI_HP_CN_STATE_POWERED) {
                        curr_state = DDI_HP_CN_STATE_PRESENT;
                }
                break;
        default:
                break;
        }

        slot_p->hs_info.cn_state = curr_state;

skip_sync:
        if (curr_state == target_state) {
                return (DDI_SUCCESS);
        }

        if (curr_state < target_state) {
                rv = pciehpc_upgrade_slot_state(slot_p, target_state);
        } else {
                rv = pciehpc_downgrade_slot_state(slot_p, target_state);
        }

        if (sync) {
                slot_p->hs_ctrl->hc_flags &= ~PCIE_HP_SYNC_RUNNING;
        }

        return (rv);
}

typedef struct pciehpc_prop {
        const char *prop_name;
        const char *prop_value;
        bool (*prop_valid)(const char *);
        int (*prop_get)(pcie_hp_slot_t *, const char **);
        void (*prop_set)(pcie_hp_slot_t *, const char *);
} pciehpc_prop_t;

static bool
pciehpc_prop_led_valid(const char *value)
{
        return (strcmp(value, PCIEHPC_PROP_VALUE_ON) == 0 ||
            strcmp(value, PCIEHPC_PROP_VALUE_OFF) == 0 ||
            strcmp(value, PCIEHPC_PROP_VALUE_BLINK) == 0 ||
            strcmp(value, PCIEHPC_PROP_VALUE_DEFAULT) == 0);
}

static int
pciehpc_prop_card_get(pcie_hp_slot_t *slot_p, const char **value_p)
{
        pcie_hp_ctrl_t  *ctrl_p = slot_p->hs_ctrl;
        ddi_acc_handle_t handle;
        dev_info_t      *cdip;
        uint8_t         prog_class, base_class, sub_class;

        mutex_exit(&ctrl_p->hc_mutex);
        cdip = pcie_hp_devi_find(ctrl_p->hc_dip, slot_p->hs_device_num, 0);
        mutex_enter(&ctrl_p->hc_mutex);

        if ((slot_p->hs_info.cn_state != DDI_HP_CN_STATE_ENABLED) ||
            cdip == NULL) {
                /*
                 * When getting all properties, just ignore the
                 * one that's not available under certain state.
                 */
                return (DDI_ENOTSUP);
        }

        if (pci_config_setup(cdip, &handle) != DDI_SUCCESS) {
                return (DDI_FAILURE);
        }

        prog_class = pci_config_get8(handle, PCI_CONF_PROGCLASS);
        base_class = pci_config_get8(handle, PCI_CONF_BASCLASS);
        sub_class = pci_config_get8(handle, PCI_CONF_SUBCLASS);
        pci_config_teardown(&handle);

        *value_p = PCIEHPC_PROP_VALUE_UNKNOWN;
        for (size_t i = 0; i < class_pci_items; i++) {
                if ((base_class == class_pci[i].base_class) &&
                    (sub_class == class_pci[i].sub_class) &&
                    (prog_class == class_pci[i].prog_class)) {
                        *value_p = class_pci[i].short_desc;
                        break;
                }
        }

        return (DDI_SUCCESS);
}

static int
pciehpc_prop_board_get(pcie_hp_slot_t *slot_p, const char **value_p)
{
        if (slot_p->hs_info.cn_state <= DDI_HP_CN_STATE_EMPTY) {
                *value_p = PCIEHPC_PROP_VALUE_UNKNOWN;
        } else {
                *value_p = PCIEHPC_PROP_VALUE_PCIHOTPLUG;
        }

        return (DDI_SUCCESS);
}

static int
pciehpc_prop_slot_get(pcie_hp_slot_t *slot_p, const char **value_p)
{
        *value_p = pcie_slot_condition_text(slot_p->hs_condition);
        return (DDI_SUCCESS);
}

static int
pciehpc_prop_led_get_power(pcie_hp_slot_t *slot_p, const char **value_p)
{
        if (slot_p->hs_power_usr_ovr) {
                *value_p = pcie_led_state_text(slot_p->hs_power_usr_ovr_state);
        } else {
                *value_p = PCIEHPC_PROP_VALUE_DEFAULT;
        }
        return (DDI_SUCCESS);
}

static int
pciehpc_prop_led_get_attn(pcie_hp_slot_t *slot_p, const char **value_p)
{
        if (slot_p->hs_attn_usr_ovr) {
                *value_p = pcie_led_state_text(slot_p->hs_attn_usr_ovr_state);
        } else {
                *value_p = PCIEHPC_PROP_VALUE_DEFAULT;
        }
        return (DDI_SUCCESS);
}

static void
pciehpc_prop_led_set_common(bool *ovr, pcie_hp_led_state_t *state,
    const char *val)
{
        if (strcmp(val, PCIEHPC_PROP_VALUE_DEFAULT) == 0) {
                *ovr = false;
                return;
        }

        *ovr = true;
        if (strcmp(val, PCIEHPC_PROP_VALUE_ON) == 0) {
                *state = PCIE_HP_LED_ON;
        } else if (strcmp(val, PCIEHPC_PROP_VALUE_OFF) == 0) {
                *state = PCIE_HP_LED_OFF;
        } else if (strcmp(val, PCIEHPC_PROP_VALUE_BLINK) == 0) {
                *state = PCIE_HP_LED_BLINK;
        }
}

static void
pciehpc_prop_led_set_attn(pcie_hp_slot_t *slot_p, const char *value)
{
        pciehpc_prop_led_set_common(&slot_p->hs_attn_usr_ovr,
            &slot_p->hs_attn_usr_ovr_state, value);
}

static void
pciehpc_prop_led_set_power(pcie_hp_slot_t *slot_p, const char *value)
{
        pciehpc_prop_led_set_common(&slot_p->hs_power_usr_ovr,
            &slot_p->hs_power_usr_ovr_state, value);
}

static pciehpc_prop_t pciehpc_props[] = {
        { PCIEHPC_PROP_LED_POWER, PCIEHPC_PROP_VALUE_LED_DEF,
            pciehpc_prop_led_valid, pciehpc_prop_led_get_power,
            pciehpc_prop_led_set_power },
        { PCIEHPC_PROP_LED_ATTN, PCIEHPC_PROP_VALUE_LED_DEF,
            pciehpc_prop_led_valid, pciehpc_prop_led_get_attn,
            pciehpc_prop_led_set_attn },
        { PCIEHPC_PROP_CARD_TYPE, PCIEHPC_PROP_VALUE_TYPE, NULL,
            pciehpc_prop_card_get, NULL },
        { PCIEHPC_PROP_BOARD_TYPE, PCIEHPC_PROP_VALUE_TYPE, NULL,
            pciehpc_prop_board_get, NULL },
        { PCIEHPC_PROP_SLOT_CONDITION, PCIEHPC_PROP_VALUE_TYPE, NULL,
            pciehpc_prop_slot_get, NULL },
};

static bool
pciehpc_slot_prop_copyin(uintptr_t arg, ddi_hp_property_t *prop)
{
        switch (ddi_model_convert_from(get_udatamodel())) {
#ifdef  _MULTI_DATAMODEL
        case DDI_MODEL_ILP32: {
                ddi_hp_property32_t prop32;
                if (ddi_copyin((void *)arg, &prop32, sizeof (prop32), 0) != 0) {
                        return (false);
                }
                bzero(prop, sizeof (prop));
                prop->nvlist_buf = (void *)(uintptr_t)prop32.nvlist_buf;
                prop->buf_size = prop32.buf_size;
                break;
        }
#endif
        case DDI_MODEL_NONE:
                if (ddi_copyin((void *)arg, prop, sizeof (*prop), 0) != 0) {
                        return (false);
                }
                break;
        default:
                return (false);
        }

        return (true);
}

static bool
pciehpc_slot_prop_copyout(uintptr_t dest, const ddi_hp_property_t *prop)
{
        switch (ddi_model_convert_from(get_udatamodel())) {
#ifdef  _MULTI_DATAMODEL
        case DDI_MODEL_ILP32: {
                ddi_hp_property32_t prop32;

                if ((uintptr_t)prop->nvlist_buf > UINT32_MAX ||
                    prop->buf_size > UINT32_MAX) {
                        return (false);
                }
                bzero(&prop32, sizeof (prop32));
                prop32.nvlist_buf = (caddr32_t)(uintptr_t)prop->nvlist_buf;
                prop32.buf_size = (uint32_t)prop->buf_size;
                if (ddi_copyout(&prop32, (void *)dest, sizeof (prop32), 0) !=
                    0) {
                        return (false);
                }
                break;
        }
#endif
        case DDI_MODEL_NONE:
                if (ddi_copyout(prop, (void *)dest, sizeof (ddi_hp_property_t),
                    0) != 0) {
                        return (false);
                }
                break;
        default:
                return (false);
        }

        return (true);
}

int
pciehpc_slot_get_property(pcie_hp_slot_t *slot_p, uintptr_t arg, uintptr_t rval)
{
        ddi_hp_property_t request, result;
        pcie_hp_ctrl_t  *ctrl_p = slot_p->hs_ctrl;
        nvlist_t        *prop_list;
        nvlist_t        *prop_rlist; /* nvlist for return values */
        nvpair_t        *prop_pair;
        int             ret = DDI_SUCCESS;
        boolean_t       get_all_prop = B_FALSE;

        if (!pciehpc_slot_prop_copyin(arg, &request) ||
            !pciehpc_slot_prop_copyin(rval, &result))
                return (false);

        if ((ret = pcie_copyin_nvlist(request.nvlist_buf, request.buf_size,
            &prop_list)) != DDI_SUCCESS)
                return (ret);

        if (nvlist_alloc(&prop_rlist, NV_UNIQUE_NAME, 0)) {
                ret = DDI_ENOMEM;
                goto get_prop_cleanup;
        }

        /* check whether the requested property is "all" or "help" */
        prop_pair = nvlist_next_nvpair(prop_list, NULL);
        if (prop_pair && !nvlist_next_nvpair(prop_list, prop_pair)) {
                const char *name = nvpair_name(prop_pair);

                if (strcmp(name, PCIEHPC_PROP_ALL) == 0) {
                        (void) nvlist_remove_all(prop_list, PCIEHPC_PROP_ALL);

                        /*
                         * Add all properties into the request list, so that we
                         * will get the values in the following for loop.
                         */
                        for (size_t i = 0; i < ARRAY_SIZE(pciehpc_props); i++) {
                                if (nvlist_add_string(prop_list,
                                    pciehpc_props[i].prop_name, "") != 0) {
                                        ret = DDI_FAILURE;
                                        goto get_prop_cleanup1;
                                }
                        }
                        get_all_prop = B_TRUE;
                } else if (strcmp(name, PCIEHPC_PROP_HELP) == 0) {
                        /*
                         * Empty the request list, and add help strings into the
                         * return list. We will pass the following for loop.
                         */
                        (void) nvlist_remove_all(prop_list, PCIEHPC_PROP_HELP);

                        for (size_t i = 0; i < ARRAY_SIZE(pciehpc_props); i++) {
                                if (nvlist_add_string(prop_rlist,
                                    pciehpc_props[i].prop_name,
                                    pciehpc_props[i].prop_value) != 0) {
                                        ret = DDI_FAILURE;
                                        goto get_prop_cleanup1;
                                }
                        }
                }
        }

        mutex_enter(&ctrl_p->hc_mutex);

        /* get the current slot state */
        pciehpc_get_slot_state(slot_p);

        /* for each requested property, get the value and add it to nvlist */
        prop_pair = NULL;
        while ((prop_pair = nvlist_next_nvpair(prop_list, prop_pair)) != NULL) {
                const char *name = nvpair_name(prop_pair);
                const pciehpc_prop_t *prop = NULL;
                const char *value = NULL;

                for (size_t i = 0; i < ARRAY_SIZE(pciehpc_props); i++) {
                        if (strcmp(pciehpc_props[i].prop_name, name) == 0) {
                                prop = &pciehpc_props[i];
                                break;
                        }
                }

                if (prop == NULL) {
                        PCIE_DBG("Unsupported property: %s\n", name);
                        ret = DDI_EINVAL;
                        goto get_prop_cleanup2;
                }

                /*
                 * Get the property. Some things may fail with ENOTSUP and we
                 * allow that if we are being asked to get everything.
                 */
                ret = prop->prop_get(slot_p, &value);
                if (ret != DDI_SUCCESS &&
                    (get_all_prop && ret != DDI_ENOTSUP)) {
                        goto get_prop_cleanup2;
                }

                if (nvlist_add_string(prop_rlist, name, value) != 0) {
                        ret = DDI_FAILURE;
                        goto get_prop_cleanup2;
                }
        }

        /* pack nvlist and copyout */
        if ((ret = pcie_copyout_nvlist(prop_rlist, result.nvlist_buf,
            &result.buf_size)) != DDI_SUCCESS) {
                goto get_prop_cleanup2;
        }

        if (!pciehpc_slot_prop_copyout(rval, &result)) {
                ret = DDI_FAILURE;
        }

get_prop_cleanup2:
        mutex_exit(&ctrl_p->hc_mutex);
get_prop_cleanup1:
        nvlist_free(prop_rlist);
get_prop_cleanup:
        nvlist_free(prop_list);
        return (ret);
}


int
pciehpc_slot_set_property(pcie_hp_slot_t *slot_p, uintptr_t arg,
    uintptr_t rval)
{
        ddi_hp_property_t       request, result;
        pcie_hp_ctrl_t          *ctrl_p = slot_p->hs_ctrl;
        nvlist_t                *prop_list;
        nvlist_t                *prop_rlist;
        nvpair_t                *prop_pair;
        int                     ret = DDI_SUCCESS;

        if (!pciehpc_slot_prop_copyin(arg, &request) ||
            !pciehpc_slot_prop_copyin(rval, &result))
                return (false);

        if ((ret = pcie_copyin_nvlist(request.nvlist_buf, request.buf_size,
            &prop_list)) != DDI_SUCCESS)
                return (ret);

        /* check whether the requested property is "help" */
        prop_pair = nvlist_next_nvpair(prop_list, NULL);
        if (prop_pair && !nvlist_next_nvpair(prop_list, prop_pair) &&
            (strcmp(nvpair_name(prop_pair), PCIEHPC_PROP_HELP) == 0)) {
                if (!rval) {
                        ret = DDI_ENOTSUP;
                        goto set_prop_cleanup;
                }

                if (nvlist_alloc(&prop_rlist, NV_UNIQUE_NAME, 0)) {
                        ret = DDI_ENOMEM;
                        goto set_prop_cleanup;
                }

                for (size_t i = 0; i < ARRAY_SIZE(pciehpc_props); i++) {
                        if (pciehpc_props[i].prop_valid == NULL)
                                continue;

                        if (nvlist_add_string(prop_rlist,
                            pciehpc_props[i].prop_name,
                            pciehpc_props[i].prop_value) != 0) {
                                ret = DDI_FAILURE;
                                goto set_prop_cleanup1;
                        }
                }

                if ((ret = pcie_copyout_nvlist(prop_rlist, result.nvlist_buf,
                    &result.buf_size)) != DDI_SUCCESS) {
                        goto set_prop_cleanup1;
                }

                if (!pciehpc_slot_prop_copyout(rval, &result)) {
                        ret = DDI_FAILURE;
                }

set_prop_cleanup1:
                nvlist_free(prop_rlist);
                nvlist_free(prop_list);
                return (ret);
        }

        /* Validate the request */
        prop_pair = NULL;
        while ((prop_pair = nvlist_next_nvpair(prop_list, prop_pair)) != NULL) {
                const pciehpc_prop_t *prop;
                char *value;
                const char *name = nvpair_name(prop_pair);

                if (nvpair_type(prop_pair) != DATA_TYPE_STRING) {
                        PCIE_DBG("Unexpected data type of setting "
                            "property %s.\n", name);
                        ret = DDI_EINVAL;
                        goto set_prop_cleanup;
                }

                if (nvpair_value_string(prop_pair, &value)) {
                        PCIE_DBG("Get string value failed for property %s.\n",
                            name);
                        ret = DDI_FAILURE;
                        goto set_prop_cleanup;
                }

                prop = NULL;
                for (size_t i = 0; i < ARRAY_SIZE(pciehpc_props); i++) {
                        if (strcmp(pciehpc_props[i].prop_name, name) == 0) {
                                prop = &pciehpc_props[i];
                                break;
                        }
                }

                if (prop == NULL) {
                        PCIE_DBG("Unsupported property: %s\n", name);
                        ret = DDI_EINVAL;
                        goto set_prop_cleanup;
                }

                if (prop->prop_valid == NULL) {
                        PCIE_DBG("Read only property: %s\n", name);
                        ret = DDI_ENOTSUP;
                        goto set_prop_cleanup;
                } else if (!prop->prop_valid(value)) {
                        PCIE_DBG("Unsupported value ('%s') for property: %s\n",
                            value, name);
                        ret = DDI_EINVAL;
                        goto set_prop_cleanup;
                }
        }
        mutex_enter(&ctrl_p->hc_mutex);

        /* get the current slot state */
        pciehpc_get_slot_state(slot_p);

        /* set each property */
        prop_pair = NULL;
        while ((prop_pair = nvlist_next_nvpair(prop_list, prop_pair)) != NULL) {
                const char *name = nvpair_name(prop_pair);
                const pciehpc_prop_t *prop = NULL;
                char *value;

                (void) nvpair_value_string(prop_pair, &value);
                for (size_t i = 0; i < ARRAY_SIZE(pciehpc_props); i++) {
                        if (strcmp(pciehpc_props[i].prop_name, name) == 0) {
                                prop = &pciehpc_props[i];
                                break;
                        }
                }

                ASSERT3P(prop, !=, NULL);
                prop->prop_set(slot_p, value);
        }

        /*
         * Because the only properties we can set are LED related, always
         * resync.
         */
        pciehpc_sync_leds_to_hw(slot_p);

        if (rval != 0) {
                result.buf_size = 0;
                if (!pciehpc_slot_prop_copyout(rval, &result)) {
                        ret = DDI_FAILURE;
                }
        }

        mutex_exit(&ctrl_p->hc_mutex);
set_prop_cleanup:
        nvlist_free(prop_list);
        return (ret);
}

/*
 * Send a command to the PCI-E Hot Plug Controller.
 *
 * NOTES: The PCI-E spec defines the following semantics for issuing hot plug
 * commands.
 * 1) If Command Complete events/interrupts are supported then software
 *    waits for Command Complete event after issuing a command (i.e writing
 *    to the Slot Control register). The command completion could take as
 *    long as 1 second so software should be prepared to wait for 1 second
 *    before issuing another command.
 *
 * 2) If Command Complete events/interrupts are not supported then
 *    software could issue multiple Slot Control writes without any delay
 *    between writes.
 */
static void
pciehpc_issue_hpc_command(pcie_hp_ctrl_t *ctrl_p, uint16_t control)
{
        pcie_hp_slot_t  *slot_p = ctrl_p->hc_slots[0];
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);
        uint16_t        status;
        uint32_t        slot_cap;

        /*
         * PCI-E version 1.1 spec defines No Command Completed
         * Support bit (bit#18) in Slot Capabilities register. If this
         * bit is set then slot doesn't support notification of command
         * completion events.
         */
        slot_cap =  pciehpc_reg_get32(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCAP);

        /*
         * If no Command Completion event is supported or it is ACPI
         * hot plug mode then just issue the command and return.
         */
        if ((slot_cap & PCIE_SLOTCAP_NO_CMD_COMP_SUPP) ||
            (bus_p->bus_hp_curr_mode == PCIE_ACPI_HP_MODE)) {
                pciehpc_reg_put16(ctrl_p,
                    bus_p->bus_pcie_off + PCIE_SLOTCTL, control);
                return;
        }

        /*
         * **************************************
         * Command Complete events are supported.
         * **************************************
         */

        /*
         * If HPC is not yet initialized then just poll for the Command
         * Completion interrupt.
         */
        if (!(ctrl_p->hc_flags & PCIE_HP_INITIALIZED_FLAG)) {
                int retry = PCIE_HP_CMD_WAIT_RETRY;

                /* write the command to the HPC */
                pciehpc_reg_put16(ctrl_p,
                    bus_p->bus_pcie_off + PCIE_SLOTCTL, control);

                /* poll for status completion */
                while (retry--) {
                        /* wait for 10 msec before checking the status */
                        delay(drv_usectohz(PCIE_HP_CMD_WAIT_TIME));

                        status = pciehpc_reg_get16(ctrl_p,
                            bus_p->bus_pcie_off + PCIE_SLOTSTS);

                        if (status & PCIE_SLOTSTS_COMMAND_COMPLETED) {
                                /* clear the status bits */
                                pciehpc_reg_put16(ctrl_p,
                                    bus_p->bus_pcie_off + PCIE_SLOTSTS, status);
                                break;
                        }
                }
                return;
        }

        /* HPC is already initialized */

        ASSERT(MUTEX_HELD(&ctrl_p->hc_mutex));

        /*
         * If previous command is still pending then wait for its
         * completion. i.e cv_wait()
         */

        while (ctrl_p->hc_cmd_pending == B_TRUE)
                cv_wait(&ctrl_p->hc_cmd_comp_cv, &ctrl_p->hc_mutex);

        /*
         * Issue the command and wait for Command Completion or
         * the 1 sec timeout.
         */
        pciehpc_reg_put16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCTL, control);

        ctrl_p->hc_cmd_pending = B_TRUE;

        if (cv_timedwait(&ctrl_p->hc_cmd_comp_cv, &ctrl_p->hc_mutex,
            ddi_get_lbolt() + SEC_TO_TICK(1)) == -1) {

                /* it is a timeout */
                PCIE_DBG("pciehpc_issue_hpc_command: Command Complete"
                    " interrupt is not received for slot %d\n",
                    slot_p->hs_phy_slot_num);

                /* clear the status info in case interrupts are disabled? */
                status = pciehpc_reg_get16(ctrl_p,
                    bus_p->bus_pcie_off + PCIE_SLOTSTS);

                if (status & PCIE_SLOTSTS_COMMAND_COMPLETED) {
                        /* clear the status bits */
                        pciehpc_reg_put16(ctrl_p,
                            bus_p->bus_pcie_off + PCIE_SLOTSTS, status);
                }
        }

        ctrl_p->hc_cmd_pending = B_FALSE;

        /* wake up any one waiting for issuing another command to HPC */
        cv_signal(&ctrl_p->hc_cmd_comp_cv);
}

/*
 * pciehcp_attn_btn_handler()
 *
 * This handles ATTN button pressed event as per the PCI-E 1.1 spec.
 */
static void
pciehpc_attn_btn_handler(pcie_hp_ctrl_t *ctrl_p)
{
        pcie_hp_slot_t          *slot_p = ctrl_p->hc_slots[0];
        callb_cpr_t             cprinfo;

        PCIE_DBG("pciehpc_attn_btn_handler: thread started\n");

        CALLB_CPR_INIT(&cprinfo, &ctrl_p->hc_mutex, callb_generic_cpr,
            "pciehpc_attn_btn_handler");

        mutex_enter(&ctrl_p->hc_mutex);

        /* wait for ATTN button event */
        cv_wait(&slot_p->hs_attn_btn_cv, &ctrl_p->hc_mutex);

        while (slot_p->hs_attn_btn_thread_exit == B_FALSE) {
                if (slot_p->hs_attn_btn_pending == B_TRUE) {
                        /*
                         * Allow the platform to toggle an appropriate LED while
                         * we're waiting for this to take effect (generally
                         * blinking the power LED).
                         */
                        slot_p->hs_led_plat_en[PCIE_LL_ATTENTION_BUTTON] = true;
                        pciehpc_sync_leds_to_hw(slot_p);

                        /* wait for 5 seconds before taking any action */
                        if (cv_timedwait(&slot_p->hs_attn_btn_cv,
                            &ctrl_p->hc_mutex,
                            ddi_get_lbolt() + SEC_TO_TICK(5)) == -1) {
                                /*
                                 * It is a time out; make sure the ATTN pending
                                 * flag is still ON before sending the event to
                                 * DDI HP framework.
                                 */
                                if (slot_p->hs_attn_btn_pending == B_TRUE) {
                                        int hint;

                                        slot_p->hs_attn_btn_pending = B_FALSE;
                                        pciehpc_get_slot_state(slot_p);

                                        if (slot_p->hs_info.cn_state <=
                                            DDI_HP_CN_STATE_PRESENT) {
                                                /*
                                                 * Insertion.
                                                 */
                                                hint = SE_INCOMING_RES;
                                        } else {
                                                /*
                                                 * Want to remove;
                                                 */
                                                hint = SE_OUTGOING_RES;
                                        }

                                        /*
                                         * We can't call ddihp_cn_gen_sysevent
                                         * here since it's not a DDI interface.
                                         */
                                        pcie_hp_gen_sysevent_req(
                                            slot_p->hs_info.cn_name,
                                            hint,
                                            ctrl_p->hc_dip,
                                            KM_SLEEP);
                                }
                        }

                        /* restore the power LED state */
                        slot_p->hs_led_plat_en[PCIE_LL_ATTENTION_BUTTON] =
                            false;
                        pciehpc_sync_leds_to_hw(slot_p);
                        continue;
                }

                /* wait for another ATTN button event */
                cv_wait(&slot_p->hs_attn_btn_cv, &ctrl_p->hc_mutex);
        }

        PCIE_DBG("pciehpc_attn_btn_handler: thread exit\n");
        cv_signal(&slot_p->hs_attn_btn_cv);
        CALLB_CPR_EXIT(&cprinfo);
        thread_exit();
}

/*
 * convert LED state from PCIE HPC definition to pcie_hp_led_state_t
 * definition.
 */
static pcie_hp_led_state_t
pciehpc_led_state_to_hpc(uint16_t state)
{
        switch (state) {
        case PCIE_SLOTCTL_INDICATOR_STATE_ON:
                return (PCIE_HP_LED_ON);
        case PCIE_SLOTCTL_INDICATOR_STATE_BLINK:
                return (PCIE_HP_LED_BLINK);
        case PCIE_SLOTCTL_INDICATOR_STATE_OFF:
        default:
                return (PCIE_HP_LED_OFF);
        }
}

static void
pciehpc_handle_power_fault(dev_info_t *dip)
{
        /*
         * Hold the parent's ref so that it won't disappear when the taskq is
         * scheduled to run.
         */
        ndi_hold_devi(dip);

        if (taskq_dispatch(system_taskq, pciehpc_power_fault_handler, dip,
            TQ_NOSLEEP) == TASKQID_INVALID) {
                ndi_rele_devi(dip);
                PCIE_DBG("pciehpc_intr(): "
                    "Failed to dispatch power fault handler, dip %p\n", dip);
        }
}

static void
pciehpc_power_fault_handler(void *arg)
{
        dev_info_t *dip = (dev_info_t *)arg;
        pcie_hp_ctrl_t  *ctrl_p;
        pcie_hp_slot_t  *slot_p;

        /* get the soft state structure for this dip */
        if ((ctrl_p = PCIE_GET_HP_CTRL(dip)) == NULL) {
                ndi_rele_devi(dip);
                return;
        }
        slot_p = ctrl_p->hc_slots[0];

        /*
         * Send the event to DDI Hotplug framework, power off
         * the slot
         */
        (void) ndi_hp_state_change_req(dip,
            slot_p->hs_info.cn_name,
            DDI_HP_CN_STATE_PRESENT, DDI_HP_REQ_SYNC);

        mutex_enter(&ctrl_p->hc_mutex);
        slot_p->hs_led_plat_en[PCIE_LL_POWER_FAULT] = true;
        pciehpc_sync_leds_to_hw(slot_p);
        mutex_exit(&ctrl_p->hc_mutex);
        ndi_rele_devi(dip);
}

#ifdef DEBUG
/*
 * Dump PCI-E Hot Plug registers.
 */
static void
pciehpc_dump_hpregs(pcie_hp_ctrl_t *ctrl_p)
{
        pcie_hp_slot_t  *slot_p = ctrl_p->hc_slots[0];
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(ctrl_p->hc_dip);
        uint16_t        control;
        uint32_t        capabilities;

        if (!pcie_debug_flags)
                return;

        capabilities = pciehpc_reg_get32(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCAP);

        control =  pciehpc_reg_get16(ctrl_p,
            bus_p->bus_pcie_off + PCIE_SLOTCTL);

        PCIE_DBG("pciehpc_dump_hpregs: Found PCI-E hot plug slot %d\n",
            slot_p->hs_phy_slot_num);

        PCIE_DBG("Attention Button Present = %s\n",
            capabilities & PCIE_SLOTCAP_ATTN_BUTTON ? "Yes":"No");

        PCIE_DBG("Power controller Present = %s\n",
            capabilities & PCIE_SLOTCAP_POWER_CONTROLLER ? "Yes":"No");

        PCIE_DBG("MRL Sensor Present       = %s\n",
            capabilities & PCIE_SLOTCAP_MRL_SENSOR ? "Yes":"No");

        PCIE_DBG("Attn Indicator Present   = %s\n",
            capabilities & PCIE_SLOTCAP_ATTN_INDICATOR ? "Yes":"No");

        PCIE_DBG("Power Indicator Present  = %s\n",
            capabilities & PCIE_SLOTCAP_PWR_INDICATOR ? "Yes":"No");

        PCIE_DBG("HotPlug Surprise         = %s\n",
            capabilities & PCIE_SLOTCAP_HP_SURPRISE ? "Yes":"No");

        PCIE_DBG("HotPlug Capable          = %s\n",
            capabilities & PCIE_SLOTCAP_HP_CAPABLE ? "Yes":"No");

        PCIE_DBG("Physical Slot Number     = %d\n",
            PCIE_SLOTCAP_PHY_SLOT_NUM(capabilities));

        PCIE_DBG("Attn Button interrupt Enabled  = %s\n",
            control & PCIE_SLOTCTL_ATTN_BTN_EN ? "Yes":"No");

        PCIE_DBG("Power Fault interrupt Enabled  = %s\n",
            control & PCIE_SLOTCTL_PWR_FAULT_EN ? "Yes":"No");

        PCIE_DBG("MRL Sensor INTR Enabled   = %s\n",
            control & PCIE_SLOTCTL_MRL_SENSOR_EN ? "Yes":"No");

        PCIE_DBG("Presence interrupt Enabled     = %s\n",
            control & PCIE_SLOTCTL_PRESENCE_CHANGE_EN ? "Yes":"No");

        PCIE_DBG("Cmd Complete interrupt Enabled = %s\n",
            control & PCIE_SLOTCTL_CMD_INTR_EN ? "Yes":"No");

        PCIE_DBG("HotPlug interrupt Enabled      = %s\n",
            control & PCIE_SLOTCTL_HP_INTR_EN ? "Yes":"No");

        PCIE_DBG("Power Indicator LED = %s", pcie_led_state_text(
            pciehpc_led_state_to_hpc(pcie_slotctl_pwr_indicator_get(control))));

        PCIE_DBG("Attn Indicator LED = %s\n",
            pcie_led_state_text(pciehpc_led_state_to_hpc(
            pcie_slotctl_attn_indicator_get(control))));
}
#endif  /* DEBUG */