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 }