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

/*
 *  Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 *  Use is subject to license terms.
 * Copyright 2019 Joyent, Inc.
 * Copyright 2023 Oxide Computer Company
 */

/*
 * This file contains the common hotplug code that is used by Standard
 * PCIe and PCI HotPlug Controller code.
 *
 * NOTE: This file is compiled and delivered through misc/pcie module.
 */

#include <sys/types.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/note.h>
#include <sys/callb.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/sysevent.h>
#include <sys/sysevent/eventdefs.h>
#include <sys/sysevent/dr.h>
#include <sys/pci_impl.h>
#include <sys/pci_cap.h>
#include <sys/hotplug/pci/pcicfg.h>
#include <sys/hotplug/pci/pcie_hp.h>
#include <sys/hotplug/pci/pciehpc.h>
#include <sys/hotplug/pci/pcishpc.h>
#include <io/pciex/pcieb.h>

/* Local functions prototype */
static int pcie_hp_list_occupants(dev_info_t *dip, void *arg);
static int pcie_hp_register_port(dev_info_t *dip, dev_info_t *pdip,
    char *cn_name);
static int pcie_hp_register_ports_for_dev(dev_info_t *dip, int device_num);
static int pcie_hp_unregister_ports_cb(ddi_hp_cn_info_t *info, void *arg);
static int pcie_hp_get_port_state(ddi_hp_cn_info_t *info, void *arg);
static int pcie_hp_match_dev_func(dev_info_t *dip, void *hdl);
static boolean_t pcie_hp_match_dev(dev_info_t *dip, int dev_num);
static int pcie_hp_get_df_from_port_name(char *cn_name, int *dev_num,
    int *func_num);
static int pcie_hp_create_port_name_num(dev_info_t *dip,
    ddi_hp_cn_info_t *cn_info);
static int pcie_hp_check_hardware_existence(dev_info_t *dip, int dev_num,
    int func_num);

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

/*
 * return description text for led state
 */
char *
pcie_led_state_text(pcie_hp_led_state_t state)
{
        switch (state) {
        case PCIE_HP_LED_ON:
                return (PCIEHPC_PROP_VALUE_ON);
        case PCIE_HP_LED_OFF:
                return (PCIEHPC_PROP_VALUE_OFF);
        case PCIE_HP_LED_BLINK:
                return (PCIEHPC_PROP_VALUE_BLINK);
        default:
                return (PCIEHPC_PROP_VALUE_UNKNOWN);
        }
}

/*
 * return description text for slot condition
 */
char *
pcie_slot_condition_text(ap_condition_t condition)
{
        switch (condition) {
        case AP_COND_UNKNOWN:
                return (PCIEHPC_PROP_VALUE_UNKNOWN);
        case AP_COND_OK:
                return (PCIEHPC_PROP_VALUE_OK);
        case AP_COND_FAILING:
                return (PCIEHPC_PROP_VALUE_FAILING);
        case AP_COND_FAILED:
                return (PCIEHPC_PROP_VALUE_FAILED);
        case AP_COND_UNUSABLE:
                return (PCIEHPC_PROP_VALUE_UNUSABLE);
        default:
                return (PCIEHPC_PROP_VALUE_UNKNOWN);
        }
}

/*
 * routine to copy in a nvlist from userland
 */
int
pcie_copyin_nvlist(char *packed_buf, size_t packed_sz, nvlist_t **nvlp)
{
        int             ret = DDI_SUCCESS;
        char            *packed;
        nvlist_t        *dest = NULL;

        if (packed_buf == NULL || packed_sz == 0)
                return (DDI_EINVAL);

        /* copyin packed nvlist */
        if ((packed = kmem_alloc(packed_sz, KM_SLEEP)) == NULL)
                return (DDI_ENOMEM);

        if (copyin(packed_buf, packed, packed_sz) != 0) {
                cmn_err(CE_WARN, "pcie_copyin_nvlist: copyin failed.\n");
                ret = DDI_FAILURE;
                goto copyin_cleanup;
        }

        /* unpack packed nvlist */
        if ((ret = nvlist_unpack(packed, packed_sz, &dest, KM_SLEEP)) != 0) {
                cmn_err(CE_WARN, "pcie_copyin_nvlist: nvlist_unpack "
                    "failed with err %d\n", ret);
                switch (ret) {
                case EINVAL:
                case ENOTSUP:
                        ret = DDI_EINVAL;
                        goto copyin_cleanup;
                case ENOMEM:
                        ret = DDI_ENOMEM;
                        goto copyin_cleanup;
                default:
                        ret = DDI_FAILURE;
                        goto copyin_cleanup;
                }
        }
        *nvlp = dest;
copyin_cleanup:
        kmem_free(packed, packed_sz);
        return (ret);
}

/*
 * routine to copy out a nvlist to userland
 */
