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