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