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 2008 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  * Copyright (c) 2011 Bayard G. Bell. All rights reserved.
  26  */
  27 
  28 #include <sys/types.h>
  29 #include <sys/conf.h>
  30 #include <sys/open.h>
  31 #include <sys/modctl.h>
  32 #include <sys/promif.h>
  33 #include <sys/stat.h>
  34 #include <sys/ddi_impldefs.h>
  35 #include <sys/ddi.h>
  36 #include <sys/sunddi.h>
  37 #include <sys/epm.h>
  38 #include <sys/acpi/acpi.h>
  39 #include <sys/acpica.h>
  40 #include <sys/psm_types.h>
  41 
  42 /*
  43  *      ACPI Power Management Driver
  44  *
  45  *      acpippm deals with those bits of ppm functionality that
  46  *      must be mediated by ACPI
  47  *
  48  *      The routines in this driver is referenced by Platform
  49  *      Power Management driver of X86 workstation systems.
  50  *      acpippm driver is loaded because it is listed as a platform driver
  51  *      It is initially configured as a pseudo driver.
  52  */
  53 extern void pc_tod_set_rtc_offsets(ACPI_TABLE_FADT *);
  54 extern int acpica_use_safe_delay;
  55 
  56 /*
  57  * Configuration Function prototypes and data structures
  58  */
  59 static int      appm_attach(dev_info_t *, ddi_attach_cmd_t);
  60 static int      appm_detach(dev_info_t *, ddi_detach_cmd_t);
  61 static int      appm_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
  62 static int      appm_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p);
  63 static int      appm_close(dev_t dev, int flag, int otyp, cred_t *cred_p);
  64 static int      appm_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
  65 
  66 /*
  67  * Configuration data structures
  68  */
  69 static struct cb_ops appm_cbops = {
  70         appm_open,              /* open */
  71         appm_close,             /* close */
  72         nodev,                  /* strategy */
  73         nodev,                  /* print */
  74         nodev,                  /* dump */
  75         nodev,                  /* read */
  76         nodev,                  /* write */
  77         appm_ioctl,             /* ioctl */
  78         nodev,                  /* devmap */
  79         nodev,                  /* mmap */
  80         nodev,                  /* segmap */
  81         nochpoll,               /* chpoll */
  82         ddi_prop_op,            /* prop_op */
  83         NULL,                   /* stream */
  84         D_MP | D_NEW,           /* flag */
  85         CB_REV,                 /* rev */
  86         nodev,                  /* aread */
  87         nodev,                  /* awrite */
  88 };
  89 
  90 static struct dev_ops appm_ops = {
  91         DEVO_REV,               /* devo_rev */
  92         0,                      /* refcnt */
  93         appm_getinfo,           /* getinfo */
  94         nulldev,                /* identify */
  95         nulldev,                /* probe */
  96         appm_attach,            /* attach */
  97         appm_detach,            /* detach */
  98         nodev,                  /* reset */
  99         &appm_cbops,                /* cb_ops */
 100         NULL,                   /* bus_ops */
 101         NULL,                   /* power */
 102         ddi_quiesce_not_needed,         /* quiesce */
 103 };
 104 
 105 extern struct mod_ops mod_driverops;
 106 
 107 static struct modldrv modldrv = {
 108         &mod_driverops,
 109         "ACPI ppm driver",
 110         &appm_ops,
 111 };
 112 
 113 static struct modlinkage modlinkage = {
 114         MODREV_1,
 115         { &modldrv, NULL }
 116 };
 117 
 118 /*
 119  * Driver state structure
 120  */
 121 typedef struct {
 122         dev_info_t              *dip;
 123         ddi_acc_handle_t        devid_hndl;
 124         ddi_acc_handle_t        estar_hndl;
 125         int                     lyropen;                /* ref count */
 126 } appm_unit;
 127 
 128 /*
 129  * Driver global variables
 130  *
 131  * appm_lock synchronize the access of lyr handle to each appm
 132  * minor device, therefore write to tomatillo device is
 133  * sequentialized.  Lyr protocol requires pairing up lyr open
 134  * and close, so only a single reference is allowed per minor node.
 135  */
 136 static void     *appm_statep;
 137 static kmutex_t  appm_lock;
 138 
 139 /*
 140  * S3 stuff:
 141  */
 142 extern int acpi_enter_sleepstate(s3a_t *);
 143 extern int acpi_exit_sleepstate(s3a_t *);
 144 
 145 
 146 int
 147 _init(void)
 148 {
 149         int     error;
 150 
 151         if ((error = ddi_soft_state_init(&appm_statep,
 152             sizeof (appm_unit), 0)) != DDI_SUCCESS) {
 153                 return (error);
 154         }
 155 
 156         mutex_init(&appm_lock, NULL, MUTEX_DRIVER, NULL);
 157 
 158         if ((error = mod_install(&modlinkage)) != DDI_SUCCESS) {
 159                 mutex_destroy(&appm_lock);
 160                 ddi_soft_state_fini(&appm_statep);
 161                 return (error);
 162         }
 163 
 164         return (error);
 165 }
 166 
 167 int
 168 _fini(void)
 169 {
 170         int     error;
 171 
 172         if ((error = mod_remove(&modlinkage)) == DDI_SUCCESS) {
 173                 mutex_destroy(&appm_lock);
 174                 ddi_soft_state_fini(&appm_statep);
 175         }
 176 
 177         return (error);
 178 
 179 }
 180 
 181 int
 182 _info(struct modinfo *modinfop)
 183 {
 184         return (mod_info(&modlinkage, modinfop));
 185 }
 186 
 187 
 188 
 189 /*
 190  * Driver attach(9e) entry point
 191  */
 192 static int
 193 appm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
 194 {
 195         char    *str = "appm_attach";
 196         int             instance;
 197         appm_unit       *unitp;
 198         ACPI_TABLE_FADT *fadt = NULL;
 199         int             rv = DDI_SUCCESS;
 200 
 201         switch (cmd) {
 202         case DDI_ATTACH:
 203                 break;
 204         case DDI_RESUME:
 205                 return (DDI_SUCCESS);
 206         default:
 207                 cmn_err(CE_WARN, "%s: cmd %d unsupported.\n", str, cmd);
 208                 return (DDI_FAILURE);
 209         }
 210 
 211         instance = ddi_get_instance(dip);
 212         rv = ddi_soft_state_zalloc(appm_statep, instance);
 213         if (rv != DDI_SUCCESS) {
 214                 cmn_err(CE_WARN, "%s: failed alloc for dev(%s@%s)",
 215                     str, ddi_binding_name(dip),
 216                     ddi_get_name_addr(dip) ? ddi_get_name_addr(dip) : " ");
 217                 return (rv);
 218         }
 219 
 220         if ((unitp = ddi_get_soft_state(appm_statep, instance)) == NULL) {
 221                 rv = DDI_FAILURE;
 222                 goto doerrs;
 223         }
 224 
 225         /*
 226          * Export "ddi-kernel-ioctl" property - prepared to support
 227          * kernel ioctls (driver layering).
 228          * XXX is this still needed?
 229          * XXXX (RSF) Not that I am aware of.
 230          */
 231         rv = ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP,
 232             DDI_KERNEL_IOCTL, NULL, 0);
 233         if (rv != DDI_PROP_SUCCESS)
 234                 goto doerrs;
 235 
 236         ddi_report_dev(dip);
 237         unitp->dip = dip;
 238 
 239         /*
 240          * XXX here we would do whatever we need to to determine if the
 241          * XXX platform supports ACPI, and fail the attach if not.
 242          * XXX If it does, we do whatever setup is needed to get access to
 243          * XXX ACPI register space.
 244          */
 245 
 246         unitp->lyropen = 0;
 247 
 248         /*
 249          * create minor node for kernel_ioctl calls
 250          */
 251         rv = ddi_create_minor_node(dip, "acpi-ppm", S_IFCHR, instance, 0, 0);
 252         if (rv != DDI_SUCCESS)
 253                 goto doerrs;
 254 
 255         /* Get the FADT */
 256         if (AcpiGetTable(ACPI_SIG_FADT, 1,
 257             (ACPI_TABLE_HEADER **)&fadt) != AE_OK)
 258                 return (rv);
 259 
 260         /* Init the RTC offsets */
 261         if (fadt != NULL)
 262                 pc_tod_set_rtc_offsets(fadt);
 263 
 264         return (rv);
 265 
 266 doerrs:
 267 
 268         if (ddi_prop_exists(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS |
 269             DDI_PROP_NOTPROM, DDI_KERNEL_IOCTL))
 270                 ddi_prop_remove_all(dip);
 271 
 272         ddi_soft_state_free(appm_statep, instance);
 273 
 274         return (rv);
 275 }
 276 
 277 
 278 /*
 279  * Driver getinfo(9e) entry routine
 280  */
 281 /* ARGSUSED */
 282 static int
 283 appm_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
 284 {
 285         appm_unit       *unitp;
 286         int             instance;
 287 
 288         switch (cmd) {
 289         case DDI_INFO_DEVT2DEVINFO:
 290                 instance = getminor((dev_t)arg);
 291                 unitp = ddi_get_soft_state(appm_statep, instance);
 292                 if (unitp == NULL) {
 293                         return (DDI_FAILURE);
 294                 }
 295                 *result = (void *) unitp->dip;
 296                 return (DDI_SUCCESS);
 297 
 298         case DDI_INFO_DEVT2INSTANCE:
 299                 instance = getminor((dev_t)arg);
 300                 *result = (void *)(uintptr_t)instance;
 301                 return (DDI_SUCCESS);
 302 
 303         default:
 304                 return (DDI_FAILURE);
 305         }
 306 }
 307 
 308 
 309 /*
 310  * detach(9e)
 311  */
 312 /* ARGSUSED */
 313 static int
 314 appm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
 315 {
 316         char *str = "appm_detach";
 317 
 318         switch (cmd) {
 319         case DDI_DETACH:
 320                 return (DDI_FAILURE);
 321         case DDI_SUSPEND:
 322                 return (DDI_SUCCESS);
 323         default:
 324                 cmn_err(CE_WARN, "%s: cmd %d unsupported", str, cmd);
 325                 return (DDI_FAILURE);
 326         }
 327 }
 328 
 329 
 330 /* ARGSUSED */
 331 static int
 332 appm_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p)
 333 {
 334         appm_unit       *unitp;
 335 
 336         /* not intended to allow sysadmin level root process to open it */
 337         if (drv_priv(cred_p) != DDI_SUCCESS)
 338                 return (EPERM);
 339 
 340         if ((unitp = ddi_get_soft_state(
 341             appm_statep, getminor(*dev_p))) == NULL) {
 342                 cmn_err(CE_WARN, "appm_open: failed to get soft state!");
 343                 return (DDI_FAILURE);
 344         }
 345 
 346         mutex_enter(&appm_lock);
 347         if (unitp->lyropen != 0) {
 348                 mutex_exit(&appm_lock);
 349                 return (EBUSY);
 350         }
 351         unitp->lyropen++;
 352         mutex_exit(&appm_lock);
 353 
 354         return (DDI_SUCCESS);
 355 }
 356 
 357 
 358 /* ARGSUSED */
 359 static int
 360 appm_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
 361 {
 362         appm_unit       *unitp;
 363 
 364         if ((unitp =
 365             ddi_get_soft_state(appm_statep, getminor(dev))) == NULL)
 366                 return (DDI_FAILURE);
 367 
 368         mutex_enter(&appm_lock);
 369         unitp->lyropen = 0;
 370         mutex_exit(&appm_lock);
 371 
 372         return (DDI_SUCCESS);
 373 }
 374 
 375 
 376 /*
 377  * must match ppm.conf
 378  */
 379 #define APPMIOC                 ('A' << 8)
 380 #define APPMIOC_ENTER_S3        (APPMIOC | 1)   /* arg *s3a_t */
 381 #define APPMIOC_EXIT_S3         (APPMIOC | 2)   /* arg *s3a_t */
 382 
 383 /* ARGSUSED3 */
 384 static int
 385 appm_ioctl(dev_t dev, int cmd, intptr_t arg, int flag,
 386     cred_t *cred_p, int *rval_p)
 387 {
 388         static boolean_t        acpi_initted = B_FALSE;
 389         char                    *str = "appm_ioctl";
 390         int                     ret;
 391         s3a_t                   *s3ap = (s3a_t *)arg;
 392 
 393         PMD(PMD_SX, ("%s: called with %x\n", str, cmd))
 394 
 395         if (drv_priv(cred_p) != 0) {
 396                 PMD(PMD_SX, ("%s: EPERM\n", str))
 397                 return (EPERM);
 398         }
 399 
 400         if (ddi_get_soft_state(appm_statep, getminor(dev)) == NULL) {
 401                 PMD(PMD_SX, ("%s: no soft state: EIO\n", str))
 402                 return (EIO);
 403         }
 404 
 405         if (!acpi_initted) {
 406                 PMD(PMD_SX, ("%s: !acpi_initted\n", str))
 407                 if (acpica_init() == 0) {
 408                         acpi_initted = B_TRUE;
 409                 } else {
 410                         if (rval_p != NULL) {
 411                                 *rval_p = EINVAL;
 412                         }
 413                         PMD(PMD_SX, ("%s: EINVAL\n", str))
 414                         return (EINVAL);
 415                 }
 416         }
 417 
 418         PMD(PMD_SX, ("%s: looking for cmd %x\n", str, cmd))
 419         switch (cmd) {
 420         case APPMIOC_ENTER_S3:
 421                 /*
 422                  * suspend to RAM (ie S3)
 423                  */
 424                 PMD(PMD_SX, ("%s: cmd %x, arg %p\n", str, cmd, (void *)arg))
 425                 acpica_use_safe_delay = 1;
 426                 ret = acpi_enter_sleepstate(s3ap);
 427                 break;
 428 
 429         case APPMIOC_EXIT_S3:
 430                 /*
 431                  * return from S3
 432                  */
 433                 PMD(PMD_SX, ("%s: cmd %x, arg %p\n", str, cmd, (void *)arg))
 434                 ret = acpi_exit_sleepstate(s3ap);
 435                 acpica_use_safe_delay = 0;
 436                 break;
 437 
 438         default:
 439                 PMD(PMD_SX, ("%s: cmd %x unrecognized: ENOTTY\n", str, cmd))
 440                 return (ENOTTY);
 441         }
 442 
 443         /*
 444          * upon failure return EINVAL
 445          */
 446         if (ret != 0) {
 447                 if (rval_p != NULL) {
 448                         *rval_p = EINVAL;
 449                 }
 450                 return (EINVAL);
 451         }
 452 
 453         return (0);
 454 }