#include <sys/stat.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/cred.h>
#include <sys/policy.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/modctl.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/conf.h>
#include <sys/debug.h>
#include <sys/systeminfo.h>
#include <sys/fm/protocol.h>
#include <sys/devfm.h>
extern int fm_get_paddr(nvlist_t *, uint64_t *);
#if defined(__x86)
extern int fm_ioctl_physcpu_info(int, nvlist_t *, nvlist_t **);
extern int fm_ioctl_cpu_retire(int, nvlist_t *, nvlist_t **);
extern int fm_ioctl_gentopo_legacy(int, nvlist_t *, nvlist_t **);
#endif
extern int fm_ioctl_cache_info(int, nvlist_t *, nvlist_t **);
static int fm_ioctl_versions(int, nvlist_t *, nvlist_t **);
static int fm_ioctl_page_retire(int, nvlist_t *, nvlist_t **);
typedef struct fm_version {
char *interface;
uint32_t version;
} fm_vers_t;
typedef struct fm_subroutine {
int cmd;
boolean_t priv;
char *version;
int (*func)(int, nvlist_t *, nvlist_t **);
} fm_subr_t;
static const fm_vers_t fm_versions[] = {
{ FM_VERSIONS_VERSION, FM_DRV_VERSION },
{ FM_PAGE_OP_VERSION, 1 },
{ FM_CPU_OP_VERSION, 1 },
{ FM_CPU_INFO_VERSION, 1 },
{ FM_TOPO_LEGACY_VERSION, 1 },
{ FM_CACHE_INFO_VERSION, 1 },
{ NULL, 0 }
};
static const fm_subr_t fm_subrs[] = {
{ FM_IOC_VERSIONS, B_FALSE, FM_VERSIONS_VERSION, fm_ioctl_versions },
{ FM_IOC_PAGE_RETIRE, B_TRUE, FM_PAGE_OP_VERSION,
fm_ioctl_page_retire },
{ FM_IOC_PAGE_STATUS, B_FALSE, FM_PAGE_OP_VERSION,
fm_ioctl_page_retire },
{ FM_IOC_PAGE_UNRETIRE, B_TRUE, FM_PAGE_OP_VERSION,
fm_ioctl_page_retire },
#if defined(__x86)
{ FM_IOC_PHYSCPU_INFO, B_FALSE, FM_CPU_INFO_VERSION,
fm_ioctl_physcpu_info },
{ FM_IOC_CPU_RETIRE, B_TRUE, FM_CPU_OP_VERSION,
fm_ioctl_cpu_retire },
{ FM_IOC_CPU_STATUS, B_FALSE, FM_CPU_OP_VERSION,
fm_ioctl_cpu_retire },
{ FM_IOC_CPU_UNRETIRE, B_TRUE, FM_CPU_OP_VERSION,
fm_ioctl_cpu_retire },
{ FM_IOC_GENTOPO_LEGACY, B_FALSE, FM_TOPO_LEGACY_VERSION,
fm_ioctl_gentopo_legacy },
#endif
{ FM_IOC_CACHE_INFO, B_FALSE, FM_CACHE_INFO_VERSION,
fm_ioctl_cache_info },
{ -1, B_FALSE, NULL, NULL },
};
static dev_info_t *fm_dip;
static boolean_t is_i86xpv;
static nvlist_t *fm_vers_nvl;
static int
fm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
switch (cmd) {
case DDI_ATTACH:
if (ddi_create_minor_node(dip, ddi_get_name(dip), S_IFCHR,
ddi_get_instance(dip), DDI_PSEUDO, 0) != DDI_SUCCESS) {
ddi_remove_minor_node(dip, NULL);
return (DDI_FAILURE);
}
fm_dip = dip;
is_i86xpv = (strcmp(platform, "i86xpv") == 0);
break;
case DDI_RESUME:
break;
default:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
fm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int ret = DDI_SUCCESS;
switch (cmd) {
case DDI_DETACH:
ddi_remove_minor_node(dip, NULL);
fm_dip = NULL;
break;
default:
ret = DDI_FAILURE;
}
return (ret);
}
static int
fm_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
int error;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
*result = fm_dip;
error = DDI_SUCCESS;
break;
case DDI_INFO_DEVT2INSTANCE:
*result = NULL;
error = DDI_SUCCESS;
break;
default:
error = DDI_FAILURE;
}
return (error);
}
static int
fm_open(dev_t *devp, int flag, int typ, struct cred *cred)
{
if (typ != OTYP_CHR)
return (EINVAL);
if (getminor(*devp) != 0)
return (ENXIO);
return (0);
}
static int
fm_ioctl_versions(int cmd, nvlist_t *invl, nvlist_t **onvlp)
{
nvlist_t *nvl;
int err;
if ((err = nvlist_dup(fm_vers_nvl, &nvl, KM_SLEEP)) == 0)
*onvlp = nvl;
return (err);
}
static int
fm_ioctl_page_retire(int cmd, nvlist_t *invl, nvlist_t **onvlp)
{
uint64_t pa;
nvlist_t *fmri;
int err;
if (is_i86xpv)
return (ENOTSUP);
if ((err = nvlist_lookup_nvlist(invl, FM_PAGE_RETIRE_FMRI, &fmri))
!= 0)
return (err);
if ((err = fm_get_paddr(fmri, &pa)) != 0)
return (err);
switch (cmd) {
case FM_IOC_PAGE_STATUS:
return (page_retire_check(pa, NULL));
case FM_IOC_PAGE_RETIRE:
return (page_retire(pa, PR_FMA));
case FM_IOC_PAGE_UNRETIRE:
return (page_unretire(pa));
}
return (ENOTTY);
}
static int
fm_ioctl(dev_t dev, int cmd, intptr_t data, int flag, cred_t *cred, int *rvalp)
{
char *buf;
int err;
uint_t model;
const fm_subr_t *subr;
uint32_t vers;
fm_ioc_data_t fid;
nvlist_t *invl = NULL, *onvl = NULL;
#ifdef _MULTI_DATAMODEL
fm_ioc_data32_t fid32;
#endif
if (getminor(dev) != 0)
return (ENXIO);
for (subr = fm_subrs; subr->cmd != cmd; subr++)
if (subr->cmd == -1)
return (ENOTTY);
if (subr->priv && (flag & FWRITE) == 0 &&
secpolicy_sys_config(CRED(), 0) != 0)
return (EPERM);
model = ddi_model_convert_from(flag & FMODELS);
switch (model) {
#ifdef _MULTI_DATAMODEL
case DDI_MODEL_ILP32:
if (ddi_copyin((void *)data, &fid32,
sizeof (fm_ioc_data32_t), flag) != 0)
return (EFAULT);
fid.fid_version = fid32.fid_version;
fid.fid_insz = fid32.fid_insz;
fid.fid_inbuf = (caddr_t)(uintptr_t)fid32.fid_inbuf;
fid.fid_outsz = fid32.fid_outsz;
fid.fid_outbuf = (caddr_t)(uintptr_t)fid32.fid_outbuf;
break;
#endif
case DDI_MODEL_NONE:
default:
if (ddi_copyin((void *)data, &fid, sizeof (fm_ioc_data_t),
flag) != 0)
return (EFAULT);
}
if (nvlist_lookup_uint32(fm_vers_nvl, subr->version, &vers) != 0 ||
fid.fid_version != vers)
return (ENOTSUP);
if (fid.fid_insz > FM_IOC_MAXBUFSZ)
return (ENAMETOOLONG);
if (fid.fid_outsz > FM_IOC_OUT_MAXBUFSZ)
return (EINVAL);
if (fid.fid_insz != 0 && fid.fid_inbuf != (caddr_t)0) {
buf = kmem_alloc(fid.fid_insz, KM_SLEEP);
if (ddi_copyin(fid.fid_inbuf, buf, fid.fid_insz, flag) != 0) {
kmem_free(buf, fid.fid_insz);
return (EFAULT);
}
err = nvlist_unpack(buf, fid.fid_insz, &invl, KM_SLEEP);
kmem_free(buf, fid.fid_insz);
if (err != 0)
return (err);
}
err = subr->func(cmd, invl, &onvl);
nvlist_free(invl);
if (err != 0) {
nvlist_free(onvl);
return (err);
}
if (onvl != NULL) {
size_t sz;
if ((err = nvlist_size(onvl, &sz, NV_ENCODE_NATIVE)) != 0) {
nvlist_free(onvl);
return (err);
}
if (sz > fid.fid_outsz) {
nvlist_free(onvl);
return (ENAMETOOLONG);
}
buf = kmem_alloc(sz, KM_SLEEP);
if ((err = nvlist_pack(onvl, &buf, &sz, NV_ENCODE_NATIVE,
KM_SLEEP)) != 0) {
kmem_free(buf, sz);
nvlist_free(onvl);
return (err);
}
nvlist_free(onvl);
if (ddi_copyout(buf, fid.fid_outbuf, sz, flag) != 0) {
kmem_free(buf, sz);
return (EFAULT);
}
kmem_free(buf, sz);
fid.fid_outsz = sz;
switch (model) {
#ifdef _MULTI_DATAMODEL
case DDI_MODEL_ILP32:
fid32.fid_outsz = (size32_t)fid.fid_outsz;
if (ddi_copyout(&fid32, (void *)data,
sizeof (fm_ioc_data32_t), flag) != 0)
return (EFAULT);
break;
#endif
case DDI_MODEL_NONE:
default:
if (ddi_copyout(&fid, (void *)data,
sizeof (fm_ioc_data_t), flag) != 0)
return (EFAULT);
}
}
return (err);
}
static struct cb_ops fm_cb_ops = {
fm_open,
nulldev,
nodev,
nodev,
nodev,
nodev,
nodev,
fm_ioctl,
nodev,
nodev,
nodev,
nochpoll,
ddi_prop_op,
NULL,
D_NEW | D_MP | D_64BIT | D_U64BIT
};
static struct dev_ops fm_ops = {
DEVO_REV,
0,
fm_info,
nulldev,
nulldev,
fm_attach,
fm_detach,
nodev,
&fm_cb_ops,
(struct bus_ops *)0
};
static struct modldrv modldrv = {
&mod_driverops, "fault management driver", &fm_ops,
};
static struct modlinkage modlinkage = {
MODREV_1, &modldrv, NULL
};
int
_init(void)
{
const fm_vers_t *p;
int ret;
if ((ret = mod_install(&modlinkage)) == 0) {
(void) nvlist_alloc(&fm_vers_nvl, NV_UNIQUE_NAME, KM_SLEEP);
for (p = fm_versions; p->interface != NULL; p++)
(void) nvlist_add_uint32(fm_vers_nvl, p->interface,
p->version);
}
return (ret);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
_fini(void)
{
int ret;
if ((ret = mod_remove(&modlinkage)) == 0) {
nvlist_free(fm_vers_nvl);
}
return (ret);
}