#include <sys/types.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/conf.h>
#include <sys/stat.h>
#include <sys/processor.h>
#include <sys/cpuvar.h>
#include <sys/disp.h>
#include <sys/kmem.h>
#include <sys/modctl.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/nvpair.h>
#include <sys/policy.h>
#include <sys/machsystm.h>
#include <sys/cpc_impl.h>
#include <sys/cpc_pcbe.h>
#include <sys/kcpc.h>
static int kcpc_copyin_set(kcpc_set_t **set, void *ubuf, size_t len);
static int kcpc_verify_set(kcpc_set_t *set);
static uint32_t kcpc_nvlist_npairs(nvlist_t *list);
#define ATTRLIST "picnum"
#define SEPARATOR ","
static int
cpc(int cmd, id_t lwpid, void *udata1, void *udata2, void *udata3)
{
kthread_t *t;
int error;
int size;
const char *str;
int code;
ASSERT(pcbe_ops != NULL);
if (curproc->p_agenttp == curthread) {
if ((t = idtot(curproc, lwpid)) == NULL || t == curthread)
return (set_errno(ESRCH));
ASSERT(t->t_tid == lwpid && ttolwp(t) != NULL);
} else
t = curthread;
if (t->t_cpc_set == NULL && (cmd == CPC_SAMPLE || cmd == CPC_RELE))
return (set_errno(EINVAL));
switch (cmd) {
case CPC_BIND:
rw_enter(&kcpc_cpuctx_lock, RW_READER);
if (kcpc_cpuctx || dtrace_cpc_in_use) {
rw_exit(&kcpc_cpuctx_lock);
return (set_errno(EAGAIN));
}
if (kcpc_hw_lwp_hook() != 0) {
rw_exit(&kcpc_cpuctx_lock);
return (set_errno(EACCES));
}
if (t->t_cpc_set != NULL)
(void) kcpc_unbind(t->t_cpc_set);
ASSERT(t->t_cpc_set == NULL);
if ((error = kcpc_copyin_set(&t->t_cpc_set, udata1,
(size_t)udata2)) != 0) {
rw_exit(&kcpc_cpuctx_lock);
return (set_errno(error));
}
if ((error = kcpc_verify_set(t->t_cpc_set)) != 0) {
rw_exit(&kcpc_cpuctx_lock);
kcpc_free_set(t->t_cpc_set);
t->t_cpc_set = NULL;
if (copyout(&error, udata3, sizeof (error)) == -1)
return (set_errno(EFAULT));
return (set_errno(EINVAL));
}
if ((error = kcpc_bind_thread(t->t_cpc_set, t, &code)) != 0) {
rw_exit(&kcpc_cpuctx_lock);
kcpc_free_set(t->t_cpc_set);
t->t_cpc_set = NULL;
if ((error == EINVAL || error == EACCES) &&
copyout(&code, udata3, sizeof (code)) == -1)
return (set_errno(EFAULT));
return (set_errno(error));
}
rw_exit(&kcpc_cpuctx_lock);
return (0);
case CPC_SAMPLE:
if (t->t_cpc_set->ks_ctx->kc_cpuid != -1)
return (set_errno(EINVAL));
if ((error = kcpc_sample(t->t_cpc_set, udata1, udata2,
udata3)) != 0)
return (set_errno(error));
return (0);
case CPC_PRESET:
case CPC_RESTART:
if (t->t_cpc_set == NULL)
return (set_errno(EINVAL));
if (cmd == CPC_PRESET) {
if ((error = kcpc_preset(t->t_cpc_set, (intptr_t)udata1,
((uint64_t)(uintptr_t)udata2 << 32ULL) |
(uint64_t)(uintptr_t)udata3)) != 0)
return (set_errno(error));
} else {
if ((error = kcpc_restart(t->t_cpc_set)) != 0)
return (set_errno(error));
}
return (0);
case CPC_ENABLE:
case CPC_DISABLE:
udata1 = 0;
case CPC_USR_EVENTS:
case CPC_SYS_EVENTS:
if (t != curthread || t->t_cpc_set == NULL)
return (set_errno(EINVAL));
rw_enter(&kcpc_cpuctx_lock, RW_READER);
if ((error = kcpc_enable(t,
cmd, (int)(uintptr_t)udata1)) != 0) {
rw_exit(&kcpc_cpuctx_lock);
return (set_errno(error));
}
rw_exit(&kcpc_cpuctx_lock);
return (0);
case CPC_NPIC:
return (cpc_ncounters);
case CPC_CAPS:
return (pcbe_ops->pcbe_caps);
case CPC_EVLIST_SIZE:
case CPC_LIST_EVENTS:
if ((uintptr_t)udata2 >= cpc_ncounters)
return (set_errno(EINVAL));
size = strlen(
pcbe_ops->pcbe_list_events((uintptr_t)udata2)) + 1;
if (cmd == CPC_EVLIST_SIZE) {
if (suword32(udata1, size) == -1)
return (set_errno(EFAULT));
} else {
if (copyout(
pcbe_ops->pcbe_list_events((uintptr_t)udata2),
udata1, size) == -1)
return (set_errno(EFAULT));
}
return (0);
case CPC_ATTRLIST_SIZE:
case CPC_LIST_ATTRS:
str = pcbe_ops->pcbe_list_attrs();
size = strlen(str) + sizeof (SEPARATOR ATTRLIST) + 1;
if (str[0] != '\0')
size += 1;
if (cmd == CPC_ATTRLIST_SIZE) {
if (suword32(udata1, size) == -1)
return (set_errno(EFAULT));
} else {
if (copyout(str, udata1, strlen(str)) == -1)
return (set_errno(EFAULT));
if (str[0] != '\0') {
if (copyout(SEPARATOR ATTRLIST,
((char *)udata1) + strlen(str),
strlen(SEPARATOR ATTRLIST) + 1)
== -1)
return (set_errno(EFAULT));
} else
if (copyout(ATTRLIST,
(char *)udata1 + strlen(str),
strlen(ATTRLIST) + 1) == -1)
return (set_errno(EFAULT));
}
return (0);
case CPC_IMPL_NAME:
case CPC_CPUREF:
if (cmd == CPC_IMPL_NAME) {
str = pcbe_ops->pcbe_impl_name();
ASSERT(strlen(str) < CPC_MAX_IMPL_NAME);
} else {
str = pcbe_ops->pcbe_cpuref();
ASSERT(strlen(str) < CPC_MAX_CPUREF);
}
if (copyout(str, udata1, strlen(str) + 1) != 0)
return (set_errno(EFAULT));
return (0);
case CPC_INVALIDATE:
kcpc_invalidate(t);
return (0);
case CPC_RELE:
if ((error = kcpc_unbind(t->t_cpc_set)) != 0)
return (set_errno(error));
return (0);
default:
return (set_errno(EINVAL));
}
}
static int
kcpc_ioctl(dev_t dev, int cmd, intptr_t data, int flags, cred_t *cr, int *rvp)
{
kthread_t *t = curthread;
processorid_t cpuid;
void *udata1 = NULL;
void *udata2 = NULL;
void *udata3 = NULL;
int error;
int code;
STRUCT_DECL(__cpc_args, args);
STRUCT_INIT(args, flags);
if (curthread->t_bind_cpu != getminor(dev))
return (EAGAIN);
cpuid = getminor(dev);
if (cmd == CPCIO_BIND || cmd == CPCIO_SAMPLE) {
if (copyin((void *)data, STRUCT_BUF(args),
STRUCT_SIZE(args)) == -1)
return (EFAULT);
udata1 = STRUCT_FGETP(args, udata1);
udata2 = STRUCT_FGETP(args, udata2);
udata3 = STRUCT_FGETP(args, udata3);
}
switch (cmd) {
case CPCIO_BIND:
if (t->t_cpc_set != NULL) {
(void) kcpc_unbind(t->t_cpc_set);
ASSERT(t->t_cpc_set == NULL);
}
if ((error = kcpc_copyin_set(&t->t_cpc_set, udata1,
(size_t)udata2)) != 0) {
return (error);
}
if ((error = kcpc_verify_set(t->t_cpc_set)) != 0) {
kcpc_free_set(t->t_cpc_set);
t->t_cpc_set = NULL;
if (copyout(&error, udata3, sizeof (error)) == -1)
return (EFAULT);
return (EINVAL);
}
if ((error = kcpc_bind_cpu(t->t_cpc_set, cpuid, &code)) != 0) {
kcpc_free_set(t->t_cpc_set);
t->t_cpc_set = NULL;
if ((error == EINVAL || error == EACCES) &&
copyout(&code, udata3, sizeof (code)) == -1)
return (EFAULT);
return (error);
}
return (0);
case CPCIO_SAMPLE:
if (t->t_cpc_set == NULL)
return (EINVAL);
if ((error = kcpc_sample(t->t_cpc_set, udata1, udata2,
udata3)) != 0)
return (error);
return (0);
case CPCIO_RELE:
if (t->t_cpc_set == NULL)
return (EINVAL);
return (kcpc_unbind(t->t_cpc_set));
default:
return (EINVAL);
}
}
#define KCPC_MINOR_SHARED ((minor_t)0x3fffful)
static ulong_t *kcpc_cpumap;
static int
kcpc_open(dev_t *dev, int flags, int otyp, cred_t *cr)
{
processorid_t cpuid;
int error;
ASSERT(pcbe_ops != NULL);
if ((error = secpolicy_cpc_cpu(cr)) != 0)
return (error);
if (getminor(*dev) != KCPC_MINOR_SHARED)
return (ENXIO);
if ((cpuid = curthread->t_bind_cpu) == PBIND_NONE)
return (EINVAL);
if (cpuid > max_cpuid)
return (EINVAL);
rw_enter(&kcpc_cpuctx_lock, RW_WRITER);
if (++kcpc_cpuctx == 1) {
ASSERT(kcpc_cpumap == NULL);
if (dtrace_cpc_in_use) {
kcpc_cpuctx--;
rw_exit(&kcpc_cpuctx_lock);
return (EAGAIN);
}
kcpc_cpumap = kmem_zalloc(BT_SIZEOFMAP(max_cpuid + 1),
KM_SLEEP);
kcpc_invalidate_all();
} else if (BT_TEST(kcpc_cpumap, cpuid)) {
kcpc_cpuctx--;
rw_exit(&kcpc_cpuctx_lock);
return (EAGAIN);
} else if (kcpc_hw_cpu_hook(cpuid, kcpc_cpumap) != 0) {
kcpc_cpuctx--;
rw_exit(&kcpc_cpuctx_lock);
return (EACCES);
}
BT_SET(kcpc_cpumap, cpuid);
rw_exit(&kcpc_cpuctx_lock);
*dev = makedevice(getmajor(*dev), (minor_t)cpuid);
return (0);
}
static int
kcpc_close(dev_t dev, int flags, int otyp, cred_t *cr)
{
rw_enter(&kcpc_cpuctx_lock, RW_WRITER);
BT_CLEAR(kcpc_cpumap, getminor(dev));
if (--kcpc_cpuctx == 0) {
kmem_free(kcpc_cpumap, BT_SIZEOFMAP(max_cpuid + 1));
kcpc_cpumap = NULL;
}
ASSERT(kcpc_cpuctx >= 0);
rw_exit(&kcpc_cpuctx_lock);
return (0);
}
#define CPC_MIN_PACKSIZE 4
#define CPC_MAX_PACKSIZE 10000
#define CPC_MAX_NREQS 100
#define CPC_MAX_ATTRS 50
int
kcpc_copyin_set(kcpc_set_t **inset, void *ubuf, size_t len)
{
kcpc_set_t *set;
int i;
int j;
char *packbuf;
nvlist_t *nvl;
nvpair_t *nvp = NULL;
nvlist_t *attrs;
nvpair_t *nvp_attr;
kcpc_attr_t *attrp;
nvlist_t **reqlist;
uint_t nreqs;
uint64_t uint64;
uint32_t uint32;
uint32_t setflags = (uint32_t)-1;
char *string;
char *name;
if (len < CPC_MIN_PACKSIZE || len > CPC_MAX_PACKSIZE)
return (EINVAL);
packbuf = kmem_alloc(len, KM_SLEEP);
if (copyin(ubuf, packbuf, len) == -1) {
kmem_free(packbuf, len);
return (EFAULT);
}
if (nvlist_unpack(packbuf, len, &nvl, KM_SLEEP) != 0) {
kmem_free(packbuf, len);
return (EINVAL);
}
kmem_free(packbuf, len);
i = 0;
while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
switch (nvpair_type(nvp)) {
case DATA_TYPE_UINT32:
if (strcmp(nvpair_name(nvp), "flags") != 0 ||
nvpair_value_uint32(nvp, &setflags) != 0) {
nvlist_free(nvl);
return (EINVAL);
}
break;
case DATA_TYPE_NVLIST_ARRAY:
if (strcmp(nvpair_name(nvp), "reqs") != 0 ||
nvpair_value_nvlist_array(nvp, &reqlist,
&nreqs) != 0) {
nvlist_free(nvl);
return (EINVAL);
}
break;
default:
nvlist_free(nvl);
return (EINVAL);
}
i++;
}
if (i != 2) {
nvlist_free(nvl);
return (EINVAL);
}
if (nreqs > CPC_MAX_NREQS) {
nvlist_free(nvl);
return (EINVAL);
}
set = kmem_zalloc(sizeof (kcpc_set_t), KM_SLEEP);
set->ks_req = (kcpc_request_t *)kmem_zalloc(sizeof (kcpc_request_t) *
nreqs, KM_SLEEP);
set->ks_nreqs = nreqs;
set->ks_flags = setflags;
set->ks_state &= ~KCPC_SET_BOUND;
for (i = 0; i < nreqs; i++) {
nvp = NULL;
set->ks_req[i].kr_picnum = -1;
while ((nvp = nvlist_next_nvpair(reqlist[i], nvp)) != NULL) {
name = nvpair_name(nvp);
switch (nvpair_type(nvp)) {
case DATA_TYPE_UINT32:
if (nvpair_value_uint32(nvp, &uint32) == EINVAL)
goto inval;
if (strcmp(name, "cr_flags") == 0)
set->ks_req[i].kr_flags = uint32;
if (strcmp(name, "cr_index") == 0)
set->ks_req[i].kr_index = uint32;
break;
case DATA_TYPE_UINT64:
if (nvpair_value_uint64(nvp, &uint64) == EINVAL)
goto inval;
if (strcmp(name, "cr_preset") == 0)
set->ks_req[i].kr_preset = uint64;
break;
case DATA_TYPE_STRING:
if (nvpair_value_string(nvp, &string) == EINVAL)
goto inval;
if (strcmp(name, "cr_event") == 0)
(void) strncpy(set->ks_req[i].kr_event,
string, CPC_MAX_EVENT_LEN);
break;
case DATA_TYPE_NVLIST:
if (strcmp(name, "cr_attr") != 0)
goto inval;
if (nvpair_value_nvlist(nvp, &attrs) == EINVAL)
goto inval;
nvp_attr = NULL;
if (nvlist_lookup_uint64(attrs, "picnum",
&uint64) == 0) {
if (nvlist_remove(attrs, "picnum",
DATA_TYPE_UINT64) != 0)
panic("nvlist %p faulty",
(void *)attrs);
set->ks_req[i].kr_picnum = uint64;
}
if ((set->ks_req[i].kr_nattrs =
kcpc_nvlist_npairs(attrs)) == 0)
break;
if (set->ks_req[i].kr_nattrs > CPC_MAX_ATTRS)
goto inval;
set->ks_req[i].kr_attr =
kmem_alloc(set->ks_req[i].kr_nattrs *
sizeof (kcpc_attr_t), KM_SLEEP);
j = 0;
while ((nvp_attr = nvlist_next_nvpair(attrs,
nvp_attr)) != NULL) {
attrp = &set->ks_req[i].kr_attr[j];
if (nvpair_type(nvp_attr) !=
DATA_TYPE_UINT64)
goto inval;
(void) strncpy(attrp->ka_name,
nvpair_name(nvp_attr),
CPC_MAX_ATTR_LEN);
if (nvpair_value_uint64(nvp_attr,
&(attrp->ka_val)) == EINVAL)
goto inval;
j++;
}
ASSERT(j == set->ks_req[i].kr_nattrs);
default:
break;
}
}
}
nvlist_free(nvl);
*inset = set;
return (0);
inval:
nvlist_free(nvl);
kcpc_free_set(set);
return (EINVAL);
}
static uint32_t
kcpc_nvlist_npairs(nvlist_t *list)
{
nvpair_t *nvp = NULL;
uint32_t n = 0;
while ((nvp = nvlist_next_nvpair(list, nvp)) != NULL)
n++;
return (n);
}
static int
kcpc_verify_set(kcpc_set_t *set)
{
kcpc_request_t *rp;
int i;
uint64_t bitmap = 0;
int n;
if (set->ks_nreqs > cpc_ncounters)
return (-1);
if (CPC_SET_VALID_FLAGS(set->ks_flags) == 0)
return (-1);
for (i = 0; i < set->ks_nreqs; i++) {
rp = &set->ks_req[i];
if (rp->kr_picnum >= (int)cpc_ncounters)
return (CPC_INVALID_PICNUM);
if ((n = set->ks_req[i].kr_picnum) != -1) {
if ((bitmap & (1 << n)) != 0)
return (-1);
bitmap |= (1 << n);
}
if (rp->kr_index < 0 || rp->kr_index >= set->ks_nreqs)
return (-1);
if (KCPC_REQ_VALID_FLAGS(rp->kr_flags) == 0)
return (CPC_REQ_INVALID_FLAGS);
}
return (0);
}
static struct cb_ops cb_ops = {
kcpc_open,
kcpc_close,
nodev,
nodev,
nodev,
nodev,
nodev,
kcpc_ioctl,
nodev,
nodev,
nodev,
nochpoll,
ddi_prop_op,
NULL,
D_NEW | D_MP
};
static int
kcpc_probe(dev_info_t *devi)
{
return (DDI_PROBE_SUCCESS);
}
static dev_info_t *kcpc_devi;
static int
kcpc_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
if (cmd != DDI_ATTACH)
return (DDI_FAILURE);
kcpc_devi = devi;
return (ddi_create_minor_node(devi, "shared", S_IFCHR,
KCPC_MINOR_SHARED, DDI_PSEUDO, 0));
}
static int
kcpc_getinfo(dev_info_t *devi, ddi_info_cmd_t cmd, void *arg, void **result)
{
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
switch (getminor((dev_t)arg)) {
case KCPC_MINOR_SHARED:
*result = kcpc_devi;
return (DDI_SUCCESS);
default:
break;
}
break;
case DDI_INFO_DEVT2INSTANCE:
*result = 0;
return (DDI_SUCCESS);
default:
break;
}
return (DDI_FAILURE);
}
static struct dev_ops dev_ops = {
DEVO_REV,
0,
kcpc_getinfo,
nulldev,
kcpc_probe,
kcpc_attach,
nodev,
nodev,
&cb_ops,
(struct bus_ops *)0,
NULL,
ddi_quiesce_not_needed,
};
static struct modldrv modldrv = {
&mod_driverops,
"cpc sampling driver",
&dev_ops
};
static struct sysent cpc_sysent = {
5,
SE_NOUNLOAD | SE_ARGC | SE_32RVAL1,
cpc
};
static struct modlsys modlsys = {
&mod_syscallops,
"cpc sampling system call",
&cpc_sysent
};
#ifdef _SYSCALL32_IMPL
static struct modlsys modlsys32 = {
&mod_syscallops32,
"32-bit cpc sampling system call",
&cpc_sysent
};
#endif
static struct modlinkage modl = {
MODREV_1,
&modldrv,
&modlsys,
#ifdef _SYSCALL32_IMPL
&modlsys32,
#endif
};
int
_init(void)
{
if (kcpc_init() != 0)
return (ENOTSUP);
return (mod_install(&modl));
}
int
_fini(void)
{
return (mod_remove(&modl));
}
int
_info(struct modinfo *mi)
{
return (mod_info(&modl, mi));
}