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 */ 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(FADT_DESCRIPTOR *); 54 55 /* 56 * Configuration Function prototypes and data structures 57 */ 58 static int appm_attach(dev_info_t *, ddi_attach_cmd_t); 59 static int appm_detach(dev_info_t *, ddi_detach_cmd_t); 60 static int appm_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); 61 static int appm_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p); 62 static int appm_close(dev_t dev, int flag, int otyp, cred_t *cred_p); 63 static int appm_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); 64 65 /* 66 * Configuration data structures 67 */ 68 static struct cb_ops appm_cbops = { 69 appm_open, /* open */ 70 appm_close, /* close */ 71 nodev, /* strategy */ 72 nodev, /* print */ 73 nodev, /* dump */ 74 nodev, /* read */ 75 nodev, /* write */ 76 appm_ioctl, /* ioctl */ 77 nodev, /* devmap */ 78 nodev, /* mmap */ 79 nodev, /* segmap */ 80 nochpoll, /* chpoll */ 81 ddi_prop_op, /* prop_op */ 82 NULL, /* stream */ 83 D_MP | D_NEW, /* flag */ 84 CB_REV, /* rev */ 85 nodev, /* aread */ 86 nodev, /* awrite */ 87 }; 88 89 static struct dev_ops appm_ops = { 90 DEVO_REV, /* devo_rev */ 91 0, /* refcnt */ 92 appm_getinfo, /* getinfo */ 93 nulldev, /* identify */ 94 nulldev, /* probe */ 95 appm_attach, /* attach */ 96 appm_detach, /* detach */ 97 nodev, /* reset */ 98 &appm_cbops, /* cb_ops */ 99 NULL, /* bus_ops */ 100 NULL, /* power */ 101 ddi_quiesce_not_needed, /* quiesce */ 102 }; 103 104 extern struct mod_ops mod_driverops; 105 106 static struct modldrv modldrv = { 107 &mod_driverops, 108 "ACPI ppm driver", 109 &appm_ops, 110 }; 111 112 static struct modlinkage modlinkage = { 113 MODREV_1, 114 &modldrv, 115 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 char _depends_on[] = "misc/acpica"; 143 144 extern int acpi_enter_sleepstate(s3a_t *); 145 extern int acpi_exit_sleepstate(s3a_t *); 146 147 148 int 149 _init(void) 150 { 151 int error; 152 153 if ((error = ddi_soft_state_init(&appm_statep, 154 sizeof (appm_unit), 0)) != DDI_SUCCESS) { 155 return (error); 156 } 157 158 mutex_init(&appm_lock, NULL, MUTEX_DRIVER, NULL); 159 160 if ((error = mod_install(&modlinkage)) != DDI_SUCCESS) { 161 mutex_destroy(&appm_lock); 162 ddi_soft_state_fini(&appm_statep); 163 return (error); 164 } 165 166 return (error); 167 } 168 169 int 170 _fini(void) 171 { 172 int error; 173 174 if ((error = mod_remove(&modlinkage)) == DDI_SUCCESS) { 175 mutex_destroy(&appm_lock); 176 ddi_soft_state_fini(&appm_statep); 177 } 178 179 return (error); 180 181 } 182 183 int 184 _info(struct modinfo *modinfop) 185 { 186 return (mod_info(&modlinkage, modinfop)); 187 } 188 189 190 191 /* 192 * Driver attach(9e) entry point 193 */ 194 static int 195 appm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) 196 { 197 char *str = "appm_attach"; 198 int instance; 199 appm_unit *unitp; 200 FADT_DESCRIPTOR *fadt = NULL; 201 int rv = DDI_SUCCESS; 202 203 switch (cmd) { 204 case DDI_ATTACH: 205 break; 206 case DDI_RESUME: 207 return (DDI_SUCCESS); 208 default: 209 cmn_err(CE_WARN, "%s: cmd %d unsupported.\n", str, cmd); 210 return (DDI_FAILURE); 211 } 212 213 instance = ddi_get_instance(dip); 214 rv = ddi_soft_state_zalloc(appm_statep, instance); 215 if (rv != DDI_SUCCESS) { 216 cmn_err(CE_WARN, "%s: failed alloc for dev(%s@%s)", 217 str, ddi_binding_name(dip), 218 ddi_get_name_addr(dip) ? ddi_get_name_addr(dip) : " "); 219 return (rv); 220 } 221 222 if ((unitp = ddi_get_soft_state(appm_statep, instance)) == NULL) { 223 rv = DDI_FAILURE; 224 goto doerrs; 225 } 226 227 /* 228 * Export "ddi-kernel-ioctl" property - prepared to support 229 * kernel ioctls (driver layering). 230 * XXX is this still needed? 231 * XXXX (RSF) Not that I am aware of. 232 */ 233 rv = ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP, 234 DDI_KERNEL_IOCTL, NULL, 0); 235 if (rv != DDI_PROP_SUCCESS) 236 goto doerrs; 237 238 ddi_report_dev(dip); 239 unitp->dip = dip; 240 241 /* 242 * XXX here we would do whatever we need to to determine if the 243 * XXX platform supports ACPI, and fail the attach if not. 244 * XXX If it does, we do whatever setup is needed to get access to 245 * XXX ACPI register space. 246 */ 247 248 unitp->lyropen = 0; 249 250 /* 251 * create minor node for kernel_ioctl calls 252 */ 253 rv = ddi_create_minor_node(dip, "acpi-ppm", S_IFCHR, instance, 0, 0); 254 if (rv != DDI_SUCCESS) 255 goto doerrs; 256 257 /* Get the FADT */ 258 if (AcpiGetFirmwareTable(FADT_SIG, 1, ACPI_LOGICAL_ADDRESSING, 259 (ACPI_TABLE_HEADER **)&fadt) != AE_OK) 260 return (rv); 261 262 /* Init the RTC offsets */ 263 if (fadt != NULL) 264 pc_tod_set_rtc_offsets(fadt); 265 266 return (rv); 267 268 doerrs: 269 270 if (ddi_prop_exists(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS | 271 DDI_PROP_NOTPROM, DDI_KERNEL_IOCTL)) 272 ddi_prop_remove_all(dip); 273 274 ddi_soft_state_free(appm_statep, instance); 275 276 return (rv); 277 } 278 279 280 /* 281 * Driver getinfo(9e) entry routine 282 */ 283 /* ARGSUSED */ 284 static int 285 appm_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result) 286 { 287 appm_unit *unitp; 288 int instance; 289 290 switch (cmd) { 291 case DDI_INFO_DEVT2DEVINFO: 292 instance = getminor((dev_t)arg); 293 unitp = ddi_get_soft_state(appm_statep, instance); 294 if (unitp == NULL) { 295 return (DDI_FAILURE); 296 } 297 *result = (void *) unitp->dip; 298 return (DDI_SUCCESS); 299 300 case DDI_INFO_DEVT2INSTANCE: 301 instance = getminor((dev_t)arg); 302 *result = (void *)(uintptr_t)instance; 303 return (DDI_SUCCESS); 304 305 default: 306 return (DDI_FAILURE); 307 } 308 } 309 310 311 /* 312 * detach(9e) 313 */ 314 /* ARGSUSED */ 315 static int 316 appm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) 317 { 318 char *str = "appm_detach"; 319 320 switch (cmd) { 321 case DDI_DETACH: 322 return (DDI_FAILURE); 323 case DDI_SUSPEND: 324 return (DDI_SUCCESS); 325 default: 326 cmn_err(CE_WARN, "%s: cmd %d unsupported", str, cmd); 327 return (DDI_FAILURE); 328 } 329 } 330 331 332 /* ARGSUSED */ 333 static int 334 appm_open(dev_t *dev_p, int flag, int otyp, cred_t *cred_p) 335 { 336 appm_unit *unitp; 337 338 /* not intended to allow sysadmin level root process to open it */ 339 if (drv_priv(cred_p) != DDI_SUCCESS) 340 return (EPERM); 341 342 if ((unitp = ddi_get_soft_state( 343 appm_statep, getminor(*dev_p))) == NULL) { 344 cmn_err(CE_WARN, "appm_open: failed to get soft state!"); 345 return (DDI_FAILURE); 346 } 347 348 mutex_enter(&appm_lock); 349 if (unitp->lyropen != 0) { 350 mutex_exit(&appm_lock); 351 return (EBUSY); 352 } 353 unitp->lyropen++; 354 mutex_exit(&appm_lock); 355 356 return (DDI_SUCCESS); 357 } 358 359 360 /* ARGSUSED */ 361 static int 362 appm_close(dev_t dev, int flag, int otyp, cred_t *cred_p) 363 { 364 appm_unit *unitp; 365 366 if ((unitp = 367 ddi_get_soft_state(appm_statep, getminor(dev))) == NULL) 368 return (DDI_FAILURE); 369 370 mutex_enter(&appm_lock); 371 unitp->lyropen = 0; 372 mutex_exit(&appm_lock); 373 374 return (DDI_SUCCESS); 375 } 376 377 378 /* 379 * must match ppm.conf 380 */ 381 #define APPMIOC ('A' << 8) 382 #define APPMIOC_ENTER_S3 (APPMIOC | 1) /* arg *s3a_t */ 383 #define APPMIOC_EXIT_S3 (APPMIOC | 2) /* arg *s3a_t */ 384 385 /* ARGSUSED3 */ 386 static int 387 appm_ioctl(dev_t dev, int cmd, intptr_t arg, int flag, 388 cred_t *cred_p, int *rval_p) 389 { 390 static boolean_t acpi_initted = B_FALSE; 391 char *str = "appm_ioctl"; 392 int ret; 393 s3a_t *s3ap = (s3a_t *)arg; 394 395 PMD(PMD_SX, ("%s: called with %x\n", str, cmd)) 396 397 if (drv_priv(cred_p) != 0) { 398 PMD(PMD_SX, ("%s: EPERM\n", str)) 399 return (EPERM); 400 } 401 402 if (ddi_get_soft_state(appm_statep, getminor(dev)) == NULL) { 403 PMD(PMD_SX, ("%s: no soft state: EIO\n", str)) 404 return (EIO); 405 } 406 407 if (!acpi_initted) { 408 PMD(PMD_SX, ("%s: !acpi_initted\n", str)) 409 if (acpica_init() == 0) { 410 acpi_initted = B_TRUE; 411 } else { 412 if (rval_p != NULL) { 413 *rval_p = EINVAL; 414 } 415 PMD(PMD_SX, ("%s: EINVAL\n", str)) 416 return (EINVAL); 417 } 418 } 419 420 PMD(PMD_SX, ("%s: looking for cmd %x\n", str, cmd)) 421 switch (cmd) { 422 case APPMIOC_ENTER_S3: 423 /* 424 * suspend to RAM (ie S3) 425 */ 426 PMD(PMD_SX, ("%s: cmd %x, arg %p\n", str, cmd, (void *)arg)) 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 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 } 455