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,
 116         NULL
 117 };
 118 
 119 /*
 120  * Driver state structure
 121  */
 122 typedef struct {
 123         dev_info_t              *dip;
 124         ddi_acc_handle_t        devid_hndl;
 125         ddi_acc_handle_t        estar_hndl;
 126         int                     lyropen;                /* ref count */
 127 } appm_unit;
 128 
 129 /*
 130  * Driver global variables
 131  *
 132  * appm_lock synchronize the access of lyr handle to each appm
 133  * minor device, therefore write to tomatillo device is
 134  * sequentialized.  Lyr protocol requires pairing up lyr open
 135  * and close, so only a single reference is allowed per minor node.
 136  */
 137 static void     *appm_statep;
 138 static kmutex_t  appm_lock;
 139 
 140 /*
 141  * S3 stuff:
 142  */
 143 extern int acpi_enter_sleepstate(s3a_t *);
 144 extern int acpi_exit_sleepstate(s3a_t *);
 145 
 146 
 147 int
 148 _init(void)
 149 {
 150         int     error;
 151 
 152         if ((error = ddi_soft_state_init(&appm_statep,
 153             sizeof (appm_unit), 0)) != DDI_SUCCESS) {
 154                 return (error);
 155         }
 156 
 157         mutex_init(&appm_lock, NULL, MUTEX_DRIVER, NULL);
 158 
 159         if ((error = mod_install(&modlinkage)) != DDI_SUCCESS) {
 160                 mutex_destroy(&appm_lock);
 161                 ddi_soft_state_fini(&appm_statep);
 162                 return (error);
 163         }
 164 
 165         return (error);
 166 }
 167 
 168 int
 169 _fini(void)
 170 {
 171         int     error;
 172 
 173         if ((error = mod_remove(&modlinkage)) == DDI_SUCCESS) {
 174                 mutex_destroy(&appm_lock);
 175                 ddi_soft_state_fini(&appm_statep);
 176         }
 177 
 178         return (error);
 179 
 180 }
 181 
 182 int
 183 _info(struct modinfo *modinfop)
 184 {
 185         return (mod_info(&modlinkage, modinfop));
 186 }
 187 
 188 
 189 
 190 /*
 191  * Driver attach(9e) entry point
 192  */
 193 static int
 194 appm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
 195 {
 196         char    *str = "appm_attach";
 197         int             instance;
 198         appm_unit       *unitp;
 199         ACPI_TABLE_FADT *fadt = NULL;
 200         int             rv = DDI_SUCCESS;
 201 
 202         switch (cmd) {
 203         case DDI_ATTACH:
 204                 break;
 205         case DDI_RESUME:
 206                 return (DDI_SUCCESS);
 207         default:
 208                 cmn_err(CE_WARN, "%s: cmd %d unsupported.\n", str, cmd);
 209                 return (DDI_FAILURE);
 210         }
 211 
 212         instance = ddi_get_instance(dip);
 213         rv = ddi_soft_state_zalloc(appm_statep, instance);
 214         if (rv != DDI_SUCCESS) {
 215                 cmn_err(CE_WARN, "%s: failed alloc for dev(%s@%s)",
 216                     str, ddi_binding_name(dip),
 217                     ddi_get_name_addr(dip) ? ddi_get_name_addr(dip) : " ");
 218                 return (rv);
 219         }
 220 
 221         if ((unitp = ddi_get_soft_state(appm_statep, instance)) == NULL) {
 222                 rv = DDI_FAILURE;
 223                 goto doerrs;
 224         }
 225 
 226         /*
 227          * Export "ddi-kernel-ioctl" property - prepared to support
 228          * kernel ioctls (driver layering).
 229          * XXX is this still needed?
 230          * XXXX (RSF) Not that I am aware of.
 231          */
 232         rv = ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP,
 233             DDI_KERNEL_IOCTL, NULL, 0);
 234         if (rv != DDI_PROP_SUCCESS)
 235                 goto doerrs;
 236 
 237         ddi_report_dev(dip);
 238         unitp->dip = dip;
 239 
 240         /*
 241          * XXX here we would do whatever we need to to determine if the
 242          * XXX platform supports ACPI, and fail the attach if not.
 243          * XXX If it does, we do whatever setup is needed to get access to
 244          * XXX ACPI register space.
 245          */
 246 
 247         unitp->lyropen = 0;
 248 
 249         /*
 250          * create minor node for kernel_ioctl calls
 251          */
 252         rv = ddi_create_minor_node(dip, "acpi-ppm", S_IFCHR, instance, 0, 0);
 253         if (rv != DDI_SUCCESS)
 254                 goto doerrs;
 255 
 256         /* Get the FADT */
 257         if (AcpiGetTable(ACPI_SIG_FADT, 1,
 258             (ACPI_TABLE_HEADER **)&fadt) != AE_OK)
 259                 return (rv);
 260 
 261         /* Init the RTC offsets */
 262         if (fadt != NULL)
 263                 pc_tod_set_rtc_offsets(fadt);
 264 
 265         return (rv);
 266 
 267 doerrs:
 268 
 269         if (ddi_prop_exists(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS |
 270             DDI_PROP_NOTPROM, DDI_KERNEL_IOCTL))
 271                 ddi_prop_remove_all(dip);
 272 
 273         ddi_soft_state_free(appm_statep, instance);
 274 
 275         return (rv);
 276 }
 277 
 278 
 279 /*
 280  * Driver getinfo(9e) entry routine
 281  */
 282 /* ARGSUSED */
 283 static int
 284 appm_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
 285 {
 286         appm_unit       *unitp;
 287         int             instance;
 288 
 289         switch (cmd) {
 290         case DDI_INFO_DEVT2DEVINFO:
 291                 instance = getminor((dev_t)arg);
 292                 unitp = ddi_get_soft_state(appm_statep, instance);
 293                 if (unitp == NULL) {
 294                         return (DDI_FAILURE);
 295                 }
 296                 *result = (void *) unitp->dip;
 297                 return (DDI_SUCCESS);
 298 
 299         case DDI_INFO_DEVT2INSTANCE:
 300                 instance = getminor((dev_t)arg);
 301                 *result = (void *)(uintptr_t)instance;
 302                 return (DDI_SUCCESS);
 303 
 304         default:
 305                 return (DDI_FAILURE);
 306         }
 307 }
 308 
 309 
 310 /*
 311  * detach(9e)
 312  */
 313 /* ARGSUSED */
 314 static int
 315 appm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
 316 {
 317         char *str = "appm_detach";
 318 
 319         switch (cmd) {
 320         case DDI_DETACH:
 321                 return (DDI_FAILURE);
 322         case DDI_SUSPEND:
 323                 return (DDI_SUCCESS);
 324         default:
 325                 cmn_err(CE_WARN, "%s: cmd %d unsupported", str, cmd);
 326                 return (DDI_FAILURE);
 327         }
 328 }
 329 
 330 
 331 /* ARGSUSED */
 332 static int
 333 appm_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p)
 334 {
 335         appm_unit       *unitp;
 336 
 337         /* not intended to allow sysadmin level root process to open it */
 338         if (drv_priv(cred_p) != DDI_SUCCESS)
 339                 return (EPERM);
 340 
 341         if ((unitp = ddi_get_soft_state(
 342             appm_statep, getminor(*dev_p))) == NULL) {
 343                 cmn_err(CE_WARN, "appm_open: failed to get soft state!");
 344                 return (DDI_FAILURE);
 345         }
 346 
 347         mutex_enter(&appm_lock);
 348         if (unitp->lyropen != 0) {
 349                 mutex_exit(&appm_lock);
 350                 return (EBUSY);
 351         }
 352         unitp->lyropen++;
 353         mutex_exit(&appm_lock);
 354 
 355         return (DDI_SUCCESS);
 356 }
 357 
 358 
 359 /* ARGSUSED */
 360 static int
 361 appm_close(dev_t dev, int flag, int otyp, cred_t *cred_p)
 362 {
 363         appm_unit       *unitp;
 364 
 365         if ((unitp =
 366             ddi_get_soft_state(appm_statep, getminor(dev))) == NULL)
 367                 return (DDI_FAILURE);
 368 
 369         mutex_enter(&appm_lock);
 370         unitp->lyropen = 0;
 371         mutex_exit(&appm_lock);
 372 
 373         return (DDI_SUCCESS);
 374 }
 375 
 376 
 377 /*
 378  * must match ppm.conf
 379  */
 380 #define APPMIOC                 ('A' << 8)
 381 #define APPMIOC_ENTER_S3        (APPMIOC | 1)   /* arg *s3a_t */
 382 #define APPMIOC_EXIT_S3         (APPMIOC | 2)   /* arg *s3a_t */
 383 
 384 /* ARGSUSED3 */
 385 static int
 386 appm_ioctl(dev_t dev, int cmd, intptr_t arg, int flag,
 387     cred_t *cred_p, int *rval_p)
 388 {
 389         static boolean_t        acpi_initted = B_FALSE;
 390         char                    *str = "appm_ioctl";
 391         int                     ret;
 392         s3a_t                   *s3ap = (s3a_t *)arg;
 393 
 394         PMD(PMD_SX, ("%s: called with %x\n", str, cmd))
 395 
 396         if (drv_priv(cred_p) != 0) {
 397                 PMD(PMD_SX, ("%s: EPERM\n", str))
 398                 return (EPERM);
 399         }
 400 
 401         if (ddi_get_soft_state(appm_statep, getminor(dev)) == NULL) {
 402                 PMD(PMD_SX, ("%s: no soft state: EIO\n", str))
 403                 return (EIO);
 404         }
 405 
 406         if (!acpi_initted) {
 407                 PMD(PMD_SX, ("%s: !acpi_initted\n", str))
 408                 if (acpica_init() == 0) {
 409                         acpi_initted = B_TRUE;
 410                 } else {
 411                         if (rval_p != NULL) {
 412                                 *rval_p = EINVAL;
 413                         }
 414                         PMD(PMD_SX, ("%s: EINVAL\n", str))
 415                         return (EINVAL);
 416                 }
 417         }
 418 
 419         PMD(PMD_SX, ("%s: looking for cmd %x\n", str, cmd))
 420         switch (cmd) {
 421         case APPMIOC_ENTER_S3:
 422                 /*
 423                  * suspend to RAM (ie S3)
 424                  */
 425                 PMD(PMD_SX, ("%s: cmd %x, arg %p\n", str, cmd, (void *)arg))
 426                 acpica_use_safe_delay = 1;
 427                 ret = acpi_enter_sleepstate(s3ap);
 428                 break;
 429 
 430         case APPMIOC_EXIT_S3:
 431                 /*
 432                  * return from S3
 433                  */
 434                 PMD(PMD_SX, ("%s: cmd %x, arg %p\n", str, cmd, (void *)arg))
 435                 ret = acpi_exit_sleepstate(s3ap);
 436                 acpica_use_safe_delay = 0;
 437                 break;
 438 
 439         default:
 440                 PMD(PMD_SX, ("%s: cmd %x unrecognized: ENOTTY\n", str, cmd))
 441                 return (ENOTTY);
 442         }
 443 
 444         /*
 445          * upon failure return EINVAL
 446          */
 447         if (ret != 0) {
 448                 if (rval_p != NULL) {
 449                         *rval_p = EINVAL;
 450                 }
 451                 return (EINVAL);
 452         }
 453 
 454         return (0);
 455 }