diff options
Diffstat (limited to 'usr/src/uts/sun4u/io/ppm.c')
| -rw-r--r-- | usr/src/uts/sun4u/io/ppm.c | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/usr/src/uts/sun4u/io/ppm.c b/usr/src/uts/sun4u/io/ppm.c new file mode 100644 index 0000000000..4aced98ace --- /dev/null +++ b/usr/src/uts/sun4u/io/ppm.c @@ -0,0 +1,560 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + + +/* + * common code for ppm drivers + */ +#include <sys/modctl.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/ddi_impldefs.h> +#include <sys/ppmvar.h> +#include <sys/ppmio.h> +#include <sys/epm.h> +#include <sys/open.h> +#include <sys/file.h> +#include <sys/policy.h> + + +#ifdef DEBUG +uint_t ppm_debug = 0; +#endif + +int ppm_inst = -1; +char *ppm_prefix; +void *ppm_statep; + + +/* + * common module _init + */ +int +ppm_init(struct modlinkage *mlp, size_t size, char *prefix) +{ +#ifdef DEBUG + char *str = "ppm_init"; +#endif + int error; + + ppm_prefix = prefix; + + error = ddi_soft_state_init(&ppm_statep, size, 1); + DPRINTF(D_INIT, ("%s: ss init %d\n", str, error)); + if (error != DDI_SUCCESS) + return (error); + + if (error = mod_install(mlp)) + ddi_soft_state_fini(&ppm_statep); + DPRINTF(D_INIT, ("%s: mod_install %d\n", str, error)); + + return (error); +} + + +/* ARGSUSED */ +int +ppm_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resultp) +{ + struct ppm_unit *overlay; + int rval; + + if (ppm_inst == -1) + return (DDI_FAILURE); + + switch (cmd) { + case DDI_INFO_DEVT2DEVINFO: + if (overlay = ddi_get_soft_state(ppm_statep, ppm_inst)) { + *resultp = overlay->dip; + rval = DDI_SUCCESS; + } else + rval = DDI_FAILURE; + return (rval); + + case DDI_INFO_DEVT2INSTANCE: + *resultp = (void *)ppm_inst; + return (DDI_SUCCESS); + + default: + return (DDI_FAILURE); + } +} + + +/* ARGSUSED */ +int +ppm_open(dev_t *devp, int flag, int otyp, cred_t *cred_p) +{ + if (otyp != OTYP_CHR) + return (EINVAL); + DPRINTF(D_OPEN, ("ppm_open: \"%s\", devp 0x%p, flag 0x%x, otyp %d\n", + ppm_prefix, devp, flag, otyp)); + return (0); +} + + +/* ARGSUSED */ +int +ppm_close(dev_t dev, int flag, int otyp, cred_t *credp) +{ + DPRINTF(D_CLOSE, ("ppm_close: \"%s\", dev 0x%lx, flag 0x%x, otyp %d\n", + ppm_prefix, dev, flag, otyp)); + return (DDI_SUCCESS); +} + + +/* + * lookup arrays of strings from configuration data (XXppm.conf) + */ +static int +ppm_get_confdata(struct ppm_cdata **cdp, dev_info_t *dip) +{ + struct ppm_cdata *cinfo; + int err; + + for (; (cinfo = *cdp) != NULL; cdp++) { + err = ddi_prop_lookup_string_array( + DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + cinfo->name, &cinfo->strings, &cinfo->cnt); + if (err != DDI_PROP_SUCCESS) { + DPRINTF(D_ERROR, + ("ppm_get_confdata: no %s found\n", cinfo->name)); + break; + } + } + return (err); +} + + +/* + * free allocated ddi prop strings, and free + * ppm_db_t lists where there's an error. + */ +static int +ppm_attach_err(struct ppm_cdata **cdp, int err) +{ + ppm_domain_t **dompp; + ppm_db_t *db, *tmp; + + if (cdp) { + for (; *cdp; cdp++) { + if ((*cdp)->strings) { + ddi_prop_free((*cdp)->strings); + (*cdp)->strings = NULL; + } + } + } + + if (err != DDI_SUCCESS) { + for (dompp = ppm_domains; *dompp; dompp++) { + for (db = (*dompp)->conflist; (tmp = db) != NULL; ) { + db = db->next; + kmem_free(tmp->name, strlen(tmp->name) + 1); + kmem_free(tmp, sizeof (*tmp)); + } + (*dompp)->conflist = NULL; + } + err = DDI_FAILURE; + } + + return (err); +} + + +ppm_domain_t * +ppm_lookup_domain(char *dname) +{ + ppm_domain_t **dompp; + + for (dompp = ppm_domains; *dompp; dompp++) + if (strcmp(dname, (*dompp)->name) == 0) + break; + return (*dompp); +} + + +/* + * create a ppm-private database from parsed .conf data; we start with + * two string arrays (device pathnames and domain names) and treat them + * as matched pairs where device[N] is part of domain[N] + */ +int +ppm_create_db(dev_info_t *dip) +{ +#ifdef DEBUG + char *str = "ppm_create_db"; +#endif + struct ppm_cdata devdata, domdata, *cdata[3]; + ppm_domain_t *domp; + ppm_db_t *new; + char **dev_namep, **dom_namep; + char *wild; + int err; + + bzero(&devdata, sizeof (devdata)); + bzero(&domdata, sizeof (domdata)); + devdata.name = "ppm-devices"; + domdata.name = "ppm-domains"; + cdata[0] = &devdata; + cdata[1] = &domdata; + cdata[2] = NULL; + if (err = ppm_get_confdata(cdata, dip)) + return (ppm_attach_err(cdata, err)); + else if (devdata.cnt != domdata.cnt) { + DPRINTF(D_ERROR, + ("%s: %sppm.conf has a mismatched number of %s and %s\n", + str, ppm_prefix, devdata.name, domdata.name)); + return (ppm_attach_err(cdata, DDI_FAILURE)); + } + + /* + * loop through device/domain pairs and build + * a linked list of devices within known domains + */ + for (dev_namep = devdata.strings, dom_namep = domdata.strings; + *dev_namep; dev_namep++, dom_namep++) { + domp = ppm_lookup_domain(*dom_namep); + if (domp == NULL) { + DPRINTF(D_ERROR, ("%s: invalid domain \"%s\" for " + "device \"%s\"\n", str, *dom_namep, *dev_namep)); + return (ppm_attach_err(cdata, DDI_FAILURE)); + } + + /* + * allocate a new ppm db entry and link it to + * the front of conflist within this domain + */ + new = kmem_zalloc(sizeof (*new), KM_SLEEP); + new->name = kmem_zalloc(strlen(*dev_namep) + 1, KM_SLEEP); + (void) strcpy(new->name, *dev_namep); + new->next = domp->conflist; + domp->conflist = new; + + /* + * when the device name contains a wildcard, + * save the length of the preceding string + */ + if (wild = strchr(new->name, '*')) + new->plen = (wild - new->name); + DPRINTF(D_CREATEDB, ("%s: \"%s\", added \"%s\"\n", + str, domp->name, new->name)); + } + + return (ppm_attach_err(cdata, DDI_SUCCESS)); +} + + +/* + * scan conf devices within each domain for a matching device name + */ +ppm_domain_t * +ppm_lookup_dev(dev_info_t *dip) +{ + char path[MAXNAMELEN]; + ppm_domain_t **dompp; + ppm_db_t *dbp; + + (void) ddi_pathname(dip, path); + for (dompp = ppm_domains; *dompp; dompp++) { + for (dbp = (*dompp)->conflist; dbp; dbp = dbp->next) { + if (dbp->plen == 0) { + if (strcmp(path, dbp->name) == 0) + return (*dompp); + } else if (strncmp(path, dbp->name, dbp->plen) == 0) + return (*dompp); + } + } + + return (NULL); +} + + +/* + * returns 1 (claimed), 0 (not claimed) + */ +int +ppm_claim_dev(dev_info_t *dip) +{ + ppm_domain_t *domp; + + domp = ppm_lookup_dev(dip); + +#ifdef DEBUG + if (domp) { + char path[MAXNAMELEN]; + DPRINTF(D_CLAIMDEV, + ("ppm_claim_dev: \"%s\", matched \"%s\"\n", + domp->name, ddi_pathname(dip, path))); + } + +#endif + + return (domp != NULL); +} + + +/* + * create/init a new ppm device and link into the domain + */ +static ppm_dev_t * +ppm_add_dev(dev_info_t *dip, ppm_domain_t *domp) +{ + char path[MAXNAMELEN]; + ppm_dev_t *new = NULL; + int cmpt; + + ASSERT(MUTEX_HELD(&domp->lock)); + (void) ddi_pathname(dip, path); + /* + * For devs which have exported "pm-components" we want to create + * a data structure for each component. When a driver chooses not + * to export the prop we treat its device as having a single + * component and build a structure for it anyway. All other ppm + * logic will act as if this device were always up and can thus + * make correct decisions about it in relation to other devices + * in its domain. + */ + for (cmpt = PM_GET_PM_INFO(dip) ? PM_NUMCMPTS(dip) : 1; cmpt--; ) { + new = kmem_zalloc(sizeof (*new), KM_SLEEP); + new->path = kmem_zalloc(strlen(path) + 1, KM_SLEEP); + (void) strcpy(new->path, path); + new->domp = domp; + new->dip = dip; + new->cmpt = cmpt; + if (ppmf.dev_init) + (*ppmf.dev_init)(new); + new->next = domp->devlist; + domp->devlist = new; + DPRINTF(D_ADDDEV, + ("ppm_add_dev: \"%s\", \"%s\", ppm_dev 0x%p\n", + new->path, domp->name, new)); + } + + ASSERT(new != NULL); + /* + * devi_pm_ppm_private should be set only after all + * ppm_dev s related to all components have been + * initialized and domain's pwr_cnt is incremented + * for each of them. + */ + PPM_SET_PRIVATE(dip, new); + + return (new); +} + + +/* + * returns an existing or newly created ppm device reference + */ +ppm_dev_t * +ppm_get_dev(dev_info_t *dip, ppm_domain_t *domp) +{ + ppm_dev_t *pdp; + + mutex_enter(&domp->lock); + pdp = PPM_GET_PRIVATE(dip); + if (pdp == NULL) + pdp = ppm_add_dev(dip, domp); + mutex_exit(&domp->lock); + + return (pdp); +} + + +/* + * scan a domain's device list and remove those with .dip + * matching the arg *dip; we need to scan the entire list + * for the case of devices with multiple components + */ +void +ppm_rem_dev(dev_info_t *dip) +{ + ppm_dev_t *pdp, **devpp; + ppm_domain_t *domp; + + pdp = PPM_GET_PRIVATE(dip); + ASSERT(pdp); + domp = pdp->domp; + ASSERT(domp); + + mutex_enter(&domp->lock); + for (devpp = &domp->devlist; (pdp = *devpp) != NULL; ) { + if (pdp->dip != dip) { + devpp = &pdp->next; + continue; + } + + DPRINTF(D_REMDEV, ("ppm_rem_dev: path \"%s\", ppm_dev 0x%p\n", + pdp->path, pdp)); + + PPM_SET_PRIVATE(dip, NULL); + *devpp = pdp->next; + if (ppmf.dev_fini) + (*ppmf.dev_fini)(pdp); + kmem_free(pdp->path, strlen(pdp->path) + 1); + kmem_free(pdp, sizeof (*pdp)); + } + mutex_exit(&domp->lock); +} + + +/* ARGSUSED */ +int +ppm_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, + cred_t *cred_p, int *rval_p) +{ +#ifdef DEBUG + char *str = "ppm_ioctl"; + char *rwfmt = "%s: mode error: 0x%x is missing %s perm, cmd 0x%x\n"; + char *iofmt = "%s: copy%s error, arg 0x%p\n"; +#endif + ppmreq_t req; + uint8_t level; + + DPRINTF(D_IOCTL, ("%s: dev 0x%lx, cmd 0x%x, arg 0x%p, mode 0x%x\n", + str, dev, cmd, arg, mode)); + + if (ddi_copyin((caddr_t)arg, &req, sizeof (req), mode)) { + DPRINTF(D_IOCTL, (iofmt, str, "in", arg)); + return (EFAULT); + } + + /* + * Currently, only PPM_INTERNAL_DEVICE_POWER device type is supported + */ + if (req.ppmdev != PPM_INTERNAL_DEVICE_POWER) { + DPRINTF(D_IOCTL, ("%s: unrecognized device type %d\n", + str, req.ppmdev)); + return (EINVAL); + } + + switch (cmd) { + case PPMIOCSET: + if (secpolicy_power_mgmt(cred_p) != 0) { + DPRINTF(D_IOCTL, ("%s: bad cred for cmd 0x%x\n", + str, cmd)); + return (EPERM); + } else if (!(mode & FWRITE)) { + DPRINTF(D_IOCTL, (rwfmt, str, mode, "write")); + return (EPERM); + } + + level = req.ppmop.idev_power.level; + if ((level != PPM_IDEV_POWER_ON) && + (level != PPM_IDEV_POWER_OFF)) { + DPRINTF(D_IOCTL, + ("%s: invalid power level %d, cmd 0x%x\n", + str, level, cmd)); + return (EINVAL); + } + if (ppmf.iocset == NULL) + return (ENOTSUP); + (*ppmf.iocset)(level); + break; + + case PPMIOCGET: + if (!(mode & FREAD)) { + DPRINTF(D_IOCTL, (rwfmt, str, mode, "read")); + return (EPERM); + } + + if (ppmf.iocget == NULL) + return (ENOTSUP); + req.ppmop.idev_power.level = (*ppmf.iocget)(); + if (ddi_copyout((const void *)&req, (void *)arg, + sizeof (req), mode)) { + DPRINTF(D_ERROR, (iofmt, str, "out", arg)); + return (EFAULT); + } + break; + + default: + DPRINTF(D_IOCTL, ("%s: unrecognized cmd 0x%x\n", str, cmd)); + return (EINVAL); + } + + return (0); +} + + +#ifdef DEBUG +#define FLINTSTR(flags, sym) { flags, sym, #sym } +#define PMR_UNKNOWN -1 +/* + * convert a ctlop integer to a char string. this helps printing + * meaningful info when cltops are received from the pm framework. + * since some ctlops are so frequent, we use mask to limit output: + * a valid string is returned when ctlop is found and when + * (cmd.flags & mask) is true; otherwise NULL is returned. + */ +char * +ppm_get_ctlstr(int ctlop, uint_t mask) +{ + struct ctlop_cmd { + uint_t flags; + int ctlop; + char *str; + }; + + struct ctlop_cmd *ccp; + static struct ctlop_cmd cmds[] = { + FLINTSTR(D_SETPWR, PMR_SET_POWER), + FLINTSTR(D_CTLOPS2, PMR_SUSPEND), + FLINTSTR(D_CTLOPS2, PMR_RESUME), + FLINTSTR(D_CTLOPS2, PMR_PRE_SET_POWER), + FLINTSTR(D_CTLOPS2, PMR_POST_SET_POWER), + FLINTSTR(D_CTLOPS2, PMR_PPM_SET_POWER), + FLINTSTR(0, PMR_PPM_ATTACH), + FLINTSTR(0, PMR_PPM_DETACH), + FLINTSTR(D_CTLOPS1, PMR_PPM_POWER_CHANGE_NOTIFY), + FLINTSTR(D_CTLOPS1, PMR_REPORT_PMCAP), + FLINTSTR(D_CTLOPS1, PMR_CHANGED_POWER), + FLINTSTR(D_CTLOPS2, PMR_PPM_PRE_PROBE), + FLINTSTR(D_CTLOPS2, PMR_PPM_POST_PROBE), + FLINTSTR(D_CTLOPS2, PMR_PPM_PRE_ATTACH), + FLINTSTR(D_CTLOPS2, PMR_PPM_POST_ATTACH), + FLINTSTR(D_CTLOPS2, PMR_PPM_PRE_DETACH), + FLINTSTR(D_CTLOPS2, PMR_PPM_POST_DETACH), + FLINTSTR(D_CTLOPS1, PMR_PPM_UNMANAGE), + FLINTSTR(D_CTLOPS2, PMR_PPM_PRE_RESUME), + FLINTSTR(D_CTLOPS1, PMR_PPM_ALL_LOWEST), + FLINTSTR(D_LOCKS, PMR_PPM_LOCK_POWER), + FLINTSTR(D_LOCKS, PMR_PPM_UNLOCK_POWER), + FLINTSTR(D_LOCKS, PMR_PPM_TRY_LOCK_POWER), + FLINTSTR(D_CTLOPS1 | D_CTLOPS2, PMR_UNKNOWN), + }; + + for (ccp = cmds; ccp->ctlop != PMR_UNKNOWN; ccp++) + if (ctlop == ccp->ctlop) + break; + + if (ccp->flags & mask) + return (ccp->str); + return (NULL); +} +#endif |
