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