#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;
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);
}
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 *)(uintptr_t)ppm_inst;
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
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, (void *)devp, flag, otyp));
return (0);
}
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);
}
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);
}
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);
}
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));
}
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));
}
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;
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));
}
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);
}
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);
}
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 (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, (void *)new));
}
ASSERT(new != NULL);
PPM_SET_PRIVATE(dip, new);
return (new);
}
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);
}
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, (void *)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);
}
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%lx, 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);
}
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
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