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 */
52static int ddihp_cn_pre_change_state(ddi_hp_cn_handle_t *hdlp,
53    ddi_hp_cn_state_t target_state);
54static int ddihp_cn_post_change_state(ddi_hp_cn_handle_t *hdlp,
55    ddi_hp_cn_state_t new_state);
56static int ddihp_cn_handle_state_change(ddi_hp_cn_handle_t *hdlp);
57static int ddihp_cn_change_children_state(ddi_hp_cn_handle_t *hdlp,
58    boolean_t online);
59/* Port operations */
60static int ddihp_port_change_state(ddi_hp_cn_handle_t *hdlp,
61    ddi_hp_cn_state_t target_state);
62static int ddihp_port_upgrade_state(ddi_hp_cn_handle_t *hdlp,
63    ddi_hp_cn_state_t target_state);
64static int ddihp_port_downgrade_state(ddi_hp_cn_handle_t *hdlp,
65    ddi_hp_cn_state_t target_state);
66/* Misc routines */
67static void ddihp_update_last_change(ddi_hp_cn_handle_t *hdlp);
68static 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 */
78int
79ddihp_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
157done:
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 */
168int
169ddihp_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 */
203int
204ddihp_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 */
235ddi_hp_cn_handle_t *
236ddihp_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 */
262int
263ddihp_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 */
319int
320ddihp_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 */
419void
420ddihp_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
572done:
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 */
588static int
589ddihp_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 */
646static int
647ddihp_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 */
686static int
687ddihp_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 */
742static int
743ddihp_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 */
838static int
839ddihp_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 */
861static int
862ddihp_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 */
939static int
940ddihp_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 */
1022static void
1023ddihp_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 */
1045static boolean_t
1046ddihp_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