xref: /illumos-gate/usr/src/uts/sun4u/io/ppm_xgsubr.c (revision 5cff782560a1c3cf913ba5574a5123a299f3315e)
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