Home | History | Annotate | Download | only in os
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 /*
     22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
     23  * Use is subject to license terms.
     24  */
     25 
     26 /*
     27  * Sun DDI hotplug implementation specific functions
     28  */
     29 
     30 #include <sys/sysmacros.h>
     31 #include <sys/types.h>
     32 #include <sys/file.h>
     33 #include <sys/param.h>
     34 #include <sys/systm.h>
     35 #include <sys/kmem.h>
     36 #include <sys/cmn_err.h>
     37 #include <sys/debug.h>
     38 #include <sys/avintr.h>
     39 #include <sys/autoconf.h>
     40 #include <sys/ddi.h>
     41 #include <sys/sunndi.h>
     42 #include <sys/ndi_impldefs.h>
     43 #include <sys/sysevent.h>
     44 #include <sys/sysevent/eventdefs.h>
     45 #include <sys/sysevent/dr.h>
     46 #include <sys/fs/dv_node.h>
     47 
     48 /*
     49  * Local function prototypes
     50  */
     51 /* Connector operations */
     52 static int ddihp_cn_pre_change_state(ddi_hp_cn_handle_t *hdlp,
     53     ddi_hp_cn_state_t target_state);
     54 static int ddihp_cn_post_change_state(ddi_hp_cn_handle_t *hdlp,
     55     ddi_hp_cn_state_t new_state);
     56 static int ddihp_cn_handle_state_change(ddi_hp_cn_handle_t *hdlp);
     57 static int ddihp_cn_change_children_state(ddi_hp_cn_handle_t *hdlp,
     58     boolean_t online);
     59 /* Port operations */
     60 static int ddihp_port_change_state(ddi_hp_cn_handle_t *hdlp,
     61     ddi_hp_cn_state_t target_state);
     62 static int ddihp_port_upgrade_state(ddi_hp_cn_handle_t *hdlp,
     63     ddi_hp_cn_state_t target_state);
     64 static int ddihp_port_downgrade_state(ddi_hp_cn_handle_t *hdlp,
     65     ddi_hp_cn_state_t target_state);
     66 /* Misc routines */
     67 static void ddihp_update_last_change(ddi_hp_cn_handle_t *hdlp);
     68 static boolean_t ddihp_check_status_prop(dev_info_t *dip);
     69 
     70 /*
     71  * Global functions (called within hotplug framework)
     72  */
     73 
     74 /*
     75  * Implement modctl() commands for hotplug.
     76  * Called by modctl_hp() in modctl.c
     77  */
     78 int
     79 ddihp_modctl(int hp_op, char *path, char *cn_name, uintptr_t arg,
     80     uintptr_t rval)
     81 {
     82 	dev_info_t		*dip;
     83 	ddi_hp_cn_handle_t	*hdlp;
     84 	ddi_hp_op_t		op = (ddi_hp_op_t)hp_op;
     85 	int			count, rv, error;
     86 
     87 	/* Get the dip of nexus node */
     88 	dip = e_ddi_hold_devi_by_path(path, 0);
     89 
     90 	if (dip == NULL)
     91 		return (ENXIO);
     92 
     93 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_modctl: dip %p op %x path %s "
     94 	    "cn_name %s arg %p rval %p\n", (void *)dip, hp_op, path, cn_name,
     95 	    (void *)arg, (void *)rval));
     96 
     97 	if (!NEXUS_HAS_HP_OP(dip)) {
     98 		ddi_release_devi(dip);
     99 		return (ENOTSUP);
    100 	}
    101 
    102 	/* Lock before access */
    103 	ndi_devi_enter(dip, &count);
    104 
    105 	hdlp = ddihp_cn_name_to_handle(dip, cn_name);
    106 
    107 	if (hp_op == DDI_HPOP_CN_CREATE_PORT) {
    108 		if (hdlp != NULL) {
    109 			/* this port already exists. */
    110 			error = EEXIST;
    111 
    112 			goto done;
    113 		}
    114 		rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))(
    115 		    dip, cn_name, op, NULL, NULL);
    116 	} else {
    117 		if (hdlp == NULL) {
    118 			/* Invalid Connection name */
    119 			error = ENXIO;
    120 
    121 			goto done;
    122 		}
    123 		if (hp_op == DDI_HPOP_CN_CHANGE_STATE) {
    124 			ddi_hp_cn_state_t target_state = (ddi_hp_cn_state_t)arg;
    125 			ddi_hp_cn_state_t result_state = 0;
    126 
    127 			DDIHP_CN_OPS(hdlp, op, (void *)&target_state,
    128 			    (void *)&result_state, rv);
    129 
    130 			DDI_HP_IMPLDBG((CE_CONT, "ddihp_modctl: target_state="
    131 			    "%x, result_state=%x, rv=%x \n",
    132 			    target_state, result_state, rv));
    133 		} else {
    134 			DDIHP_CN_OPS(hdlp, op, (void *)arg, (void *)rval, rv);
    135 		}
    136 	}
    137 	switch (rv) {
    138 	case DDI_SUCCESS:
    139 		error = 0;
    140 		break;
    141 	case DDI_EINVAL:
    142 		error = EINVAL;
    143 		break;
    144 	case DDI_EBUSY:
    145 		error = EBUSY;
    146 		break;
    147 	case DDI_ENOTSUP:
    148 		error = ENOTSUP;
    149 		break;
    150 	case DDI_ENOMEM:
    151 		error = ENOMEM;
    152 		break;
    153 	default:
    154 		error = EIO;
    155 	}
    156 
    157 done:
    158 	ndi_devi_exit(dip, count);
    159 
    160 	ddi_release_devi(dip);
    161 
    162 	return (error);
    163 }
    164 
    165 /*
    166  * Return the state of Hotplug Connection (CN)
    167  */
    168 int
    169 ddihp_cn_getstate(ddi_hp_cn_handle_t *hdlp)
    170 {
    171 	ddi_hp_cn_state_t	new_state;
    172 	int			ret;
    173 
    174 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: pdip %p hdlp %p\n",
    175 	    (void *)hdlp->cn_dip, (void *)hdlp));
    176 
    177 	ASSERT(DEVI_BUSY_OWNED(hdlp->cn_dip));
    178 
    179 	DDIHP_CN_OPS(hdlp, DDI_HPOP_CN_GET_STATE,
    180 	    NULL, (void *)&new_state, ret);
    181 	if (ret != DDI_SUCCESS) {
    182 		DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: "
    183 		    "CN %p getstate command failed\n", (void *)hdlp));
    184 
    185 		return (ret);
    186 	}
    187 
    188 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_getstate: hdlp %p "
    189 	    "current Connection state %x new Connection state %x\n",
    190 	    (void *)hdlp, hdlp->cn_info.cn_state, new_state));
    191 
    192 	if (new_state != hdlp->cn_info.cn_state) {
    193 		hdlp->cn_info.cn_state = new_state;
    194 		ddihp_update_last_change(hdlp);
    195 	}
    196 
    197 	return (ret);
    198 }
    199 
    200 /*
    201  * Implementation function for unregistering the Hotplug Connection (CN)
    202  */
    203 int
    204 ddihp_cn_unregister(ddi_hp_cn_handle_t *hdlp)
    205 {
    206 	dev_info_t	*dip = hdlp->cn_dip;
    207 
    208 	DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_unregister: hdlp %p\n",
    209 	    (void *)hdlp));
    210 
    211 	ASSERT(DEVI_BUSY_OWNED(dip));
    212 
    213 	(void) ddihp_cn_getstate(hdlp);
    214 
    215 	if (hdlp->cn_info.cn_state > DDI_HP_CN_STATE_OFFLINE) {
    216 		DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_unregister: dip %p, hdlp %p "
    217 		    "state %x. Device busy, failed to unregister connection!\n",
    218 		    (void *)dip, (void *)hdlp, hdlp->cn_info.cn_state));
    219 
    220 		return (DDI_EBUSY);
    221 	}
    222 
    223 	/* unlink the handle */
    224 	DDIHP_LIST_REMOVE(ddi_hp_cn_handle_t, (DEVI(dip)->devi_hp_hdlp), hdlp);
    225 
    226 	kmem_free(hdlp->cn_info.cn_name, strlen(hdlp->cn_info.cn_name) + 1);
    227 	kmem_free(hdlp, sizeof (ddi_hp_cn_handle_t));
    228 	return (DDI_SUCCESS);
    229 }
    230 
    231 /*
    232  * For a given Connection name and the dip node where the Connection is
    233  * supposed to be, find the corresponding hotplug handle.
    234  */
    235 ddi_hp_cn_handle_t *
    236 ddihp_cn_name_to_handle(dev_info_t *dip, char *cn_name)
    237 {
    238 	ddi_hp_cn_handle_t *hdlp;
    239 
    240 	ASSERT(DEVI_BUSY_OWNED(dip));
    241 
    242 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: "
    243 	    "dip %p cn_name to find: %s", (void *)dip, cn_name));
    244 	for (hdlp = DEVI(dip)->devi_hp_hdlp; hdlp; hdlp = hdlp->next) {
    245 		DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: "
    246 		    "current cn_name: %s", hdlp->cn_info.cn_name));
    247 
    248 		if (strcmp(cn_name, hdlp->cn_info.cn_name) == 0) {
    249 			/* found */
    250 			return (hdlp);
    251 		}
    252 	}
    253 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_name_to_handle: "
    254 	    "failed to find cn_name"));
    255 	return (NULL);
    256 }
    257 
    258 /*
    259  * Process the hotplug operations for Connector and also create Port
    260  * upon user command.
    261  */
    262 int
    263 ddihp_connector_ops(ddi_hp_cn_handle_t *hdlp, ddi_hp_op_t op,
    264     void *arg, void *result)
    265 {
    266 	int			rv = DDI_SUCCESS;
    267 	dev_info_t		*dip = hdlp->cn_dip;
    268 
    269 	ASSERT(DEVI_BUSY_OWNED(dip));
    270 
    271 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: pdip=%p op=%x "
    272 	    "hdlp=%p arg=%p\n", (void *)dip, op, (void *)hdlp, arg));
    273 
    274 	if (op == DDI_HPOP_CN_CHANGE_STATE) {
    275 		ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg;
    276 
    277 		rv = ddihp_cn_pre_change_state(hdlp, target_state);
    278 		if (rv != DDI_SUCCESS) {
    279 			/* the state is not changed */
    280 			*((ddi_hp_cn_state_t *)result) =
    281 			    hdlp->cn_info.cn_state;
    282 			return (rv);
    283 		}
    284 	}
    285 	ASSERT(NEXUS_HAS_HP_OP(dip));
    286 	rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))(
    287 	    dip, hdlp->cn_info.cn_name, op, arg, result);
    288 
    289 	if (rv != DDI_SUCCESS) {
    290 		DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: "
    291 		    "bus_hp_op failed: pdip=%p cn_name:%s op=%x "
    292 		    "hdlp=%p arg=%p\n", (void *)dip, hdlp->cn_info.cn_name,
    293 		    op, (void *)hdlp, arg));
    294 	}
    295 	if (op == DDI_HPOP_CN_CHANGE_STATE) {
    296 		int rv_post;
    297 
    298 		DDI_HP_IMPLDBG((CE_CONT, "ddihp_connector_ops: "
    299 		    "old_state=%x, new_state=%x, rv=%x\n",
    300 		    hdlp->cn_info.cn_state, *(ddi_hp_cn_state_t *)result, rv));
    301 
    302 		/*
    303 		 * After state change op is successfully done or
    304 		 * failed at some stages, continue to do some jobs.
    305 		 */
    306 		rv_post = ddihp_cn_post_change_state(hdlp,
    307 		    *(ddi_hp_cn_state_t *)result);
    308 
    309 		if (rv_post != DDI_SUCCESS)
    310 			rv = rv_post;
    311 	}
    312 
    313 	return (rv);
    314 }
    315 
    316 /*
    317  * Process the hotplug op for Port
    318  */
    319 int
    320 ddihp_port_ops(ddi_hp_cn_handle_t *hdlp, ddi_hp_op_t op,
    321     void *arg, void *result)
    322 {
    323 	int		ret = DDI_SUCCESS;
    324 
    325 	ASSERT(DEVI_BUSY_OWNED(hdlp->cn_dip));
    326 
    327 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_port_ops: pdip=%p op=%x hdlp=%p "
    328 	    "arg=%p\n", (void *)hdlp->cn_dip, op, (void *)hdlp, arg));
    329 
    330 	switch (op) {
    331 	case DDI_HPOP_CN_GET_STATE:
    332 	{
    333 		int state;
    334 
    335 		state = hdlp->cn_info.cn_state;
    336 
    337 		if (hdlp->cn_info.cn_child == NULL) {
    338 			/* No child. Either present or empty. */
    339 			if (state >= DDI_HP_CN_STATE_PORT_PRESENT)
    340 				state = DDI_HP_CN_STATE_PORT_PRESENT;
    341 			else
    342 				state = DDI_HP_CN_STATE_PORT_EMPTY;
    343 
    344 		} else { /* There is a child of this Port */
    345 
    346 			/* Check DEVI(dip)->devi_node_state */
    347 			switch (i_ddi_node_state(hdlp->cn_info.cn_child)) {
    348 			case	DS_INVAL:
    349 			case	DS_PROTO:
    350 			case	DS_LINKED:
    351 			case	DS_BOUND:
    352 			case	DS_INITIALIZED:
    353 			case	DS_PROBED:
    354 				state = DDI_HP_CN_STATE_OFFLINE;
    355 				break;
    356 			case	DS_ATTACHED:
    357 				state = DDI_HP_CN_STATE_MAINTENANCE;
    358 				break;
    359 			case	DS_READY:
    360 				state = DDI_HP_CN_STATE_ONLINE;
    361 				break;
    362 			default:
    363 				/* should never reach here */
    364 				ASSERT("unknown devinfo state");
    365 			}
    366 			/*
    367 			 * Check DEVI(dip)->devi_state in case the node is
    368 			 * downgraded or quiesced.
    369 			 */
    370 			if (state == DDI_HP_CN_STATE_ONLINE &&
    371 			    ddi_get_devstate(hdlp->cn_info.cn_child) !=
    372 			    DDI_DEVSTATE_UP)
    373 				state = DDI_HP_CN_STATE_MAINTENANCE;
    374 		}
    375 
    376 		*((ddi_hp_cn_state_t *)result) = state;
    377 
    378 		break;
    379 	}
    380 	case DDI_HPOP_CN_CHANGE_STATE:
    381 	{
    382 		ddi_hp_cn_state_t target_state = *(ddi_hp_cn_state_t *)arg;
    383 		ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state;
    384 
    385 		ret = ddihp_port_change_state(hdlp, target_state);
    386 		if (curr_state != hdlp->cn_info.cn_state) {
    387 			ddihp_update_last_change(hdlp);
    388 		}
    389 		*((ddi_hp_cn_state_t *)result) = hdlp->cn_info.cn_state;
    390 
    391 		break;
    392 	}
    393 	case DDI_HPOP_CN_REMOVE_PORT:
    394 	{
    395 		(void) ddihp_cn_getstate(hdlp);
    396 
    397 		if (hdlp->cn_info.cn_state != DDI_HP_CN_STATE_PORT_EMPTY) {
    398 			/* Only empty PORT can be removed by commands */
    399 			ret = DDI_EBUSY;
    400 
    401 			break;
    402 		}
    403 
    404 		ret = ddihp_cn_unregister(hdlp);
    405 		break;
    406 	}
    407 	default:
    408 		ret = DDI_ENOTSUP;
    409 		break;
    410 	}
    411 
    412 	return (ret);
    413 }
    414 
    415 /*
    416  * Generate the system event with a possible hint
    417  */
    418 /* ARGSUSED */
    419 void
    420 ddihp_cn_gen_sysevent(ddi_hp_cn_handle_t *hdlp,
    421     ddi_hp_cn_sysevent_t event_sub_class, int hint, int kmflag)
    422 {
    423 	dev_info_t	*dip = hdlp->cn_dip;
    424 	char		*cn_path, *ap_id;
    425 	char		*ev_subclass = NULL;
    426 	nvlist_t	*ev_attr_list = NULL;
    427 	sysevent_id_t	eid;
    428 	int		ap_id_len, err;
    429 
    430 	cn_path = kmem_zalloc(MAXPATHLEN, kmflag);
    431 	if (cn_path == NULL) {
    432 		cmn_err(CE_WARN,
    433 		    "%s%d: Failed to allocate memory for hotplug"
    434 		    " connection: %s\n",
    435 		    ddi_driver_name(dip), ddi_get_instance(dip),
    436 		    hdlp->cn_info.cn_name);
    437 
    438 		return;
    439 	}
    440 
    441 	/*
    442 	 * Minor device name will be bus path
    443 	 * concatenated with connection name.
    444 	 * One of consumers of the sysevent will pass it
    445 	 * to cfgadm as AP ID.
    446 	 */
    447 	(void) strcpy(cn_path, "/devices");
    448 	(void) ddi_pathname(dip, cn_path + strlen("/devices"));
    449 
    450 	ap_id_len = strlen(cn_path) + strlen(":") +
    451 	    strlen(hdlp->cn_info.cn_name) + 1;
    452 	ap_id = kmem_zalloc(ap_id_len, kmflag);
    453 	if (ap_id == NULL) {
    454 		cmn_err(CE_WARN,
    455 		    "%s%d: Failed to allocate memory for AP ID: %s:%s\n",
    456 		    ddi_driver_name(dip), ddi_get_instance(dip),
    457 		    cn_path, hdlp->cn_info.cn_name);
    458 		kmem_free(cn_path, MAXPATHLEN);
    459 
    460 		return;
    461 	}
    462 
    463 	(void) strcpy(ap_id, cn_path);
    464 	(void) strcat(ap_id, ":");
    465 	(void) strcat(ap_id, hdlp->cn_info.cn_name);
    466 	kmem_free(cn_path, MAXPATHLEN);
    467 
    468 	err = nvlist_alloc(&ev_attr_list, NV_UNIQUE_NAME_TYPE, kmflag);
    469 
    470 	if (err != 0) {
    471 		cmn_err(CE_WARN,
    472 		    "%s%d: Failed to allocate memory for event subclass %d\n",
    473 		    ddi_driver_name(dip), ddi_get_instance(dip),
    474 		    event_sub_class);
    475 		kmem_free(ap_id, ap_id_len);
    476 
    477 		return;
    478 	}
    479 
    480 	switch (event_sub_class) {
    481 	case DDI_HP_CN_STATE_CHANGE:
    482 		ev_subclass = ESC_DR_AP_STATE_CHANGE;
    483 
    484 		switch (hint) {
    485 		case SE_NO_HINT:	/* fall through */
    486 		case SE_HINT_INSERT:	/* fall through */
    487 		case SE_HINT_REMOVE:
    488 			err = nvlist_add_string(ev_attr_list, DR_HINT,
    489 			    SE_HINT2STR(hint));
    490 
    491 			if (err != 0) {
    492 				cmn_err(CE_WARN, "%s%d: Failed to add attr [%s]"
    493 				    " for %s event\n", ddi_driver_name(dip),
    494 				    ddi_get_instance(dip), DR_HINT,
    495 				    ESC_DR_AP_STATE_CHANGE);
    496 
    497 				goto done;
    498 			}
    499 			break;
    500 
    501 		default:
    502 			cmn_err(CE_WARN, "%s%d: Unknown hint on sysevent\n",
    503 			    ddi_driver_name(dip), ddi_get_instance(dip));
    504 
    505 			goto done;
    506 		}
    507 
    508 		break;
    509 
    510 	/* event sub class: DDI_HP_CN_REQ */
    511 	case DDI_HP_CN_REQ:
    512 		ev_subclass = ESC_DR_REQ;
    513 
    514 		switch (hint) {
    515 		case SE_INVESTIGATE_RES: /* fall through */
    516 		case SE_INCOMING_RES:	/* fall through */
    517 		case SE_OUTGOING_RES:	/* fall through */
    518 			err = nvlist_add_string(ev_attr_list, DR_REQ_TYPE,
    519 			    SE_REQ2STR(hint));
    520 
    521 			if (err != 0) {
    522 				cmn_err(CE_WARN,
    523 				    "%s%d: Failed to add attr [%s] for %s \n"
    524 				    "event", ddi_driver_name(dip),
    525 				    ddi_get_instance(dip),
    526 				    DR_REQ_TYPE, ESC_DR_REQ);
    527 
    528 				goto done;
    529 			}
    530 			break;
    531 
    532 		default:
    533 			cmn_err(CE_WARN, "%s%d:  Unknown hint on sysevent\n",
    534 			    ddi_driver_name(dip), ddi_get_instance(dip));
    535 
    536 			goto done;
    537 		}
    538 
    539 		break;
    540 
    541 	default:
    542 		cmn_err(CE_WARN, "%s%d:  Unknown Event subclass\n",
    543 		    ddi_driver_name(dip), ddi_get_instance(dip));
    544 
    545 		goto done;
    546 	}
    547 
    548 	/*
    549 	 * Add Hotplug Connection (CN) as attribute (common attribute)
    550 	 */
    551 	err = nvlist_add_string(ev_attr_list, DR_AP_ID, ap_id);
    552 	if (err != 0) {
    553 		cmn_err(CE_WARN, "%s%d: Failed to add attr [%s] for %s event\n",
    554 		    ddi_driver_name(dip), ddi_get_instance(dip),
    555 		    DR_AP_ID, EC_DR);
    556 
    557 		goto done;
    558 	}
    559 
    560 	/*
    561 	 * Log this event with sysevent framework.
    562 	 */
    563 	err = ddi_log_sysevent(dip, DDI_VENDOR_SUNW, EC_DR,
    564 	    ev_subclass, ev_attr_list, &eid,
    565 	    ((kmflag == KM_SLEEP) ? DDI_SLEEP : DDI_NOSLEEP));
    566 
    567 	if (err != 0) {
    568 		cmn_err(CE_WARN, "%s%d: Failed to log %s event\n",
    569 		    ddi_driver_name(dip), ddi_get_instance(dip), EC_DR);
    570 	}
    571 
    572 done:
    573 	nvlist_free(ev_attr_list);
    574 	kmem_free(ap_id, ap_id_len);
    575 }
    576 
    577 /*
    578  * Local functions (called within this file)
    579  */
    580 
    581 /*
    582  * Connector operations
    583  */
    584 
    585 /*
    586  * Prepare to change state for a Connector: offline, unprobe, etc.
    587  */
    588 static int
    589 ddihp_cn_pre_change_state(ddi_hp_cn_handle_t *hdlp,
    590     ddi_hp_cn_state_t target_state)
    591 {
    592 	ddi_hp_cn_state_t	curr_state = hdlp->cn_info.cn_state;
    593 	dev_info_t		*dip = hdlp->cn_dip;
    594 	int			rv = DDI_SUCCESS;
    595 
    596 	if (curr_state > target_state &&
    597 	    curr_state == DDI_HP_CN_STATE_ENABLED) {
    598 		/*
    599 		 * If the Connection goes to a lower state from ENABLED,
    600 		 *  then offline all children under it.
    601 		 */
    602 		rv = ddihp_cn_change_children_state(hdlp, B_FALSE);
    603 		if (rv != DDI_SUCCESS) {
    604 			cmn_err(CE_WARN,
    605 			    "(%s%d): "
    606 			    "failed to unconfigure the device in the"
    607 			    " Connection %s\n", ddi_driver_name(dip),
    608 			    ddi_get_instance(dip),
    609 			    hdlp->cn_info.cn_name);
    610 
    611 			return (rv);
    612 		}
    613 		ASSERT(NEXUS_HAS_HP_OP(dip));
    614 		/*
    615 		 * Remove all the children and their ports
    616 		 * after they are offlined.
    617 		 */
    618 		rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))(
    619 		    dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_UNPROBE,
    620 		    NULL, NULL);
    621 		if (rv != DDI_SUCCESS) {
    622 			cmn_err(CE_WARN,
    623 			    "(%s%d): failed"
    624 			    " to unprobe the device in the Connector"
    625 			    " %s\n", ddi_driver_name(dip),
    626 			    ddi_get_instance(dip),
    627 			    hdlp->cn_info.cn_name);
    628 
    629 			return (rv);
    630 		}
    631 
    632 		DDI_HP_NEXDBG((CE_CONT,
    633 		    "ddihp_connector_ops (%s%d): device"
    634 		    " is unconfigured and unprobed in Connector %s\n",
    635 		    ddi_driver_name(dip), ddi_get_instance(dip),
    636 		    hdlp->cn_info.cn_name));
    637 	}
    638 
    639 	return (rv);
    640 }
    641 
    642 /*
    643  * Jobs after change state of a Connector: update last change time,
    644  * probe, online, sysevent, etc.
    645  */
    646 static int
    647 ddihp_cn_post_change_state(ddi_hp_cn_handle_t *hdlp,
    648     ddi_hp_cn_state_t new_state)
    649 {
    650 	int			rv = DDI_SUCCESS;
    651 	ddi_hp_cn_state_t	curr_state = hdlp->cn_info.cn_state;
    652 
    653 	/* Update the state in handle */
    654 	if (new_state != curr_state) {
    655 		hdlp->cn_info.cn_state = new_state;
    656 		ddihp_update_last_change(hdlp);
    657 	}
    658 
    659 	if (curr_state < new_state &&
    660 	    new_state == DDI_HP_CN_STATE_ENABLED) {
    661 		/*
    662 		 * Probe and online devices if state is
    663 		 * upgraded to ENABLED.
    664 		 */
    665 		rv = ddihp_cn_handle_state_change(hdlp);
    666 	}
    667 	if (curr_state != hdlp->cn_info.cn_state) {
    668 		/*
    669 		 * For Connector, generate a sysevent on
    670 		 * state change.
    671 		 */
    672 		ddihp_cn_gen_sysevent(hdlp, DDI_HP_CN_STATE_CHANGE,
    673 		    SE_NO_HINT, KM_SLEEP);
    674 	}
    675 
    676 	return (rv);
    677 }
    678 
    679 /*
    680  * Handle Connector state change.
    681  *
    682  * This function is called after connector is upgraded to ENABLED sate.
    683  * It probes the device plugged in the connector to setup devinfo nodes
    684  * and then online the nodes.
    685  */
    686 static int
    687 ddihp_cn_handle_state_change(ddi_hp_cn_handle_t *hdlp)
    688 {
    689 	dev_info_t		*dip = hdlp->cn_dip;
    690 	int			rv = DDI_SUCCESS;
    691 
    692 	ASSERT(DEVI_BUSY_OWNED(dip));
    693 	ASSERT(NEXUS_HAS_HP_OP(dip));
    694 	/*
    695 	 * If the Connection went to state ENABLED from a lower state,
    696 	 * probe it.
    697 	 */
    698 	rv = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))(
    699 	    dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_PROBE, NULL, NULL);
    700 
    701 	if (rv != DDI_SUCCESS) {
    702 		ddi_hp_cn_state_t	target_state = DDI_HP_CN_STATE_POWERED;
    703 		ddi_hp_cn_state_t	result_state = 0;
    704 
    705 		/*
    706 		 * Probe failed. Disable the connector so that it can
    707 		 * be enabled again by a later try from userland.
    708 		 */
    709 		(void) (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_hp_op))(
    710 		    dip, hdlp->cn_info.cn_name, DDI_HPOP_CN_CHANGE_STATE,
    711 		    (void *)&target_state, (void *)&result_state);
    712 
    713 		if (result_state && result_state != hdlp->cn_info.cn_state) {
    714 			hdlp->cn_info.cn_state = result_state;
    715 			ddihp_update_last_change(hdlp);
    716 		}
    717 
    718 		cmn_err(CE_WARN,
    719 		    "(%s%d): failed to probe the Connection %s\n",
    720 		    ddi_driver_name(dip), ddi_get_instance(dip),
    721 		    hdlp->cn_info.cn_name);
    722 
    723 		return (rv);
    724 	}
    725 	/*
    726 	 * Try to online all the children of CN.
    727 	 */
    728 	(void) ddihp_cn_change_children_state(hdlp, B_TRUE);
    729 
    730 	DDI_HP_NEXDBG((CE_CONT, "ddihp_cn_event_handler (%s%d): "
    731 	    "device is configured in the Connection %s\n",
    732 	    ddi_driver_name(dip), ddi_get_instance(dip),
    733 	    hdlp->cn_info.cn_name));
    734 	return (rv);
    735 }
    736 
    737 /*
    738  * Online/Offline all the children under the Hotplug Connection (CN)
    739  *
    740  * Do online operation when the online parameter is true; otherwise do offline.
    741  */
    742 static int
    743 ddihp_cn_change_children_state(ddi_hp_cn_handle_t *hdlp, boolean_t online)
    744 {
    745 	dev_info_t		*dip = hdlp->cn_dip;
    746 	dev_info_t		*cdip;
    747 	ddi_hp_cn_handle_t	*h;
    748 	int			rv = DDI_SUCCESS;
    749 
    750 	DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_change_children_state:"
    751 	    " dip %p hdlp %p, online %x\n",
    752 	    (void *)dip, (void *)hdlp, online));
    753 
    754 	ASSERT(DEVI_BUSY_OWNED(dip));
    755 
    756 	/*
    757 	 * Return invalid if Connection state is < DDI_HP_CN_STATE_ENABLED
    758 	 * when try to online children.
    759 	 */
    760 	if (online && hdlp->cn_info.cn_state < DDI_HP_CN_STATE_ENABLED) {
    761 		DDI_HP_IMPLDBG((CE_CONT, "ddihp_cn_change_children_state: "
    762 		    "Connector %p is not in probed state\n", (void *)hdlp));
    763 
    764 		return (DDI_EINVAL);
    765 	}
    766 
    767 	/* Now, online/offline all the devices depending on the Connector */
    768 
    769 	if (!online) {
    770 		/*
    771 		 * For offline operation we need to firstly clean up devfs
    772 		 * so as not to prevent driver detach.
    773 		 */
    774 		(void) devfs_clean(dip, NULL, DV_CLEAN_FORCE);
    775 	}
    776 	for (h = DEVI(dip)->devi_hp_hdlp; h; h = h->next) {
    777 		if (h->cn_info.cn_type != DDI_HP_CN_TYPE_VIRTUAL_PORT)
    778 			continue;
    779 
    780 		if (h->cn_info.cn_num_dpd_on !=
    781 		    hdlp->cn_info.cn_num)
    782 			continue;
    783 
    784 		cdip = h->cn_info.cn_child;
    785 		ASSERT(cdip);
    786 		if (online) {
    787 			/* online children */
    788 			if (!ddihp_check_status_prop(dip))
    789 				continue;
    790 
    791 			if (ndi_devi_online(cdip,
    792 			    NDI_ONLINE_ATTACH | NDI_CONFIG) != NDI_SUCCESS) {
    793 				cmn_err(CE_WARN,
    794 				    "(%s%d):"
    795 				    " failed to attach driver for a device"
    796 				    " (%s%d) under the Connection %s\n",
    797 				    ddi_driver_name(dip), ddi_get_instance(dip),
    798 				    ddi_driver_name(cdip),
    799 				    ddi_get_instance(cdip),
    800 				    hdlp->cn_info.cn_name);
    801 				/*
    802 				 * One of the devices failed to online, but we
    803 				 * want to continue to online the rest siblings
    804 				 * after mark the failure here.
    805 				 */
    806 				rv = DDI_FAILURE;
    807 
    808 				continue;
    809 			}
    810 		} else {
    811 			/* offline children */
    812 			if (ndi_devi_offline(cdip, NDI_UNCONFIG) !=
    813 			    NDI_SUCCESS) {
    814 				cmn_err(CE_WARN,
    815 				    "(%s%d):"
    816 				    " failed to dettach driver for the device"
    817 				    " (%s%d) in the Connection %s\n",
    818 				    ddi_driver_name(dip), ddi_get_instance(dip),
    819 				    ddi_driver_name(cdip),
    820 				    ddi_get_instance(cdip),
    821 				    hdlp->cn_info.cn_name);
    822 
    823 				return (DDI_EBUSY);
    824 			}
    825 		}
    826 	}
    827 
    828 	return (rv);
    829 }
    830 
    831 /*
    832  * Port operations
    833  */
    834 
    835 /*
    836  * Change Port state to target_state.
    837  */
    838 static int
    839 ddihp_port_change_state(ddi_hp_cn_handle_t *hdlp,
    840     ddi_hp_cn_state_t target_state)
    841 {
    842 	ddi_hp_cn_state_t curr_state = hdlp->cn_info.cn_state;
    843 
    844 	if (target_state < DDI_HP_CN_STATE_PORT_EMPTY ||
    845 	    target_state > DDI_HP_CN_STATE_ONLINE) {
    846 
    847 		return (DDI_EINVAL);
    848 	}
    849 
    850 	if (curr_state < target_state)
    851 		return (ddihp_port_upgrade_state(hdlp, target_state));
    852 	else if (curr_state > target_state)
    853 		return (ddihp_port_downgrade_state(hdlp, target_state));
    854 	else
    855 		return (DDI_SUCCESS);
    856 }
    857 
    858 /*
    859  * Upgrade port state to target_state.
    860  */
    861 static int
    862 ddihp_port_upgrade_state(ddi_hp_cn_handle_t *hdlp,
    863     ddi_hp_cn_state_t target_state)
    864 {
    865 	ddi_hp_cn_state_t	curr_state, new_state, result_state;
    866 	dev_info_t		*cdip;
    867 	int			rv = DDI_SUCCESS;
    868 
    869 	curr_state = hdlp->cn_info.cn_state;
    870 	while (curr_state < target_state) {
    871 		switch (curr_state) {
    872 		case DDI_HP_CN_STATE_PORT_EMPTY:
    873 			/* Check the existence of the corresponding hardware */
    874 			new_state = DDI_HP_CN_STATE_PORT_PRESENT;
    875 			rv = ddihp_connector_ops(hdlp,
    876 			    DDI_HPOP_CN_CHANGE_STATE,
    877 			    (void *)&new_state, (void *)&result_state);
    878 			if (rv == DDI_SUCCESS) {
    879 				hdlp->cn_info.cn_state =
    880 				    result_state;
    881 			}
    882 			break;
    883 		case DDI_HP_CN_STATE_PORT_PRESENT:
    884 			/* Read-only probe the corresponding hardware. */
    885 			new_state = DDI_HP_CN_STATE_OFFLINE;
    886 			rv = ddihp_connector_ops(hdlp,
    887 			    DDI_HPOP_CN_CHANGE_STATE,
    888 			    (void *)&new_state, &cdip);
    889 			if (rv == DDI_SUCCESS) {
    890 				hdlp->cn_info.cn_state =
    891 				    DDI_HP_CN_STATE_OFFLINE;
    892 
    893 				ASSERT(hdlp->cn_info.cn_child == NULL);
    894 				hdlp->cn_info.cn_child = cdip;
    895 			}
    896 			break;
    897 		case DDI_HP_CN_STATE_OFFLINE:
    898 			/* fall through */
    899 		case DDI_HP_CN_STATE_MAINTENANCE:
    900 
    901 			cdip = hdlp->cn_info.cn_child;
    902 
    903 			rv = ndi_devi_online(cdip,
    904 			    NDI_ONLINE_ATTACH | NDI_CONFIG);
    905 			if (rv == NDI_SUCCESS) {
    906 				hdlp->cn_info.cn_state =
    907 				    DDI_HP_CN_STATE_ONLINE;
    908 				rv = DDI_SUCCESS;
    909 			} else {
    910 				rv = DDI_FAILURE;
    911 				DDI_HP_IMPLDBG((CE_CONT,
    912 				    "ddihp_port_upgrade_state: "
    913 				    "failed to online device %p at port: %s\n",
    914 				    (void *)cdip, hdlp->cn_info.cn_name));
    915 			}
    916 			break;
    917 		case DDI_HP_CN_STATE_ONLINE:
    918 
    919 			break;
    920 		default:
    921 			/* should never reach here */
    922 			ASSERT("unknown devinfo state");
    923 		}
    924 		curr_state = hdlp->cn_info.cn_state;
    925 		if (rv != DDI_SUCCESS) {
    926 			DDI_HP_IMPLDBG((CE_CONT, "ddihp_port_upgrade_state: "
    927 			    "failed curr_state=%x, target_state=%x \n",
    928 			    curr_state, target_state));
    929 			return (rv);
    930 		}
    931 	}
    932 
    933 	return (rv);
    934 }
    935 
    936 /*
    937  * Downgrade state to target_state
    938  */
    939 static int
    940 ddihp_port_downgrade_state(ddi_hp_cn_handle_t *hdlp,
    941     ddi_hp_cn_state_t target_state)
    942 {
    943 	ddi_hp_cn_state_t	curr_state, new_state, result_state;
    944 	dev_info_t		*dip = hdlp->cn_dip;
    945 	dev_info_t		*cdip;
    946 	int			rv = DDI_SUCCESS;
    947 
    948 	curr_state = hdlp->cn_info.cn_state;
    949 	while (curr_state > target_state) {
    950 
    951 		switch (curr_state) {
    952 		case DDI_HP_CN_STATE_PORT_EMPTY:
    953 
    954 			break;
    955 		case DDI_HP_CN_STATE_PORT_PRESENT:
    956 			/* Check the existence of the corresponding hardware */
    957 			new_state = DDI_HP_CN_STATE_PORT_EMPTY;
    958 			rv = ddihp_connector_ops(hdlp,
    959 			    DDI_HPOP_CN_CHANGE_STATE,
    960 			    (void *)&new_state, (void *)&result_state);
    961 			if (rv == DDI_SUCCESS)
    962 				hdlp->cn_info.cn_state =
    963 				    result_state;
    964 
    965 			break;
    966 		case DDI_HP_CN_STATE_OFFLINE:
    967 			/*
    968 			 * Read-only unprobe the corresponding hardware:
    969 			 * 1. release the assigned resource;
    970 			 * 2. remove the node pointed by the port's cn_child
    971 			 */
    972 			new_state = DDI_HP_CN_STATE_PORT_PRESENT;
    973 			rv = ddihp_connector_ops(hdlp,
    974 			    DDI_HPOP_CN_CHANGE_STATE,
    975 			    (void *)&new_state, (void *)&result_state);
    976 			if (rv == DDI_SUCCESS)
    977 				hdlp->cn_info.cn_state =
    978 				    DDI_HP_CN_STATE_PORT_PRESENT;
    979 			break;
    980 		case DDI_HP_CN_STATE_MAINTENANCE:
    981 			/* fall through. */
    982 		case DDI_HP_CN_STATE_ONLINE:
    983 			cdip = hdlp->cn_info.cn_child;
    984 
    985 			(void) devfs_clean(dip, NULL, DV_CLEAN_FORCE);
    986 			rv = ndi_devi_offline(cdip, NDI_UNCONFIG);
    987 			if (rv == NDI_SUCCESS) {
    988 				hdlp->cn_info.cn_state =
    989 				    DDI_HP_CN_STATE_OFFLINE;
    990 				rv = DDI_SUCCESS;
    991 			} else {
    992 				rv = DDI_EBUSY;
    993 				DDI_HP_IMPLDBG((CE_CONT,
    994 				    "ddihp_port_downgrade_state: failed "
    995 				    "to offline node, rv=%x, cdip=%p \n",
    996 				    rv, (void *)cdip));
    997 			}
    998 
    999 			break;
   1000 		default:
   1001 			/* should never reach here */
   1002 			ASSERT("unknown devinfo state");
   1003 		}
   1004 		curr_state = hdlp->cn_info.cn_state;
   1005 		if (rv != DDI_SUCCESS) {
   1006 			DDI_HP_IMPLDBG((CE_CONT,
   1007 			    "ddihp_port_downgrade_state: failed "
   1008 			    "curr_state=%x, target_state=%x \n",
   1009 			    curr_state, target_state));
   1010 			return (rv);
   1011 		}
   1012 	}
   1013 
   1014 	return (rv);
   1015 }
   1016 
   1017 /*
   1018  * Misc routines
   1019  */
   1020 
   1021 /* Update the last state change time */
   1022 static void
   1023 ddihp_update_last_change(ddi_hp_cn_handle_t *hdlp)
   1024 {
   1025 	time_t			time;
   1026 
   1027 	if (drv_getparm(TIME, (void *)&time) != DDI_SUCCESS)
   1028 		hdlp->cn_info.cn_last_change = (time_t)-1;
   1029 	else
   1030 		hdlp->cn_info.cn_last_change = (time32_t)time;
   1031 }
   1032 
   1033 /*
   1034  * Check the device for a 'status' property.  A conforming device
   1035  * should have a status of "okay", "disabled", "fail", or "fail-xxx".
   1036  *
   1037  * Return FALSE for a conforming device that is disabled or faulted.
   1038  * Return TRUE in every other case.
   1039  *
   1040  * 'status' property is NOT a bus specific property. It is defined in page 184,
   1041  * IEEE 1275 spec. The full name of the spec is "IEEE Standard for
   1042  * Boot (Initialization Configuration) Firmware: Core Requirements and
   1043  * Practices".
   1044  */
   1045 static boolean_t
   1046 ddihp_check_status_prop(dev_info_t *dip)
   1047 {
   1048 	char		*status_prop;
   1049 	boolean_t	rv = B_TRUE;
   1050 
   1051 	/* try to get the 'status' property */
   1052 	if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
   1053 	    "status", &status_prop) == DDI_PROP_SUCCESS) {
   1054 		/*
   1055 		 * test if the status is "disabled", "fail", or
   1056 		 * "fail-xxx".
   1057 		 */
   1058 		if (strcmp(status_prop, "disabled") == 0) {
   1059 			rv = B_FALSE;
   1060 			DDI_HP_IMPLDBG((CE_CONT, "ddihp_check_status_prop "
   1061 			    "(%s%d): device is in disabled state",
   1062 			    ddi_driver_name(dip), ddi_get_instance(dip)));
   1063 		} else if (strncmp(status_prop, "fail", 4) == 0) {
   1064 			rv = B_FALSE;
   1065 			cmn_err(CE_WARN,
   1066 			    "hotplug (%s%d): device is in fault state (%s)\n",
   1067 			    ddi_driver_name(dip), ddi_get_instance(dip),
   1068 			    status_prop);
   1069 		}
   1070 
   1071 		ddi_prop_free(status_prop);
   1072 	}
   1073 
   1074 	return (rv);
   1075 }
   1076