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 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 29 /* 30 * common code for ppm drivers 31 */ 32 #include <sys/modctl.h> 33 #include <sys/ddi.h> 34 #include <sys/sunddi.h> 35 #include <sys/ddi_impldefs.h> 36 #include <sys/ppmvar.h> 37 #include <sys/ppmio.h> 38 #include <sys/epm.h> 39 #include <sys/open.h> 40 #include <sys/file.h> 41 #include <sys/policy.h> 42 43 44 #ifdef DEBUG 45 uint_t ppm_debug = 0; 46 #endif 47 48 int ppm_inst = -1; 49 char *ppm_prefix; 50 void *ppm_statep; 51 52 53 /* 54 * common module _init 55 */ 56 int 57 ppm_init(struct modlinkage *mlp, size_t size, char *prefix) 58 { 59 #ifdef DEBUG 60 char *str = "ppm_init"; 61 #endif 62 int error; 63 64 ppm_prefix = prefix; 65 66 error = ddi_soft_state_init(&ppm_statep, size, 1); 67 DPRINTF(D_INIT, ("%s: ss init %d\n", str, error)); 68 if (error != DDI_SUCCESS) 69 return (error); 70 71 if (error = mod_install(mlp)) 72 ddi_soft_state_fini(&ppm_statep); 73 DPRINTF(D_INIT, ("%s: mod_install %d\n", str, error)); 74 75 return (error); 76 } 77 78 79 /* ARGSUSED */ 80 int 81 ppm_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp) 82 { 83 struct ppm_unit *overlay; 84 int rval; 85 86 if (ppm_inst == -1) 87 return (DDI_FAILURE); 88 89 switch (cmd) { 90 case DDI_INFO_DEVT2DEVINFO: 91 if (overlay = ddi_get_soft_state(ppm_statep, ppm_inst)) { 92 *resultp = overlay->dip; 93 rval = DDI_SUCCESS; 94 } else 95 rval = DDI_FAILURE; 96 return (rval); 97 98 case DDI_INFO_DEVT2INSTANCE: 99 *resultp = (void *)(uintptr_t)ppm_inst; 100 return (DDI_SUCCESS); 101 102 default: 103 return (DDI_FAILURE); 104 } 105 } 106 107 108 /* ARGSUSED */ 109 int 110 ppm_open(dev_t *devp, int flag, int otyp, cred_t *cred_p) 111 { 112 if (otyp != OTYP_CHR) 113 return (EINVAL); 114 DPRINTF(D_OPEN, ("ppm_open: \"%s\", devp 0x%p, flag 0x%x, otyp %d\n", 115 ppm_prefix, devp, flag, otyp)); 116 return (0); 117 } 118 119 120 /* ARGSUSED */ 121 int 122 ppm_close(dev_t dev, int flag, int otyp, cred_t *credp) 123 { 124 DPRINTF(D_CLOSE, ("ppm_close: \"%s\", dev 0x%lx, flag 0x%x, otyp %d\n", 125 ppm_prefix, dev, flag, otyp)); 126 return (DDI_SUCCESS); 127 } 128 129 130 /* 131 * lookup arrays of strings from configuration data (XXppm.conf) 132 */ 133 static int 134 ppm_get_confdata(struct ppm_cdata **cdp, dev_info_t *dip) 135 { 136 struct ppm_cdata *cinfo; 137 int err; 138 139 for (; (cinfo = *cdp) != NULL; cdp++) { 140 err = ddi_prop_lookup_string_array( 141 DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, 142 cinfo->name, &cinfo->strings, &cinfo->cnt); 143 if (err != DDI_PROP_SUCCESS) { 144 DPRINTF(D_ERROR, 145 ("ppm_get_confdata: no %s found\n", cinfo->name)); 146 break; 147 } 148 } 149 return (err); 150 } 151 152 153 /* 154 * free allocated ddi prop strings, and free 155 * ppm_db_t lists where there's an error. 156 */ 157 static int 158 ppm_attach_err(struct ppm_cdata **cdp, int err) 159 { 160 ppm_domain_t **dompp; 161 ppm_db_t *db, *tmp; 162 163 if (cdp) { 164 for (; *cdp; cdp++) { 165 if ((*cdp)->strings) { 166 ddi_prop_free((*cdp)->strings); 167 (*cdp)->strings = NULL; 168 } 169 } 170 } 171 172 if (err != DDI_SUCCESS) { 173 for (dompp = ppm_domains; *dompp; dompp++) { 174 for (db = (*dompp)->conflist; (tmp = db) != NULL; ) { 175 db = db->next; 176 kmem_free(tmp->name, strlen(tmp->name) + 1); 177 kmem_free(tmp, sizeof (*tmp)); 178 } 179 (*dompp)->conflist = NULL; 180 } 181 err = DDI_FAILURE; 182 } 183 184 return (err); 185 } 186 187 188 ppm_domain_t * 189 ppm_lookup_domain(char *dname) 190 { 191 ppm_domain_t **dompp; 192 193 for (dompp = ppm_domains; *dompp; dompp++) 194 if (strcmp(dname, (*dompp)->name) == 0) 195 break; 196 return (*dompp); 197 } 198 199 200 /* 201 * create a ppm-private database from parsed .conf data; we start with 202 * two string arrays (device pathnames and domain names) and treat them 203 * as matched pairs where device[N] is part of domain[N] 204 */ 205 int 206 ppm_create_db(dev_info_t *dip) 207 { 208 #ifdef DEBUG 209 char *str = "ppm_create_db"; 210 #endif 211 struct ppm_cdata devdata, domdata, *cdata[3]; 212 ppm_domain_t *domp; 213 ppm_db_t *new; 214 char **dev_namep, **dom_namep; 215 char *wild; 216 int err; 217 218 bzero(&devdata, sizeof (devdata)); 219 bzero(&domdata, sizeof (domdata)); 220 devdata.name = "ppm-devices"; 221 domdata.name = "ppm-domains"; 222 cdata[0] = &devdata; 223 cdata[1] = &domdata; 224 cdata[2] = NULL; 225 if (err = ppm_get_confdata(cdata, dip)) 226 return (ppm_attach_err(cdata, err)); 227 else if (devdata.cnt != domdata.cnt) { 228 DPRINTF(D_ERROR, 229 ("%s: %sppm.conf has a mismatched number of %s and %s\n", 230 str, ppm_prefix, devdata.name, domdata.name)); 231 return (ppm_attach_err(cdata, DDI_FAILURE)); 232 } 233 234 /* 235 * loop through device/domain pairs and build 236 * a linked list of devices within known domains 237 */ 238 for (dev_namep = devdata.strings, dom_namep = domdata.strings; 239 *dev_namep; dev_namep++, dom_namep++) { 240 domp = ppm_lookup_domain(*dom_namep); 241 if (domp == NULL) { 242 DPRINTF(D_ERROR, ("%s: invalid domain \"%s\" for " 243 "device \"%s\"\n", str, *dom_namep, *dev_namep)); 244 return (ppm_attach_err(cdata, DDI_FAILURE)); 245 } 246 247 /* 248 * allocate a new ppm db entry and link it to 249 * the front of conflist within this domain 250 */ 251 new = kmem_zalloc(sizeof (*new), KM_SLEEP); 252 new->name = kmem_zalloc(strlen(*dev_namep) + 1, KM_SLEEP); 253 (void) strcpy(new->name, *dev_namep); 254 new->next = domp->conflist; 255 domp->conflist = new; 256 257 /* 258 * when the device name contains a wildcard, 259 * save the length of the preceding string 260 */ 261 if (wild = strchr(new->name, '*')) 262 new->plen = (wild - new->name); 263 DPRINTF(D_CREATEDB, ("%s: \"%s\", added \"%s\"\n", 264 str, domp->name, new->name)); 265 } 266 267 return (ppm_attach_err(cdata, DDI_SUCCESS)); 268 } 269 270 271 /* 272 * scan conf devices within each domain for a matching device name 273 */ 274 ppm_domain_t * 275 ppm_lookup_dev(dev_info_t *dip) 276 { 277 char path[MAXNAMELEN]; 278 ppm_domain_t **dompp; 279 ppm_db_t *dbp; 280 281 (void) ddi_pathname(dip, path); 282 for (dompp = ppm_domains; *dompp; dompp++) { 283 for (dbp = (*dompp)->conflist; dbp; dbp = dbp->next) { 284 if (dbp->plen == 0) { 285 if (strcmp(path, dbp->name) == 0) 286 return (*dompp); 287 } else if (strncmp(path, dbp->name, dbp->plen) == 0) 288 return (*dompp); 289 } 290 } 291 292 return (NULL); 293 } 294 295 296 /* 297 * returns 1 (claimed), 0 (not claimed) 298 */ 299 int 300 ppm_claim_dev(dev_info_t *dip) 301 { 302 ppm_domain_t *domp; 303 304 domp = ppm_lookup_dev(dip); 305 306 #ifdef DEBUG 307 if (domp) { 308 char path[MAXNAMELEN]; 309 DPRINTF(D_CLAIMDEV, 310 ("ppm_claim_dev: \"%s\", matched \"%s\"\n", 311 domp->name, ddi_pathname(dip, path))); 312 } 313 314 #endif 315 316 return (domp != NULL); 317 } 318 319 320 /* 321 * create/init a new ppm device and link into the domain 322 */ 323 ppm_dev_t * 324 ppm_add_dev(dev_info_t *dip, ppm_domain_t *domp) 325 { 326 char path[MAXNAMELEN]; 327 ppm_dev_t *new = NULL; 328 int cmpt; 329 330 ASSERT(MUTEX_HELD(&domp->lock)); 331 (void) ddi_pathname(dip, path); 332 /* 333 * For devs which have exported "pm-components" we want to create 334 * a data structure for each component. When a driver chooses not 335 * to export the prop we treat its device as having a single 336 * component and build a structure for it anyway. All other ppm 337 * logic will act as if this device were always up and can thus 338 * make correct decisions about it in relation to other devices 339 * in its domain. 340 */ 341 for (cmpt = PM_GET_PM_INFO(dip) ? PM_NUMCMPTS(dip) : 1; cmpt--; ) { 342 new = kmem_zalloc(sizeof (*new), KM_SLEEP); 343 new->path = kmem_zalloc(strlen(path) + 1, KM_SLEEP); 344 (void) strcpy(new->path, path); 345 new->domp = domp; 346 new->dip = dip; 347 new->cmpt = cmpt; 348 if (ppmf.dev_init) 349 (*ppmf.dev_init)(new); 350 new->next = domp->devlist; 351 domp->devlist = new; 352 DPRINTF(D_ADDDEV, 353 ("ppm_add_dev: \"%s\", \"%s\", ppm_dev 0x%p\n", 354 new->path, domp->name, new)); 355 } 356 357 ASSERT(new != NULL); 358 /* 359 * devi_pm_ppm_private should be set only after all 360 * ppm_dev s related to all components have been 361 * initialized and domain's pwr_cnt is incremented 362 * for each of them. 363 */ 364 PPM_SET_PRIVATE(dip, new); 365 366 return (new); 367 } 368 369 370 /* 371 * returns an existing or newly created ppm device reference 372 */ 373 ppm_dev_t * 374 ppm_get_dev(dev_info_t *dip, ppm_domain_t *domp) 375 { 376 ppm_dev_t *pdp; 377 378 mutex_enter(&domp->lock); 379 pdp = PPM_GET_PRIVATE(dip); 380 if (pdp == NULL) 381 pdp = ppm_add_dev(dip, domp); 382 mutex_exit(&domp->lock); 383 384 return (pdp); 385 } 386 387 388 /* 389 * scan a domain's device list and remove those with .dip 390 * matching the arg *dip; we need to scan the entire list 391 * for the case of devices with multiple components 392 */ 393 void 394 ppm_rem_dev(dev_info_t *dip) 395 { 396 ppm_dev_t *pdp, **devpp; 397 ppm_domain_t *domp; 398 399 pdp = PPM_GET_PRIVATE(dip); 400 ASSERT(pdp); 401 domp = pdp->domp; 402 ASSERT(domp); 403 404 mutex_enter(&domp->lock); 405 for (devpp = &domp->devlist; (pdp = *devpp) != NULL; ) { 406 if (pdp->dip != dip) { 407 devpp = &pdp->next; 408 continue; 409 } 410 411 DPRINTF(D_REMDEV, ("ppm_rem_dev: path \"%s\", ppm_dev 0x%p\n", 412 pdp->path, pdp)); 413 414 PPM_SET_PRIVATE(dip, NULL); 415 *devpp = pdp->next; 416 if (ppmf.dev_fini) 417 (*ppmf.dev_fini)(pdp); 418 kmem_free(pdp->path, strlen(pdp->path) + 1); 419 kmem_free(pdp, sizeof (*pdp)); 420 } 421 mutex_exit(&domp->lock); 422 } 423 424 425 /* ARGSUSED */ 426 int 427 ppm_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, 428 cred_t *cred_p, int *rval_p) 429 { 430 #ifdef DEBUG 431 char *str = "ppm_ioctl"; 432 char *rwfmt = "%s: mode error: 0x%x is missing %s perm, cmd 0x%x\n"; 433 char *iofmt = "%s: copy%s error, arg 0x%p\n"; 434 #endif 435 ppmreq_t req; 436 uint8_t level; 437 438 DPRINTF(D_IOCTL, ("%s: dev 0x%lx, cmd 0x%x, arg 0x%lx, mode 0x%x\n", 439 str, dev, cmd, arg, mode)); 440 441 if (ddi_copyin((caddr_t)arg, &req, sizeof (req), mode)) { 442 DPRINTF(D_IOCTL, (iofmt, str, "in", arg)); 443 return (EFAULT); 444 } 445 446 /* 447 * Currently, only PPM_INTERNAL_DEVICE_POWER device type is supported 448 */ 449 if (req.ppmdev != PPM_INTERNAL_DEVICE_POWER) { 450 DPRINTF(D_IOCTL, ("%s: unrecognized device type %d\n", 451 str, req.ppmdev)); 452 return (EINVAL); 453 } 454 455 switch (cmd) { 456 case PPMIOCSET: 457 if (secpolicy_power_mgmt(cred_p) != 0) { 458 DPRINTF(D_IOCTL, ("%s: bad cred for cmd 0x%x\n", 459 str, cmd)); 460 return (EPERM); 461 } else if (!(mode & FWRITE)) { 462 DPRINTF(D_IOCTL, (rwfmt, str, mode, "write")); 463 return (EPERM); 464 } 465 466 level = req.ppmop.idev_power.level; 467 if ((level != PPM_IDEV_POWER_ON) && 468 (level != PPM_IDEV_POWER_OFF)) { 469 DPRINTF(D_IOCTL, 470 ("%s: invalid power level %d, cmd 0x%x\n", 471 str, level, cmd)); 472 return (EINVAL); 473 } 474 if (ppmf.iocset == NULL) 475 return (ENOTSUP); 476 (*ppmf.iocset)(level); 477 break; 478 479 case PPMIOCGET: 480 if (!(mode & FREAD)) { 481 DPRINTF(D_IOCTL, (rwfmt, str, mode, "read")); 482 return (EPERM); 483 } 484 485 if (ppmf.iocget == NULL) 486 return (ENOTSUP); 487 req.ppmop.idev_power.level = (*ppmf.iocget)(); 488 if (ddi_copyout((const void *)&req, (void *)arg, 489 sizeof (req), mode)) { 490 DPRINTF(D_ERROR, (iofmt, str, "out", arg)); 491 return (EFAULT); 492 } 493 break; 494 495 default: 496 DPRINTF(D_IOCTL, ("%s: unrecognized cmd 0x%x\n", str, cmd)); 497 return (EINVAL); 498 } 499 500 return (0); 501 } 502 503 504 #ifdef DEBUG 505 #define FLINTSTR(flags, sym) { flags, sym, #sym } 506 #define PMR_UNKNOWN -1 507 /* 508 * convert a ctlop integer to a char string. this helps printing 509 * meaningful info when cltops are received from the pm framework. 510 * since some ctlops are so frequent, we use mask to limit output: 511 * a valid string is returned when ctlop is found and when 512 * (cmd.flags & mask) is true; otherwise NULL is returned. 513 */ 514 char * 515 ppm_get_ctlstr(int ctlop, uint_t mask) 516 { 517 struct ctlop_cmd { 518 uint_t flags; 519 int ctlop; 520 char *str; 521 }; 522 523 struct ctlop_cmd *ccp; 524 static struct ctlop_cmd cmds[] = { 525 FLINTSTR(D_SETPWR, PMR_SET_POWER), 526 FLINTSTR(D_CTLOPS2, PMR_SUSPEND), 527 FLINTSTR(D_CTLOPS2, PMR_RESUME), 528 FLINTSTR(D_CTLOPS2, PMR_PRE_SET_POWER), 529 FLINTSTR(D_CTLOPS2, PMR_POST_SET_POWER), 530 FLINTSTR(D_CTLOPS2, PMR_PPM_SET_POWER), 531 FLINTSTR(0, PMR_PPM_ATTACH), 532 FLINTSTR(0, PMR_PPM_DETACH), 533 FLINTSTR(D_CTLOPS1, PMR_PPM_POWER_CHANGE_NOTIFY), 534 FLINTSTR(D_CTLOPS1, PMR_REPORT_PMCAP), 535 FLINTSTR(D_CTLOPS1, PMR_CHANGED_POWER), 536 FLINTSTR(D_CTLOPS2, PMR_PPM_PRE_PROBE), 537 FLINTSTR(D_CTLOPS2, PMR_PPM_POST_PROBE), 538 FLINTSTR(D_CTLOPS2, PMR_PPM_PRE_ATTACH), 539 FLINTSTR(D_CTLOPS2, PMR_PPM_POST_ATTACH), 540 FLINTSTR(D_CTLOPS2, PMR_PPM_PRE_DETACH), 541 FLINTSTR(D_CTLOPS2, PMR_PPM_POST_DETACH), 542 FLINTSTR(D_CTLOPS1, PMR_PPM_UNMANAGE), 543 FLINTSTR(D_CTLOPS2, PMR_PPM_PRE_RESUME), 544 FLINTSTR(D_CTLOPS1, PMR_PPM_ALL_LOWEST), 545 FLINTSTR(D_LOCKS, PMR_PPM_LOCK_POWER), 546 FLINTSTR(D_LOCKS, PMR_PPM_UNLOCK_POWER), 547 FLINTSTR(D_LOCKS, PMR_PPM_TRY_LOCK_POWER), 548 FLINTSTR(D_CTLOPS1 | D_CTLOPS2, PMR_UNKNOWN), 549 }; 550 551 for (ccp = cmds; ccp->ctlop != PMR_UNKNOWN; ccp++) 552 if (ctlop == ccp->ctlop) 553 break; 554 555 if (ccp->flags & mask) 556 return (ccp->str); 557 return (NULL); 558 } 559 #endif 560