#include <sys/fm/protocol.h>
#include <fm/topo_mod.h>
#include <libhotplug.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#define TOPO_PGROUP_LIBHP "libhp"
#define TOPO_PGROUP_LIBHP_CONNECTOR "connector"
#define TOPO_PGROUP_LIBHP_OPTION "option"
#define TOPO_PGROUP_LIBHP_OPT_ON "opt_on"
#define TOPO_PGROUP_LIBHP_OPT_OFF "opt_off"
#define LIBHP_OFF_MODE_VALUE "value"
#define LIBHP_OFF_MODE_CONN_POWER "conn_power"
#define FAC_PROV_LIBHP_OPTLEN 128
static hp_node_t
fac_prov_libhp_find_node(topo_mod_t *mod, const char *conn)
{
char cfg[PATH_MAX], link[PATH_MAX];
ssize_t ret;
const char *prefix = "/devices";
char *start, *end;
hp_node_t node;
if (snprintf(cfg, sizeof (cfg), "/dev/cfg/%s", conn) >= sizeof (cfg)) {
topo_mod_dprintf(mod, "failed to construct /dev/cfg path");
return (NULL);
}
ret = readlink(cfg, link, sizeof (link));
if (ret < 0) {
topo_mod_dprintf(mod, "failed to readlink %s: %s", cfg,
strerror(errno));
return (NULL);
}
if ((size_t)ret >= sizeof (link)) {
topo_mod_dprintf(mod, "cannot process readlink of %s: link "
"did not fit in buffer", cfg);
return (NULL);
}
link[ret] = '\0';
start = strstr(link, prefix);
if (start == NULL) {
topo_mod_dprintf(mod, "failed to find %s in %s", prefix, link);
return (NULL);
}
start += strlen(prefix);
end = strchr(start, ':');
if (end == NULL) {
topo_mod_dprintf(mod, "failed to find ':' to indicate start of "
"minor node in %s", start);
return (NULL);
}
*end = '\0';
topo_mod_dprintf(mod, "attempting to hp_init %s %s", start, conn);
node = hp_init(start, conn, 0);
if (node == NULL) {
topo_mod_dprintf(mod, "failed to init hp node: %s\n",
strerror(errno));
return (NULL);
}
return (node);
}
static int
fac_prov_libhp_set_val(topo_mod_t *mod, hp_node_t hp, const char *opt_name,
const char *val)
{
int ret;
char buf[FAC_PROV_LIBHP_OPTLEN];
char *res = NULL;
if (snprintf(buf, sizeof (buf), "%s=%s", opt_name, val) >=
sizeof (buf)) {
topo_mod_dprintf(mod, "failed to construct option buf");
return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
}
ret = hp_set_private(hp, buf, &res);
if (ret != 0) {
topo_mod_dprintf(mod, "failed to set prop %s: %s", buf,
strerror(ret));
return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
}
free(res);
return (0);
}
static int
fac_prov_libhp_get_opt(topo_mod_t *mod, hp_node_t hp, const char *opt_name,
const char *opt_on, nvlist_t **nvout)
{
int ret;
char *val;
uint32_t state;
nvlist_t *nvl;
char buf[FAC_PROV_LIBHP_OPTLEN];
if (snprintf(buf, sizeof (buf), "%s=%s", opt_name, opt_on) >=
sizeof (buf)) {
topo_mod_dprintf(mod, "failed to construct option buf");
return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
}
ret = hp_get_private(hp, opt_name, &val);
if (ret != 0) {
topo_mod_dprintf(mod, "failed to get hp node private prop "
"%s: %s", opt_name, strerror(ret));
return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
}
topo_mod_dprintf(mod, "got hp node opt %s", val);
if (strcmp(val, buf) == 0) {
state = TOPO_LED_STATE_ON;
} else {
state = TOPO_LED_STATE_OFF;
}
free(val);
if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
nvlist_add_string(nvl, TOPO_PROP_VAL_NAME, TOPO_LED_MODE) != 0 ||
nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_UINT32) != 0 ||
nvlist_add_uint32(nvl, TOPO_PROP_VAL_VAL, state) != 0) {
topo_mod_dprintf(mod, "failed to construct output nvl for "
"libhp node state");
nvlist_free(nvl);
return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
}
*nvout = nvl;
return (0);
}
static int
fac_prov_libhp_opt_set(topo_mod_t *mod, tnode_t *tn, topo_version_t vers,
nvlist_t *in, nvlist_t **nvout)
{
int err, ret = -1;
char *conn = NULL, *opt_name = NULL, *opt_on = NULL, *opt_off = NULL;
hp_node_t hp = NULL;
nvlist_t *pargs;
if (vers != 0) {
return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW));
}
if (topo_prop_get_string(tn, TOPO_PGROUP_LIBHP,
TOPO_PGROUP_LIBHP_CONNECTOR, &conn, &err) != 0 ||
topo_prop_get_string(tn, TOPO_PGROUP_LIBHP,
TOPO_PGROUP_LIBHP_OPTION, &opt_name, &err) != 0 ||
topo_prop_get_string(tn, TOPO_PGROUP_LIBHP,
TOPO_PGROUP_LIBHP_OPT_ON, &opt_on, &err) != 0 ||
topo_prop_get_string(tn, TOPO_PGROUP_LIBHP,
TOPO_PGROUP_LIBHP_OPT_OFF, &opt_off, &err) != 0) {
topo_mod_dprintf(mod, "failed to get required libhp props: %s",
topo_strerror(err));
(void) topo_mod_seterrno(mod, err);
goto out;
}
hp = fac_prov_libhp_find_node(mod, conn);
if (hp == NULL) {
(void) topo_mod_seterrno(mod, EMOD_UNKNOWN);
goto out;
}
if ((nvlist_lookup_nvlist(in, TOPO_PROP_PARGS, &pargs) == 0) &&
nvlist_exists(pargs, TOPO_PROP_VAL_VAL)) {
uint32_t val;
err = nvlist_lookup_uint32(pargs, TOPO_PROP_VAL_VAL, &val);
if (err != 0) {
ret = topo_mod_seterrno(mod, EMOD_NVL_INVAL);
goto out;
}
switch (val) {
case TOPO_LED_STATE_ON:
ret = fac_prov_libhp_set_val(mod, hp, opt_name, opt_on);
break;
case TOPO_LED_STATE_OFF:
ret = fac_prov_libhp_set_val(mod, hp, opt_name,
opt_off);
break;
default:
topo_mod_dprintf(mod, "unknown LED mode: 0x%x\n", val);
ret = topo_mod_seterrno(mod, EMOD_NVL_INVAL);
break;
}
} else {
ret = fac_prov_libhp_get_opt(mod, hp, opt_name, opt_on, nvout);
}
out:
topo_mod_strfree(mod, conn);
topo_mod_strfree(mod, opt_name);
topo_mod_strfree(mod, opt_on);
topo_mod_strfree(mod, opt_off);
if (hp != NULL) {
hp_fini(hp);
}
return (ret);
}
static const topo_method_t fac_prov_libhp_methods[] = {
{ "libhp_opt_set", TOPO_PROP_METH_DESC, 0,
TOPO_STABILITY_INTERNAL, fac_prov_libhp_opt_set },
{ NULL }
};
static int
topo_fac_prov_libhp_enum(topo_mod_t *mod, tnode_t *tn, const char *name,
topo_instance_t min, topo_instance_t max, void *modarg, void *data)
{
const char *tname = topo_node_name(tn);
topo_instance_t inst = topo_node_instance(tn);
int flags = topo_node_flags(tn);
topo_mod_dprintf(mod, "asked to enum %s [%" PRIu64 ", %" PRIu64 "] on "
"%s[%" PRIu64 "]", name, min, max, tname, inst);
if (flags != TOPO_NODE_FACILITY) {
topo_mod_dprintf(mod, "node %s[%" PRIu64 "] has unexpected "
"flags: 0x%x", tname, inst, flags);
return (-1);
}
if (topo_method_register(mod, tn, fac_prov_libhp_methods) != 0) {
topo_mod_dprintf(mod, "failed to register libhp facility "
"methods: %s", topo_mod_errmsg(mod));
return (-1);
}
return (0);
}
static const topo_modops_t fac_prov_libhp_ops = {
topo_fac_prov_libhp_enum, NULL
};
static const topo_modinfo_t fac_prov_libhp_mod = {
"libhotplug facility provider", FM_FMRI_SCHEME_HC, TOPO_VERSION,
&fac_prov_libhp_ops
};
int
_topo_init(topo_mod_t *mod, topo_version_t version)
{
if (getenv("TOPOFACLIBHPDEBUG") != NULL)
topo_mod_setdebug(mod);
return (topo_mod_register(mod, &fac_prov_libhp_mod, TOPO_VERSION));
}
void
_topo_fini(topo_mod_t *mod)
{
topo_mod_unregister(mod);
}