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
ddihp_modctl(int hp_op,char * path,char * cn_name,uintptr_t arg,uintptr_t rval)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
ddihp_cn_getstate(ddi_hp_cn_handle_t * hdlp)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
ddihp_cn_unregister(ddi_hp_cn_handle_t * hdlp)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 *
ddihp_cn_name_to_handle(dev_info_t * dip,char * cn_name)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
ddihp_connector_ops(ddi_hp_cn_handle_t * hdlp,ddi_hp_op_t op,void * arg,void * result)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
ddihp_port_ops(ddi_hp_cn_handle_t * hdlp,ddi_hp_op_t op,void * arg,void * result)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
ddihp_cn_gen_sysevent(ddi_hp_cn_handle_t * hdlp,ddi_hp_cn_sysevent_t event_sub_class,int hint,int kmflag)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
ddihp_cn_pre_change_state(ddi_hp_cn_handle_t * hdlp,ddi_hp_cn_state_t target_state)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
ddihp_cn_post_change_state(ddi_hp_cn_handle_t * hdlp,ddi_hp_cn_state_t new_state)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
ddihp_cn_handle_state_change(ddi_hp_cn_handle_t * hdlp)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
ddihp_cn_change_children_state(ddi_hp_cn_handle_t * hdlp,boolean_t online)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
ddihp_port_change_state(ddi_hp_cn_handle_t * hdlp,ddi_hp_cn_state_t target_state)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
ddihp_port_upgrade_state(ddi_hp_cn_handle_t * hdlp,ddi_hp_cn_state_t target_state)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
ddihp_port_downgrade_state(ddi_hp_cn_handle_t * hdlp,ddi_hp_cn_state_t target_state)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
ddihp_update_last_change(ddi_hp_cn_handle_t * hdlp)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
ddihp_check_status_prop(dev_info_t * dip)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