#include <sys/param.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/sbuf.h>
#include <sys/stdarg.h>
#include <sys/sysctl.h>
#include <sys/syslog.h>
#include <sys/systm.h>
#include <sys/sx.h>
#include <sys/taskqueue.h>
#include <xen/xen-os.h>
#include <xen/gnttab.h>
#include <xen/xenstore/xenstorevar.h>
#include <xen/xenbus/xenbusb.h>
#include <xen/xenbus/xenbusvar.h>
static void
xenbusb_free_child_ivars(struct xenbus_device_ivars *ivars)
{
if (ivars->xd_otherend_watch.node != NULL) {
xs_unregister_watch(&ivars->xd_otherend_watch);
free(ivars->xd_otherend_watch.node, M_XENBUS);
ivars->xd_otherend_watch.node = NULL;
}
if (ivars->xd_local_watch.node != NULL) {
xs_unregister_watch(&ivars->xd_local_watch);
ivars->xd_local_watch.node = NULL;
}
if (ivars->xd_node != NULL) {
free(ivars->xd_node, M_XENBUS);
ivars->xd_node = NULL;
}
ivars->xd_node_len = 0;
if (ivars->xd_type != NULL) {
free(ivars->xd_type, M_XENBUS);
ivars->xd_type = NULL;
}
if (ivars->xd_otherend_path != NULL) {
free(ivars->xd_otherend_path, M_XENBUS);
ivars->xd_otherend_path = NULL;
}
ivars->xd_otherend_path_len = 0;
free(ivars, M_XENBUS);
}
static void
xenbusb_otherend_watch_cb(struct xs_watch *watch, const char **vec,
unsigned int vec_size __unused)
{
struct xenbus_device_ivars *ivars;
device_t child;
device_t bus;
const char *path;
enum xenbus_state newstate;
ivars = (struct xenbus_device_ivars *)watch->callback_data;
child = ivars->xd_dev;
bus = device_get_parent(child);
path = vec[XS_WATCH_PATH];
if (ivars->xd_otherend_path == NULL
|| strncmp(ivars->xd_otherend_path, path, ivars->xd_otherend_path_len))
return;
newstate = xenbus_read_driver_state(ivars->xd_otherend_path);
XENBUSB_OTHEREND_CHANGED(bus, child, newstate);
}
static void
xenbusb_local_watch_cb(struct xs_watch *watch, const char **vec,
unsigned int vec_size __unused)
{
struct xenbus_device_ivars *ivars;
device_t child;
device_t bus;
const char *path;
ivars = (struct xenbus_device_ivars *)watch->callback_data;
child = ivars->xd_dev;
bus = device_get_parent(child);
path = vec[XS_WATCH_PATH];
if (ivars->xd_node == NULL
|| strncmp(ivars->xd_node, path, ivars->xd_node_len))
return;
XENBUSB_LOCALEND_CHANGED(bus, child, &path[ivars->xd_node_len]);
}
static device_t
xenbusb_device_exists(device_t dev, const char *node)
{
device_t *kids;
device_t result;
struct xenbus_device_ivars *ivars;
int i, count;
if (device_get_children(dev, &kids, &count))
return (FALSE);
result = NULL;
for (i = 0; i < count; i++) {
ivars = device_get_ivars(kids[i]);
if (!strcmp(ivars->xd_node, node)) {
result = kids[i];
break;
}
}
free(kids, M_TEMP);
return (result);
}
static void
xenbusb_delete_child(device_t dev, device_t child)
{
struct xenbus_device_ivars *ivars;
ivars = device_get_ivars(child);
if (ivars->xd_otherend_watch.node != NULL)
xs_unregister_watch(&ivars->xd_otherend_watch);
if (ivars->xd_local_watch.node != NULL)
xs_unregister_watch(&ivars->xd_local_watch);
device_delete_child(dev, child);
xenbusb_free_child_ivars(ivars);
}
static void
xenbusb_verify_device(device_t dev, device_t child)
{
if (xs_exists(XST_NIL, xenbus_get_node(child), "state") == 0) {
xenbusb_delete_child(dev, child);
}
}
static int
xenbusb_enumerate_bus(struct xenbusb_softc *xbs)
{
const char **types;
u_int type_idx;
u_int type_count;
int error;
error = xs_directory(XST_NIL, xbs->xbs_node, "", &type_count, &types);
if (error)
return (error);
for (type_idx = 0; type_idx < type_count; type_idx++)
XENBUSB_ENUMERATE_TYPE(xbs->xbs_dev, types[type_idx]);
free(types, M_XENSTORE);
return (0);
}
static int
xenbusb_device_sysctl_handler(SYSCTL_HANDLER_ARGS)
{
device_t dev;
const char *value;
dev = (device_t)arg1;
switch (arg2) {
case XENBUS_IVAR_NODE:
value = xenbus_get_node(dev);
break;
case XENBUS_IVAR_TYPE:
value = xenbus_get_type(dev);
break;
case XENBUS_IVAR_STATE:
value = xenbus_strstate(xenbus_get_state(dev));
break;
case XENBUS_IVAR_OTHEREND_ID:
return (sysctl_handle_int(oidp, NULL,
xenbus_get_otherend_id(dev),
req));
case XENBUS_IVAR_OTHEREND_PATH:
value = xenbus_get_otherend_path(dev);
break;
default:
return (EINVAL);
}
return (SYSCTL_OUT_STR(req, value));
}
static void
xenbusb_device_sysctl_init(device_t dev)
{
struct sysctl_ctx_list *ctx;
struct sysctl_oid *tree;
ctx = device_get_sysctl_ctx(dev);
tree = device_get_sysctl_tree(dev);
SYSCTL_ADD_PROC(ctx,
SYSCTL_CHILDREN(tree),
OID_AUTO,
"xenstore_path",
CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
dev,
XENBUS_IVAR_NODE,
xenbusb_device_sysctl_handler,
"A",
"XenStore path to device");
SYSCTL_ADD_PROC(ctx,
SYSCTL_CHILDREN(tree),
OID_AUTO,
"xenbus_dev_type",
CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
dev,
XENBUS_IVAR_TYPE,
xenbusb_device_sysctl_handler,
"A",
"XenBus device type");
SYSCTL_ADD_PROC(ctx,
SYSCTL_CHILDREN(tree),
OID_AUTO,
"xenbus_connection_state",
CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
dev,
XENBUS_IVAR_STATE,
xenbusb_device_sysctl_handler,
"A",
"XenBus state of peer connection");
SYSCTL_ADD_PROC(ctx,
SYSCTL_CHILDREN(tree),
OID_AUTO,
"xenbus_peer_domid",
CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE,
dev,
XENBUS_IVAR_OTHEREND_ID,
xenbusb_device_sysctl_handler,
"I",
"Xen domain ID of peer");
SYSCTL_ADD_PROC(ctx,
SYSCTL_CHILDREN(tree),
OID_AUTO,
"xenstore_peer_path",
CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE,
dev,
XENBUS_IVAR_OTHEREND_PATH,
xenbusb_device_sysctl_handler,
"A",
"XenStore path to peer device");
}
static void
xenbusb_release_confighook(struct xenbusb_softc *xbs)
{
mtx_lock(&xbs->xbs_lock);
KASSERT(xbs->xbs_connecting_children > 0,
("Connecting device count error\n"));
xbs->xbs_connecting_children--;
if (xbs->xbs_connecting_children == 0
&& (xbs->xbs_flags & XBS_ATTACH_CH_ACTIVE) != 0) {
xbs->xbs_flags &= ~XBS_ATTACH_CH_ACTIVE;
mtx_unlock(&xbs->xbs_lock);
config_intrhook_disestablish(&xbs->xbs_attach_ch);
} else {
mtx_unlock(&xbs->xbs_lock);
}
}
static int
xenbusb_probe_children(device_t dev)
{
device_t *kids;
struct xenbus_device_ivars *ivars;
int i, count, error;
if (device_get_children(dev, &kids, &count) == 0) {
for (i = 0; i < count; i++) {
if (device_get_state(kids[i]) != DS_NOTPRESENT) {
xenbusb_verify_device(dev, kids[i]);
continue;
}
error = device_probe_and_attach(kids[i]);
if (error == ENXIO) {
struct xenbusb_softc *xbs;
xenbusb_delete_child(dev, kids[i]);
xbs = device_get_softc(dev);
xenbusb_release_confighook(xbs);
continue;
} else if (error) {
xenbus_set_state(kids[i], XenbusStateClosed);
xenbusb_delete_child(dev, kids[i]);
continue;
}
xenbusb_device_sysctl_init(kids[i]);
ivars = device_get_ivars(kids[i]);
xs_register_watch(&ivars->xd_otherend_watch);
xs_register_watch(&ivars->xd_local_watch);
}
free(kids, M_TEMP);
}
return (0);
}
static void
xenbusb_probe_children_cb(void *arg, int pending __unused)
{
device_t dev = (device_t)arg;
bus_topo_lock();
xenbusb_probe_children(dev);
bus_topo_unlock();
}
static void
xenbusb_devices_changed(struct xs_watch *watch, const char **vec,
unsigned int len)
{
struct xenbusb_softc *xbs;
device_t dev;
char *node;
char *type;
char *id;
char *p;
u_int component;
xbs = (struct xenbusb_softc *)watch->callback_data;
dev = xbs->xbs_dev;
if (len <= XS_WATCH_PATH) {
device_printf(dev, "xenbusb_devices_changed: "
"Short Event Data.\n");
return;
}
node = strdup(vec[XS_WATCH_PATH], M_XENBUS);
p = strchr(node, '/');
if (p == NULL)
goto out;
*p = 0;
type = p + 1;
p = strchr(type, '/');
if (p == NULL)
goto out;
*p++ = 0;
id = p;
for (component = 0; component < xbs->xbs_id_components; component++) {
p = strchr(p, '/');
if (p == NULL)
break;
p++;
}
if (p != NULL)
*p = 0;
if (*id != 0 && component >= xbs->xbs_id_components - 1) {
xenbusb_add_device(xbs->xbs_dev, type, id);
taskqueue_enqueue(taskqueue_thread, &xbs->xbs_probe_children);
}
out:
free(node, M_XENBUS);
}
static void
xenbusb_nop_confighook_cb(void *arg __unused)
{
}
void
xenbusb_identify(driver_t *driver, device_t parent)
{
BUS_ADD_CHILD(parent, 0, driver->name, 0);
}
int
xenbusb_add_device(device_t dev, const char *type, const char *id)
{
struct xenbusb_softc *xbs;
struct sbuf *devpath_sbuf;
char *devpath;
struct xenbus_device_ivars *ivars;
int error;
xbs = device_get_softc(dev);
devpath_sbuf = sbuf_new_auto();
sbuf_printf(devpath_sbuf, "%s/%s/%s", xbs->xbs_node, type, id);
sbuf_finish(devpath_sbuf);
devpath = sbuf_data(devpath_sbuf);
ivars = malloc(sizeof(*ivars), M_XENBUS, M_ZERO|M_WAITOK);
error = ENXIO;
if (xs_exists(XST_NIL, devpath, "") != 0) {
device_t child;
enum xenbus_state state;
char *statepath;
child = xenbusb_device_exists(dev, devpath);
if (child != NULL) {
error = 0;
goto out;
}
state = xenbus_read_driver_state(devpath);
if (state != XenbusStateInitialising) {
printf("xenbusb_add_device: Device %s ignored. "
"State %d\n", devpath, state);
error = 0;
goto out;
}
sx_init(&ivars->xd_lock, "xdlock");
ivars->xd_flags = XDF_CONNECTING;
ivars->xd_node = strdup(devpath, M_XENBUS);
ivars->xd_node_len = strlen(devpath);
ivars->xd_type = strdup(type, M_XENBUS);
ivars->xd_state = XenbusStateInitialising;
error = XENBUSB_GET_OTHEREND_NODE(dev, ivars);
if (error) {
printf("xenbus_update_device: %s no otherend id\n",
devpath);
goto out;
}
statepath = malloc(ivars->xd_otherend_path_len
+ strlen("/state") + 1, M_XENBUS, M_WAITOK);
sprintf(statepath, "%s/state", ivars->xd_otherend_path);
ivars->xd_otherend_watch.node = statepath;
ivars->xd_otherend_watch.callback = xenbusb_otherend_watch_cb;
ivars->xd_otherend_watch.callback_data = (uintptr_t)ivars;
ivars->xd_otherend_watch.max_pending = 1;
ivars->xd_local_watch.node = ivars->xd_node;
ivars->xd_local_watch.callback = xenbusb_local_watch_cb;
ivars->xd_local_watch.callback_data = (uintptr_t)ivars;
ivars->xd_local_watch.max_pending = 0;
mtx_lock(&xbs->xbs_lock);
xbs->xbs_connecting_children++;
mtx_unlock(&xbs->xbs_lock);
child = device_add_child(dev, NULL, DEVICE_UNIT_ANY);
ivars->xd_dev = child;
device_set_ivars(child, ivars);
}
out:
sbuf_delete(devpath_sbuf);
if (error != 0)
xenbusb_free_child_ivars(ivars);
return (error);
}
int
xenbusb_attach(device_t dev, char *bus_node, u_int id_components)
{
struct xenbusb_softc *xbs;
xbs = device_get_softc(dev);
mtx_init(&xbs->xbs_lock, "xenbusb softc lock", NULL, MTX_DEF);
xbs->xbs_node = bus_node;
xbs->xbs_id_components = id_components;
xbs->xbs_dev = dev;
xbs->xbs_attach_ch.ich_func = xenbusb_nop_confighook_cb;
xbs->xbs_attach_ch.ich_arg = dev;
config_intrhook_establish(&xbs->xbs_attach_ch);
xbs->xbs_flags |= XBS_ATTACH_CH_ACTIVE;
xbs->xbs_connecting_children = 1;
(void)xenbusb_enumerate_bus(xbs);
xenbusb_probe_children(dev);
xbs->xbs_device_watch.node = bus_node;
xbs->xbs_device_watch.callback = xenbusb_devices_changed;
xbs->xbs_device_watch.callback_data = (uintptr_t)xbs;
xbs->xbs_device_watch.max_pending = 0;
TASK_INIT(&xbs->xbs_probe_children, 0, xenbusb_probe_children_cb, dev);
xs_register_watch(&xbs->xbs_device_watch);
xenbusb_release_confighook(xbs);
return (0);
}
int
xenbusb_resume(device_t dev)
{
device_t *kids;
struct xenbus_device_ivars *ivars;
int i, count, error;
char *statepath;
if (device_get_children(dev, &kids, &count) == 0) {
for (i = 0; i < count; i++) {
if (device_get_state(kids[i]) == DS_NOTPRESENT)
continue;
if (xen_suspend_cancelled) {
DEVICE_RESUME(kids[i]);
continue;
}
ivars = device_get_ivars(kids[i]);
xs_unregister_watch(&ivars->xd_otherend_watch);
xenbus_set_state(kids[i], XenbusStateInitialising);
error = XENBUSB_GET_OTHEREND_NODE(dev, ivars);
if (error)
return (error);
statepath = malloc(ivars->xd_otherend_path_len
+ strlen("/state") + 1, M_XENBUS, M_WAITOK);
sprintf(statepath, "%s/state", ivars->xd_otherend_path);
free(ivars->xd_otherend_watch.node, M_XENBUS);
ivars->xd_otherend_watch.node = statepath;
DEVICE_RESUME(kids[i]);
xs_register_watch(&ivars->xd_otherend_watch);
#if 0
sx_xlock(&ivars->xd_lock);
while (ivars->xd_state != XenbusStateClosed
&& ivars->xd_state != XenbusStateConnected)
sx_sleep(&ivars->xd_state, &ivars->xd_lock,
0, "xdresume", 0);
sx_xunlock(&ivars->xd_lock);
#endif
}
free(kids, M_TEMP);
}
return (0);
}
int
xenbusb_print_child(device_t dev, device_t child)
{
struct xenbus_device_ivars *ivars = device_get_ivars(child);
int retval = 0;
retval += bus_print_child_header(dev, child);
retval += printf(" at %s", ivars->xd_node);
retval += bus_print_child_footer(dev, child);
return (retval);
}
int
xenbusb_read_ivar(device_t dev, device_t child, int index, uintptr_t *result)
{
struct xenbus_device_ivars *ivars = device_get_ivars(child);
switch (index) {
case XENBUS_IVAR_NODE:
*result = (uintptr_t) ivars->xd_node;
return (0);
case XENBUS_IVAR_TYPE:
*result = (uintptr_t) ivars->xd_type;
return (0);
case XENBUS_IVAR_STATE:
*result = (uintptr_t) ivars->xd_state;
return (0);
case XENBUS_IVAR_OTHEREND_ID:
*result = (uintptr_t) ivars->xd_otherend_id;
return (0);
case XENBUS_IVAR_OTHEREND_PATH:
*result = (uintptr_t) ivars->xd_otherend_path;
return (0);
}
return (ENOENT);
}
int
xenbusb_write_ivar(device_t dev, device_t child, int index, uintptr_t value)
{
struct xenbus_device_ivars *ivars = device_get_ivars(child);
enum xenbus_state newstate;
int currstate;
switch (index) {
case XENBUS_IVAR_STATE:
{
int error;
struct xs_transaction xst;
newstate = (enum xenbus_state)value;
sx_xlock(&ivars->xd_lock);
if (ivars->xd_state == newstate) {
error = 0;
goto out;
}
do {
error = xs_transaction_start(&xst);
if (error != 0)
goto out;
do {
error = xs_scanf(xst, ivars->xd_node, "state",
NULL, "%d", &currstate);
} while (error == EAGAIN);
if (error)
goto out;
do {
error = xs_printf(xst, ivars->xd_node, "state",
"%d", newstate);
} while (error == EAGAIN);
if (error) {
if (newstate != XenbusStateClosing)
xenbus_dev_fatal(dev, error,
"writing new state");
goto out;
}
} while (xs_transaction_end(xst, 0));
ivars->xd_state = newstate;
if ((ivars->xd_flags & XDF_CONNECTING) != 0 &&
(newstate == XenbusStateClosed ||
newstate == XenbusStateConnected)) {
struct xenbusb_softc *xbs;
ivars->xd_flags &= ~XDF_CONNECTING;
xbs = device_get_softc(dev);
xenbusb_release_confighook(xbs);
}
wakeup(&ivars->xd_state);
out:
if (error != 0)
xs_transaction_end(xst, 1);
sx_xunlock(&ivars->xd_lock);
return (error == ENOENT ? 0 : error);
}
case XENBUS_IVAR_NODE:
case XENBUS_IVAR_TYPE:
case XENBUS_IVAR_OTHEREND_ID:
case XENBUS_IVAR_OTHEREND_PATH:
return (EINVAL);
}
return (ENOENT);
}
void
xenbusb_otherend_changed(device_t bus, device_t child, enum xenbus_state state)
{
XENBUS_OTHEREND_CHANGED(child, state);
}
void
xenbusb_localend_changed(device_t bus, device_t child, const char *path)
{
if (strcmp(path, "/state") != 0) {
struct xenbus_device_ivars *ivars;
ivars = device_get_ivars(child);
sx_xlock(&ivars->xd_lock);
ivars->xd_state = xenbus_read_driver_state(ivars->xd_node);
sx_xunlock(&ivars->xd_lock);
}
XENBUS_LOCALEND_CHANGED(child, path);
}