#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <strings.h>
#include <libnvpair.h>
#include <string.h>
#include <sys/types.h>
#include <sys/devfm.h>
#include <fmd_agent_impl.h>
int
fmd_agent_errno(fmd_agent_hdl_t *hdl)
{
return (hdl->agent_errno);
}
int
fmd_agent_seterrno(fmd_agent_hdl_t *hdl, int err)
{
hdl->agent_errno = err;
return (-1);
}
const char *
fmd_agent_strerr(int err)
{
return (strerror(err));
}
const char *
fmd_agent_errmsg(fmd_agent_hdl_t *hdl)
{
return (fmd_agent_strerr(hdl->agent_errno));
}
static int
cleanup_set_errno(fmd_agent_hdl_t *hdl, nvlist_t *innvl, nvlist_t *outnvl,
int err)
{
nvlist_free(innvl);
nvlist_free(outnvl);
return (fmd_agent_seterrno(hdl, err));
}
int
fmd_agent_nvl_ioctl(fmd_agent_hdl_t *hdl, int cmd, uint32_t ver,
nvlist_t *innvl, nvlist_t **outnvlp)
{
fm_ioc_data_t fid;
int err = 0;
char *inbuf = NULL, *outbuf = NULL;
size_t insz = 0, outsz = 0;
if (innvl != NULL) {
if ((err = nvlist_size(innvl, &insz, NV_ENCODE_NATIVE)) != 0)
return (err);
if (insz > FM_IOC_MAXBUFSZ)
return (ENAMETOOLONG);
if ((inbuf = umem_alloc(insz, UMEM_DEFAULT)) == NULL)
return (errno);
if ((err = nvlist_pack(innvl, &inbuf, &insz,
NV_ENCODE_NATIVE, 0)) != 0) {
umem_free(inbuf, insz);
return (err);
}
}
if (outnvlp != NULL) {
outsz = FM_IOC_OUT_BUFSZ;
}
for (;;) {
if (outnvlp != NULL) {
outbuf = umem_alloc(outsz, UMEM_DEFAULT);
if (outbuf == NULL) {
err = errno;
break;
}
}
fid.fid_version = ver;
fid.fid_insz = insz;
fid.fid_inbuf = inbuf;
fid.fid_outsz = outsz;
fid.fid_outbuf = outbuf;
if (ioctl(hdl->agent_devfd, cmd, &fid) < 0) {
if (errno == ENAMETOOLONG && outsz != 0 &&
outsz <= (FM_IOC_OUT_MAXBUFSZ / 2)) {
umem_free(outbuf, outsz);
outbuf = NULL;
outsz *= 2;
} else {
err = errno;
break;
}
} else if (outnvlp != NULL) {
err = nvlist_unpack(fid.fid_outbuf, fid.fid_outsz,
outnvlp, 0);
break;
} else {
break;
}
}
if (inbuf != NULL)
umem_free(inbuf, insz);
if (outbuf != NULL)
umem_free(outbuf, outsz);
return (err);
}
static fmd_agent_hdl_t *
fmd_agent_open_dev(int ver, int mode)
{
fmd_agent_hdl_t *hdl;
int fd, err;
nvlist_t *nvl;
if ((fd = open("/dev/fm", mode)) < 0)
return (NULL);
if ((hdl = umem_alloc(sizeof (fmd_agent_hdl_t),
UMEM_DEFAULT)) == NULL) {
err = errno;
(void) close(fd);
errno = err;
return (NULL);
}
hdl->agent_devfd = fd;
hdl->agent_version = ver;
if ((err = fmd_agent_nvl_ioctl(hdl, FM_IOC_VERSIONS, ver, NULL, &nvl))
< 0) {
(void) close(fd);
umem_free(hdl, sizeof (fmd_agent_hdl_t));
errno = err;
return (NULL);
}
hdl->agent_ioc_versions = nvl;
return (hdl);
}
fmd_agent_hdl_t *
fmd_agent_open(int ver)
{
if (ver > FMD_AGENT_VERSION) {
errno = ENOTSUP;
return (NULL);
}
return (fmd_agent_open_dev(ver, O_RDONLY));
}
void
fmd_agent_close(fmd_agent_hdl_t *hdl)
{
(void) close(hdl->agent_devfd);
nvlist_free(hdl->agent_ioc_versions);
umem_free(hdl, sizeof (fmd_agent_hdl_t));
}
int
fmd_agent_version(fmd_agent_hdl_t *hdl, const char *op, uint32_t *verp)
{
int err;
err = nvlist_lookup_uint32(hdl->agent_ioc_versions,
op, verp);
if (err != 0) {
errno = err;
return (-1);
}
return (0);
}
static int
fmd_agent_pageop_v1(fmd_agent_hdl_t *hdl, int cmd, nvlist_t *fmri)
{
int err;
nvlist_t *nvl = NULL;
if ((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME_TYPE, 0)) != 0 ||
(err = nvlist_add_nvlist(nvl, FM_PAGE_RETIRE_FMRI, fmri)) != 0 ||
(err = fmd_agent_nvl_ioctl(hdl, cmd, 1, nvl, NULL)) != 0)
return (cleanup_set_errno(hdl, nvl, NULL, err));
nvlist_free(nvl);
return (0);
}
static int
fmd_agent_pageop(fmd_agent_hdl_t *hdl, int cmd, nvlist_t *fmri)
{
uint32_t ver;
if (fmd_agent_version(hdl, FM_PAGE_OP_VERSION, &ver) == -1)
return (fmd_agent_seterrno(hdl, errno));
switch (ver) {
case 1:
return (fmd_agent_pageop_v1(hdl, cmd, fmri));
default:
return (fmd_agent_seterrno(hdl, ENOTSUP));
}
}
int
fmd_agent_page_retire(fmd_agent_hdl_t *hdl, nvlist_t *fmri)
{
int rc = fmd_agent_pageop(hdl, FM_IOC_PAGE_RETIRE, fmri);
int err = fmd_agent_errno(hdl);
if (rc == 0 || err == EIO || err == EINVAL) {
if (rc == 0)
(void) fmd_agent_seterrno(hdl, 0);
return (FMD_AGENT_RETIRE_DONE);
}
if (err == EAGAIN)
return (FMD_AGENT_RETIRE_ASYNC);
return (FMD_AGENT_RETIRE_FAIL);
}
int
fmd_agent_page_unretire(fmd_agent_hdl_t *hdl, nvlist_t *fmri)
{
int rc = fmd_agent_pageop(hdl, FM_IOC_PAGE_UNRETIRE, fmri);
int err = fmd_agent_errno(hdl);
if (rc == 0 || err == EIO || err == EINVAL) {
if (rc == 0)
(void) fmd_agent_seterrno(hdl, 0);
return (FMD_AGENT_RETIRE_DONE);
}
return (FMD_AGENT_RETIRE_FAIL);
}
int
fmd_agent_page_isretired(fmd_agent_hdl_t *hdl, nvlist_t *fmri)
{
int rc = fmd_agent_pageop(hdl, FM_IOC_PAGE_STATUS, fmri);
int err = fmd_agent_errno(hdl);
if (rc == 0 || err == EINVAL) {
if (rc == 0)
(void) fmd_agent_seterrno(hdl, 0);
return (FMD_AGENT_RETIRE_DONE);
}
if (err == EAGAIN)
return (FMD_AGENT_RETIRE_ASYNC);
return (FMD_AGENT_RETIRE_FAIL);
}
void
fmd_agent_cache_info_free(fmd_agent_hdl_t *hdl __unused,
fmd_agent_cpu_cache_list_t *cache)
{
for (uint_t cpuno = 0; cpuno < cache->fmc_ncpus; cpuno++) {
fmd_agent_cpu_cache_t *cpu_cache = &cache->fmc_cpus[cpuno];
if (cpu_cache->fmcc_caches == NULL)
continue;
for (uint_t cacheno = 0; cacheno < cpu_cache->fmcc_ncaches;
cacheno++) {
nvlist_free(cpu_cache->fmcc_caches[cacheno]);
}
umem_free(cpu_cache->fmcc_caches, sizeof (nvlist_t *) *
cpu_cache->fmcc_ncaches);
cpu_cache->fmcc_caches = NULL;
}
if (cache->fmc_cpus != NULL) {
umem_free(cache->fmc_cpus, sizeof (fmd_agent_cpu_cache_t) *
cache->fmc_ncpus);
cache->fmc_cpus = NULL;
cache->fmc_ncpus = 0;
}
}
static int
fmd_agent_cache_info_pop_cpu(fmd_agent_cpu_cache_list_t *cache,
nvlist_t *cpu_nvl, uint_t cpuno)
{
int ret;
char cpustr[32];
nvlist_t **cache_nvls;
uint_t ncache_nvls;
fmd_agent_cpu_cache_t *cpu = &cache->fmc_cpus[cpuno];
(void) snprintf(cpustr, sizeof (cpustr), "%u", cpuno);
if ((ret = nvlist_lookup_nvlist_array(cpu_nvl, cpustr, &cache_nvls,
&ncache_nvls)) != 0) {
return (ret);
}
if (ncache_nvls == 0) {
cpu->fmcc_ncaches = 0;
cpu->fmcc_caches = NULL;
return (0);
}
cpu->fmcc_caches = umem_zalloc(sizeof (nvlist_t *) * ncache_nvls,
UMEM_DEFAULT);
if (cpu->fmcc_caches == NULL) {
return (errno);
}
cpu->fmcc_ncaches = ncache_nvls;
for (uint_t i = 0; i < cpu->fmcc_ncaches; i++) {
ret = nvlist_dup(cache_nvls[i], &cpu->fmcc_caches[i], 0);
if (ret != 0) {
return (ret);
}
}
return (0);
}
int
fmd_agent_cache_info(fmd_agent_hdl_t *hdl, fmd_agent_cpu_cache_list_t *cache)
{
int err, ret = 0;
uint32_t ncpus;
nvlist_t *nvl = NULL;
bzero(cache, sizeof (fmd_agent_cpu_cache_list_t));
if ((err = fmd_agent_nvl_ioctl(hdl, FM_IOC_CACHE_INFO, 1, NULL,
&nvl)) != 0) {
ret = fmd_agent_seterrno(hdl, err);
goto out;
}
if ((err = nvlist_lookup_uint32(nvl, FM_CACHE_INFO_NCPUS, &ncpus)) !=
0) {
ret = fmd_agent_seterrno(hdl, err);
goto out;
}
cache->fmc_cpus = umem_zalloc(sizeof (fmd_agent_cpu_cache_t) * ncpus,
UMEM_DEFAULT);
if (cache->fmc_cpus == NULL) {
ret = fmd_agent_seterrno(hdl, errno);
goto out;
}
cache->fmc_ncpus = ncpus;
for (uint_t i = 0; i < cache->fmc_ncpus; i++) {
if ((err = fmd_agent_cache_info_pop_cpu(cache, nvl, i)) != 0) {
ret = fmd_agent_seterrno(hdl, errno);
goto out;
}
}
out:
if (ret != 0) {
fmd_agent_cache_info_free(hdl, cache);
}
nvlist_free(nvl);
return (ret);
}