int
pcie_copyout_nvlist(nvlist_t *nvl, char *packed_buf, size_t *buf_sz)
{
        int     err = 0;
        char    *buf = NULL;
        size_t  packed_sz;

        if (nvl == NULL || packed_buf == NULL || buf_sz == NULL)
                return (DDI_EINVAL);

        /* pack nvlist, the library will allocate memory */
        if ((err = nvlist_pack(nvl, &buf, &packed_sz, NV_ENCODE_NATIVE, 0))
            != 0) {
                cmn_err(CE_WARN, "pcie_copyout_nvlist: nvlist_pack "
                    "failed with err %d\n", err);
                switch (err) {
                case EINVAL:
                case ENOTSUP:
                        return (DDI_EINVAL);
                case ENOMEM:
                        return (DDI_ENOMEM);
                default:
                        return (DDI_FAILURE);
                }
        }
        if (packed_sz > *buf_sz) {
                return (DDI_EINVAL);
        }

        /* copyout packed nvlist */
        if (copyout(buf, packed_buf, packed_sz) != 0) {
                cmn_err(CE_WARN, "pcie_copyout_nvlist: copyout " "failed.\n");
                kmem_free(buf, packed_sz);
                return (DDI_FAILURE);
        }

        *buf_sz = packed_sz;
        kmem_free(buf, packed_sz);
        return (DDI_SUCCESS);
}

/*
 * init bus_hp_op entry and init hotpluggable slots & virtual ports
 */
int
pcie_hp_init(dev_info_t *dip, caddr_t arg)
{
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(dip);
        int             ret = DDI_SUCCESS;
        dev_info_t      *cdip;

        if (PCIE_IS_PCIE_HOTPLUG_CAPABLE(bus_p)) {
                /* Init hotplug controller */
                ret = pciehpc_init(dip, arg);
        } else if (PCIE_IS_PCI_HOTPLUG_CAPABLE(bus_p)) {
                ret = pcishpc_init(dip);
        }

        if (ret != DDI_SUCCESS) {
                PCIE_DBG("pcie_hp_init: initialize hotplug "
                    "controller failed with %d\n", ret);
                return (ret);
        }

        ndi_devi_enter(dip);

        /* Create port for the first level children */
        cdip = ddi_get_child(dip);
        while (cdip != NULL) {
                if ((ret = pcie_hp_register_port(cdip, dip, NULL))
                    != DDI_SUCCESS) {
                        /* stop and cleanup */
                        break;
                }
                cdip = ddi_get_next_sibling(cdip);
        }
        ndi_devi_exit(dip);
        if (ret != DDI_SUCCESS) {
                cmn_err(CE_WARN, "pcie_hp_init: initialize virtual "
                    "hotplug port failed with %d\n", ret);
                (void) pcie_hp_uninit(dip);

                return (ret);
        }

        return (DDI_SUCCESS);
}

/*
 * uninit the hotpluggable slots and virtual ports
 */
int
pcie_hp_uninit(dev_info_t *dip)
{
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(dip);
        pcie_hp_unreg_port_t arg;

        /*
         * Must set arg.rv to NDI_SUCCESS so that if there's no port
         * under this dip, we still return success thus the bridge
         * driver can be successfully detached.
         *
         * Note that during the probe PCI configurator calls
         * ndi_devi_offline() to detach driver for a new probed bridge,
         * so that it can reprogram the resources for the bridge,
         * ndi_devi_offline() calls into pcieb_detach() which in turn
         * calls into this function. In this case there are no ports
         * created under a new probe bridge dip, as ports are only
         * created after the configurator finishing probing, thus the
         * ndi_hp_walk_cn() will see no ports when this is called
         * from the PCI configurtor.
         */
        arg.nexus_dip = dip;
        arg.connector_num = DDI_HP_CN_NUM_NONE;
        arg.rv = NDI_SUCCESS;

        /* tear down all virtual hotplug handles */
        ndi_hp_walk_cn(dip, pcie_hp_unregister_ports_cb, &arg);

        if (arg.rv != NDI_SUCCESS)
                return (DDI_FAILURE);

        if (PCIE_IS_PCIE_HOTPLUG_ENABLED(bus_p))
                (void) pciehpc_uninit(dip);
        else if (PCIE_IS_PCI_HOTPLUG_ENABLED(bus_p))
                (void) pcishpc_uninit(dip);

        return (DDI_SUCCESS);
}

/*
 * interrupt handler
 */
int
pcie_hp_intr(dev_info_t *dip)
{
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(dip);
        int             ret = DDI_INTR_UNCLAIMED;

        if (PCIE_IS_PCIE_HOTPLUG_ENABLED(bus_p))
                ret = pciehpc_intr(dip);
        else if (PCIE_IS_PCI_HOTPLUG_ENABLED(bus_p))
                ret = pcishpc_intr(dip);

        return (ret);
}

/*
 * Probe the given PCIe/PCI Hotplug Connection (CN).
 */
/*ARGSUSED*/
int
pcie_hp_probe(pcie_hp_slot_t *slot_p)
{
        pcie_hp_ctrl_t  *ctrl_p = slot_p->hs_ctrl;
        dev_info_t      *dip = ctrl_p->hc_dip;

        /*
         * Call the configurator to probe a given PCI hotplug
         * Hotplug Connection (CN).
         */
        if (pcicfg_configure(dip, slot_p->hs_device_num, PCICFG_ALL_FUNC, 0)
            != PCICFG_SUCCESS) {
                PCIE_DBG("pcie_hp_probe() failed\n");
                return (DDI_FAILURE);
        }
        slot_p->hs_condition = AP_COND_OK;
        pcie_hp_create_occupant_props(dip, makedevice(ddi_driver_major(dip),
            slot_p->hs_minor), slot_p->hs_device_num);

        /*
         * Create ports for the newly probed devices.
         * Note, this is only for the first level children because the
         * descendants' ports will be created during bridge driver attach.
         */
        return (pcie_hp_register_ports_for_dev(dip, slot_p->hs_device_num));
}

