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
ppm_init(struct modlinkage * mlp,size_t size,char * prefix)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
ppm_getinfo(dev_info_t * dip,ddi_info_cmd_t cmd,void * arg,void ** resultp)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
ppm_open(dev_t * devp,int flag,int otyp,cred_t * cred_p)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
ppm_close(dev_t dev,int flag,int otyp,cred_t * credp)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
ppm_get_confdata(struct ppm_cdata ** cdp,dev_info_t * dip)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
ppm_attach_err(struct ppm_cdata ** cdp,int err)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 *
ppm_lookup_domain(char * dname)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
ppm_create_db(dev_info_t * dip)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 *
ppm_lookup_dev(dev_info_t * dip)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
ppm_claim_dev(dev_info_t * dip)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 *
ppm_add_dev(dev_info_t * dip,ppm_domain_t * domp)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 *
ppm_get_dev(dev_info_t * dip,ppm_domain_t * domp)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
ppm_rem_dev(dev_info_t * dip)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
ppm_ioctl(dev_t dev,int cmd,intptr_t arg,int mode,cred_t * cred_p,int * rval_p)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 *
ppm_get_ctlstr(int ctlop,uint_t mask)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