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