summaryrefslogtreecommitdiff
path: root/usr/src/uts/sun4u/io/ppm.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/uts/sun4u/io/ppm.c')
-rw-r--r--usr/src/uts/sun4u/io/ppm.c560
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