/*
 * Unprobe the given PCIe/PCI Hotplug Connection (CN):
 *      1. remove all child device nodes
 *      2. unregister all dependent ports
 */
/*ARGSUSED*/
int
pcie_hp_unprobe(pcie_hp_slot_t *slot_p)
{
        pcie_hp_ctrl_t  *ctrl_p = slot_p->hs_ctrl;
        dev_info_t      *dip = ctrl_p->hc_dip;
        pcie_hp_unreg_port_t arg;

        /*
         * Call the configurator to unprobe a given PCI hotplug
         * Hotplug Connection (CN).
         */
        if (pcicfg_unconfigure(dip, slot_p->hs_device_num, PCICFG_ALL_FUNC, 0)
            != PCICFG_SUCCESS) {
                PCIE_DBG("pcie_hp_unprobe() failed\n");
                return (DDI_FAILURE);
        }
        slot_p->hs_condition = AP_COND_UNKNOWN;
        pcie_hp_delete_occupant_props(dip, makedevice(ddi_driver_major(dip),
            slot_p->hs_minor));

        /*
         * Remove ports for the unprobed devices.
         * Note, this is only for the first level children because the
         * descendants' ports were already removed during bridge driver dettach.
         */
        arg.nexus_dip = dip;
        arg.connector_num = slot_p->hs_info.cn_num;
        arg.rv = NDI_SUCCESS;
        ndi_hp_walk_cn(dip, pcie_hp_unregister_ports_cb, &arg);

        return (arg.rv == NDI_SUCCESS) ? (DDI_SUCCESS) : (DDI_FAILURE);
}

/* Read-only probe: no hardware register programming. */
int
pcie_read_only_probe(dev_info_t *dip, char *cn_name, dev_info_t **pcdip)
{
        long dev, func;
        int ret;
        char *sp;
        dev_info_t *cdip;

        *pcdip = NULL;
        /*
         * Parse the string of a pci Port name and get the device number
         * and function number.
         */
        if (ddi_strtol(cn_name + 4, &sp, 10, &dev) != 0)
                return (DDI_EINVAL);
        if (ddi_strtol(sp + 1, NULL, 10, &func) != 0)
                return (DDI_EINVAL);

        ret = pcicfg_configure(dip, (int)dev, (int)func,
            PCICFG_FLAG_READ_ONLY);
        if (ret == PCICFG_SUCCESS) {
                cdip = pcie_hp_devi_find(dip, (int)dev, (int)func);
                *pcdip = cdip;
        }
        return (ret);
}

/* Read-only unprobe: no hardware register programming. */
int
pcie_read_only_unprobe(dev_info_t *dip, char *cn_name)
{
        long dev, func;
        int ret;
        char *sp;

        /*
         * Parse the string of a pci Port name and get the device number
         * and function number.
         */
        if (ddi_strtol(cn_name + 4, &sp, 10, &dev) != 0)
                return (DDI_EINVAL);
        if (ddi_strtol(sp + 1, NULL, 10, &func) != 0)
                return (DDI_EINVAL);

        ret = pcicfg_unconfigure(dip, (int)dev, (int)func,
            PCICFG_FLAG_READ_ONLY);

        return (ret);
}

/* Control structure used to find a device in the devinfo tree */
struct pcie_hp_find_ctrl {
        uint_t          device;
        uint_t          function;
        dev_info_t      *dip;
};

/*
 * find a devinfo node with specified device and function number
 * in the device tree under 'dip'
 */
dev_info_t *
pcie_hp_devi_find(dev_info_t *dip, uint_t device, uint_t function)
{
        struct pcie_hp_find_ctrl        ctrl;

        ctrl.device = device;
        ctrl.function = function;
        ctrl.dip = NULL;

        ndi_devi_enter(dip);
        ddi_walk_devs(ddi_get_child(dip), pcie_hp_match_dev_func,
            (void *)&ctrl);
        ndi_devi_exit(dip);

        return (ctrl.dip);
}

/*
 * routine to create 'pci-occupant' property for a hotplug slot
 */
void
pcie_hp_create_occupant_props(dev_info_t *dip, dev_t dev, int pci_dev)
{
        pcie_bus_t              *bus_p = PCIE_DIP2BUS(dip);
        pcie_hp_ctrl_t          *ctrl_p = (pcie_hp_ctrl_t *)bus_p->bus_hp_ctrl;
        pcie_hp_slot_t          *slotp = NULL;
        pcie_hp_cn_cfg_t        cn_cfg;
        pcie_hp_occupant_info_t *occupant;
        int                     i;

        ndi_devi_enter(dip);

        if (PCIE_IS_PCIE_HOTPLUG_ENABLED(bus_p)) {
                slotp = (ctrl_p && (pci_dev == 0)) ?
                    ctrl_p->hc_slots[pci_dev] : NULL;
        } else if (PCIE_IS_PCI_HOTPLUG_ENABLED(bus_p)) {
                if (ctrl_p) {
                        int     slot_num;

                        slot_num = (ctrl_p->hc_device_increases) ?
                            (pci_dev - ctrl_p->hc_device_start) :
                            (pci_dev + ctrl_p->hc_device_start);

                        slotp = ctrl_p->hc_slots[slot_num];
                } else {
                        slotp = NULL;
                }
        }

        if (slotp == NULL)
                return;

        occupant = kmem_alloc(sizeof (pcie_hp_occupant_info_t), KM_SLEEP);
        occupant->i = 0;

        cn_cfg.flag = B_FALSE;
        cn_cfg.rv = NDI_SUCCESS;
        cn_cfg.dip = NULL;
        cn_cfg.slotp = (void *)slotp;
        cn_cfg.cn_private = (void *)occupant;

        ddi_walk_devs(ddi_get_child(dip), pcie_hp_list_occupants,
            (void *)&cn_cfg);

        if (occupant->i == 0) {
                /* no occupants right now, need to create stub property */
                char *c[] = { "" };
                (void) ddi_prop_update_string_array(dev, dip, "pci-occupant",
                    c, 1);
        } else {
                (void) ddi_prop_update_string_array(dev, dip, "pci-occupant",
                    occupant->id, occupant->i);
        }

        for (i = 0; i < occupant->i; i++)
                kmem_free(occupant->id[i], sizeof (char[MAXPATHLEN]));

        kmem_free(occupant, sizeof (pcie_hp_occupant_info_t));

        ndi_devi_exit(dip);
}

