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, 163 NULL 164 }; 165 166 /* 167 * Module initialization routines. 168 */ 169 int 170 _init(void) 171 { 172 int error; 173 174 /* Initialize soft state pointer. */ 175 if ((error = ddi_soft_state_init(&acpinex_softstates, 176 sizeof (acpinex_softstate_t), 8)) != 0) { 177 cmn_err(CE_WARN, 178 "acpinex: failed to initialize soft state structure."); 179 return (error); 180 } 181 182 /* Initialize event subsystem. */ 183 acpinex_event_init(); 184 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); 189 return (error); 190 } 191 192 mutex_init(&acpinex_lock, NULL, MUTEX_DRIVER, NULL); 193 194 return (0); 195 } 196 197 int 198 _fini(void) 199 { 200 int error; 201 202 /* Remove the module. */ 203 if ((error = mod_remove(&modlinkage)) != 0) { 204 return (error); 205 } 206 207 /* Shut down event subsystem. */ 208 acpinex_event_fini(); 209 210 /* Free the soft state info. */ 211 ddi_soft_state_fini(&acpinex_softstates); 212 213 mutex_destroy(&acpinex_lock); 214 215 return (0); 216 } 217 218 int 219 _info(struct modinfo *modinfop) 220 { 221 return (mod_info(&modlinkage, modinfop)); 222 } 223 224 static int 225 acpinex_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) 226 { 227 _NOTE(ARGUNUSED(dip)); 228 229 dev_t dev; 230 int instance; 231 232 if (infocmd == DDI_INFO_DEVT2INSTANCE) { 233 dev = (dev_t)arg; 234 instance = ACPINEX_GET_INSTANCE(getminor(dev)); 235 *result = (void *)(uintptr_t)instance; 236 return (DDI_SUCCESS); 237 } 238 239 return (DDI_FAILURE); 240 } 241 242 static int 243 acpinex_attach(dev_info_t *devi, ddi_attach_cmd_t cmd) 244 { 245 int instance; 246 acpinex_softstate_t *softsp; 247 248 switch (cmd) { 249 case DDI_ATTACH: 250 break; 251 252 case DDI_RESUME: 253 return (DDI_SUCCESS); 254 255 default: 256 return (DDI_FAILURE); 257 } 258 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); 266 } 267 268 /* Get soft state structure. */ 269 if (ddi_soft_state_zalloc(acpinex_softstates, instance) 270 != DDI_SUCCESS) { 271 cmn_err(CE_WARN, "!acpinex: failed to allocate soft state " 272 "object in acpinex_attach()."); 273 return (DDI_FAILURE); 274 } 275 softsp = ddi_get_soft_state(acpinex_softstates, instance); 276 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.", 283 softsp->ans_path); 284 ddi_soft_state_free(acpinex_softstates, instance); 285 return (DDI_FAILURE); 286 } 287 mutex_init(&softsp->ans_lock, NULL, MUTEX_DRIVER, NULL); 288 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); 293 } 294 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); 300 301 acpinex_fm_init(softsp); 302 ddi_report_dev(devi); 303 304 return (DDI_SUCCESS); 305 } 306 307 static int 308 acpinex_detach(dev_info_t *devi, ddi_detach_cmd_t cmd) 309 { 310 int instance; 311 acpinex_softstate_t *softsp; 312 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); 319 } 320 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); 326 } 327 328 switch (cmd) { 329 case DDI_DETACH: 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); 334 } 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); 342 343 case DDI_SUSPEND: 344 return (DDI_SUCCESS); 345 346 default: 347 return (DDI_FAILURE); 348 } 349 } 350 351 static int 352 name_child(dev_info_t *child, char *name, int namelen) 353 { 354 char *unitaddr; 355 356 ddi_set_parent_data(child, NULL); 357 358 name[0] = '\0'; 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); 363 } else { 364 ACPINEX_DEBUG(CE_NOTE, "!acpinex: failed to lookup child " 365 "unit-address prop for %p.", (void *)child); 366 } 367 368 return (DDI_SUCCESS); 369 } 370 371 static int 372 init_child(dev_info_t *child) 373 { 374 char name[MAXNAMELEN]; 375 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); 382 } 383 384 return (DDI_SUCCESS); 385 } 386 387 /* 388 * Control ops entry point: 389 * 390 * Requests handled completely: 391 * DDI_CTLOPS_INITCHILD 392 * DDI_CTLOPS_UNINITCHILD 393 * All others are passed to the parent. 394 */ 395 static int 396 acpinex_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t op, void *arg, 397 void *result) 398 { 399 int rval = DDI_SUCCESS; 400 401 switch (op) { 402 case DDI_CTLOPS_INITCHILD: 403 rval = init_child((dev_info_t *)arg); 404 break; 405 406 case DDI_CTLOPS_UNINITCHILD: 407 impl_ddi_sunbus_removechild((dev_info_t *)arg); 408 break; 409 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)); 416 break; 417 } 418 419 default: 420 rval = ddi_ctlops(dip, rdip, op, arg, result); 421 break; 422 } 423 424 return (rval); 425 } 426 427 /* ARGSUSED */ 428 static int 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) 431 { 432 ACPINEX_DEBUG(CE_WARN, 433 "!acpinex: acpinex_bus_map called and it's unimplemented."); 434 return (DDI_ME_UNIMPLEMENTED); 435 } 436 437 static int 438 acpinex_open(dev_t *devi, int flags, int otyp, cred_t *credp) 439 { 440 _NOTE(ARGUNUSED(flags, otyp, credp)); 441 442 minor_t minor, instance; 443 acpinex_softstate_t *softsp; 444 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); 451 return (EINVAL); 452 } 453 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); 458 return (EINVAL); 459 } 460 461 if (ACPINEX_IS_DEVCTL(minor)) { 462 return (0); 463 } else { 464 ACPINEX_DEBUG(CE_WARN, 465 "!acpinex: invalid minor number %d in acpinex_open().", 466 minor); 467 return (EINVAL); 468 } 469 } 470 471 static int 472 acpinex_close(dev_t dev, int flags, int otyp, cred_t *credp) 473 { 474 _NOTE(ARGUNUSED(flags, otyp, credp)); 475 476 minor_t minor, instance; 477 acpinex_softstate_t *softsp; 478 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); 485 return (EINVAL); 486 } 487 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); 492 return (EINVAL); 493 } 494 495 if (ACPINEX_IS_DEVCTL(minor)) { 496 return (0); 497 } else { 498 ACPINEX_DEBUG(CE_WARN, 499 "!acpinex: invalid minor number %d in acpinex_close().", 500 minor); 501 return (EINVAL); 502 } 503 } 504 505 static int 506 acpinex_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, 507 int *rvalp) 508 { 509 _NOTE(ARGUNUSED(cmd, arg, mode, credp, rvalp)); 510 511 int rv = 0; 512 minor_t minor, instance; 513 acpinex_softstate_t *softsp; 514 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); 521 return (EINVAL); 522 } 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); 527 return (EINVAL); 528 } 529 530 rv = ENOTSUP; 531 ACPINEX_DEBUG(CE_WARN, 532 "!acpinex: invalid minor number %d in acpinex_ioctl().", minor); 533 534 return (rv); 535 } 536 537 /* 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. 541 */ 542 static int 543 acpinex_err_callback(dev_info_t *dip, ddi_fm_error_t *derr, 544 const void *impl_data) 545 { 546 _NOTE(ARGUNUSED(impl_data)); 547 548 /* Call our childrens error handlers */ 549 return (ndi_fm_handler_dispatch(dip, NULL, derr)); 550 } 551 552 /* 553 * Initialize our FMA resources 554 */ 555 static void 556 acpinex_fm_init(acpinex_softstate_t *softsp) 557 { 558 softsp->ans_fm_cap = DDI_FM_EREPORT_CAPABLE | DDI_FM_ERRCB_CAPABLE | 559 DDI_FM_ACCCHK_CAPABLE | DDI_FM_DMACHK_CAPABLE; 560 561 /* 562 * Request our capability level and get our parent's capability and ibc. 563 */ 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) { 566 /* 567 * Register error callback with our parent if supported. 568 */ 569 ddi_fm_handler_register(softsp->ans_dip, acpinex_err_callback, 570 softsp); 571 } 572 } 573 574 /* 575 * Breakdown our FMA resources 576 */ 577 static void 578 acpinex_fm_fini(acpinex_softstate_t *softsp) 579 { 580 /* Clean up allocated fm structures */ 581 if (softsp->ans_fm_cap & DDI_FM_ERRCB_CAPABLE) { 582 ddi_fm_handler_unregister(softsp->ans_dip); 583 } 584 ddi_fm_fini(softsp->ans_dip); 585 } 586 587 /* 588 * Initialize FMA resources for child devices. 589 * Called when child calls ddi_fm_init(). 590 */ 591 static int 592 acpinex_fm_init_child(dev_info_t *dip, dev_info_t *tdip, int cap, 593 ddi_iblock_cookie_t *ibc) 594 { 595 _NOTE(ARGUNUSED(tdip, cap)); 596 597 acpinex_softstate_t *softsp = ddi_get_soft_state(acpinex_softstates, 598 ddi_get_instance(dip)); 599 600 *ibc = softsp->ans_fm_ibc; 601 602 return (softsp->ans_fm_cap); 603 } 604