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