/*
 * routine to remove 'pci-occupant' property for a hotplug slot
 */
void
pcie_hp_delete_occupant_props(dev_info_t *dip, dev_t dev)
{
        (void) ddi_prop_remove(dev, dip, "pci-occupant");
}

/*
 * general code to create a minor node, called from hotplug controller
 * drivers.
 */
int
pcie_create_minor_node(pcie_hp_ctrl_t *ctrl_p, int slot)
{
        dev_info_t              *dip = ctrl_p->hc_dip;
        pcie_hp_slot_t          *slot_p = ctrl_p->hc_slots[slot];
        ddi_hp_cn_info_t        *info_p = &slot_p->hs_info;

        if (ddi_create_minor_node(dip, info_p->cn_name,
            S_IFCHR, slot_p->hs_minor,
            DDI_NT_PCI_ATTACHMENT_POINT, 0) != DDI_SUCCESS) {
                return (DDI_FAILURE);
        }

        (void) ddi_prop_update_int(DDI_DEV_T_NONE,
            dip, "ap-names", 1 << slot_p->hs_device_num);

        return (DDI_SUCCESS);
}

/*
 * general code to remove a minor node, called from hotplug controller
 * drivers.
 */
void
pcie_remove_minor_node(pcie_hp_ctrl_t *ctrl_p, int slot)
{
        ddi_remove_minor_node(ctrl_p->hc_dip,
            ctrl_p->hc_slots[slot]->hs_info.cn_name);
}

/*
 * Local functions (called within this file)
 */

/*
 * Register ports for all the children with device number device_num
 */
static int
pcie_hp_register_ports_for_dev(dev_info_t *dip, int device_num)
{
        dev_info_t      *cdip;
        int             rv;

        for (cdip = ddi_get_child(dip); cdip;
            cdip = ddi_get_next_sibling(cdip)) {
                if (pcie_hp_match_dev(cdip, device_num)) {
                        /*
                         * Found the newly probed device under the
                         * current slot. Register a port for it.
                         */
                        if ((rv = pcie_hp_register_port(cdip, dip, NULL))
                            != DDI_SUCCESS)
                                return (rv);
                } else {
                        continue;
                }
        }

        return (DDI_SUCCESS);
}

/*
 * Unregister ports of a pci bridge dip, get called from ndi_hp_walk_cn()
 *
 * If connector_num is specified, then unregister the slot's dependent ports
 * only; Otherwise, unregister all ports of a pci bridge dip.
 */
static int
pcie_hp_unregister_ports_cb(ddi_hp_cn_info_t *info, void *arg)
{
        pcie_hp_unreg_port_t *unreg_arg = (pcie_hp_unreg_port_t *)arg;
        dev_info_t *dip = unreg_arg->nexus_dip;
        int rv = NDI_SUCCESS;

        if (info->cn_type != DDI_HP_CN_TYPE_VIRTUAL_PORT) {
                unreg_arg->rv = rv;
                return (DDI_WALK_CONTINUE);
        }

        if (unreg_arg->connector_num != DDI_HP_CN_NUM_NONE) {
                /* Unregister ports for all unprobed devices under a slot. */
                if (unreg_arg->connector_num == info->cn_num_dpd_on) {

                        rv = ndi_hp_unregister(dip, info->cn_name);
                }
        } else {

                /* Unregister all ports of a pci bridge dip. */
                rv = ndi_hp_unregister(dip, info->cn_name);
        }

        unreg_arg->rv = rv;
        if (rv == NDI_SUCCESS)
                return (DDI_WALK_CONTINUE);
        else
                return (DDI_WALK_TERMINATE);
}

/*
 * Find a port according to cn_name and get the port's state.
 */
static int
pcie_hp_get_port_state(ddi_hp_cn_info_t *info, void *arg)
{
        pcie_hp_port_state_t *port = (pcie_hp_port_state_t *)arg;

        if (info->cn_type != DDI_HP_CN_TYPE_VIRTUAL_PORT)
                return (DDI_WALK_CONTINUE);

        if (strcmp(info->cn_name, port->cn_name) == 0) {
                /* Matched. */
                port->cn_state = info->cn_state;
                port->rv = DDI_SUCCESS;

                return (DDI_WALK_TERMINATE);
        }

        return (DDI_WALK_CONTINUE);
}

