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.
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.
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]
23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
27 * Copyright (c) 2009-2010, Intel Corporation.
28 * All rights reserved.
31 * Copyright 2012 Garrett D'Amore <garrett@damore.org>. All rights reserved.
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.
39 #include <sys/types.h>
40 #include <sys/cmn_err.h>
42 #include <sys/modctl.h>
44 #include <sys/ddi_impldefs.h>
45 #include <sys/ddifm.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>
53 /* Patchable through /etc/system. */
55 int acpinex_debug
= 1;
57 int acpinex_debug
= 0;
63 static kmutex_t acpinex_lock
;
64 static void *acpinex_softstates
;
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 *,
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
);
81 extern void make_ddi_ppd(dev_info_t
*, struct ddi_parent_private_data
**);
84 * Configuration data structures
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 */
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 */
125 nodev
, /* cb_write */
126 acpinex_ioctl
, /* cb_ioctl */
127 nodev
, /* cb_devmap */
129 nodev
, /* cb_segmap */
130 nochpoll
, /* cb_poll */
131 ddi_prop_op
, /* cb_prop_op */
133 D_NEW
| D_MP
| D_HOTPLUG
, /* Driver compatibility flag */
135 nodev
, /* int (*cb_aread)() */
136 nodev
/* int (*cb_awrite)() */
139 static struct dev_ops acpinex_ops
= {
140 DEVO_REV
, /* devo_rev, */
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 */
154 static struct modldrv modldrv
= {
155 &mod_driverops
, /* Type of module */
156 "ACPI virtual bus driver", /* name of module */
157 &acpinex_ops
, /* driver ops */
160 static struct modlinkage modlinkage
= {
167 * Module initialization routines.
174 /* Initialize soft state pointer. */
175 if ((error
= ddi_soft_state_init(&acpinex_softstates
,
176 sizeof (acpinex_softstate_t
), 8)) != 0) {
178 "acpinex: failed to initialize soft state structure.");
182 /* Initialize event subsystem. */
183 acpinex_event_init();
185 /* Install the module. */
186 if ((error
= mod_install(&modlinkage
)) != 0) {
187 cmn_err(CE_WARN
, "acpinex: failed to install module.");
188 ddi_soft_state_fini(&acpinex_softstates
);
192 mutex_init(&acpinex_lock
, NULL
, MUTEX_DRIVER
, NULL
);
202 /* Remove the module. */
203 if ((error
= mod_remove(&modlinkage
)) != 0) {
207 /* Shut down event subsystem. */
208 acpinex_event_fini();
210 /* Free the soft state info. */
211 ddi_soft_state_fini(&acpinex_softstates
);
213 mutex_destroy(&acpinex_lock
);
219 _info(struct modinfo
*modinfop
)
221 return (mod_info(&modlinkage
, modinfop
));
225 acpinex_info(dev_info_t
*dip
, ddi_info_cmd_t infocmd
, void *arg
, void **result
)
227 _NOTE(ARGUNUSED(dip
));
232 if (infocmd
== DDI_INFO_DEVT2INSTANCE
) {
234 instance
= ACPINEX_GET_INSTANCE(getminor(dev
));
235 *result
= (void *)(uintptr_t)instance
;
236 return (DDI_SUCCESS
);
239 return (DDI_FAILURE
);
243 acpinex_attach(dev_info_t
*devi
, ddi_attach_cmd_t cmd
)
246 acpinex_softstate_t
*softsp
;
253 return (DDI_SUCCESS
);
256 return (DDI_FAILURE
);
259 /* Get and check instance number. */
260 instance
= ddi_get_instance(devi
);
261 if (instance
>= ACPINEX_INSTANCE_MAX
) {
262 cmn_err(CE_WARN
, "acpinex: instance number %d is out of range "
263 "in acpinex_attach(), max %d.",
264 instance
, ACPINEX_INSTANCE_MAX
- 1);
265 return (DDI_FAILURE
);
268 /* Get soft state structure. */
269 if (ddi_soft_state_zalloc(acpinex_softstates
, instance
)
271 cmn_err(CE_WARN
, "!acpinex: failed to allocate soft state "
272 "object in acpinex_attach().");
273 return (DDI_FAILURE
);
275 softsp
= ddi_get_soft_state(acpinex_softstates
, instance
);
277 /* Initialize soft state structure */
278 softsp
->ans_dip
= devi
;
279 (void) ddi_pathname(devi
, softsp
->ans_path
);
280 if (ACPI_FAILURE(acpica_get_handle(devi
, &softsp
->ans_hdl
))) {
281 ACPINEX_DEBUG(CE_WARN
,
282 "!acpinex: failed to get ACPI handle for %s.",
284 ddi_soft_state_free(acpinex_softstates
, instance
);
285 return (DDI_FAILURE
);
287 mutex_init(&softsp
->ans_lock
, NULL
, MUTEX_DRIVER
, NULL
);
289 /* Install event handler for child/descendant objects. */
290 if (acpinex_event_scan(softsp
, B_TRUE
) != DDI_SUCCESS
) {
291 cmn_err(CE_WARN
, "!acpinex: failed to install event handler "
292 "for children of %s.", softsp
->ans_path
);
295 /* nothing to suspend/resume here */
296 (void) ddi_prop_update_string(DDI_DEV_T_NONE
, devi
,
297 "pm-hardware-state", "no-suspend-resume");
298 (void) ddi_prop_update_int(DDI_DEV_T_NONE
, devi
,
299 DDI_NO_AUTODETACH
, 1);
301 acpinex_fm_init(softsp
);
302 ddi_report_dev(devi
);
304 return (DDI_SUCCESS
);
308 acpinex_detach(dev_info_t
*devi
, ddi_detach_cmd_t cmd
)
311 acpinex_softstate_t
*softsp
;
313 instance
= ddi_get_instance(devi
);
314 if (instance
>= ACPINEX_INSTANCE_MAX
) {
315 cmn_err(CE_WARN
, "acpinex: instance number %d is out of range "
316 "in acpinex_detach(), max %d.",
317 instance
, ACPINEX_INSTANCE_MAX
- 1);
318 return (DDI_FAILURE
);
321 softsp
= ddi_get_soft_state(acpinex_softstates
, instance
);
322 if (softsp
== NULL
) {
323 ACPINEX_DEBUG(CE_WARN
, "!acpinex: failed to get soft state "
324 "object for instance %d in acpinex_detach()", instance
);
325 return (DDI_FAILURE
);
330 if (acpinex_event_scan(softsp
, B_FALSE
) != DDI_SUCCESS
) {
331 cmn_err(CE_WARN
, "!acpinex: failed to uninstall event "
332 "handler for children of %s.", softsp
->ans_path
);
333 return (DDI_FAILURE
);
335 ddi_remove_minor_node(devi
, NULL
);
336 acpinex_fm_fini(softsp
);
337 mutex_destroy(&softsp
->ans_lock
);
338 ddi_soft_state_free(acpinex_softstates
, instance
);
339 (void) ddi_prop_update_int(DDI_DEV_T_NONE
, devi
,
340 DDI_NO_AUTODETACH
, 0);
341 return (DDI_SUCCESS
);
344 return (DDI_SUCCESS
);
347 return (DDI_FAILURE
);
352 name_child(dev_info_t
*child
, char *name
, int namelen
)
356 ddi_set_parent_data(child
, NULL
);
359 if (ddi_prop_lookup_string(DDI_DEV_T_ANY
, child
, DDI_PROP_DONTPASS
,
360 ACPIDEV_PROP_NAME_UNIT_ADDR
, &unitaddr
) == DDI_SUCCESS
) {
361 (void) strlcpy(name
, unitaddr
, namelen
);
362 ddi_prop_free(unitaddr
);
364 ACPINEX_DEBUG(CE_NOTE
, "!acpinex: failed to lookup child "
365 "unit-address prop for %p.", (void *)child
);
368 return (DDI_SUCCESS
);
372 init_child(dev_info_t
*child
)
374 char name
[MAXNAMELEN
];
376 (void) name_child(child
, name
, MAXNAMELEN
);
377 ddi_set_name_addr(child
, name
);
378 if ((ndi_dev_is_persistent_node(child
) == 0) &&
379 (ndi_merge_node(child
, name_child
) == DDI_SUCCESS
)) {
380 impl_ddi_sunbus_removechild(child
);
381 return (DDI_FAILURE
);
384 return (DDI_SUCCESS
);
388 * Control ops entry point:
390 * Requests handled completely:
391 * DDI_CTLOPS_INITCHILD
392 * DDI_CTLOPS_UNINITCHILD
393 * All others are passed to the parent.
396 acpinex_ctlops(dev_info_t
*dip
, dev_info_t
*rdip
, ddi_ctl_enum_t op
, void *arg
,
399 int rval
= DDI_SUCCESS
;
402 case DDI_CTLOPS_INITCHILD
:
403 rval
= init_child((dev_info_t
*)arg
);
406 case DDI_CTLOPS_UNINITCHILD
:
407 impl_ddi_sunbus_removechild((dev_info_t
*)arg
);
410 case DDI_CTLOPS_REPORTDEV
: {
411 if (rdip
== (dev_info_t
*)0)
412 return (DDI_FAILURE
);
413 cmn_err(CE_CONT
, "?acpinex: %s@%s, %s%d\n",
414 ddi_node_name(rdip
), ddi_get_name_addr(rdip
),
415 ddi_driver_name(rdip
), ddi_get_instance(rdip
));
420 rval
= ddi_ctlops(dip
, rdip
, op
, arg
, result
);
429 acpinex_bus_map(dev_info_t
*dip
, dev_info_t
*rdip
, ddi_map_req_t
*mp
,
430 off_t offset
, off_t len
, caddr_t
*vaddrp
)
432 ACPINEX_DEBUG(CE_WARN
,
433 "!acpinex: acpinex_bus_map called and it's unimplemented.");
434 return (DDI_ME_UNIMPLEMENTED
);
438 acpinex_open(dev_t
*devi
, int flags
, int otyp
, cred_t
*credp
)
440 _NOTE(ARGUNUSED(flags
, otyp
, credp
));
442 minor_t minor
, instance
;
443 acpinex_softstate_t
*softsp
;
445 minor
= getminor(*devi
);
446 instance
= ACPINEX_GET_INSTANCE(minor
);
447 if (instance
>= ACPINEX_INSTANCE_MAX
) {
448 ACPINEX_DEBUG(CE_WARN
, "!acpinex: instance number %d out of "
449 "range in acpinex_open, max %d.",
450 instance
, ACPINEX_INSTANCE_MAX
- 1);
454 softsp
= ddi_get_soft_state(acpinex_softstates
, instance
);
455 if (softsp
== NULL
) {
456 ACPINEX_DEBUG(CE_WARN
, "!acpinex: failed to get soft state "
457 "object for instance %d in acpinex_open().", instance
);
461 if (ACPINEX_IS_DEVCTL(minor
)) {
464 ACPINEX_DEBUG(CE_WARN
,
465 "!acpinex: invalid minor number %d in acpinex_open().",
472 acpinex_close(dev_t dev
, int flags
, int otyp
, cred_t
*credp
)
474 _NOTE(ARGUNUSED(flags
, otyp
, credp
));
476 minor_t minor
, instance
;
477 acpinex_softstate_t
*softsp
;
479 minor
= getminor(dev
);
480 instance
= ACPINEX_GET_INSTANCE(minor
);
481 if (instance
>= ACPINEX_INSTANCE_MAX
) {
482 ACPINEX_DEBUG(CE_WARN
, "!acpinex: instance number %d out of "
483 "range in acpinex_close(), max %d.",
484 instance
, ACPINEX_INSTANCE_MAX
- 1);
488 softsp
= ddi_get_soft_state(acpinex_softstates
, instance
);
489 if (softsp
== NULL
) {
490 ACPINEX_DEBUG(CE_WARN
, "!acpinex: failed to get soft state "
491 "object for instance %d in acpinex_close().", instance
);
495 if (ACPINEX_IS_DEVCTL(minor
)) {
498 ACPINEX_DEBUG(CE_WARN
,
499 "!acpinex: invalid minor number %d in acpinex_close().",
506 acpinex_ioctl(dev_t dev
, int cmd
, intptr_t arg
, int mode
, cred_t
*credp
,
509 _NOTE(ARGUNUSED(cmd
, arg
, mode
, credp
, rvalp
));
512 minor_t minor
, instance
;
513 acpinex_softstate_t
*softsp
;
515 minor
= getminor(dev
);
516 instance
= ACPINEX_GET_INSTANCE(minor
);
517 if (instance
>= ACPINEX_INSTANCE_MAX
) {
518 ACPINEX_DEBUG(CE_NOTE
, "!acpinex: instance number %d out of "
519 "range in acpinex_ioctl(), max %d.",
520 instance
, ACPINEX_INSTANCE_MAX
- 1);
523 softsp
= ddi_get_soft_state(acpinex_softstates
, instance
);
524 if (softsp
== NULL
) {
525 ACPINEX_DEBUG(CE_WARN
, "!acpinex: failed to get soft state "
526 "object for instance %d in acpinex_ioctl().", instance
);
531 ACPINEX_DEBUG(CE_WARN
,
532 "!acpinex: invalid minor number %d in acpinex_ioctl().", minor
);
538 * FMA error callback.
539 * Register error handling callback with our parent. We will just call
540 * our children's error callbacks and return their status.
543 acpinex_err_callback(dev_info_t
*dip
, ddi_fm_error_t
*derr
,
544 const void *impl_data
)
546 _NOTE(ARGUNUSED(impl_data
));
548 /* Call our childrens error handlers */
549 return (ndi_fm_handler_dispatch(dip
, NULL
, derr
));
553 * Initialize our FMA resources
556 acpinex_fm_init(acpinex_softstate_t
*softsp
)
558 softsp
->ans_fm_cap
= DDI_FM_EREPORT_CAPABLE
| DDI_FM_ERRCB_CAPABLE
|
559 DDI_FM_ACCCHK_CAPABLE
| DDI_FM_DMACHK_CAPABLE
;
562 * Request our capability level and get our parent's capability and ibc.
564 ddi_fm_init(softsp
->ans_dip
, &softsp
->ans_fm_cap
, &softsp
->ans_fm_ibc
);
565 if (softsp
->ans_fm_cap
& DDI_FM_ERRCB_CAPABLE
) {
567 * Register error callback with our parent if supported.
569 ddi_fm_handler_register(softsp
->ans_dip
, acpinex_err_callback
,
575 * Breakdown our FMA resources
578 acpinex_fm_fini(acpinex_softstate_t
*softsp
)
580 /* Clean up allocated fm structures */
581 if (softsp
->ans_fm_cap
& DDI_FM_ERRCB_CAPABLE
) {
582 ddi_fm_handler_unregister(softsp
->ans_dip
);
584 ddi_fm_fini(softsp
->ans_dip
);
588 * Initialize FMA resources for child devices.
589 * Called when child calls ddi_fm_init().
592 acpinex_fm_init_child(dev_info_t
*dip
, dev_info_t
*tdip
, int cap
,
593 ddi_iblock_cookie_t
*ibc
)
595 _NOTE(ARGUNUSED(tdip
, cap
));
597 acpinex_softstate_t
*softsp
= ddi_get_soft_state(acpinex_softstates
,
598 ddi_get_instance(dip
));
600 *ibc
= softsp
->ans_fm_ibc
;
602 return (softsp
->ans_fm_cap
);