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
_init(void)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
_fini(void)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
_info(struct modinfo * modinfop)216 _info(struct modinfo *modinfop)
217 {
218 return (mod_info(&modlinkage, modinfop));
219 }
220
221 static int
acpinex_info(dev_info_t * dip,ddi_info_cmd_t infocmd,void * arg,void ** result)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
acpinex_attach(dev_info_t * devi,ddi_attach_cmd_t cmd)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
acpinex_detach(dev_info_t * devi,ddi_detach_cmd_t cmd)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
name_child(dev_info_t * child,char * name,int namelen)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
init_child(dev_info_t * child)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
acpinex_ctlops(dev_info_t * dip,dev_info_t * rdip,ddi_ctl_enum_t op,void * arg,void * result)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
acpinex_bus_map(dev_info_t * dip,dev_info_t * rdip,ddi_map_req_t * mp,off_t offset,off_t len,caddr_t * vaddrp)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
acpinex_open(dev_t * devi,int flags,int otyp,cred_t * credp)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
acpinex_close(dev_t dev,int flags,int otyp,cred_t * credp)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
acpinex_ioctl(dev_t dev,int cmd,intptr_t arg,int mode,cred_t * credp,int * rvalp)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
acpinex_err_callback(dev_info_t * dip,ddi_fm_error_t * derr,const void * impl_data)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
acpinex_fm_init(acpinex_softstate_t * softsp)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
acpinex_fm_fini(acpinex_softstate_t * softsp)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
acpinex_fm_init_child(dev_info_t * dip,dev_info_t * tdip,int cap,ddi_iblock_cookie_t * ibc)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