/*
 * Find the physical slot with the given device number;
 * return the slot if found.
 */
static pcie_hp_slot_t *
pcie_find_physical_slot(dev_info_t *dip, int dev_num)
{
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(dip);
        pcie_hp_ctrl_t  *ctrl = PCIE_GET_HP_CTRL(dip);

        if (PCIE_IS_PCIE_HOTPLUG_CAPABLE(bus_p)) {
                /* PCIe has only one slot */
                return (dev_num == 0) ? (ctrl->hc_slots[0]) : (NULL);
        } else if (PCIE_IS_PCI_HOTPLUG_CAPABLE(bus_p)) {
                for (int slot = 0; slot < ctrl->hc_num_slots_impl; slot++) {
                        if (ctrl->hc_slots[slot]->hs_device_num == dev_num) {
                                /* found */
                                return (ctrl->hc_slots[slot]);
                        }
                }
        }

        return (NULL);
}

/*
 * setup slot name/slot-number info for the port which is being registered.
 */
static int
pcie_hp_create_port_name_num(dev_info_t *dip, ddi_hp_cn_info_t *cn_info)
{
        int             ret, dev_num, func_num, name_len;
        dev_info_t      *pdip = ddi_get_parent(dip);
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(pdip);
        pcie_hp_slot_t  *slot;
        pcie_req_id_t   bdf;
        char            tmp[PCIE_HP_DEV_FUNC_NUM_STRING_LEN];

        ret = pcie_get_bdf_from_dip(dip, &bdf);
        if (ret != DDI_SUCCESS) {
                return (ret);
        }
        if (PCIE_IS_RP(bus_p) || PCIE_IS_SWD(bus_p) ||
            PCIE_IS_PCI2PCIE(bus_p)) {
                /*
                 * It is under a PCIe device, devcie number is always 0;
                 * function number might > 8 in ARI supported case.
                 */
                dev_num = 0;
                func_num = (bdf & ((~PCI_REG_BUS_M) >> 8));
        } else {
                dev_num = (bdf & (PCI_REG_DEV_M >> 8)) >> 3;
                func_num = bdf & (PCI_REG_FUNC_M >> 8);
        }
        /*
         * The string length of dev_num and func_num must be no longer than 4
         * including the string end mark. (With ARI case considered, e.g.,
         * dev_num=0x0, func_num=0xff.)
         */
        (void) snprintf(tmp, PCIE_HP_DEV_FUNC_NUM_STRING_LEN, "%x%x",
            dev_num, func_num);
        /*
         * Calculate the length of cn_name.
         * The format of pci port name is: pci.d,f
         * d stands for dev_num, f stands for func_num. So the length of the
         * name string can be calculated as following.
         */
        name_len = strlen(tmp) + PCIE_HP_PORT_NAME_STRING_LEN + 1;

        cn_info->cn_name = (char *)kmem_zalloc(name_len, KM_SLEEP);
        (void) snprintf(cn_info->cn_name, name_len, "pci.%x,%x",
            dev_num, func_num);
        cn_info->cn_num = (dev_num << 8) | func_num;
        slot = pcie_find_physical_slot(pdip, dev_num);

        cn_info->cn_num_dpd_on = slot ?
            slot->hs_info.cn_num : DDI_HP_CN_NUM_NONE;

        return (DDI_SUCCESS);
}

/*
 * Extract device and function number from port name, whose format is
 * something like 'pci.1,0'
 */
static int
pcie_hp_get_df_from_port_name(char *cn_name, int *dev_num, int *func_num)
{
        int name_len, ret;
        long d, f;
        char *sp;

        /* some checks for the input name */
        name_len = strlen(cn_name);
        if ((name_len <= PCIE_HP_PORT_NAME_STRING_LEN) ||
            (name_len > (PCIE_HP_PORT_NAME_STRING_LEN +
            PCIE_HP_DEV_FUNC_NUM_STRING_LEN - 1)) ||
            (strncmp("pci.", cn_name, 4) != 0)) {
                return (DDI_EINVAL);
        }
        ret = ddi_strtol(cn_name + 4, &sp, 10, &d);
        if (ret != DDI_SUCCESS)
                return (ret);

        if (strncmp(",", sp, 1) != 0)
                return (DDI_EINVAL);

        ret = ddi_strtol(sp + 1, NULL, 10, &f);
        if (ret != DDI_SUCCESS)
                return (ret);
        *dev_num = (int)d;
        *func_num = (int)f;

        return (ret);
}

/*
 * Check/copy cn_name and set connection numbers.
 * If it is a valid name, then setup cn_info for the newly created port.
 */
static int
pcie_hp_setup_port_name_num(dev_info_t *pdip, char *cn_name,
    ddi_hp_cn_info_t *cn_info)
{
        int dev_num, func_num, ret;
        pcie_hp_slot_t *slot;

        if ((ret = pcie_hp_get_df_from_port_name(cn_name, &dev_num, &func_num))
            != DDI_SUCCESS)
                return (ret);

        if (pcie_hp_check_hardware_existence(pdip, dev_num, func_num) ==
            DDI_SUCCESS) {
                cn_info->cn_state = DDI_HP_CN_STATE_PRESENT;
        } else {
                cn_info->cn_state = DDI_HP_CN_STATE_EMPTY;
        }

        cn_info->cn_name = ddi_strdup(cn_name, KM_SLEEP);
        cn_info->cn_num = (dev_num << 8) | func_num;

        slot = pcie_find_physical_slot(pdip, dev_num);
        if (slot) {
                cn_info->cn_num_dpd_on = slot->hs_info.cn_num;
        } else {
                cn_info->cn_num_dpd_on = DDI_HP_CN_NUM_NONE;
        }
        return (DDI_SUCCESS);
}

