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
43uint_t	ppm_debug = 0;
44#endif
45
46int	ppm_inst = -1;
47char	*ppm_prefix;
48void	*ppm_statep;
49
50
51/*
52 * common module _init
53 */
54int
55ppm_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 */
78int
79ppm_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 */
107int
108ppm_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 */
119int
120ppm_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 */
131static int
132ppm_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 */
155static int
156ppm_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
186ppm_domain_t *
187ppm_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 */
203int
204ppm_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 */
272ppm_domain_t *
273ppm_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 */
297int
298ppm_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 */
321ppm_dev_t *
322ppm_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 */
371ppm_dev_t *
372ppm_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 */
391void
392ppm_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 */
424int
425ppm_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 */
512char *
513ppm_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