xref: /illumos-gate/usr/src/uts/common/os/ddi_hp_impl.c (revision afab0816ecb604f0099a09ad8ee398f0d7b77b1c)
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