static int
ndi2ddi(int n)
{
        int ret;

        switch (n) {
        case NDI_SUCCESS:
                ret = DDI_SUCCESS;
                break;
        case NDI_NOMEM:
                ret = DDI_ENOMEM;
                break;
        case NDI_BUSY:
                ret = DDI_EBUSY;
                break;
        case NDI_EINVAL:
                ret = DDI_EINVAL;
                break;
        case NDI_ENOTSUP:
                ret = DDI_ENOTSUP;
                break;
        case NDI_FAILURE:
        default:
                ret = DDI_FAILURE;
                break;
        }
        return (ret);
}

/*
 * Common routine to create and register a new port
 *
 * Create an empty port if dip is NULL, and cn_name needs to be specified in
 * this case. Otherwise, create a port mapping to the specified dip, and cn_name
 * is not needed in this case.
 */
static int
pcie_hp_register_port(dev_info_t *dip, dev_info_t *pdip, char *cn_name)
{
        ddi_hp_cn_info_t        *cn_info;
        int                     ret;

        ASSERT((dip == NULL) != (cn_name == NULL));
        cn_info = kmem_zalloc(sizeof (ddi_hp_cn_info_t), KM_SLEEP);
        if (dip != NULL)
                ret = pcie_hp_create_port_name_num(dip, cn_info);
        else
                ret = pcie_hp_setup_port_name_num(pdip, cn_name, cn_info);

        if (ret != DDI_SUCCESS) {
                kmem_free(cn_info, sizeof (ddi_hp_cn_info_t));
                return (ret);
        }

        cn_info->cn_child = dip;
        cn_info->cn_type = DDI_HP_CN_TYPE_VIRTUAL_PORT;
        cn_info->cn_type_str = DDI_HP_CN_TYPE_STR_PORT;

        ret = ndi_hp_register(pdip, cn_info);

        kmem_free(cn_info->cn_name, strlen(cn_info->cn_name) + 1);
        kmem_free(cn_info, sizeof (ddi_hp_cn_info_t));

        return (ndi2ddi(ret));
}

/* Check if there is a piece of hardware exist corresponding to the cn_name */
static int
pcie_hp_check_hardware_existence(dev_info_t *dip, int dev_num, int func_num)
{

        /*
         * VHPTODO:
         * According to device and function number, check if there is a hardware
         * device exists. Currently, this function can not be reached before
         * we enable state transition to or from "Port-Empty" or "Port-Present"
         * states. When the pci device type project is integrated, we are going
         * to call the pci config space access interfaces introduced by it.
         */
        _NOTE(ARGUNUSED(dip, dev_num, func_num));

        return (DDI_SUCCESS);
}

/*
 * Dispatch hotplug commands to different hotplug controller drivers, including
 * physical and virtual hotplug operations.
 */
