#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <synch.h>
#include <thread.h>
#include <unistd.h>
#include <utility.h>
#include <sys/mdesc.h>
#include <sys/mdesc_impl.h>
#include <sys/debug.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include "ldma.h"
#include "libds.h"
#include "libv12n.h"
static void *v12n_ds_dlhdl = NULL;
static int (*v12n_ds_send_msg)(ds_hdl_t, void *, size_t) = NULL;
static int (*v12n_ds_clnt_reg)(ds_capability_t *, ds_ops_t *);
static int (*v12n_ds_unreg_svc)(char *, boolean_t);
#define LDMA_SYSTEM_NVERS \
(sizeof (v12n_ldma_system_vers) / sizeof (ds_ver_t))
static ds_ver_t v12n_ldma_system_vers[] = { { 1, 0} };
static ds_capability_t v12n_ldma_cap = {
LDMA_NAME_SYSTEM,
v12n_ldma_system_vers,
LDMA_SYSTEM_NVERS
};
static void v12n_ldma_register_handler(ds_hdl_t hdl, ds_cb_arg_t arg,
ds_ver_t *ver, ds_domain_hdl_t dhdl);
static void v12n_ldma_data_handler(ds_hdl_t hdl, ds_cb_arg_t arg, void *buf,
size_t buflen);
static ds_ops_t v12n_ldma_ops = {
v12n_ldma_register_handler,
NULL,
v12n_ldma_data_handler,
NULL
};
#define V12N_LDMA_CVINVALID -1
#define V12N_LDMA_REGWAITING 0
#define V12N_LDMA_REGRECEIVED 1
#define V12N_LDMA_MSGWAITING 2
#define V12N_LDMA_MSGRECEIVED 3
#define V12N_LDMA_MSGERROR 4
static ds_hdl_t v12n_ldma_ctrl_hdl = DS_INVALID_HDL;
static int v12n_ldma_msgtype;
static char *v12n_ldma_msgstr;
static mutex_t v12n_ldma_lock = DEFAULTMUTEX;
static cond_t v12n_ldma_cv = DEFAULTCV;
static int v12n_ldma_cv_state = V12N_LDMA_CVINVALID;
static mutex_t v12n_ldma_cv_lock = DEFAULTMUTEX;
static int v12n_ldma_timeout = 15;
static int v12n_ldma_sleeptime = 1;
#define V12N_LDOMS_SUPPORTED (V12N_CAP_SUPPORTED | V12N_CAP_ENABLED | \
V12N_CAP_IMPL_LDOMS)
#define MD_DEVICE "/dev/mdesc"
static void
v12n_md_free(void *buf, size_t n)
{
free(buf);
}
static md_t *
v12n_md_init()
{
md_t *mdp;
char *buf = NULL;
md_header_t mdh;
int md_size;
int fd;
fd = open(MD_DEVICE, O_RDONLY);
if (fd == -1) {
return (NULL);
}
if (read(fd, &mdh, sizeof (md_header_t)) != sizeof (md_header_t))
goto errdone;
md_size = sizeof (md_header_t) + mdh.node_blk_sz + mdh.name_blk_sz +
mdh.data_blk_sz;
if ((buf = malloc(md_size)) == NULL)
goto errdone;
(void) memcpy(buf, &mdh, sizeof (md_header_t));
if (read(fd, buf + sizeof (md_header_t),
md_size - sizeof (md_header_t)) != md_size - sizeof (md_header_t)) {
goto errdone;
}
mdp = md_init_intern((uint64_t *)((void *)buf), malloc, v12n_md_free);
(void) close(fd);
return (mdp);
errdone:
(void) close(fd);
free(buf);
return (NULL);
}
static void
v12n_md_fini(void *md)
{
md_impl_t *mdp = (md_impl_t *)md;
if (mdp) {
free(mdp->caddr);
(void) md_fini(md);
}
}
static int
v12n_domaining_enabled()
{
mde_cookie_t *nodes, rootnode;
int nnodes;
uint64_t prop_val = 0;
md_t *mdp;
if ((mdp = v12n_md_init()) == NULL) {
return (0);
}
nnodes = md_node_count(mdp);
nodes = malloc(nnodes * sizeof (mde_cookie_t));
if (nodes == NULL) {
v12n_md_fini(mdp);
return (0);
}
rootnode = md_root_node(mdp);
nnodes = md_scan_dag(mdp, rootnode, md_find_name(mdp, "platform"),
md_find_name(mdp, "fwd"), nodes);
if (nnodes >= 1) {
(void) md_get_prop_val(mdp, nodes[0], "domaining-enabled",
&prop_val);
}
v12n_md_fini(mdp);
free(nodes);
return (prop_val == 1);
}
int
v12n_capabilities()
{
struct utsname uinfo;
struct stat st;
int cap;
if (uname(&uinfo) == -1 || strcmp(uinfo.machine, "sun4v")) {
cap = 0;
} else if (stat(MD_DEVICE, &st) == 0) {
cap = (V12N_CAP_SUPPORTED | V12N_CAP_IMPL_LDOMS |
(v12n_domaining_enabled() ? V12N_CAP_ENABLED : 0));
} else if (errno == ENOENT) {
cap = (V12N_CAP_SUPPORTED | V12N_CAP_IMPL_LDOMS);
}
return (cap);
}
static int
v12n_scan_md_nodes(md_t *mdp, char *node_name, char *node_str_prop,
char **props)
{
mde_cookie_t *nodes, rootnode;
int nnodes, i, j;
char *prop_str;
nnodes = md_node_count(mdp);
nodes = malloc(nnodes * sizeof (mde_cookie_t));
if (nodes == NULL) {
return (0);
}
rootnode = md_root_node(mdp);
nnodes = md_scan_dag(mdp, rootnode, md_find_name(mdp, node_name),
md_find_name(mdp, "fwd"), nodes);
if (node_str_prop == NULL)
return (nnodes > 0);
for (i = 0; i < nnodes; i++) {
if (md_get_prop_str(mdp, nodes[i], node_str_prop, &prop_str))
continue;
for (j = 0; props[j] != NULL; j++) {
if (strcmp(prop_str, props[j]) == 0) {
free(nodes);
return (1);
}
}
}
free(nodes);
return (0);
}
static int
v12n_check_hv_access(md_t *mdp)
{
static char *hvctl_str[] = {
"hvctl",
NULL
};
return (v12n_scan_md_nodes(mdp, "virtual-device-port", "vldc-svc-name",
hvctl_str));
}
static int
v12n_check_virtual_service(md_t *mdp)
{
static char *vdevs[] = {
"vcc",
"vsw",
"vds",
NULL
};
return (v12n_scan_md_nodes(mdp, "virtual-device", "device-type",
vdevs));
}
static int
v12n_check_io_service(md_t *mdp)
{
return (v12n_scan_md_nodes(mdp, "iodevice", NULL, NULL));
}
static int
v12n_check_root(md_t *mdp)
{
static char *pciex[] = {
"pciex",
NULL
};
return (v12n_scan_md_nodes(mdp, "iodevice", "device-type", pciex));
}
int
v12n_domain_roles()
{
md_t *mdp;
int roles = 0;
if (v12n_capabilities() != V12N_LDOMS_SUPPORTED) {
errno = ENOTSUP;
return (-1);
}
if ((mdp = v12n_md_init()) == NULL) {
errno = EACCES;
return (-1);
}
if (v12n_check_hv_access(mdp))
roles |= V12N_ROLE_CONTROL;
if (v12n_check_virtual_service(mdp))
roles |= V12N_ROLE_SERVICE;
if (v12n_check_io_service(mdp))
roles |= V12N_ROLE_IO;
if (v12n_check_root(mdp))
roles |= V12N_ROLE_ROOT;
v12n_md_fini(mdp);
return (roles);
}
static int
v12n_get_md_domain_name(md_t *mdp, char **vds_dnamep)
{
mde_cookie_t *vdev_nodes, rootnode;
int list_size, nvdevs, num_nodes, i, rv;
char *vldc_name;
num_nodes = md_node_count(mdp);
list_size = num_nodes * sizeof (mde_cookie_t);
vdev_nodes = malloc(list_size);
if (vdev_nodes == NULL) {
return (0);
}
rootnode = md_root_node(mdp);
nvdevs = md_scan_dag(mdp, rootnode, md_find_name(mdp, "virtual-device"),
md_find_name(mdp, "fwd"), vdev_nodes);
rv = 0;
for (i = 0; i < nvdevs; i++) {
if (md_get_prop_str(mdp, vdev_nodes[i], "name", &vldc_name))
continue;
if (strcmp(vldc_name, "virtual-domain-service") == 0) {
rv = (md_get_prop_str(mdp, vdev_nodes[i],
"vlds-domain-name", vds_dnamep) == 0);
break;
}
}
free(vdev_nodes);
return (rv);
}
static size_t
v12n_string_copyout(char *sout, char *sfrom, size_t count)
{
size_t ret = strlen(sfrom) + 1;
if (sout != NULL && count > 0) {
count = MIN(ret, count);
(void) memcpy(sout, sfrom, count);
}
return (ret);
}
size_t
v12n_domain_name(char *buf, size_t count)
{
md_t *mdp = NULL;
char *ldmname;
int rv = -1;
if (v12n_capabilities() != V12N_LDOMS_SUPPORTED) {
errno = ENOTSUP;
} else if ((mdp = v12n_md_init()) == NULL) {
errno = EACCES;
} else if (!v12n_get_md_domain_name(mdp, &ldmname)) {
errno = ESRCH;
} else {
rv = v12n_string_copyout(buf, ldmname, count);
}
v12n_md_fini(mdp);
return (rv);
}
static int
v12n_get_md_uuid_str(md_t *mdp, char **uuid_strp)
{
mde_cookie_t *plat_nodes, rootnode;
int list_size, npnodes, num_nodes, rv;
num_nodes = md_node_count(mdp);
list_size = num_nodes * sizeof (mde_cookie_t);
plat_nodes = malloc(list_size);
if (plat_nodes == NULL) {
return (0);
}
rootnode = md_root_node(mdp);
npnodes = md_scan_dag(mdp, rootnode, md_find_name(mdp, "platform"),
md_find_name(mdp, "fwd"), plat_nodes);
if (npnodes >= 1)
rv = !md_get_prop_str(mdp, plat_nodes[0], "uuid", uuid_strp);
else
rv = 0;
free(plat_nodes);
return (rv);
}
int
v12n_domain_uuid(uuid_t uuid)
{
md_t *mdp = NULL;
char *uuid_str;
int rv = -1;
if (v12n_capabilities() != V12N_LDOMS_SUPPORTED) {
errno = ENOTSUP;
} else if ((mdp = v12n_md_init()) == NULL) {
errno = EACCES;
} else if (!v12n_get_md_uuid_str(mdp, &uuid_str)) {
errno = ESRCH;
} else {
rv = uuid_parse(uuid_str, uuid);
}
v12n_md_fini(mdp);
return (rv);
}
static int
v12n_ldma_send_request()
{
ldma_message_header_t ldmamsg;
if (v12n_ds_send_msg == NULL || v12n_ldma_ctrl_hdl == DS_INVALID_HDL)
return (ENOENT);
ldmamsg.msg_num = 0;
ldmamsg.msg_type = v12n_ldma_msgtype;
ldmamsg.msg_info = 0;
return (v12n_ds_send_msg(v12n_ldma_ctrl_hdl, (char *)&ldmamsg,
sizeof (ldmamsg)));
}
static void
v12n_ldma_register_handler(ds_hdl_t hdl, ds_cb_arg_t arg, ds_ver_t *ver,
ds_domain_hdl_t dhdl)
{
if (dhdl == 0) {
(void) mutex_lock(&v12n_ldma_cv_lock);
if (v12n_ldma_cv_state == V12N_LDMA_REGWAITING) {
v12n_ldma_ctrl_hdl = hdl;
v12n_ldma_cv_state = V12N_LDMA_REGRECEIVED;
(void) cond_signal(&v12n_ldma_cv);
}
(void) mutex_unlock(&v12n_ldma_cv_lock);
}
}
static void
v12n_ldma_data_handler(ds_hdl_t hdl, ds_cb_arg_t arg, void *buf,
size_t buflen)
{
char *data;
ldma_message_header_t *ldmp;
int n;
int cv_state = V12N_LDMA_MSGERROR;
if (v12n_ldma_ctrl_hdl != hdl)
return;
if (buflen < LDMA_MESSAGE_HEADER_SIZE)
return;
ldmp = (ldma_message_header_t *)buf;
if (ldmp->msg_num != 0)
return;
switch (ldmp->msg_type) {
case LDMA_MSG_RESULT:
if (ldmp->msg_info == 0 ||
ldmp->msg_info > LDMA_MESSAGE_DLEN(buflen)) {
cv_state = V12N_LDMA_MSGERROR;
break;
}
data = LDMA_HDR2DATA(buf);
data[ldmp->msg_info - 1] = '\0';
switch (v12n_ldma_msgtype) {
case LDMA_MSGSYS_GET_SYSINFO:
n = strlen(data);
if (LDMA_MESSAGE_DLEN(buflen) <= n + 3) {
cv_state = V12N_LDMA_MSGERROR;
break;
}
data += n + 1;
if ((v12n_ldma_msgstr = strdup(data)) == NULL)
cv_state = V12N_LDMA_MSGERROR;
else
cv_state = V12N_LDMA_MSGRECEIVED;
break;
case LDMA_MSGSYS_GET_CHASSISNO:
if ((v12n_ldma_msgstr = strdup(data)) == NULL)
cv_state = V12N_LDMA_MSGERROR;
else
cv_state = V12N_LDMA_MSGRECEIVED;
break;
default:
ASSERT(0);
}
break;
case LDMA_MSG_ERROR:
cv_state = V12N_LDMA_MSGERROR;
break;
default:
return;
}
(void) mutex_lock(&v12n_ldma_cv_lock);
v12n_ldma_cv_state = cv_state;
(void) cond_signal(&v12n_ldma_cv);
(void) mutex_unlock(&v12n_ldma_cv_lock);
}
static int
v12n_libds_init(void)
{
if (v12n_ds_dlhdl != NULL) {
if (v12n_ds_clnt_reg == NULL || v12n_ds_send_msg == NULL ||
v12n_ds_unreg_svc == NULL)
return (ENOENT);
return (0);
}
if ((v12n_ds_dlhdl = dlopen("libds.so.1",
RTLD_NOW | RTLD_GLOBAL)) == NULL)
return (ENOENT);
if ((v12n_ds_clnt_reg = (int (*)(ds_capability_t *, ds_ops_t *))
dlsym(v12n_ds_dlhdl, "ds_clnt_reg")) == NULL)
return (ENOENT);
if ((v12n_ds_send_msg = (int (*)(ds_hdl_t, void *, size_t))
dlsym(v12n_ds_dlhdl, "ds_send_msg")) == NULL)
return (ENOENT);
if ((v12n_ds_unreg_svc = (int (*)(char *, boolean_t))
dlsym(v12n_ds_dlhdl, "ds_unreg_svc")) == NULL)
return (ENOENT);
return (0);
}
static int
v12n_get_ldma_system_msg(int msgtype, char **strp)
{
int tout;
int err = 0;
timestruc_t timeout;
(void) mutex_lock(&v12n_ldma_lock);
if ((err = v12n_libds_init()) != 0) {
(void) mutex_unlock(&v12n_ldma_lock);
return (err);
}
v12n_ldma_msgtype = msgtype;
v12n_ldma_msgstr = NULL;
(void) mutex_lock(&v12n_ldma_cv_lock);
v12n_ldma_cv_state = V12N_LDMA_REGWAITING;
(void) mutex_unlock(&v12n_ldma_cv_lock);
for (tout = 0; tout < v12n_ldma_timeout; tout += v12n_ldma_sleeptime) {
if ((err = v12n_ds_clnt_reg(&v12n_ldma_cap,
&v12n_ldma_ops)) == 0)
break;
if (err != EALREADY) {
goto done;
}
(void) sleep(v12n_ldma_sleeptime);
}
if (tout >= v12n_ldma_timeout) {
err = EBUSY;
goto done;
}
timeout.tv_sec = v12n_ldma_timeout;
timeout.tv_nsec = 0;
(void) mutex_lock(&v12n_ldma_cv_lock);
while (v12n_ldma_cv_state == V12N_LDMA_REGWAITING) {
if ((err = cond_reltimedwait(&v12n_ldma_cv,
&v12n_ldma_cv_lock, &timeout)) != EINTR)
break;
}
if (v12n_ldma_cv_state != V12N_LDMA_REGRECEIVED) {
if (err == 0)
err = EPROTO;
(void) mutex_unlock(&v12n_ldma_cv_lock);
goto done;
}
v12n_ldma_cv_state = V12N_LDMA_MSGWAITING;
if ((err = v12n_ldma_send_request()) != 0) {
(void) mutex_unlock(&v12n_ldma_cv_lock);
goto done;
}
while (v12n_ldma_cv_state == V12N_LDMA_MSGWAITING) {
if ((err = cond_reltimedwait(&v12n_ldma_cv,
&v12n_ldma_cv_lock, &timeout)) != EINTR)
break;
}
if (v12n_ldma_cv_state != V12N_LDMA_MSGRECEIVED) {
if (err == 0)
err = EPROTO;
(void) mutex_unlock(&v12n_ldma_cv_lock);
goto done;
}
v12n_ldma_cv_state = V12N_LDMA_CVINVALID;
(void) mutex_unlock(&v12n_ldma_cv_lock);
if (v12n_ldma_msgstr == NULL)
err = ENODATA;
else {
if (*v12n_ldma_msgstr == '\0' ||
(*strp = strdup(v12n_ldma_msgstr)) == NULL)
err = ENODATA;
free(v12n_ldma_msgstr);
v12n_ldma_msgstr = NULL;
}
done:
v12n_ds_unreg_svc(LDMA_NAME_SYSTEM, B_TRUE);
v12n_ldma_msgtype = -1;
v12n_ldma_ctrl_hdl = DS_INVALID_HDL;
(void) mutex_unlock(&v12n_ldma_lock);
return (err);
}
size_t
v12n_ctrl_domain(char *buf, size_t count)
{
char *str;
int err;
size_t rv = (size_t)(-1);
if (v12n_capabilities() != V12N_LDOMS_SUPPORTED) {
errno = ENOTSUP;
} else if ((err = v12n_get_ldma_system_msg(LDMA_MSGSYS_GET_SYSINFO,
&str)) != 0) {
errno = err;
} else {
rv = v12n_string_copyout(buf, str, count);
}
return (rv);
}
size_t
v12n_chassis_serialno(char *buf, size_t count)
{
char *str;
int err;
size_t rv = (size_t)(-1);
if (v12n_capabilities() != V12N_LDOMS_SUPPORTED) {
errno = ENOTSUP;
} else if ((err = v12n_get_ldma_system_msg(LDMA_MSGSYS_GET_CHASSISNO,
&str)) != 0) {
errno = err;
} else {
rv = v12n_string_copyout(buf, str, count);
}
return (rv);
}