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 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 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 216 _info(struct modinfo *modinfop) 217 { 218 return (mod_info(&modlinkage, modinfop)); 219 } 220 221 static int 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 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 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 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 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 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 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 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 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 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 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 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 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 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