/* ARGSUSED */
int
pcie_hp_common_ops(dev_info_t *dip, char *cn_name, ddi_hp_op_t op,
    void *arg, void *result)
{
        pcie_bus_t      *bus_p = PCIE_DIP2BUS(dip);
        int             ret = DDI_SUCCESS;

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

        switch (op) {
        case DDI_HPOP_CN_CREATE_PORT:
        {
                /* create an empty port */
                return (pcie_hp_register_port(NULL, dip, cn_name));
        }
        case DDI_HPOP_CN_CHANGE_STATE:
        {
                ddi_hp_cn_state_t curr_state;
                ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg;
                pcie_hp_port_state_t state_arg;

                if (target_state < DDI_HP_CN_STATE_PORT_EMPTY) {
                        /* this is for physical slot state change */
                        break;
                }
                PCIE_DBG("pcie_hp_common_ops: change port state"
                    " dip=%p cn_name=%s"
                    " op=%x arg=%p\n", (void *)dip, cn_name, op, arg);

                state_arg.rv = DDI_FAILURE;
                state_arg.cn_name = cn_name;
                ndi_hp_walk_cn(dip, pcie_hp_get_port_state, &state_arg);
                if (state_arg.rv != DDI_SUCCESS) {
                        /* can not find the port */
                        return (DDI_EINVAL);
                }
                curr_state = state_arg.cn_state;
                /*
                 * Check if this is for changing port's state: change to/from
                 * PORT_EMPTY/PRESENT states.
                 */
                if (curr_state < target_state) {
                        /* Upgrade state */
                        switch (curr_state) {
                        case DDI_HP_CN_STATE_PORT_EMPTY:
                                if (target_state ==
                                    DDI_HP_CN_STATE_PORT_PRESENT) {
                                        int dev_num, func_num;

                                        ret = pcie_hp_get_df_from_port_name(
                                            cn_name, &dev_num, &func_num);
                                        if (ret != DDI_SUCCESS)
                                                goto port_state_done;

                                        ret = pcie_hp_check_hardware_existence(
                                            dip, dev_num, func_num);
                                } else if (target_state ==
                                    DDI_HP_CN_STATE_OFFLINE) {
                                        ret = pcie_read_only_probe(dip,
                                            cn_name, (dev_info_t **)result);
                                } else
                                        ret = DDI_EINVAL;

                                goto port_state_done;
                        case DDI_HP_CN_STATE_PORT_PRESENT:
                                if (target_state ==
                                    DDI_HP_CN_STATE_OFFLINE)
                                        ret = pcie_read_only_probe(dip,
                                            cn_name, (dev_info_t **)result);
                                else
                                        ret = DDI_EINVAL;

                                goto port_state_done;
                        default:
                                ASSERT("unexpected state");
                        }
                } else {
                        /* Downgrade state */
                        switch (curr_state) {
                        case DDI_HP_CN_STATE_PORT_PRESENT:
                        {
                                int dev_num, func_num;

                                ret = pcie_hp_get_df_from_port_name(cn_name,
                                    &dev_num, &func_num);
                                if (ret != DDI_SUCCESS)
                                        goto port_state_done;

                                ret = pcie_hp_check_hardware_existence(dip,
                                    dev_num, func_num);

                                goto port_state_done;
                        }
                        case DDI_HP_CN_STATE_OFFLINE:
                                ret = pcie_read_only_unprobe(dip, cn_name);

                                goto port_state_done;
                        default:
                                ASSERT("unexpected state");
                        }
                }
port_state_done:
                *(ddi_hp_cn_state_t *)result = curr_state;
                return (ret);
        }
        default:
                break;
        }

        if (PCIE_IS_PCIE_HOTPLUG_CAPABLE(bus_p)) {
                /* PCIe hotplug */
                ret = pciehpc_hp_ops(dip, cn_name, op, arg, result);
        } else if (PCIE_IS_PCI_HOTPLUG_CAPABLE(bus_p)) {
                /* PCI SHPC hotplug */
                ret = pcishpc_hp_ops(dip, cn_name, op, arg, result);
        } else {
                cmn_err(CE_WARN, "pcie_hp_common_ops: op is not supported."
                    " dip=%p cn_name=%s"
                    " op=%x arg=%p\n", (void *)dip, cn_name, op, arg);
                ret = DDI_ENOTSUP;
        }

#if defined(__x86)
        /*
         * like in attach, since hotplugging can change error registers,
         * we need to ensure that the proper bits are set on this port
         * after a configure operation
         */
        if ((ret == DDI_SUCCESS) && (op == DDI_HPOP_CN_CHANGE_STATE) &&
            (*(ddi_hp_cn_state_t *)arg == DDI_HP_CN_STATE_ENABLED))
                pcieb_intel_error_workaround(dip);
#endif

        return (ret);
}

/*
 * pcie_hp_match_dev_func:
 * Match dip's PCI device number and function number with input ones.
 */
static int
pcie_hp_match_dev_func(dev_info_t *dip, void *hdl)
{
        struct pcie_hp_find_ctrl        *ctrl = (struct pcie_hp_find_ctrl *)hdl;
        pci_regspec_t                   *pci_rp;
        int                             length;
        int                             pci_dev, pci_func;

        if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
            "reg", (int **)&pci_rp, (uint_t *)&length) != DDI_PROP_SUCCESS) {
                ctrl->dip = NULL;
                return (DDI_WALK_TERMINATE);
        }

        /* get the PCI device address info */
        pci_dev = PCI_REG_DEV_G(pci_rp->pci_phys_hi);
        pci_func = PCI_REG_FUNC_G(pci_rp->pci_phys_hi);

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

        if ((pci_dev == ctrl->device) && (pci_func == ctrl->function)) {
                /* found the match for the specified device address */
                ctrl->dip = dip;
                return (DDI_WALK_TERMINATE);
        }

        /*
         * continue the walk to the next sibling to look for a match.
         */
        return (DDI_WALK_PRUNECHILD);
}

/*
 * pcie_hp_match_dev:
 * Match the dip's pci device number with the input dev_num
 */
static boolean_t
pcie_hp_match_dev(dev_info_t *dip, int dev_num)
{
        pci_regspec_t                   *pci_rp;
        int                             length;
        int                             pci_dev;

        if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
            "reg", (int **)&pci_rp, (uint_t *)&length) != DDI_PROP_SUCCESS) {
                return (B_FALSE);
        }

        /* get the PCI device address info */
        pci_dev = PCI_REG_DEV_G(pci_rp->pci_phys_hi);

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

        if (pci_dev == dev_num) {
                /* found the match for the specified device address */
                return (B_TRUE);
        }

        return (B_FALSE);
}

/*
 * Callback function to match with device number in order to list
 * occupants under a specific slot
 */
