ddi_hp_impl.c revision 4f6e674fbbae301788090311a9ecf340d0ef7f8b
/*
* 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.
*/
/*
* Sun DDI hotplug implementation specific functions
*/
#include <sys/sysmacros.h>
#include <sys/autoconf.h>
#include <sys/ndi_impldefs.h>
#include <sys/sysevent.h>
/*
* Local function prototypes
*/
/* Connector operations */
/* Port operations */
/* Misc routines */
/*
* Global functions (called within hotplug framework)
*/
/*
* Implement modctl() commands for hotplug.
* Called by modctl_hp() in modctl.c
*/
int
{
/* Get the dip of nexus node */
return (ENXIO);
if (!NEXUS_HAS_HP_OP(dip)) {
return (ENOTSUP);
}
/* Lock before access */
if (hp_op == DDI_HPOP_CN_CREATE_PORT) {
/* this port already exists. */
goto done;
}
} else {
/* Invalid Connection name */
goto done;
}
if (hp_op == DDI_HPOP_CN_CHANGE_STATE) {
(void *)&result_state, rv);
"%x, result_state=%x, rv=%x \n",
} else {
}
}
switch (rv) {
case DDI_SUCCESS:
error = 0;
break;
case DDI_EINVAL:
break;
case DDI_EBUSY:
break;
case DDI_ENOTSUP:
break;
case DDI_ENOMEM:
break;
default:
}
done:
return (error);
}
/*
* Return the state of Hotplug Connection (CN)
*/
int
{
int ret;
if (ret != DDI_SUCCESS) {
"CN %p getstate command failed\n", (void *)hdlp));
return (ret);
}
"current Connection state %x new Connection state %x\n",
}
return (ret);
}
/*
* Implementation function for unregistering the Hotplug Connection (CN)
*/
int
{
(void *)hdlp));
(void) ddihp_cn_getstate(hdlp);
"state %x. Device busy, failed to unregister connection!\n",
return (DDI_EBUSY);
}
/* unlink the handle */
return (DDI_SUCCESS);
}
/*
* For a given Connection name and the dip node where the Connection is
* supposed to be, find the corresponding hotplug handle.
*/
{
/* found */
return (hdlp);
}
}
"failed to find cn_name"));
return (NULL);
}
/*
* Process the hotplug operations for Connector and also create Port
* upon user command.
*/
int
{
int rv = DDI_SUCCESS;
if (op == DDI_HPOP_CN_CHANGE_STATE) {
if (rv != DDI_SUCCESS) {
/* the state is not changed */
*((ddi_hp_cn_state_t *)result) =
return (rv);
}
}
if (rv != DDI_SUCCESS) {
"bus_hp_op failed: pdip=%p cn_name:%s op=%x "
}
if (op == DDI_HPOP_CN_CHANGE_STATE) {
int rv_post;
"old_state=%x, new_state=%x, rv=%x\n",
/*
* After state change op is successfully done or
* failed at some stages, continue to do some jobs.
*/
*(ddi_hp_cn_state_t *)result);
if (rv_post != DDI_SUCCESS)
}
return (rv);
}
/*
* Process the hotplug op for Port
*/
int
{
int ret = DDI_SUCCESS;
switch (op) {
case DDI_HPOP_CN_GET_STATE:
{
int state;
/* No child. Either present or empty. */
if (state >= DDI_HP_CN_STATE_PORT_PRESENT)
else
} else { /* There is a child of this Port */
/* Check DEVI(dip)->devi_node_state */
case DS_INVAL:
case DS_PROTO:
case DS_LINKED:
case DS_BOUND:
case DS_INITIALIZED:
case DS_PROBED:
break;
case DS_ATTACHED:
break;
case DS_READY:
break;
default:
/* should never reach here */
ASSERT("unknown devinfo state");
}
/*
* Check DEVI(dip)->devi_state in case the node is
* downgraded or quiesced.
*/
if (state == DDI_HP_CN_STATE_ONLINE &&
}
break;
}
case DDI_HPOP_CN_CHANGE_STATE:
{
}
break;
}
case DDI_HPOP_CN_REMOVE_PORT:
{
(void) ddihp_cn_getstate(hdlp);
/* Only empty PORT can be removed by commands */
break;
}
break;
}
default:
ret = DDI_ENOTSUP;
break;
}
return (ret);
}
/*
* Generate the system event with a possible hint
*/
/* ARGSUSED */
void
{
char *ev_subclass = NULL;
"%s%d: Failed to allocate memory for hotplug"
" connection: %s\n",
return;
}
/*
* Minor device name will be bus path
* concatenated with connection name.
* One of consumers of the sysevent will pass it
* to cfgadm as AP ID.
*/
"%s%d: Failed to allocate memory for AP ID: %s:%s\n",
return;
}
if (err != 0) {
"%s%d: Failed to allocate memory for event subclass %d\n",
return;
}
switch (event_sub_class) {
case DDI_HP_CN_STATE_CHANGE:
switch (hint) {
case SE_NO_HINT: /* fall through */
case SE_HINT_INSERT: /* fall through */
case SE_HINT_REMOVE:
SE_HINT2STR(hint));
if (err != 0) {
goto done;
}
break;
default:
goto done;
}
break;
/* event sub class: DDI_HP_CN_REQ */
case DDI_HP_CN_REQ:
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] for %s \n"
goto done;
}
break;
default:
goto done;
}
break;
default:
goto done;
}
/*
* Add Hotplug Connection (CN) as attribute (common attribute)
*/
if (err != 0) {
goto done;
}
/*
* Log this event with sysevent framework.
*/
if (err != 0) {
}
done:
}
/*
* Local functions (called within this file)
*/
/*
* Connector operations
*/
/*
* Prepare to change state for a Connector: offline, unprobe, etc.
*/
static int
{
int rv = DDI_SUCCESS;
if (curr_state > target_state &&
/*
* If the Connection goes to a lower state from ENABLED,
* then offline all children under it.
*/
if (rv != DDI_SUCCESS) {
"(%s%d): "
"failed to unconfigure the device in the"
return (rv);
}
/*
* Remove all the children and their ports
* after they are offlined.
*/
if (rv != DDI_SUCCESS) {
"(%s%d): failed"
" to unprobe the device in the Connector"
return (rv);
}
"ddihp_connector_ops (%s%d): device"
" is unconfigured and unprobed in Connector %s\n",
}
return (rv);
}
/*
* Jobs after change state of a Connector: update last change time,
* probe, online, sysevent, etc.
*/
static int
{
int rv = DDI_SUCCESS;
/* Update the state in handle */
if (new_state != curr_state) {
}
if (curr_state < new_state &&
/*
* Probe and online devices if state is
* upgraded to ENABLED.
*/
}
/*
* For Connector, generate a sysevent on
* state change.
*/
}
return (rv);
}
/*
* Handle Connector state change.
*
* This function is called after connector is upgraded to ENABLED sate.
* It probes the device plugged in the connector to setup devinfo nodes
* and then online the nodes.
*/
static int
{
int rv = DDI_SUCCESS;
/*
* If the Connection went to state ENABLED from a lower state,
* probe it.
*/
if (rv != DDI_SUCCESS) {
/*
* Probe failed. Disable the connector so that it can
* be enabled again by a later try from userland.
*/
(void *)&target_state, (void *)&result_state);
}
"(%s%d): failed to probe the Connection %s\n",
return (rv);
}
/*
* Try to online all the children of CN.
*/
"device is configured in the Connection %s\n",
return (rv);
}
/*
*
* Do online operation when the online parameter is true; otherwise do offline.
*/
static int
{
int rv = DDI_SUCCESS;
" dip %p hdlp %p, online %x\n",
/*
* Return invalid if Connection state is < DDI_HP_CN_STATE_ENABLED
* when try to online children.
*/
"Connector %p is not in probed state\n", (void *)hdlp));
return (DDI_EINVAL);
}
if (!online) {
/*
* For offline operation we need to firstly clean up devfs
* so as not to prevent driver detach.
*/
}
continue;
if (h->cn_info.cn_num_dpd_on !=
continue;
if (online) {
/* online children */
if (!ddihp_check_status_prop(dip))
continue;
if (ndi_devi_online(cdip,
"(%s%d):"
" failed to attach driver for a device"
" (%s%d) under the Connection %s\n",
/*
* One of the devices failed to online, but we
* want to continue to online the rest siblings
* after mark the failure here.
*/
rv = DDI_FAILURE;
continue;
}
} else {
/* offline children */
NDI_SUCCESS) {
"(%s%d):"
" failed to dettach driver for the device"
" (%s%d) in the Connection %s\n",
return (DDI_EBUSY);
}
}
}
return (rv);
}
/*
* Port operations
*/
/*
* Change Port state to target_state.
*/
static int
{
if (target_state < DDI_HP_CN_STATE_PORT_EMPTY ||
return (DDI_EINVAL);
}
if (curr_state < target_state)
else if (curr_state > target_state)
else
return (DDI_SUCCESS);
}
/*
* Upgrade port state to target_state.
*/
static int
{
int rv = DDI_SUCCESS;
while (curr_state < target_state) {
switch (curr_state) {
/* Check the existence of the corresponding hardware */
(void *)&new_state, (void *)&result_state);
if (rv == DDI_SUCCESS) {
}
break;
/* Read-only probe the corresponding hardware. */
if (rv == DDI_SUCCESS) {
}
break;
case DDI_HP_CN_STATE_OFFLINE:
/* fall through */
if (rv == NDI_SUCCESS) {
rv = DDI_SUCCESS;
} else {
rv = DDI_FAILURE;
"ddihp_port_upgrade_state: "
"failed to online device %p at port: %s\n",
}
break;
case DDI_HP_CN_STATE_ONLINE:
break;
default:
/* should never reach here */
ASSERT("unknown devinfo state");
}
if (rv != DDI_SUCCESS) {
"failed curr_state=%x, target_state=%x \n",
return (rv);
}
}
return (rv);
}
/*
* Downgrade state to target_state
*/
static int
{
int rv = DDI_SUCCESS;
while (curr_state > target_state) {
switch (curr_state) {
break;
/* Check the existence of the corresponding hardware */
(void *)&new_state, (void *)&result_state);
if (rv == DDI_SUCCESS)
break;
case DDI_HP_CN_STATE_OFFLINE:
/*
* Read-only unprobe the corresponding hardware:
* 1. release the assigned resource;
* 2. remove the node pointed by the port's cn_child
*/
(void *)&new_state, (void *)&result_state);
if (rv == DDI_SUCCESS)
break;
/* fall through. */
case DDI_HP_CN_STATE_ONLINE:
if (rv == NDI_SUCCESS) {
rv = DDI_SUCCESS;
} else {
"ddihp_port_downgrade_state: failed "
"to offline node, rv=%x, cdip=%p \n",
}
break;
default:
/* should never reach here */
ASSERT("unknown devinfo state");
}
if (rv != DDI_SUCCESS) {
"ddihp_port_downgrade_state: failed "
"curr_state=%x, target_state=%x \n",
return (rv);
}
}
return (rv);
}
/*
* Misc routines
*/
/* Update the last state change time */
static void
{
else
}
/*
* Check the device for a 'status' property. A conforming device
* should have a status of "okay", "disabled", "fail", or "fail-xxx".
*
* Return FALSE for a conforming device that is disabled or faulted.
* Return TRUE in every other case.
*
* 'status' property is NOT a bus specific property. It is defined in page 184,
* IEEE 1275 spec. The full name of the spec is "IEEE Standard for
* Boot (Initialization Configuration) Firmware: Core Requirements and
* Practices".
*/
static boolean_t
{
char *status_prop;
/* try to get the 'status' property */
/*
* test if the status is "disabled", "fail", or
* "fail-xxx".
*/
"(%s%d): device is in disabled state",
"hotplug (%s%d): device is in fault state (%s)\n",
}
}
return (rv);
}