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