static int
pcie_hp_list_occupants(dev_info_t *dip, void *arg)
{
        pcie_hp_cn_cfg_t        *cn_cfg_p = (pcie_hp_cn_cfg_t *)arg;
        pcie_hp_occupant_info_t *occupant =
            (pcie_hp_occupant_info_t *)cn_cfg_p->cn_private;
        pcie_hp_slot_t          *slot_p =
            (pcie_hp_slot_t *)cn_cfg_p->slotp;
        int                     pci_dev;
        pci_regspec_t           *pci_rp;
        int                     length;
        major_t                 major;

        /*
         * Get the PCI device number information from the devinfo
         * node. Since the node may not have the address field
         * setup (this is done in the DDI_INITCHILD of the parent)
         * we look up the 'reg' property to decode that information.
         */
        if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, dip,
            DDI_PROP_DONTPASS, "reg", (int **)&pci_rp,
            (uint_t *)&length) != DDI_PROP_SUCCESS) {
                cn_cfg_p->rv = DDI_FAILURE;
                cn_cfg_p->dip = dip;
                return (DDI_WALK_TERMINATE);
        }

        /* get the pci device id information */
        pci_dev = PCI_REG_DEV_G(pci_rp->pci_phys_hi);

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

        /*
         * Match the node for the device number of the slot.
         */
        if (pci_dev == slot_p->hs_device_num) {

                major = ddi_driver_major(dip);

                /*
                 * If the node is not yet attached, then don't list it
                 * as an occupant. This is valid, since nothing can be
                 * consuming it until it is attached, and cfgadm will
                 * ask for the property explicitly which will cause it
                 * to be re-freshed right before checking with rcm.
                 */
                if ((major == DDI_MAJOR_T_NONE) || !i_ddi_devi_attached(dip))
                        return (DDI_WALK_PRUNECHILD);

                /*
                 * If we have used all our occupants then print mesage
                 * and terminate walk.
                 */
                if (occupant->i >= PCIE_HP_MAX_OCCUPANTS) {
                        cmn_err(CE_WARN,
                            "pcie (%s%d): unable to list all occupants",
                            ddi_driver_name(ddi_get_parent(dip)),
                            ddi_get_instance(ddi_get_parent(dip)));
                        return (DDI_WALK_TERMINATE);
                }

                /*
                 * No need to hold the dip as ddi_walk_devs
                 * has already arranged that for us.
                 */
                occupant->id[occupant->i] =
                    kmem_alloc(sizeof (char[MAXPATHLEN]), KM_SLEEP);
                (void) ddi_pathname(dip, (char *)occupant->id[occupant->i]);
                occupant->i++;
        }

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

/*
 * Generate the System Event for ESC_DR_REQ.
 * One of the consumers is pcidr, it calls to libcfgadm to perform a
 * configure or unconfigure operation to the AP.
 */
void
pcie_hp_gen_sysevent_req(char *slot_name, int hint,
    dev_info_t *self, int kmflag)
{
        sysevent_id_t   eid;
        nvlist_t        *ev_attr_list = NULL;
        char            cn_path[MAXPATHLEN];
        char            *ap_id;
        int             err, ap_id_len;

        /*
         * Minor device name (AP) will be bus path
         * concatenated with slot name
         */
        (void) strcpy(cn_path, "/devices");
        (void) ddi_pathname(self, cn_path + strlen("/devices"));

        ap_id_len = strlen(cn_path) + strlen(":") +
            strlen(slot_name) + 1;
        ap_id = kmem_zalloc(ap_id_len, kmflag);
        if (ap_id == NULL) {
                cmn_err(CE_WARN,
                    "%s%d: Failed to allocate memory for AP ID: %s:%s",
                    ddi_driver_name(self), ddi_get_instance(self),
                    cn_path, slot_name);

                return;
        }

        (void) strcpy(ap_id, cn_path);
        (void) strcat(ap_id, ":");
        (void) strcat(ap_id, slot_name);

        err = nvlist_alloc(&ev_attr_list, NV_UNIQUE_NAME_TYPE, kmflag);
        if (err != 0) {
                cmn_err(CE_WARN,
                    "%s%d: Failed to allocate memory "
                    "for event attributes%s", ddi_driver_name(self),
                    ddi_get_instance(self), ESC_DR_REQ);

                kmem_free(ap_id, ap_id_len);
                return;
        }

        switch (hint) {

        case SE_INVESTIGATE_RES:        /* fall through */
        case SE_INCOMING_RES:           /* fall through */
        case SE_OUTGOING_RES:           /* fall through */

                err = nvlist_add_string(ev_attr_list, DR_REQ_TYPE,
                    SE_REQ2STR(hint));

                if (err != 0) {
                        cmn_err(CE_WARN,
                            "%s%d: Failed to add attr [%s] "
                            "for %s event", ddi_driver_name(self),
                            ddi_get_instance(self),
                            DR_REQ_TYPE, ESC_DR_REQ);

                        goto done;
                }
                break;

        default:
                cmn_err(CE_WARN, "%s%d:  Unknown hint on sysevent",
                    ddi_driver_name(self), ddi_get_instance(self));

                goto done;
        }

        /*
         * Add attachment point as attribute (common attribute)
         */

        err = nvlist_add_string(ev_attr_list, DR_AP_ID, ap_id);

        if (err != 0) {
                cmn_err(CE_WARN, "%s%d: Failed to add attr [%s] for %s event",
                    ddi_driver_name(self), ddi_get_instance(self),
                    DR_AP_ID, EC_DR);

                goto done;
        }


        /*
         * Log this event with sysevent framework.
         */

        err = ddi_log_sysevent(self, DDI_VENDOR_SUNW, EC_DR,
            ESC_DR_REQ, ev_attr_list, &eid,
            ((kmflag == KM_SLEEP) ? DDI_SLEEP : DDI_NOSLEEP));
        if (err != 0) {
                cmn_err(CE_WARN, "%s%d: Failed to log %s event",
                    ddi_driver_name(self), ddi_get_instance(self), EC_DR);
        }

done:
        nvlist_free(ev_attr_list);
        kmem_free(ap_id, ap_id_len);
}