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 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 /*
27  * Copyright (c) 2009-2010, Intel Corporation.
28  * All rights reserved.
29  */
30 /*
31  * This module implements a nexus driver for the ACPI virtual bus.
32  * It does not handle any of the DDI functions passed up to it by the child
33  * drivers, but instead allows them to bubble up to the root node.
34  */
35 
36 #include <sys/types.h>
37 #include <sys/cmn_err.h>
38 #include <sys/conf.h>
39 #include <sys/modctl.h>
40 #include <sys/ddi.h>
41 #include <sys/ddi_impldefs.h>
42 #include <sys/ddifm.h>
43 #include <sys/note.h>
44 #include <sys/ndifm.h>
45 #include <sys/sunddi.h>
46 #include <sys/sunndi.h>
47 #include <sys/acpidev.h>
48 #include <sys/acpinex.h>
49 
50 /* Patchable through /etc/system. */
51 #ifdef	DEBUG
52 int acpinex_debug = 1;
53 #else
54 int acpinex_debug = 0;
55 #endif
56 
57 /*
58  * Driver globals
59  */
60 static kmutex_t acpinex_lock;
61 static void *acpinex_softstates;
62 
63 static int acpinex_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
64 static int acpinex_attach(dev_info_t *, ddi_attach_cmd_t);
65 static int acpinex_detach(dev_info_t *, ddi_detach_cmd_t);
66 static int acpinex_open(dev_t *, int, int, cred_t *);
67 static int acpinex_close(dev_t, int, int, cred_t *);
68 static int acpinex_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
69 static int acpinex_bus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
70     off_t offset, off_t len, caddr_t *vaddrp);
71 static int acpinex_ctlops(dev_info_t *, dev_info_t *, ddi_ctl_enum_t, void *,
72     void *);
73 static int acpinex_fm_init_child(dev_info_t *, dev_info_t *, int,
74     ddi_iblock_cookie_t *);
75 static void acpinex_fm_init(acpinex_softstate_t *softsp);
76 static void acpinex_fm_fini(acpinex_softstate_t *softsp);
77 
78 extern void make_ddi_ppd(dev_info_t *, struct ddi_parent_private_data **);
79 
80 /*
81  * Configuration data structures
82  */
83 static struct bus_ops acpinex_bus_ops = {
84 	BUSO_REV,			/* busops_rev */
85 	acpinex_bus_map,		/* bus_map */
86 	NULL,				/* bus_get_intrspec */
87 	NULL,				/* bus_add_intrspec */
88 	NULL,				/* bus_remove_intrspec */
89 	i_ddi_map_fault,		/* bus_map_fault */
90 	ddi_dma_map,			/* bus_dma_map */
91 	ddi_dma_allochdl,		/* bus_dma_allochdl */
92 	ddi_dma_freehdl,		/* bus_dma_freehdl */
93 	ddi_dma_bindhdl,		/* bus_dma_bindhdl */
94 	ddi_dma_unbindhdl,		/* bus_dma_unbindhdl */
95 	ddi_dma_flush,			/* bus_dma_flush */
96 	ddi_dma_win,			/* bus_dma_win */
97 	ddi_dma_mctl,			/* bus_dma_ctl */
98 	acpinex_ctlops,			/* bus_ctl */
99 	ddi_bus_prop_op,		/* bus_prop_op */
100 	ndi_busop_get_eventcookie,	/* bus_get_eventcookie */
101 	ndi_busop_add_eventcall,	/* bus_add_eventcall */
102 	ndi_busop_remove_eventcall,	/* bus_remove_eventcall */
103 	ndi_post_event,			/* bus_post_event */
104 	NULL,				/* bus_intr_ctl */
105 	NULL,				/* bus_config */
106 	NULL,				/* bus_unconfig */
107 	acpinex_fm_init_child,		/* bus_fm_init */
108 	NULL,				/* bus_fm_fini */
109 	NULL,				/* bus_fm_access_enter */
110 	NULL,				/* bus_fm_access_exit */
111 	NULL,				/* bus_power */
112 	i_ddi_intr_ops			/* bus_intr_op */
113 };
114 
115 static struct cb_ops acpinex_cb_ops = {
116 	acpinex_open,			/* cb_open */
117 	acpinex_close,			/* cb_close */
118 	nodev,				/* cb_strategy */
119 	nodev,				/* cb_print */
120 	nodev,				/* cb_dump */
121 	nodev,				/* cb_read */
122 	nodev,				/* cb_write */
123 	acpinex_ioctl,			/* cb_ioctl */
124 	nodev,				/* cb_devmap */
125 	nodev,				/* cb_mmap */
126 	nodev,				/* cb_segmap */
127 	nochpoll,			/* cb_poll */
128 	ddi_prop_op,			/* cb_prop_op */
129 	NULL,				/* cb_str */
130 	D_NEW | D_MP | D_HOTPLUG,	/* Driver compatibility flag */
131 	CB_REV,				/* rev */
132 	nodev,				/* int (*cb_aread)() */
133 	nodev				/* int (*cb_awrite)() */
134 };
135 
136 static struct dev_ops acpinex_ops = {
137 	DEVO_REV,			/* devo_rev, */
138 	0,				/* devo_refcnt */
139 	acpinex_info,			/* devo_getinfo */
140 	nulldev,			/* devo_identify */
141 	nulldev,			/* devo_probe */
142 	acpinex_attach,			/* devo_attach */
143 	acpinex_detach,			/* devo_detach */
144 	nulldev,			/* devo_reset */
145 	&acpinex_cb_ops,		/* devo_cb_ops */
146 	&acpinex_bus_ops,		/* devo_bus_ops */
147 	nulldev,			/* devo_power */
148 	ddi_quiesce_not_needed		/* devo_quiesce */
149 };
150 
151 static struct modldrv modldrv = {
152 	&mod_driverops,			/* Type of module */
153 	"ACPI virtual bus driver",	/* name of module */
154 	&acpinex_ops,			/* driver ops */
155 };
156 
157 static struct modlinkage modlinkage = {
158 	MODREV_1,			/* rev */
159 	(void *)&modldrv,
160 	NULL
161 };
162 
163 /*
164  * Module initialization routines.
165  */
166 int
167 _init(void)
168 {
169 	int error;
170 
171 	/* Initialize soft state pointer. */
172 	if ((error = ddi_soft_state_init(&acpinex_softstates,
173 	    sizeof (acpinex_softstate_t), 8)) != 0) {
174 		cmn_err(CE_WARN,
175 		    "acpinex: failed to initialize soft state structure.");
176 		return (error);
177 	}
178 
179 	/* Initialize event subsystem. */
180 	acpinex_event_init();
181 
182 	/* Install the module. */
183 	if ((error = mod_install(&modlinkage)) != 0) {
184 		cmn_err(CE_WARN, "acpinex: failed to install module.");
185 		ddi_soft_state_fini(&acpinex_softstates);
186 		return (error);
187 	}
188 
189 	mutex_init(&acpinex_lock, NULL, MUTEX_DRIVER, NULL);
190 
191 	return (0);
192 }
193 
194 int
195 _fini(void)
196 {
197 	int error;
198 
199 	/* Remove the module. */
200 	if ((error = mod_remove(&modlinkage)) != 0) {
201 		return (error);
202 	}
203 
204 	/* Shut down event subsystem. */
205 	acpinex_event_fini();
206 
207 	/* Free the soft state info. */
208 	ddi_soft_state_fini(&acpinex_softstates);
209 
210 	mutex_destroy(&acpinex_lock);
211 
212 	return (0);
213 }
214 
215 int
216 _info(struct modinfo *modinfop)
217 {
218 	return (mod_info(&modlinkage, modinfop));
219 }
220 
221 static int
222 acpinex_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
223 {
224 	_NOTE(ARGUNUSED(dip));
225 
226 	dev_t	dev;
227 	int	instance;
228 
229 	if (infocmd == DDI_INFO_DEVT2INSTANCE) {
230 		dev = (dev_t)arg;
231 		instance = ACPINEX_GET_INSTANCE(getminor(dev));
232 		*result = (void *)(uintptr_t)instance;
233 		return (DDI_SUCCESS);
234 	}
235 
236 	return (DDI_FAILURE);
237 }
238 
239 static int
240 acpinex_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
241 {
242 	int instance;
243 	acpinex_softstate_t *softsp;
244 
245 	switch (cmd) {
246 	case DDI_ATTACH:
247 		break;
248 
249 	case DDI_RESUME:
250 		return (DDI_SUCCESS);
251 
252 	default:
253 		return (DDI_FAILURE);
254 	}
255 
256 	/* Get and check instance number. */
257 	instance = ddi_get_instance(devi);
258 	if (instance >= ACPINEX_INSTANCE_MAX) {
259 		cmn_err(CE_WARN, "acpinex: instance number %d is out of range "
260 		    "in acpinex_attach(), max %d.",
261 		    instance, ACPINEX_INSTANCE_MAX - 1);
262 		return (DDI_FAILURE);
263 	}
264 
265 	/* Get soft state structure. */
266 	if (ddi_soft_state_zalloc(acpinex_softstates, instance)
267 	    != DDI_SUCCESS) {
268 		cmn_err(CE_WARN, "!acpinex: failed to allocate soft state "
269 		    "object in acpinex_attach().");
270 		return (DDI_FAILURE);
271 	}
272 	softsp = ddi_get_soft_state(acpinex_softstates, instance);
273 
274 	/* Initialize soft state structure */
275 	softsp->ans_dip = devi;
276 	(void) ddi_pathname(devi, softsp->ans_path);
277 	if (ACPI_FAILURE(acpica_get_handle(devi, &softsp->ans_hdl))) {
278 		ACPINEX_DEBUG(CE_WARN,
279 		    "!acpinex: failed to get ACPI handle for %s.",
280 		    softsp->ans_path);
281 		ddi_soft_state_free(acpinex_softstates, instance);
282 		return (DDI_FAILURE);
283 	}
284 	mutex_init(&softsp->ans_lock, NULL, MUTEX_DRIVER, NULL);
285 
286 	/* Install event handler for child/descendant objects. */
287 	if (acpinex_event_scan(softsp, B_TRUE) != DDI_SUCCESS) {
288 		cmn_err(CE_WARN, "!acpinex: failed to install event handler "
289 		    "for children of %s.", softsp->ans_path);
290 	}
291 
292 	/* nothing to suspend/resume here */
293 	(void) ddi_prop_update_string(DDI_DEV_T_NONE, devi,
294 	    "pm-hardware-state", "no-suspend-resume");
295 	(void) ddi_prop_update_int(DDI_DEV_T_NONE, devi,
296 	    DDI_NO_AUTODETACH, 1);
297 
298 	acpinex_fm_init(softsp);
299 	ddi_report_dev(devi);
300 
301 	return (DDI_SUCCESS);
302 }
303 
304 static int
305 acpinex_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
306 {
307 	int instance;
308 	acpinex_softstate_t *softsp;
309 
310 	instance = ddi_get_instance(devi);
311 	if (instance >= ACPINEX_INSTANCE_MAX) {
312 		cmn_err(CE_WARN, "acpinex: instance number %d is out of range "
313 		    "in acpinex_detach(), max %d.",
314 		    instance, ACPINEX_INSTANCE_MAX - 1);
315 		return (DDI_FAILURE);
316 	}
317 
318 	softsp = ddi_get_soft_state(acpinex_softstates, instance);
319 	if (softsp == NULL) {
320 		ACPINEX_DEBUG(CE_WARN, "!acpinex: failed to get soft state "
321 		    "object for instance %d in acpinex_detach()", instance);
322 		return (DDI_FAILURE);
323 	}
324 
325 	switch (cmd) {
326 	case DDI_DETACH:
327 		if (acpinex_event_scan(softsp, B_FALSE) != DDI_SUCCESS) {
328 			cmn_err(CE_WARN, "!acpinex: failed to uninstall event "
329 			    "handler for children of %s.", softsp->ans_path);
330 			return (DDI_FAILURE);
331 		}
332 		ddi_remove_minor_node(devi, NULL);
333 		acpinex_fm_fini(softsp);
334 		mutex_destroy(&softsp->ans_lock);
335 		ddi_soft_state_free(acpinex_softstates, instance);
336 		(void) ddi_prop_update_int(DDI_DEV_T_NONE, devi,
337 		    DDI_NO_AUTODETACH, 0);
338 		return (DDI_SUCCESS);
339 
340 	case DDI_SUSPEND:
341 		return (DDI_SUCCESS);
342 
343 	default:
344 		return (DDI_FAILURE);
345 	}
346 }
347 
348 static int
349 name_child(dev_info_t *child, char *name, int namelen)
350 {
351 	char *unitaddr;
352 
353 	ddi_set_parent_data(child, NULL);
354 
355 	name[0] = '\0';
356 	if (ddi_prop_lookup_string(DDI_DEV_T_ANY, child, DDI_PROP_DONTPASS,
357 	    ACPIDEV_PROP_NAME_UNIT_ADDR, &unitaddr) == DDI_SUCCESS) {
358 		(void) strlcpy(name, unitaddr, namelen);
359 		ddi_prop_free(unitaddr);
360 	} else {
361 		ACPINEX_DEBUG(CE_NOTE, "!acpinex: failed to lookup child "
362 		    "unit-address prop for %p.", (void *)child);
363 	}
364 
365 	return (DDI_SUCCESS);
366 }
367 
368 static int
369 init_child(dev_info_t *child)
370 {
371 	char name[MAXNAMELEN];
372 
373 	(void) name_child(child, name, MAXNAMELEN);
374 	ddi_set_name_addr(child, name);
375 	if ((ndi_dev_is_persistent_node(child) == 0) &&
376 	    (ndi_merge_node(child, name_child) == DDI_SUCCESS)) {
377 		impl_ddi_sunbus_removechild(child);
378 		return (DDI_FAILURE);
379 	}
380 
381 	return (DDI_SUCCESS);
382 }
383 
384 /*
385  * Control ops entry point:
386  *
387  * Requests handled completely:
388  *      DDI_CTLOPS_INITCHILD
389  *      DDI_CTLOPS_UNINITCHILD
390  * All others are passed to the parent.
391  */
392 static int
393 acpinex_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op, void *arg,
394     void *result)
395 {
396 	int rval = DDI_SUCCESS;
397 
398 	switch (op) {
399 	case DDI_CTLOPS_INITCHILD:
400 		rval = init_child((dev_info_t *)arg);
401 		break;
402 
403 	case DDI_CTLOPS_UNINITCHILD:
404 		impl_ddi_sunbus_removechild((dev_info_t *)arg);
405 		break;
406 
407 	case DDI_CTLOPS_REPORTDEV: {
408 		if (rdip == (dev_info_t *)0)
409 			return (DDI_FAILURE);
410 		cmn_err(CE_CONT, "?acpinex: %s@%s, %s%d\n",
411 		    ddi_node_name(rdip), ddi_get_name_addr(rdip),
412 		    ddi_driver_name(rdip), ddi_get_instance(rdip));
413 		break;
414 	}
415 
416 	default:
417 		rval = ddi_ctlops(dip, rdip, op, arg, result);
418 		break;
419 	}
420 
421 	return (rval);
422 }
423 
424 /* ARGSUSED */
425 static int
426 acpinex_bus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
427     off_t offset, off_t len, caddr_t *vaddrp)
428 {
429 	ACPINEX_DEBUG(CE_WARN,
430 	    "!acpinex: acpinex_bus_map called and it's unimplemented.");
431 	return (DDI_ME_UNIMPLEMENTED);
432 }
433 
434 static int
435 acpinex_open(dev_t *devi, int flags, int otyp, cred_t *credp)
436 {
437 	_NOTE(ARGUNUSED(flags, otyp, credp));
438 
439 	minor_t minor, instance;
440 	acpinex_softstate_t *softsp;
441 
442 	minor = getminor(*devi);
443 	instance = ACPINEX_GET_INSTANCE(minor);
444 	if (instance >= ACPINEX_INSTANCE_MAX) {
445 		ACPINEX_DEBUG(CE_WARN, "!acpinex: instance number %d out of "
446 		    "range in acpinex_open, max %d.",
447 		    instance, ACPINEX_INSTANCE_MAX - 1);
448 		return (EINVAL);
449 	}
450 
451 	softsp = ddi_get_soft_state(acpinex_softstates, instance);
452 	if (softsp == NULL) {
453 		ACPINEX_DEBUG(CE_WARN, "!acpinex: failed to get soft state "
454 		    "object for instance %d in acpinex_open().", instance);
455 		return (EINVAL);
456 	}
457 
458 	if (ACPINEX_IS_DEVCTL(minor)) {
459 		return (0);
460 	} else {
461 		ACPINEX_DEBUG(CE_WARN,
462 		    "!acpinex: invalid minor number %d in acpinex_open().",
463 		    minor);
464 		return (EINVAL);
465 	}
466 }
467 
468 static int
469 acpinex_close(dev_t dev, int flags, int otyp, cred_t *credp)
470 {
471 	_NOTE(ARGUNUSED(flags, otyp, credp));
472 
473 	minor_t minor, instance;
474 	acpinex_softstate_t *softsp;
475 
476 	minor = getminor(dev);
477 	instance = ACPINEX_GET_INSTANCE(minor);
478 	if (instance >= ACPINEX_INSTANCE_MAX) {
479 		ACPINEX_DEBUG(CE_WARN, "!acpinex: instance number %d out of "
480 		    "range in acpinex_close(), max %d.",
481 		    instance, ACPINEX_INSTANCE_MAX - 1);
482 		return (EINVAL);
483 	}
484 
485 	softsp = ddi_get_soft_state(acpinex_softstates, instance);
486 	if (softsp == NULL) {
487 		ACPINEX_DEBUG(CE_WARN, "!acpinex: failed to get soft state "
488 		    "object for instance %d in acpinex_close().", instance);
489 		return (EINVAL);
490 	}
491 
492 	if (ACPINEX_IS_DEVCTL(minor)) {
493 		return (0);
494 	} else {
495 		ACPINEX_DEBUG(CE_WARN,
496 		    "!acpinex: invalid minor number %d in acpinex_close().",
497 		    minor);
498 		return (EINVAL);
499 	}
500 }
501 
502 static int
503 acpinex_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
504     int *rvalp)
505 {
506 	_NOTE(ARGUNUSED(cmd, arg, mode, credp, rvalp));
507 
508 	int rv = 0;
509 	minor_t minor, instance;
510 	acpinex_softstate_t *softsp;
511 
512 	minor = getminor(dev);
513 	instance = ACPINEX_GET_INSTANCE(minor);
514 	if (instance >= ACPINEX_INSTANCE_MAX) {
515 		ACPINEX_DEBUG(CE_NOTE, "!acpinex: instance number %d out of "
516 		    "range in acpinex_ioctl(), max %d.",
517 		    instance, ACPINEX_INSTANCE_MAX - 1);
518 		return (EINVAL);
519 	}
520 	softsp = ddi_get_soft_state(acpinex_softstates, instance);
521 	if (softsp == NULL) {
522 		ACPINEX_DEBUG(CE_WARN, "!acpinex: failed to get soft state "
523 		    "object for instance %d in acpinex_ioctl().", instance);
524 		return (EINVAL);
525 	}
526 
527 	rv = ENOTSUP;
528 	ACPINEX_DEBUG(CE_WARN,
529 	    "!acpinex: invalid minor number %d in acpinex_ioctl().", minor);
530 
531 	return (rv);
532 }
533 
534 /*
535  * FMA error callback.
536  * Register error handling callback with our parent. We will just call
537  * our children's error callbacks and return their status.
538  */
539 static int
540 acpinex_err_callback(dev_info_t *dip, ddi_fm_error_t *derr,
541     const void *impl_data)
542 {
543 	_NOTE(ARGUNUSED(impl_data));
544 
545 	/* Call our childrens error handlers */
546 	return (ndi_fm_handler_dispatch(dip, NULL, derr));
547 }
548 
549 /*
550  * Initialize our FMA resources
551  */
552 static void
553 acpinex_fm_init(acpinex_softstate_t *softsp)
554 {
555 	softsp->ans_fm_cap = DDI_FM_EREPORT_CAPABLE | DDI_FM_ERRCB_CAPABLE |
556 	    DDI_FM_ACCCHK_CAPABLE | DDI_FM_DMACHK_CAPABLE;
557 
558 	/*
559 	 * Request our capability level and get our parent's capability and ibc.
560 	 */
561 	ddi_fm_init(softsp->ans_dip, &softsp->ans_fm_cap, &softsp->ans_fm_ibc);
562 	if (softsp->ans_fm_cap & DDI_FM_ERRCB_CAPABLE) {
563 		/*
564 		 * Register error callback with our parent if supported.
565 		 */
566 		ddi_fm_handler_register(softsp->ans_dip, acpinex_err_callback,
567 		    softsp);
568 	}
569 }
570 
571 /*
572  * Breakdown our FMA resources
573  */
574 static void
575 acpinex_fm_fini(acpinex_softstate_t *softsp)
576 {
577 	/* Clean up allocated fm structures */
578 	if (softsp->ans_fm_cap & DDI_FM_ERRCB_CAPABLE) {
579 		ddi_fm_handler_unregister(softsp->ans_dip);
580 	}
581 	ddi_fm_fini(softsp->ans_dip);
582 }
583 
584 /*
585  * Initialize FMA resources for child devices.
586  * Called when child calls ddi_fm_init().
587  */
588 static int
589 acpinex_fm_init_child(dev_info_t *dip, dev_info_t *tdip, int cap,
590     ddi_iblock_cookie_t *ibc)
591 {
592 	_NOTE(ARGUNUSED(tdip, cap));
593 
594 	acpinex_softstate_t *softsp = ddi_get_soft_state(acpinex_softstates,
595 	    ddi_get_instance(dip));
596 
597 	*ibc = softsp->ans_fm_ibc;
598 
599 	return (softsp->ans_fm_cap);
600 }
601