/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2023 Oxide Computer Company */ /* * ppm driver subroutines */ #include #include #include #include #include #include #include #include #include #include #include /* * Append address to the device path, if it is set. Routine * ddi_pathname does not look for device address if the node is in * DS_INITIALIZED state. */ #define PPM_GET_PATHNAME(dip, path) \ (void) ddi_pathname((dip), (path)); \ if ((i_ddi_node_state((dip)) < DS_INITIALIZED) && \ (ddi_get_name_addr((dip)) != NULL)) { \ (void) strcat((path), "@"); \ (void) strcat((path), ddi_get_name_addr((dip)));\ } int ppm_parse_dc(char **, ppm_dc_t *); int ppm_match_devs(char *, ppm_db_t *); ppm_db_t *ppm_parse_pattern(struct ppm_db **, char *); int ppm_count_char(char *, char); int ppm_stoi(char *, uint_t *); int ppm_convert(char *, uint_t *); void ppm_prop_free(struct ppm_cdata **); /* * lookup string property from configuration file ppm.conf */ static int ppm_get_confdata(struct ppm_cdata **cdp, dev_info_t *dip) { #ifdef DEBUG char *str = "ppm_get_confdata"; #endif 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) { PPMD(D_ERROR, ("%s: no %s found, err(%d)\n", str, cinfo->name, err)) break; } } return (err); } void ppm_prop_free(struct ppm_cdata **cdp) { if (cdp) { for (; *cdp; cdp++) { if ((*cdp)->name) { kmem_free((*cdp)->name, strlen((*cdp)->name) + 1); (*cdp)->name = NULL; } if ((*cdp)->strings) { ddi_prop_free((*cdp)->strings); (*cdp)->strings = NULL; } } } } /* * free ddi prop strings. Under error condition, free ppm_db_t lists as well. */ static int ppm_attach_err(struct ppm_cdata **cdp, int err) { ppm_domain_t *domp; ppm_db_t *db, *tmp; ppm_prop_free(cdp); if (err != DDI_SUCCESS) { for (domp = ppm_domain_p; domp; domp = domp->next) { for (db = domp->conflist; (tmp = db) != NULL; ) { db = db->next; kmem_free(tmp->name, strlen(tmp->name) + 1); kmem_free(tmp, sizeof (*tmp)); } domp->conflist = NULL; } err = DDI_FAILURE; } return (err); } ppm_domain_t * ppm_lookup_domain(char *dname) { ppm_domain_t *domp; for (domp = ppm_domain_p; domp; domp = domp->next) { if (strcmp(dname, domp->name) == 0) break; } return (domp); } /* * for the purpose of optimizing we search for identical dc->path * that has been opened per previous visit here. If search results * in a hit, copy the device handle, else open the device. */ ppm_dc_t * ppm_lookup_hndl(int model, ppm_dc_t *key_dc) { #ifdef DEBUG char *str = "ppm_lookup_hndl"; #endif char *key_path = key_dc->path; ppm_domain_t *domp; ppm_dc_t *dc; /* search domain by domain.model */ for (domp = ppm_domain_p; domp; domp = domp->next) { if (domp->model == model) break; } /* lookup hndl from same domain model */ if (domp && PPM_DOMAIN_UP(domp)) { for (dc = domp->dc; dc; dc = dc->next) { if ((strcmp(dc->path, key_path) == 0) && (dc->lh != NULL)) { PPMD(D_PPMDC, ("%s: Hit(dc_path:%s) from SAME " "domain %s.\n", str, key_path, domp->name)) key_dc->lh = dc->lh; return (key_dc); } } } /* otherwise, check other domains */ for (domp = ppm_domain_p; domp && (domp->model != model); domp = domp->next) { if (PPM_DOMAIN_UP(domp)) { for (dc = domp->dc; dc; dc = dc->next) { if ((strcmp(dc->path, key_path) == 0) && (dc->lh != NULL)) { PPMD(D_PPMDC, ("%s: Hit(dc_path:%s) " "from domain %s\n", str, key_path, domp->name)) key_dc->lh = dc->lh; return (key_dc); } } } } PPMD(D_PPMDC, ("%s: Miss(dc_path:%s)\n", str, key_path)) return (NULL); } #define PPM_DOMAIN_PROP "ppm-domains" #define PPM_DEV_PROP_SUFFIX "-devices" #define PPM_MODEL_PROP_SUFFIX "-model" #define PPM_PROPNAME_PROP_SUFFIX "-propname" #define PPM_CTRL_PROP_SUFFIX "-control" struct ppm_domit ppm_domit_data[] = { "SX", PPMD_SX, 0, PPMD_ON, "CPU", PPMD_CPU, PPMD_LOCK_ALL, PPMD_ON, "FET", PPMD_FET, PPMD_LOCK_ONE, PPMD_ON, "PCI", PPMD_PCI, PPMD_LOCK_ONE, PPMD_ON, "PCI_PROP", PPMD_PCI_PROP, PPMD_LOCK_ONE, PPMD_ON, "LED", PPMD_LED, 0, PPMD_ON, "PCIE", PPMD_PCIE, PPMD_LOCK_ONE, PPMD_ON, NULL }; /* * store up platform dependent information provided by ppm.conf file * into private data base */ int ppm_create_db(dev_info_t *dip) { #ifdef DEBUG char *str = "ppm_create_db"; #endif ppm_domain_t *domp; ppm_db_t *db; ppm_dc_t *dc; struct ppm_cdata domdata; /* hold "ppm-domains" property */ struct ppm_cdata modeldata; /* hold "domain_xy-model" property */ struct ppm_cdata propnamedata; /* hold "domain_xy-propname" property */ struct ppm_cdata devdata; /* hold "domain_xy-devices" property */ struct ppm_cdata dcdata; /* hold "domain_xy-control" property */ struct ppm_cdata *cdata[2]; char **dom_namep, **model_namep, **dev_namep, **dc_namep; struct ppm_domit *domit_p; int err; /* * get "ppm-domains" property */ bzero(&domdata, sizeof (domdata)); domdata.name = kmem_zalloc(strlen(PPM_DOMAIN_PROP) + 1, KM_SLEEP); (void) strcpy(domdata.name, PPM_DOMAIN_PROP); cdata[0] = &domdata; cdata[1] = NULL; if (err = ppm_get_confdata(cdata, dip)) { PPMD(D_CREATEDB, ("%s: failed to get prop \"%s\"!\n", str, PPM_DOMAIN_PROP)) return (ppm_attach_err(cdata, err)); } for (dom_namep = domdata.strings; *dom_namep; dom_namep++) { domp = kmem_zalloc(sizeof (*domp), KM_SLEEP); domp->name = kmem_zalloc(strlen(*dom_namep) + 1, KM_SLEEP); (void) strcpy(domp->name, *dom_namep); mutex_init(&domp->lock, NULL, MUTEX_DRIVER, NULL); if (ppm_domain_p == NULL) ppm_domain_p = domp; else { domp->next = ppm_domain_p; ppm_domain_p = domp; } } ppm_prop_free(cdata); /* * more per domain property strings in ppm.conf file tell us * what the nature of domain, how to performe domain control, etc. * Even the property names of those per domain properties are * formed consisting its domain name string. * Here we walk through our domain list, and fullfill the details. */ for (domp = ppm_domain_p; domp; domp = domp->next) { size_t plen; /* * get "domain_xy-model" property */ bzero(&modeldata, sizeof (modeldata)); plen = strlen(domp->name) + strlen(PPM_MODEL_PROP_SUFFIX) + 1; modeldata.name = kmem_zalloc(plen, KM_SLEEP); (void) sprintf(modeldata.name, "%s%s", domp->name, PPM_MODEL_PROP_SUFFIX); cdata[0] = &modeldata; cdata[1] = NULL; if (err = ppm_get_confdata(cdata, dip)) { PPMD(D_CREATEDB, ("%s: Can't read property %s!\n", str, modeldata.name)) return (ppm_attach_err(cdata, err)); } model_namep = modeldata.strings; for (domit_p = ppm_domit_data; domit_p->name; domit_p++) { if (strcmp(domit_p->name, *model_namep) == 0) { domp->model = domit_p->model; domp->dflags = domit_p->dflags; domp->status = domit_p->status; break; } } ASSERT(domit_p); ppm_prop_free(cdata); /* get "domain_xy-propname" property */ bzero(&propnamedata, sizeof (propnamedata)); plen = strlen(domp->name) + strlen(PPM_PROPNAME_PROP_SUFFIX) + 1; propnamedata.name = kmem_zalloc(plen, KM_SLEEP); (void) sprintf(propnamedata.name, "%s%s", domp->name, PPM_PROPNAME_PROP_SUFFIX); cdata[0] = &propnamedata; cdata[1] = NULL; if (ppm_get_confdata(cdata, dip) == DDI_PROP_SUCCESS) { domp->propname = kmem_zalloc( (strlen(*propnamedata.strings) + 1), KM_SLEEP); (void) strcpy(domp->propname, *propnamedata.strings); PPMD(D_CREATEDB, ("%s: %s has property name: %s\n", str, domp->name, domp->propname)) } ppm_prop_free(cdata); /* get "domain_xy-devices" property */ bzero(&devdata, sizeof (devdata)); plen = strlen(domp->name) + strlen(PPM_DEV_PROP_SUFFIX) + 1; devdata.name = kmem_zalloc(plen, KM_SLEEP); (void) sprintf(devdata.name, "%s%s", domp->name, PPM_DEV_PROP_SUFFIX); cdata[0] = &devdata; cdata[1] = NULL; if (err = ppm_get_confdata(cdata, dip)) { PPMD(D_CREATEDB, ("%s: Can't read property %s!\n", str, devdata.name)) return (ppm_attach_err(cdata, err)); } for (dev_namep = devdata.strings; *dev_namep; dev_namep++) { if (!ppm_parse_pattern(&db, *dev_namep)) return (ppm_attach_err(cdata, err)); db->next = domp->conflist; domp->conflist = db; PPMD(D_CREATEDB, ("%s: %s add pattern: %s \n", str, devdata.name, db->name)) } PPMD(D_CREATEDB, ("\n")) ppm_prop_free(cdata); /* get "domain_xy-control" property */ bzero(&dcdata, sizeof (dcdata)); plen = strlen(domp->name) + strlen(PPM_CTRL_PROP_SUFFIX) + 1; dcdata.name = kmem_zalloc(plen, KM_SLEEP); (void) sprintf(dcdata.name, "%s%s", domp->name, PPM_CTRL_PROP_SUFFIX); cdata[0] = &dcdata; cdata[1] = NULL; if (ppm_get_confdata(cdata, dip) == DDI_PROP_SUCCESS) { for (dc_namep = dcdata.strings; *dc_namep; dc_namep++) { dc = kmem_zalloc(sizeof (*dc), KM_SLEEP); dc->next = domp->dc; domp->dc = dc; err = ppm_parse_dc(dc_namep, domp->dc); if (err != DDI_SUCCESS) return (ppm_attach_err(cdata, err)); } } ppm_prop_free(cdata); #ifdef DEBUG dc = domp->dc; while (dc) { ppm_print_dc(dc); dc = dc->next; } #endif } return (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 *domp; ppm_db_t *dbp; #ifdef __x86 char *devtype = NULL; #endif /* __x86 */ PPM_GET_PATHNAME(dip, path); for (domp = ppm_domain_p; domp; domp = domp->next) { if (PPM_DOMAIN_UP(domp)) { for (dbp = domp->conflist; dbp; dbp = dbp->next) { /* * allow claiming root without knowing * its full name */ if (dip == ddi_root_node() && strcmp(dbp->name, "/") == 0) return (domp); #ifdef __x86 /* * Special rule to catch all CPU devices on x86. */ if (domp->model == PPMD_CPU && strcmp(dbp->name, "/") == 0 && ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "device_type", &devtype) == DDI_SUCCESS) { if (strcmp(devtype, "cpu") == 0) { ddi_prop_free(devtype); return (domp); } else { ddi_prop_free(devtype); } } #endif /* __x86 */ if (ppm_match_devs(path, dbp) == 0) return (domp); } } } return (NULL); } /* * check ppm.conf file domain device pathname syntax, if correct, * create device match pattern. * return 1 for good, -1 for bad. */ ppm_db_t * ppm_parse_pattern(struct ppm_db **dbpp, char *dev_path) { char path[MAXNAMELEN]; int wccnt, i; int wcpos[2]; int pos; char *cp; ppm_db_t *dbp; (void) strcpy(path, dev_path); if ((wccnt = ppm_count_char(path, '*')) > 2) return (NULL); for (i = 0, cp = path, pos = 0; i < wccnt; i++, cp++, pos++) { for (; *cp; cp++, pos++) if (*cp == '*') break; wcpos[i] = pos; PPMD(D_CREATEDB, (" wildcard #%d, pos %d\n", (i + 1), wcpos[i])) } #ifdef DEBUG /* first '*', if exists, don't go beyond the string */ if (wccnt > 0) ASSERT(wcpos[0] < strlen(path)); /* second '*', if exists, better be the last character */ if (wccnt == 2) ASSERT(wcpos[1] == (strlen(path) - 1)); #endif /* * first '*', if followed by any char, must be immediately * followed by '@' and the rest better be bound by * ['0-9', 'a-f', A-F'] until ended '0' or second '*''0'. */ if ((wccnt > 0) && (wcpos[0] < (strlen(path) - 1))) { cp = path + wcpos[0] + 1; if (*cp != '@') return (NULL); if (!(((*(++cp) > '0') && (*cp < '9')) || ((*cp > 'a') && (*cp < 'f')) || ((*cp > 'A') && (*cp < 'F')))) return (NULL); } dbp = kmem_zalloc(sizeof (struct ppm_db), KM_SLEEP); dbp->name = kmem_zalloc((strlen(path) + 1), KM_SLEEP); (void) strcpy(dbp->name, path); dbp->wccnt = wccnt; dbp->wcpos[0] = (wccnt > 0) ? wcpos[0] : -1; dbp->wcpos[1] = (wccnt == 2) ? wcpos[1] : -1; return (*dbpp = dbp); } /* * match given device "path" to domain device pathname * pattern dbp->name that contains one or two '*' character(s). * Matching policy: * 1). If one wildcard terminates match pattern, need exact match * up to (but exclude) the wildcard; * 2). If one wildcard does not terminate match pattern, it is to * match driver name (terminates with '@') and must be followed * by exact match of rest of pattern; * 3). If two wildcards, first is to match driver name as in 2), * second is to match fcnid (terminates with '/' or '\0') and * must the last char of pattern. * * return 0 if match, and * non 0 if mismatch */ int ppm_match_devs(char *dev_path, ppm_db_t *dbp) { char path[MAXNAMELEN]; char *cp; /* points into "path", real device pathname */ char *np; /* points into "dbp->name", the pattern */ int len; if (dbp->wccnt == 0) return (strcmp(dev_path, dbp->name)); (void) strcpy(path, dev_path); /* match upto the first '*' regardless */ if (strncmp(path, dbp->name, dbp->wcpos[0]) != 0) return (-1); /* "*" */ if (dbp->name[dbp->wcpos[0] + 1] == 0) { cp = path + dbp->wcpos[0]; while (*cp && (*cp++ != '/')) ; return ((*cp == 0) ? 0 : -1); } /* locate '@' */ cp = path + dbp->wcpos[0] + 1; while (*cp && *cp != '@') cp++; np = dbp->name + dbp->wcpos[0] + 1; /* if one wildcard, match the rest in the pattern */ if (dbp->wccnt == 1) return ((strcmp(cp, np) == 0) ? 0 : (-1)); /* must have exact match after first wildcard up to second */ ASSERT(dbp->wccnt == 2); len = dbp->wcpos[1] - dbp->wcpos[0] - 1; if (strncmp(cp, np, len) != 0) return (-1); /* second wildcard match terminates with '/' or '\0' */ /* but only termination with '\0' is a successful match */ cp += len; while (*cp && (*cp != '/')) cp++; return ((*cp == 0) ? 0 : -1); } /* * By claiming a device, ppm gets involved in its power change * process: handles additional issues prior and/or post its * power(9e) call. * * If 'dip' is a PCI device, this is the time to ask its parent * what PCI bus speed it is running. * * returns 1 (claimed), 0 (not claimed) */ int ppm_claim_dev(dev_info_t *dip) { ppm_domain_t *domp; dev_info_t *pdip; uint_t pciclk; int claimed = -1; domp = ppm_lookup_dev(dip); if (!domp) claimed = 0; if (domp && PPMD_IS_PCI(domp->model) && ! (domp->dflags & (PPMD_PCI33MHZ | PPMD_PCI66MHZ))) { pdip = ddi_get_parent(dip); ASSERT(pdip); pciclk = ddi_prop_get_int(DDI_DEV_T_ANY, pdip, DDI_PROP_DONTPASS, "clock-frequency", -1); switch (pciclk) { case 33000000: domp->dflags |= PPMD_PCI33MHZ; claimed = 1; break; case 66000000: domp->dflags |= PPMD_PCI66MHZ; claimed = 1; break; default: claimed = 0; break; } } if (domp && (claimed == -1)) claimed = 1; #ifdef DEBUG if (claimed) { char path[MAXNAMELEN]; PPMD(D_CLAIMDEV, ("ppm_claim_dev: %s into domain %s\n", ddi_pathname(dip, path), domp->name)) } #endif return (claimed); } /* * add a device to the list of domain's owned devices (if it is not already * on the list). */ ppm_owned_t * ppm_add_owned(dev_info_t *dip, ppm_domain_t *domp) { char path[MAXNAMELEN]; ppm_owned_t *owned, *new_owned; ASSERT(MUTEX_HELD(&domp->lock)); PPM_GET_PATHNAME(dip, path); for (owned = domp->owned; owned; owned = owned->next) if (strcmp(path, owned->path) == 0) return (owned); new_owned = kmem_zalloc(sizeof (*new_owned), KM_SLEEP); new_owned->path = kmem_zalloc(strlen(path) + 1, KM_SLEEP); (void) strcpy(new_owned->path, path); new_owned->next = domp->owned; domp->owned = new_owned; return (domp->owned); } /* * create/init a new ppm device and link into the domain */ ppm_dev_t * ppm_add_dev(dev_info_t *dip, ppm_domain_t *domp) { char path[MAXNAMELEN]; ppm_dev_t *new = NULL; int cmpt; ppm_owned_t *owned; 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; ppm_dev_init(new); new->next = domp->devlist; domp->devlist = new; PPMD(D_ADDDEV, ("ppm_add_dev: %s to domain %s: ppm_dev(0x%p)\n", new->path, domp->name, (void *)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); /* remember this device forever */ owned = ppm_add_owned(dip, domp); /* * Initializing flag is set for devices which have gone through * PPM_PMR_INIT_CHILD ctlop. By this point, these devices have * been added to ppm structures and could participate in pm * decision making, so clear the initializing flag. */ if (owned->initializing) { owned->initializing = 0; PPMD(D_ADDDEV, ("ppm_add_dev: cleared initializing flag " "for %s@%s\n", PM_NAME(dip), (PM_ADDR(dip) == NULL) ? "" : PM_ADDR(dip))) } 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; } PPMD(D_REMDEV, ("ppm_rem_dev: path \"%s\", ppm_dev 0x%p\n", pdp->path, (void *)pdp)) PPM_SET_PRIVATE(dip, NULL); *devpp = pdp->next; ppm_dev_fini(pdp); kmem_free(pdp->path, strlen(pdp->path) + 1); kmem_free(pdp, sizeof (*pdp)); } mutex_exit(&domp->lock); } /* * prepare kernel ioctl calls: */ void ppm_init_cb(dev_info_t *dip) { char *str = "ppm_init_cb"; ppm_domain_t *domp; ppm_dc_t *dc; for (domp = ppm_domain_p; domp != NULL; domp = domp->next) { for (dc = domp->dc; dc; dc = dc->next) { /* * Warning: This code is rather confusing. * * It intends to ensure that ppm_init_lyr() is only * called ONCE for a device that may be associated * with more than one domain control. * So, what it does is first to check to see if * there is a handle, and then if not it goes on * to call the init_lyr() routine. * * The non-obvious thing is that the ppm_init_lyr() * routine, in addition to opening the device * associated with the dc (domain control) in * question, has the side-effect of creating the * handle for that dc as well. */ if (ppm_lookup_hndl(domp->model, dc) != NULL) continue; if (ppm_init_lyr(dc, dip) != DDI_SUCCESS) { domp->dflags |= PPMD_OFFLINE; cmn_err(CE_WARN, "%s: ppm domain %s will " "be offline.", str, domp->name); break; } } } } /* * ppm_init_lyr - initializing layered ioctl * Return: * DDI_SUCCESS - succeeded * DDI_FAILURE - failed * */ int ppm_init_lyr(ppm_dc_t *dc, dev_info_t *dip) { char *str = "ppm_init_lyr"; int err = 0; ldi_ident_t li; ASSERT(dc && dc->path); if (err = ldi_ident_from_dip(dip, &li)) { cmn_err(CE_WARN, "%s: get ldi identifier " "failed (err=%d)", str, err); } err = ldi_open_by_name(dc->path, FWRITE|FREAD, kcred, &(dc->lh), li); (void) ldi_ident_release(li); if (err != 0) { cmn_err(CE_WARN, "Failed to open device(%s), rv(%d)", dc->path, err); return (err); } return (DDI_SUCCESS); } /* * lock, unlock, or trylock for one power mutex */ void ppm_lock_one(ppm_dev_t *ppmd, power_req_t *reqp, int *iresp) { switch (reqp->request_type) { case PMR_PPM_LOCK_POWER: pm_lock_power_single(ppmd->dip); break; case PMR_PPM_UNLOCK_POWER: pm_unlock_power_single(ppmd->dip); break; case PMR_PPM_TRY_LOCK_POWER: *iresp = pm_try_locking_power_single(ppmd->dip); break; } } /* * lock, unlock, or trylock for all power mutexes within a domain */ void ppm_lock_all(ppm_domain_t *domp, power_req_t *reqp, int *iresp) { /* * To simplify the implementation we let all the devices * in the domain be represented by a single device (dip). * We use the first device in the domain's devlist. This * is safe because we return with the domain lock held * which prevents the list from changing. */ if (reqp->request_type == PMR_PPM_LOCK_POWER) { if (!MUTEX_HELD(&domp->lock)) mutex_enter(&domp->lock); domp->refcnt++; ASSERT(domp->devlist != NULL); pm_lock_power_single(domp->devlist->dip); /* domain lock remains held */ return; } else if (reqp->request_type == PMR_PPM_UNLOCK_POWER) { ASSERT(MUTEX_HELD(&domp->lock)); ASSERT(domp->devlist != NULL); pm_unlock_power_single(domp->devlist->dip); if (--domp->refcnt == 0) mutex_exit(&domp->lock); return; } ASSERT(reqp->request_type == PMR_PPM_TRY_LOCK_POWER); if (!MUTEX_HELD(&domp->lock)) if (!mutex_tryenter(&domp->lock)) { *iresp = 0; return; } *iresp = pm_try_locking_power_single(domp->devlist->dip); if (*iresp) domp->refcnt++; else mutex_exit(&domp->lock); } /* * return FALSE: if any detached device during its previous life exported * the "no-involuntary-power-cycles" property and detached with its * power level not at its lowest, or there is a device in the process * of being installed/attached; if a PCI domain has devices that have not * exported a property that it can tolerate clock off while bus is not * quiescent; if a 66mhz PCI domain has devices that do not support stopping * clock at D3; either one would count as a power holder. * return TRUE: otherwise. */ boolean_t ppm_none_else_holds_power(ppm_domain_t *domp) { ppm_dev_t *ppmd; ppm_owned_t *owned; int i = 0; if (PPMD_IS_PCI(domp->model)) { for (ppmd = domp->devlist; ppmd; ppmd = ppmd->next) { if ((domp->model == PPMD_PCI_PROP) && !(ppmd->flags & PPMDEV_PCI_PROP_CLKPM)) return (B_FALSE); if ((domp->dflags & PPMD_PCI66MHZ) && !(ppmd->flags & PPMDEV_PCI66_D2)) return (B_FALSE); } } for (owned = domp->owned; owned; owned = owned->next) if (pm_noinvol_detached(owned->path) || owned->initializing) i++; return (i == 0); } /* * return the number of char 'c' occurrences in string s */ int ppm_count_char(char *s, char c) { int i = 0; char *cp = s; while (*cp) { if (*cp == c) i++; cp++; } return (i); } /* * extract and convert a substring from input string "ss" in form of * "name=value" into an hex or decimal integer */ #define X_BASE 16 #define D_BASE 10 int ppm_stoi(char *ss, uint_t *val) { char *cp; int hex_ = 0, base = D_BASE; int digit; if ((cp = strchr(ss, '=')) == NULL) { *val = UINT_MAX; return (-1); } cp++; if ((*cp == '0') && (*++cp == 'x')) { hex_++; cp++; base = X_BASE; } for (digit = 0; *cp; cp++) { if (hex_ && ((*cp >= 'A') && (*cp <= 'F'))) digit = (digit * base) + ((*cp - 'A') + D_BASE); else if (hex_ && ((*cp >= 'a') && (*cp <= 'f'))) digit = (digit * base) + ((*cp - 'a') + D_BASE); else digit = (digit * base) + (*cp - '0'); } return (*val = digit); } /* * ppm_convert - convert a #define symbol to its integer value, * only the #defines for ppm_dc.cmd and ppm_dc.method fields in * ppmvar.h file are recognized. */ struct ppm_confdefs { char *sym; int val; } ppm_confdefs_table[] = { "ENTER_S3", PPMDC_ENTER_S3, "EXIT_S3", PPMDC_EXIT_S3, "CPU_NEXT", PPMDC_CPU_NEXT, "PRE_CHNG", PPMDC_PRE_CHNG, "CPU_GO", PPMDC_CPU_GO, "POST_CHNG", PPMDC_POST_CHNG, "FET_ON", PPMDC_FET_ON, "FET_OFF", PPMDC_FET_OFF, "CLK_OFF", PPMDC_CLK_OFF, "CLK_ON", PPMDC_CLK_ON, "LED_ON", PPMDC_LED_ON, "LED_OFF", PPMDC_LED_OFF, "KIO", PPMDC_KIO, "VCORE", PPMDC_VCORE, #ifdef sun4u "I2CKIO", PPMDC_I2CKIO, #endif "CPUSPEEDKIO", PPMDC_CPUSPEEDKIO, "PRE_PWR_OFF", PPMDC_PRE_PWR_OFF, "PRE_PWR_ON", PPMDC_PRE_PWR_ON, "POST_PWR_ON", PPMDC_POST_PWR_ON, "PWR_OFF", PPMDC_PWR_OFF, "PWR_ON", PPMDC_PWR_ON, "RESET_OFF", PPMDC_RESET_OFF, "RESET_ON", PPMDC_RESET_ON, NULL }; /* * convert a #define'd symbol to its integer value where * input "symbol" is expected to be in form of "SYMBOL=value" */ int ppm_convert(char *symbol, uint_t *val) { char *s; struct ppm_confdefs *pcfp; *val = UINT_MAX; if ((s = strchr(symbol, '=')) == NULL) { cmn_err(CE_WARN, "ppm_convert: token \"%s\" syntax error in " "ppm.conf file", symbol); return (-1); } s++; for (pcfp = ppm_confdefs_table; (pcfp->sym != NULL); pcfp++) { if (strcmp(s, pcfp->sym) == 0) return (*val = pcfp->val); } cmn_err(CE_WARN, "ppm_convert: Unrecognizable token \"%s\" " "in ppm.conf file", symbol); return (-1); } /* * parse a domain control property string into data structure struct ppm_dc */ int ppm_parse_dc(char **dc_namep, ppm_dc_t *dc) { char *str = "ppm_parse_dc"; char *line; char *f, *b; char **dclist; /* list of ppm_dc_t fields */ int count; /* the # of '=' indicates the # of items */ size_t len; /* length of line being parsed */ boolean_t done; int i; int err; len = strlen(*dc_namep); line = kmem_alloc(len + 1, KM_SLEEP); (void) strcpy(line, *dc_namep); count = ppm_count_char(line, '='); ASSERT((count - ppm_count_char(line, ' ')) == 1); dclist = (char **) kmem_zalloc((sizeof (char *) * (count + 1)), KM_SLEEP); for (i = 0, f = b = line, done = B_FALSE; !done; i++, f = ++b) { while (*b != ' ' && *b != 0) b++; if (*b == 0) done = B_TRUE; else *b = 0; dclist[i] = f; } for (i = 0; i < count; i++) { if (strstr(dclist[i], "cmd=")) { err = ppm_convert(dclist[i], &dc->cmd); if (err == -1) return (err); continue; } if ((f = strstr(dclist[i], "path=")) != NULL) { f += strlen("path="); dc->path = kmem_zalloc((strlen(f) + 1), KM_SLEEP); (void) strcpy(dc->path, f); continue; } if (strstr(dclist[i], "method=")) { err = ppm_convert(dclist[i], &dc->method); if (err == -1) return (err); continue; } if (strstr(dclist[i], "iowr=")) { (void) ppm_stoi(dclist[i], &dc->m_un.kio.iowr); continue; } if (strstr(dclist[i], "iord=")) { (void) ppm_stoi(dclist[i], &dc->m_un.kio.iord); continue; } if (strstr(dclist[i], "val=")) { (void) ppm_stoi(dclist[i], &dc->m_un.kio.val); continue; } if (strstr(dclist[i], "speeds=")) { ASSERT(dc->method == PPMDC_CPUSPEEDKIO); (void) ppm_stoi(dclist[i], &dc->m_un.cpu.speeds); continue; } #ifdef sun4u if (strstr(dclist[i], "mask=")) { (void) ppm_stoi(dclist[i], &dc->m_un.i2c.mask); continue; } #endif /* This must be before the if statement for delay */ if (strstr(dclist[i], "post_delay=")) { #ifdef sun4u ASSERT(dc->method == PPMDC_KIO || dc->method == PPMDC_I2CKIO); #else ASSERT(dc->method == PPMDC_KIO); #endif /* * all delays are uint_t type instead of clock_t. * If the delay is too long, it might get truncated. * But, we don't expect delay to be too long. */ switch (dc->method) { case PPMDC_KIO: (void) ppm_stoi(dclist[i], &dc->m_un.kio.post_delay); break; #ifdef sun4u case PPMDC_I2CKIO: (void) ppm_stoi(dclist[i], &dc->m_un.i2c.post_delay); break; #endif default: break; } continue; } if (strstr(dclist[i], "delay=")) { #ifdef sun4u ASSERT(dc->method == PPMDC_VCORE || dc->method == PPMDC_KIO || dc->method == PPMDC_I2CKIO); #else ASSERT(dc->method == PPMDC_VCORE || dc->method == PPMDC_KIO); #endif /* * all delays are uint_t type instead of clock_t. * If the delay is too long, it might get truncated. * But, we don't expect delay to be too long. */ switch (dc->method) { case PPMDC_KIO: (void) ppm_stoi(dclist[i], &dc->m_un.kio.delay); break; #ifdef sun4u case PPMDC_I2CKIO: (void) ppm_stoi(dclist[i], &dc->m_un.i2c.delay); break; #endif case PPMDC_VCORE: (void) ppm_stoi(dclist[i], &dc->m_un.cpu.delay); break; default: break; } continue; } /* we encounted unrecognized field, flag error */ cmn_err(CE_WARN, "%s: Unrecognized token \"%s\" in ppm.conf " "file!", str, dclist[i]); return (-1); } kmem_free(dclist, sizeof (char *) * (count + 1)); kmem_free(line, len + 1); return (DDI_SUCCESS); } /* * search for domain control handle for a claimed device coupled with a * domain control command. NULL device may indicate LED domain. */ ppm_dc_t * ppm_lookup_dc(ppm_domain_t *domp, int cmd) { #ifdef DEBUG char *str = "ppm_lookup_dc"; #endif ppm_dc_t *dc; /* * For convenience, we accept 'domp' as NULL for searching * LED domain control operation. */ if ((cmd == PPMDC_LED_OFF) || (cmd == PPMDC_LED_ON)) { for (domp = ppm_domain_p; domp; domp = domp->next) if (domp->model == PPMD_LED) break; if (!domp || !domp->dc || !domp->dc->lh || !domp->dc->next) { PPMD(D_LED, ("\tinsufficient led domain control " "information.\n")) return (NULL); } if (cmd == domp->dc->cmd) return (domp->dc); else return (domp->dc->next); } /* * for the rest of ppm domains, lookup ppm_dc starting from domp */ ASSERT(domp != NULL); switch (cmd) { case PPMDC_CPU_NEXT: case PPMDC_PRE_CHNG: case PPMDC_CPU_GO: case PPMDC_POST_CHNG: case PPMDC_FET_OFF: case PPMDC_FET_ON: case PPMDC_CLK_OFF: case PPMDC_CLK_ON: case PPMDC_PRE_PWR_OFF: case PPMDC_PRE_PWR_ON: case PPMDC_POST_PWR_ON: case PPMDC_PWR_OFF: case PPMDC_PWR_ON: case PPMDC_RESET_OFF: case PPMDC_RESET_ON: case PPMDC_ENTER_S3: case PPMDC_EXIT_S3: break; default: PPMD(D_PPMDC, ("%s: cmd(%d) unrecognized\n", str, cmd)) return (NULL); } for (dc = domp->dc; dc; dc = dc->next) { if (dc->cmd == cmd) { return (dc); } } return (NULL); } #include ppm_domain_t * ppm_get_domain_by_dev(const char *p) { dev_info_t *dip; ppm_domain_t *domp; ppm_dev_t *pdev; boolean_t found = B_FALSE; if ((dip = e_ddi_hold_devi_by_path((char *)p, 0)) == NULL) return (NULL); for (domp = ppm_domain_p; domp; domp = domp->next) { for (pdev = domp->devlist; pdev; pdev = pdev->next) { if (pdev->dip == dip) { found = B_TRUE; break; } } if (found) break; } ddi_release_devi(dip); return (domp); } #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_INIT_CHILD), FLINTSTR(D_CTLOPS2, PMR_PPM_UNINIT_CHILD), 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_LOCKS, PMR_PPM_POWER_LOCK_OWNER), FLINTSTR(D_CTLOPS1 | D_CTLOPS2, PMR_PPM_ENTER_SX), 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); } void ppm_print_dc(ppm_dc_t *dc) { ppm_dc_t *d = dc; PPMD(D_PPMDC, ("\nAdds ppm_dc: path(%s),\n cmd(%x), " "method(%x), ", d->path, d->cmd, d->method)) if (d->method == PPMDC_KIO) { PPMD(D_PPMDC, ("kio.iowr(%x), kio.val(0x%X)", d->m_un.kio.iowr, d->m_un.kio.val)) #ifdef sun4u } else if (d->method == PPMDC_I2CKIO) { PPMD(D_PPMDC, ("i2c.iowr(%x), i2c.val(0x%X), " "i2c.mask(0x%X)", d->m_un.i2c.iowr, d->m_un.i2c.val, d->m_un.i2c.mask)) #endif } else if (d->method == PPMDC_VCORE) { PPMD(D_PPMDC, ("cpu: .iord(%x), .iowr(%x), .val(0x%X), " ".delay(0x%x)", d->m_un.cpu.iord, d->m_un.cpu.iowr, d->m_un.cpu.val, d->m_un.cpu.delay)) } else if (d->method == PPMDC_CPUSPEEDKIO) { PPMD(D_PPMDC, ("cpu.iowr(%x), cpu.speeds(0x%X)", d->m_un.cpu.iowr, d->m_un.cpu.speeds)) } PPMD(D_PPMDC, ("\n")) } #endif /* DEBUG */