/*
* 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
* 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.
*/
/*
* This file contains the common hotplug code that is used by Standard
* PCIe and PCI HotPlug Controller code.
*
*/
#include <sys/autoconf.h>
#include <sys/ddi_impldefs.h>
#include <sys/sysevent.h>
#include <sys/pci_impl.h>
/* Local functions prototype */
char *cn_name);
int *func_num);
int func_num);
/*
*/
/*
* return description text for led state
*/
char *
{
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:
default:
return (PCIEHPC_PROP_VALUE_BLINK);
}
}
/*
* return description text for slot condition
*/
char *
{
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
{
char *packed;
return (DDI_EINVAL);
/* copyin packed nvlist */
return (DDI_ENOMEM);
ret = DDI_FAILURE;
goto copyin_cleanup;
}
/* unpack packed nvlist */
"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;
}
}
return (ret);
}
/*
* routine to copy out a nvlist to userland
*/
int
{
int err = 0;
return (DDI_EINVAL);
/* pack nvlist, the library will allocate memory */
!= 0) {
"failed with err %d\n", err);
switch (err) {
case EINVAL:
case ENOTSUP:
return (DDI_EINVAL);
case ENOMEM:
return (DDI_ENOMEM);
default:
return (DDI_FAILURE);
}
}
return (DDI_EINVAL);
}
/* copyout packed nvlist */
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* init bus_hp_op entry and init hotpluggable slots & virtual ports
*/
int
{
if (PCIE_IS_PCIE_HOTPLUG_CAPABLE(bus_p)) {
/* Init hotplug controller */
} else if (PCIE_IS_PCI_HOTPLUG_CAPABLE(bus_p)) {
}
if (ret != DDI_SUCCESS) {
PCIE_DBG("pcie_hp_init: initialize hotplug "
"controller failed with %d\n", ret);
return (ret);
}
/* Create port for the first level children */
!= DDI_SUCCESS) {
/* stop and cleanup */
break;
}
}
if (ret != DDI_SUCCESS) {
"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
{
/*
* 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.
*/
/* tear down all virtual hotplug handles */
return (DDI_FAILURE);
(void) pciehpc_uninit(dip);
else if (PCIE_IS_PCI_HOTPLUG_ENABLED(bus_p))
(void) pcishpc_uninit(dip);
return (DDI_SUCCESS);
}
/*
* interrupt handler
*/
int
{
else if (PCIE_IS_PCI_HOTPLUG_ENABLED(bus_p))
return (ret);
}
/*
*/
/*ARGSUSED*/
int
{
/*
* Call the configurator to probe a given PCI hotplug
* Hotplug Connection (CN).
*/
!= PCICFG_SUCCESS) {
PCIE_DBG("pcie_hp_probe() failed\n");
return (DDI_FAILURE);
}
/*
* 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.
*/
}
/*
* 1. remove all child device nodes
* 2. unregister all dependent ports
*/
/*ARGSUSED*/
int
{
/*
* Call the configurator to unprobe a given PCI hotplug
* Hotplug Connection (CN).
*/
!= PCICFG_SUCCESS) {
PCIE_DBG("pcie_hp_unprobe() failed\n");
return (DDI_FAILURE);
}
/*
* 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.
*/
}
/* Read-only probe: no hardware register programming. */
int
{
int ret;
char *sp;
/*
* Parse the string of a pci Port name and get the device number
* and function number.
*/
return (DDI_EINVAL);
return (DDI_EINVAL);
if (ret == PCICFG_SUCCESS) {
}
return (ret);
}
/* Read-only unprobe: no hardware register programming. */
int
{
int ret;
char *sp;
/*
* Parse the string of a pci Port name and get the device number
* and function number.
*/
return (DDI_EINVAL);
return (DDI_EINVAL);
return (ret);
}
/* Control structure used to find a device in the devinfo tree */
struct pcie_hp_find_ctrl {
};
/*
* find a devinfo node with specified device and function number
* in the device tree under 'dip'
*/
{
int count;
(void *)&ctrl);
}
/*
* routine to create 'pci-occupant' property for a hotplug slot
*/
void
{
int circular, i;
if (PCIE_IS_PCIE_HOTPLUG_ENABLED(bus_p)) {
} else if (PCIE_IS_PCI_HOTPLUG_ENABLED(bus_p)) {
if (ctrl_p) {
int slot_num;
} else {
}
}
return;
occupant->i = 0;
(void *)&cn_cfg);
if (occupant->i == 0) {
/* no occupants right now, need to create stub property */
char *c[] = { "" };
c, 1);
} else {
}
for (i = 0; i < occupant->i; i++)
}
/*
* routine to remove 'pci-occupant' property for a hotplug slot
*/
void
{
}
/*
* general code to create a minor node, called from hotplug controller
* drivers.
*/
int
{
DDI_NT_PCI_ATTACHMENT_POINT, 0) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
(void) ddi_prop_update_int(DDI_DEV_T_NONE,
return (DDI_SUCCESS);
}
/*
* general code to remove a minor node, called from hotplug controller
* drivers.
*/
void
{
}
/*
* Local functions (called within this file)
*/
/*
* Register ports for all the children with device number device_num
*/
static int
{
int rv;
/*
* Found the newly probed device under the
* current slot. Register a port for it.
*/
!= 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
{
return (DDI_WALK_CONTINUE);
}
/* Unregister ports for all unprobed devices under a slot. */
}
} else {
/* Unregister all ports of a pci bridge dip. */
}
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
{
return (DDI_WALK_CONTINUE);
/* Matched. */
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 *
{
if (PCIE_IS_PCIE_HOTPLUG_CAPABLE(bus_p)) {
/* PCIe has only one slot */
} else if (PCIE_IS_PCI_HOTPLUG_CAPABLE(bus_p)) {
/* found */
}
}
}
return (NULL);
}
/*
* setup slot name/slot-number info for the port which is being registered.
*/
static int
{
if (ret != DDI_SUCCESS) {
return (ret);
}
/*
* It is under a PCIe device, devcie number is always 0;
* function number might > 8 in ARI supported case.
*/
dev_num = 0;
} else {
}
/*
* 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.)
*/
/*
* 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.
*/
return (DDI_SUCCESS);
}
/*
* Extract device and function number from port name, whose format is
* something like 'pci.1,0'
*/
static int
{
long d, f;
char *sp;
/* some checks for the input name */
if ((name_len <= PCIE_HP_PORT_NAME_STRING_LEN) ||
PCIE_HP_DEV_FUNC_NUM_STRING_LEN - 1)) ||
return (DDI_EINVAL);
}
if (ret != DDI_SUCCESS)
return (ret);
return (DDI_EINVAL);
if (ret != DDI_SUCCESS)
return (ret);
*dev_num = (int)d;
*func_num = (int)f;
return (ret);
}
/*
* If it is a valid name, then setup cn_info for the newly created port.
*/
static int
{
!= DDI_SUCCESS)
return (ret);
DDI_SUCCESS) {
} else {
}
if (slot) {
} else {
}
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:
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
{
int ret;
else
if (ret != DDI_SUCCESS) {
return (ret);
}
}
/* Check if there is a piece of hardware exist corresponding to the cn_name */
static int
{
/*
* 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.
*/
return (DDI_SUCCESS);
}
/*
* Dispatch hotplug commands to different hotplug controller drivers, including
* physical and virtual hotplug operations.
*/
/* ARGSUSED */
int
{
PCIE_DBG("pcie_hp_common_ops: dip=%p cn_name=%s op=%x arg=%p\n",
switch (op) {
case DDI_HPOP_CN_CREATE_PORT:
{
/* create an empty port */
}
case DDI_HPOP_CN_CHANGE_STATE:
{
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"
/* can not find the port */
return (DDI_EINVAL);
}
/*
* PORT_EMPTY/PRESENT states.
*/
if (curr_state < target_state) {
/* Upgrade state */
switch (curr_state) {
if (target_state ==
if (ret != DDI_SUCCESS)
goto port_state_done;
} else if (target_state ==
} else
ret = DDI_EINVAL;
goto port_state_done;
if (target_state ==
else
ret = DDI_EINVAL;
goto port_state_done;
default:
ASSERT("unexpected state");
}
} else {
/* Downgrade state */
switch (curr_state) {
{
if (ret != DDI_SUCCESS)
goto port_state_done;
goto port_state_done;
}
case DDI_HP_CN_STATE_OFFLINE:
goto port_state_done;
default:
ASSERT("unexpected state");
}
}
return (ret);
}
default:
break;
}
if (PCIE_IS_PCIE_HOTPLUG_CAPABLE(bus_p)) {
/* PCIe hotplug */
} else if (PCIE_IS_PCI_HOTPLUG_CAPABLE(bus_p)) {
/* PCI SHPC hotplug */
} else {
" dip=%p cn_name=%s"
ret = DDI_ENOTSUP;
}
/*
* 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
*/
#endif
return (ret);
}
/*
* pcie_hp_match_dev_func:
* Match dip's PCI device number and function number with input ones.
*/
static int
{
int length;
return (DDI_WALK_TERMINATE);
}
/* get the PCI device address info */
/*
* free the memory allocated by ddi_prop_lookup_int_array
*/
/* found the match for the specified device address */
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
{
int length;
int pci_dev;
return (B_FALSE);
}
/* get the PCI device address info */
/*
* free the memory allocated by ddi_prop_lookup_int_array
*/
/* 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
{
int pci_dev;
int length;
/*
* 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.
*/
return (DDI_WALK_TERMINATE);
}
/* get the pci device id information */
/*
* free the memory allocated by ddi_prop_lookup_int_array
*/
/*
* Match the node for the device number of the slot.
*/
/*
* 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.
*/
return (DDI_WALK_PRUNECHILD);
/*
* If we have used all our occupants then print mesage
* and terminate walk.
*/
if (occupant->i >= PCIE_HP_MAX_OCCUPANTS) {
"pcie (%s%d): unable to list all occupants",
return (DDI_WALK_TERMINATE);
}
/*
* No need to hold the dip as ddi_walk_devs
* has already arranged that for us.
*/
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
{
char *ap_id;
/*
* Minor device name (AP) will be bus path
* concatenated with slot name
*/
"%s%d: Failed to allocate memory for AP ID: %s:%s",
return;
}
if (err != 0) {
"%s%d: Failed to allocate memory "
return;
}
switch (hint) {
case SE_INVESTIGATE_RES: /* fall through */
case SE_INCOMING_RES: /* fall through */
case SE_OUTGOING_RES: /* fall through */
SE_REQ2STR(hint));
if (err != 0) {
"%s%d: Failed to add attr [%s] "
goto done;
}
break;
default:
goto done;
}
/*
* Add attachment point as attribute (common attribute)
*/
if (err != 0) {
goto done;
}
/*
* Log this event with sysevent framework.
*/
if (err != 0) {
}
done:
}