#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>
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);
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);
}
}
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);
}
}
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);
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;
}
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);
}
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);
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);
}
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);
}
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)) {
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);
cdip = ddi_get_child(dip);
while (cdip != NULL) {
if ((ret = pcie_hp_register_port(cdip, dip, NULL))
!= DDI_SUCCESS) {
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);
}
int
pcie_hp_uninit(dev_info_t *dip)
{
pcie_bus_t *bus_p = PCIE_DIP2BUS(dip);
pcie_hp_unreg_port_t arg;
arg.nexus_dip = dip;
arg.connector_num = DDI_HP_CN_NUM_NONE;
arg.rv = NDI_SUCCESS;
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);
}
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);
}
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;
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);
return (pcie_hp_register_ports_for_dev(dip, slot_p->hs_device_num));
}
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;
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));
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);
}
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;
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);
}
int
pcie_read_only_unprobe(dev_info_t *dip, char *cn_name)
{
long dev, func;
int ret;
char *sp;
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);
}
struct pcie_hp_find_ctrl {
uint_t device;
uint_t function;
dev_info_t *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);
}
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) {
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);
}
void
pcie_hp_delete_occupant_props(dev_info_t *dip, dev_t dev)
{
(void) ddi_prop_remove(dev, dip, "pci-occupant");
}
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);
}
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);
}
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)) {
if ((rv = pcie_hp_register_port(cdip, dip, NULL))
!= DDI_SUCCESS)
return (rv);
} else {
continue;
}
}
return (DDI_SUCCESS);
}
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) {
if (unreg_arg->connector_num == info->cn_num_dpd_on) {
rv = ndi_hp_unregister(dip, info->cn_name);
}
} else {
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);
}
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) {
port->cn_state = info->cn_state;
port->rv = DDI_SUCCESS;
return (DDI_WALK_TERMINATE);
}
return (DDI_WALK_CONTINUE);
}
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)) {
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) {
return (ctrl->hc_slots[slot]);
}
}
}
return (NULL);
}
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)) {
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);
}
(void) snprintf(tmp, PCIE_HP_DEV_FUNC_NUM_STRING_LEN, "%x%x",
dev_num, func_num);
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);
}
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;
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);
}
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);
}
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));
}
static int
pcie_hp_check_hardware_existence(dev_info_t *dip, int dev_num, int func_num)
{
_NOTE(ARGUNUSED(dip, dev_num, func_num));
return (DDI_SUCCESS);
}
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:
{
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) {
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) {
return (DDI_EINVAL);
}
curr_state = state_arg.cn_state;
if (curr_state < target_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 {
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)) {
ret = pciehpc_hp_ops(dip, cn_name, op, arg, result);
} else if (PCIE_IS_PCI_HOTPLUG_CAPABLE(bus_p)) {
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)
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);
}
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);
}
pci_dev = PCI_REG_DEV_G(pci_rp->pci_phys_hi);
pci_func = PCI_REG_FUNC_G(pci_rp->pci_phys_hi);
ddi_prop_free(pci_rp);
if ((pci_dev == ctrl->device) && (pci_func == ctrl->function)) {
ctrl->dip = dip;
return (DDI_WALK_TERMINATE);
}
return (DDI_WALK_PRUNECHILD);
}
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);
}
pci_dev = PCI_REG_DEV_G(pci_rp->pci_phys_hi);
ddi_prop_free(pci_rp);
if (pci_dev == dev_num) {
return (B_TRUE);
}
return (B_FALSE);
}
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;
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);
}
pci_dev = PCI_REG_DEV_G(pci_rp->pci_phys_hi);
ddi_prop_free(pci_rp);
if (pci_dev == slot_p->hs_device_num) {
major = ddi_driver_major(dip);
if ((major == DDI_MAJOR_T_NONE) || !i_ddi_devi_attached(dip))
return (DDI_WALK_PRUNECHILD);
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);
}
occupant->id[occupant->i] =
kmem_alloc(sizeof (char[MAXPATHLEN]), KM_SLEEP);
(void) ddi_pathname(dip, (char *)occupant->id[occupant->i]);
occupant->i++;
}
return (DDI_WALK_PRUNECHILD);
}
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;
(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:
case SE_INCOMING_RES:
case SE_OUTGOING_RES:
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;
}
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;
}
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);
}