#include <sys/systm.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/thread.h>
#include <sys/cpuvar.h>
#include <sys/kmem.h>
#include <sys/cmn_err.h>
#include <sys/policy.h>
#include <sys/group.h>
#include <sys/pg.h>
#include <sys/pghw.h>
#include <sys/cpu_pm.h>
#include <sys/cap_util.h>
static group_t *pg_hw;
struct pghw_kstat {
kstat_named_t pg_id;
kstat_named_t pg_class;
kstat_named_t pg_ncpus;
kstat_named_t pg_instance_id;
kstat_named_t pg_hw;
kstat_named_t pg_policy;
} pghw_kstat = {
{ "id", KSTAT_DATA_INT32 },
{ "pg_class", KSTAT_DATA_STRING },
{ "ncpus", KSTAT_DATA_UINT32 },
{ "instance_id", KSTAT_DATA_UINT32 },
{ "hardware", KSTAT_DATA_STRING },
{ "policy", KSTAT_DATA_STRING },
};
kmutex_t pghw_kstat_lock;
struct pghw_cu_kstat {
kstat_named_t pg_id;
kstat_named_t pg_parent_id;
kstat_named_t pg_ncpus;
kstat_named_t pg_generation;
kstat_named_t pg_hw_util;
kstat_named_t pg_hw_util_time_running;
kstat_named_t pg_hw_util_time_stopped;
kstat_named_t pg_hw_util_rate;
kstat_named_t pg_hw_util_rate_max;
kstat_named_t pg_cpus;
kstat_named_t pg_relationship;
} pghw_cu_kstat = {
{ "pg_id", KSTAT_DATA_INT32 },
{ "parent_pg_id", KSTAT_DATA_INT32 },
{ "ncpus", KSTAT_DATA_UINT32 },
{ "generation", KSTAT_DATA_UINT32 },
{ "hw_util", KSTAT_DATA_UINT64 },
{ "hw_util_time_running", KSTAT_DATA_UINT64 },
{ "hw_util_time_stopped", KSTAT_DATA_UINT64 },
{ "hw_util_rate", KSTAT_DATA_UINT64 },
{ "hw_util_rate_max", KSTAT_DATA_UINT64 },
{ "cpus", KSTAT_DATA_STRING },
{ "relationship", KSTAT_DATA_STRING },
};
#define CPUSTR_LEN(ncpus) ((ncpus) * 6)
static int pg_cpulist_maxlen = 0;
static void pghw_kstat_create(pghw_t *);
static int pghw_kstat_update(kstat_t *, int);
static int pghw_cu_kstat_update(kstat_t *, int);
static int cpu2id(void *);
static group_t *pghw_set_create(pghw_type_t);
static void pghw_set_add(group_t *, pghw_t *);
static void pghw_set_remove(group_t *, pghw_t *);
static void pghw_cpulist_alloc(pghw_t *);
static int cpu2id(void *);
static pgid_t pghw_parent_id(pghw_t *);
void
pghw_init(pghw_t *pg, cpu_t *cp, pghw_type_t hw)
{
group_t *hwset;
if ((hwset = pghw_set_lookup(hw)) == NULL) {
hwset = pghw_set_create(hw);
}
pghw_set_add(hwset, pg);
pg->pghw_hw = hw;
pg->pghw_generation = 0;
pg->pghw_instance =
pg_plat_hw_instance_id(cp, hw);
pghw_kstat_create(pg);
switch (pg->pghw_hw) {
case PGHW_POW_ACTIVE:
pg->pghw_handle =
(pghw_handle_t)cpupm_domain_init(cp, CPUPM_DTYPE_ACTIVE);
break;
case PGHW_POW_IDLE:
pg->pghw_handle =
(pghw_handle_t)cpupm_domain_init(cp, CPUPM_DTYPE_IDLE);
break;
default:
pg->pghw_handle = (pghw_handle_t)NULL;
}
}
void
pghw_fini(pghw_t *pg)
{
group_t *hwset;
pghw_cmt_fini(pg);
hwset = pghw_set_lookup(pg->pghw_hw);
ASSERT(hwset != NULL);
pghw_set_remove(hwset, pg);
pg->pghw_instance = (id_t)PGHW_INSTANCE_ANON;
pg->pghw_hw = (pghw_type_t)-1;
if (pg->pghw_kstat != NULL)
kstat_delete(pg->pghw_kstat);
}
void
pghw_cmt_fini(pghw_t *pg)
{
if (pg->pghw_cpulist != NULL) {
kmem_free(pg->pghw_cpulist,
pg->pghw_cpulist_len);
pg->pghw_cpulist = NULL;
}
if (pg->pghw_cu_kstat != NULL) {
kstat_delete(pg->pghw_cu_kstat);
pg->pghw_cu_kstat = NULL;
}
}
pghw_t *
pghw_place_cpu(cpu_t *cp, pghw_type_t hw)
{
group_t *hwset;
if ((hwset = pghw_set_lookup(hw)) == NULL) {
return (NULL);
}
return ((pghw_t *)pg_cpu_find_pg(cp, hwset));
}
pghw_t *
pghw_find_pg(cpu_t *cp, pghw_type_t hw)
{
group_iter_t i;
pghw_t *pg;
group_iter_init(&i);
while ((pg = group_iterate(&cp->cpu_pg->pgs, &i)) != NULL) {
if (pg->pghw_hw == hw)
return (pg);
}
return (NULL);
}
pghw_t *
pghw_find_by_instance(id_t id, pghw_type_t hw)
{
group_iter_t i;
group_t *set;
pghw_t *pg;
set = pghw_set_lookup(hw);
if (!set)
return (NULL);
group_iter_init(&i);
while ((pg = group_iterate(set, &i)) != NULL) {
if (pg->pghw_instance == id)
return (pg);
}
return (NULL);
}
void
pghw_physid_create(cpu_t *cp)
{
int i;
cp->cpu_physid = kmem_alloc(sizeof (cpu_physid_t), KM_SLEEP);
for (i = 0; i < (sizeof (cpu_physid_t) / sizeof (id_t)); i++) {
((id_t *)cp->cpu_physid)[i] = cp->cpu_id;
}
}
void
pghw_physid_destroy(cpu_t *cp)
{
if (cp->cpu_physid) {
kmem_free(cp->cpu_physid, sizeof (cpu_physid_t));
cp->cpu_physid = NULL;
}
}
static group_t *
pghw_set_create(pghw_type_t hw)
{
group_t *g;
int ret;
if (pg_hw == NULL) {
pg_hw = kmem_alloc(sizeof (group_t), KM_SLEEP);
group_create(pg_hw);
group_expand(pg_hw, (uint_t)PGHW_NUM_COMPONENTS);
}
g = kmem_alloc(sizeof (group_t), KM_SLEEP);
group_create(g);
ret = group_add_at(pg_hw, g, (uint_t)hw);
ASSERT(ret == 0);
return (g);
}
group_t *
pghw_set_lookup(pghw_type_t hw)
{
group_t *hwset;
if (pg_hw == NULL)
return (NULL);
hwset = GROUP_ACCESS(pg_hw, (uint_t)hw);
return (hwset);
}
static void
pghw_set_add(group_t *hwset, pghw_t *pg)
{
(void) group_add(hwset, pg, GRP_RESIZE);
}
static void
pghw_set_remove(group_t *hwset, pghw_t *pg)
{
int result;
result = group_remove(hwset, pg, GRP_RESIZE);
ASSERT(result == 0);
}
char *
pghw_type_string(pghw_type_t hw)
{
switch (hw) {
case PGHW_IPIPE:
return ("Integer Pipeline");
case PGHW_CACHE:
return ("Cache");
case PGHW_FPU:
return ("Floating Point Unit");
case PGHW_MPIPE:
return ("Data Pipe to memory");
case PGHW_CHIP:
return ("Socket");
case PGHW_MEMORY:
return ("Memory");
case PGHW_POW_ACTIVE:
return ("CPU PM Active Power Domain");
case PGHW_POW_IDLE:
return ("CPU PM Idle Power Domain");
default:
return ("unknown");
}
}
void
pghw_kstat_create(pghw_t *pg)
{
char *sharing = pghw_type_string(pg->pghw_hw);
char name[KSTAT_STRLEN + 1];
(void) strncpy(name, pghw_type_string(pg->pghw_hw), KSTAT_STRLEN + 1);
strident_canon(name, KSTAT_STRLEN + 1);
if ((pg->pghw_kstat = kstat_create("pg", ((pg_t *)pg)->pg_id,
"pg", "pg",
KSTAT_TYPE_NAMED,
sizeof (pghw_kstat) / sizeof (kstat_named_t),
KSTAT_FLAG_VIRTUAL)) != NULL) {
pg->pghw_kstat->ks_data_size += PG_CLASS_NAME_MAX;
pg->pghw_kstat->ks_data_size += PGHW_KSTAT_STR_LEN_MAX;
pg->pghw_kstat->ks_data_size += PGHW_KSTAT_STR_LEN_MAX;
pg->pghw_kstat->ks_lock = &pghw_kstat_lock;
pg->pghw_kstat->ks_data = &pghw_kstat;
pg->pghw_kstat->ks_update = pghw_kstat_update;
pg->pghw_kstat->ks_private = pg;
kstat_install(pg->pghw_kstat);
}
if (pg_cpulist_maxlen == 0)
pg_cpulist_maxlen = CPUSTR_LEN(max_ncpus);
if ((pg->pghw_cu_kstat = kstat_create("pg_hw_perf", ((pg_t *)pg)->pg_id,
name, "processor_group",
KSTAT_TYPE_NAMED,
sizeof (pghw_cu_kstat) / sizeof (kstat_named_t),
KSTAT_FLAG_VIRTUAL)) != NULL) {
pg->pghw_cu_kstat->ks_lock = &pghw_kstat_lock;
pg->pghw_cu_kstat->ks_data = &pghw_cu_kstat;
pg->pghw_cu_kstat->ks_update = pghw_cu_kstat_update;
pg->pghw_cu_kstat->ks_private = pg;
pg->pghw_cu_kstat->ks_data_size += strlen(sharing) + 1;
pg->pghw_cu_kstat->ks_data_size += PGHW_KSTAT_STR_LEN_MAX;
pg->pghw_cu_kstat->ks_data_size += pg_cpulist_maxlen;
kstat_install(pg->pghw_cu_kstat);
}
}
int
pghw_kstat_update(kstat_t *ksp, int rw)
{
struct pghw_kstat *pgsp = &pghw_kstat;
pghw_t *pg = ksp->ks_private;
if (rw == KSTAT_WRITE)
return (EACCES);
pgsp->pg_id.value.ui32 = ((pg_t *)pg)->pg_id;
pgsp->pg_ncpus.value.ui32 = GROUP_SIZE(&((pg_t *)pg)->pg_cpus);
pgsp->pg_instance_id.value.ui32 = pg->pghw_instance;
kstat_named_setstr(&pgsp->pg_class, ((pg_t *)pg)->pg_class->pgc_name);
kstat_named_setstr(&pgsp->pg_hw, pghw_type_string(pg->pghw_hw));
kstat_named_setstr(&pgsp->pg_policy, pg_policy_name((pg_t *)pg));
return (0);
}
int
pghw_cu_kstat_update(kstat_t *ksp, int rw)
{
struct pghw_cu_kstat *pgsp = &pghw_cu_kstat;
pghw_t *pg = ksp->ks_private;
pghw_util_t *hw_util = &pg->pghw_stats;
boolean_t has_cpc_privilege;
if (rw == KSTAT_WRITE)
return (EACCES);
has_cpc_privilege = (secpolicy_cpc_cpu(crgetcred()) == 0);
pgsp->pg_id.value.i32 = ((pg_t *)pg)->pg_id;
pgsp->pg_parent_id.value.i32 = (int)pghw_parent_id(pg);
pgsp->pg_ncpus.value.ui32 = GROUP_SIZE(&((pg_t *)pg)->pg_cpus);
pghw_cpulist_alloc(pg);
if (pg->pghw_kstat_gen != pg->pghw_generation) {
hw_util->pghw_util = 0;
hw_util->pghw_rate_max = 0;
pg->pghw_kstat_gen = pg->pghw_generation;
}
if (mutex_tryenter(&cpu_lock)) {
if (pg->pghw_cpulist != NULL &&
*(pg->pghw_cpulist) == '\0') {
(void) group2intlist(&(((pg_t *)pg)->pg_cpus),
pg->pghw_cpulist, pg->pghw_cpulist_len, cpu2id);
}
if (has_cpc_privilege)
cu_pg_update(pg);
mutex_exit(&cpu_lock);
}
pgsp->pg_generation.value.ui32 = pg->pghw_kstat_gen;
if (pg->pghw_cpulist != NULL)
kstat_named_setstr(&pgsp->pg_cpus, pg->pghw_cpulist);
else
kstat_named_setstr(&pgsp->pg_cpus, "");
kstat_named_setstr(&pgsp->pg_relationship,
pghw_type_string(pg->pghw_hw));
if (has_cpc_privilege) {
pgsp->pg_hw_util.value.ui64 = hw_util->pghw_util;
pgsp->pg_hw_util_time_running.value.ui64 =
hw_util->pghw_time_running;
pgsp->pg_hw_util_time_stopped.value.ui64 =
hw_util->pghw_time_stopped;
pgsp->pg_hw_util_rate.value.ui64 = hw_util->pghw_rate;
pgsp->pg_hw_util_rate_max.value.ui64 = hw_util->pghw_rate_max;
} else {
pgsp->pg_hw_util.value.ui64 = 0;
pgsp->pg_hw_util_time_running.value.ui64 = 0;
pgsp->pg_hw_util_time_stopped.value.ui64 = 0;
pgsp->pg_hw_util_rate.value.ui64 = 0;
pgsp->pg_hw_util_rate_max.value.ui64 = 0;
}
return (0);
}
static void
pghw_cpulist_alloc(pghw_t *pg)
{
uint_t ncpus = GROUP_SIZE(&((pg_t *)pg)->pg_cpus);
size_t len = CPUSTR_LEN(ncpus);
if (pg->pghw_cpulist != NULL &&
pg->pghw_kstat_gen != pg->pghw_generation) {
if (len <= pg->pghw_cpulist_len) {
*(pg->pghw_cpulist) = '\0';
} else {
ASSERT(strlen(pg->pghw_cpulist) < pg->pghw_cpulist_len);
kmem_free(pg->pghw_cpulist, pg->pghw_cpulist_len);
pg->pghw_cpulist = NULL;
pg->pghw_cpulist_len = 0;
}
}
if (pg->pghw_cpulist == NULL) {
if (len > pg_cpulist_maxlen)
len = pg_cpulist_maxlen;
if (len > 0) {
pg->pghw_cpulist = kmem_zalloc(len, KM_NOSLEEP);
if (pg->pghw_cpulist != NULL)
pg->pghw_cpulist_len = len;
}
}
}
static int
cpu2id(void *v)
{
cpu_t *cp = (cpu_t *)v;
ASSERT(v != NULL);
return (cp->cpu_id);
}
static pgid_t
pghw_parent_id(pghw_t *pghw)
{
pg_t *pg = (pg_t *)pghw;
pgid_t parent_id = -1;
if (pg != NULL && strcmp(pg->pg_class->pgc_name, "cmt") == 0) {
pg_cmt_t *cmt = (pg_cmt_t *)pg;
pg_t *parent = (pg_t *)cmt->cmt_parent;
if (parent != NULL)
parent_id = parent->pg_id;
}
return (parent_id);
}