603 lines
14 KiB
C
603 lines
14 KiB
C
/******************************************************************************
|
|
* Talks to Xen Store to figure out what devices we have.
|
|
*
|
|
* Copyright (C) 2008 Doug Rabson
|
|
* Copyright (C) 2005 Rusty Russell, IBM Corporation
|
|
* Copyright (C) 2005 Mike Wray, Hewlett-Packard
|
|
* Copyright (C) 2005 XenSource Ltd
|
|
*
|
|
* This file may be distributed separately from the Linux kernel, or
|
|
* incorporated into other software packages, subject to the following license:
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this source file (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use, copy, modify,
|
|
* merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
|
* and to permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*/
|
|
|
|
#if 0
|
|
#define DPRINTK(fmt, args...) \
|
|
printf("xenbus_probe (%s:%d) " fmt ".\n", __FUNCTION__, __LINE__, ##args)
|
|
#else
|
|
#define DPRINTK(fmt, args...) ((void)0)
|
|
#endif
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#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/sysctl.h>
|
|
#include <sys/syslog.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/sx.h>
|
|
#include <sys/taskqueue.h>
|
|
|
|
#include <machine/xen/xen-os.h>
|
|
#include <machine/stdarg.h>
|
|
|
|
#include <xen/gnttab.h>
|
|
#include <xen/xenbus/xenbusvar.h>
|
|
#include <xen/xenbus/xenbus_comms.h>
|
|
|
|
struct xenbus_softc {
|
|
struct xenbus_watch xs_devicewatch;
|
|
struct task xs_probechildren;
|
|
struct intr_config_hook xs_attachcb;
|
|
device_t xs_dev;
|
|
};
|
|
|
|
struct xenbus_device_ivars {
|
|
struct xenbus_watch xd_otherend_watch; /* must be first */
|
|
struct sx xd_lock;
|
|
device_t xd_dev;
|
|
char *xd_node; /* node name in xenstore */
|
|
char *xd_type; /* xen device type */
|
|
enum xenbus_state xd_state;
|
|
int xd_otherend_id;
|
|
char *xd_otherend_path;
|
|
};
|
|
|
|
/* Simplified asprintf. */
|
|
char *
|
|
kasprintf(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
unsigned int len;
|
|
char *p, dummy[1];
|
|
|
|
va_start(ap, fmt);
|
|
/* FIXME: vsnprintf has a bug, NULL should work */
|
|
len = vsnprintf(dummy, 0, fmt, ap);
|
|
va_end(ap);
|
|
|
|
p = malloc(len + 1, M_DEVBUF, M_WAITOK);
|
|
va_start(ap, fmt);
|
|
vsprintf(p, fmt, ap);
|
|
va_end(ap);
|
|
return p;
|
|
}
|
|
|
|
static void
|
|
xenbus_identify(driver_t *driver, device_t parent)
|
|
{
|
|
|
|
BUS_ADD_CHILD(parent, 0, "xenbus", 0);
|
|
}
|
|
|
|
static int
|
|
xenbus_probe(device_t dev)
|
|
{
|
|
int err = 0;
|
|
|
|
DPRINTK("");
|
|
|
|
/* Initialize the interface to xenstore. */
|
|
err = xs_init();
|
|
if (err) {
|
|
log(LOG_WARNING,
|
|
"XENBUS: Error initializing xenstore comms: %i\n", err);
|
|
return (ENXIO);
|
|
}
|
|
err = gnttab_init();
|
|
if (err) {
|
|
log(LOG_WARNING,
|
|
"XENBUS: Error initializing grant table: %i\n", err);
|
|
return (ENXIO);
|
|
}
|
|
device_set_desc(dev, "Xen Devices");
|
|
|
|
return (0);
|
|
}
|
|
|
|
static enum xenbus_state
|
|
xenbus_otherend_state(struct xenbus_device_ivars *ivars)
|
|
{
|
|
|
|
return (xenbus_read_driver_state(ivars->xd_otherend_path));
|
|
}
|
|
|
|
static void
|
|
xenbus_backend_changed(struct xenbus_watch *watch, const char **vec,
|
|
unsigned int len)
|
|
{
|
|
struct xenbus_device_ivars *ivars;
|
|
device_t dev;
|
|
enum xenbus_state newstate;
|
|
|
|
ivars = (struct xenbus_device_ivars *) watch;
|
|
dev = ivars->xd_dev;
|
|
|
|
if (!ivars->xd_otherend_path
|
|
|| strncmp(ivars->xd_otherend_path, vec[XS_WATCH_PATH],
|
|
strlen(ivars->xd_otherend_path)))
|
|
return;
|
|
|
|
newstate = xenbus_otherend_state(ivars);
|
|
XENBUS_BACKEND_CHANGED(dev, newstate);
|
|
}
|
|
|
|
static int
|
|
xenbus_device_exists(device_t dev, const char *node)
|
|
{
|
|
device_t *kids;
|
|
struct xenbus_device_ivars *ivars;
|
|
int i, count, result;
|
|
|
|
if (device_get_children(dev, &kids, &count))
|
|
return (FALSE);
|
|
|
|
result = FALSE;
|
|
for (i = 0; i < count; i++) {
|
|
ivars = device_get_ivars(kids[i]);
|
|
if (!strcmp(ivars->xd_node, node)) {
|
|
result = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
free(kids, M_TEMP);
|
|
|
|
return (result);
|
|
}
|
|
|
|
static int
|
|
xenbus_add_device(device_t dev, const char *bus,
|
|
const char *type, const char *id)
|
|
{
|
|
device_t child;
|
|
struct xenbus_device_ivars *ivars;
|
|
enum xenbus_state state;
|
|
char *statepath;
|
|
int error;
|
|
|
|
ivars = malloc(sizeof(struct xenbus_device_ivars),
|
|
M_DEVBUF, M_ZERO|M_WAITOK);
|
|
ivars->xd_node = kasprintf("%s/%s/%s", bus, type, id);
|
|
|
|
if (xenbus_device_exists(dev, ivars->xd_node)) {
|
|
/*
|
|
* We are already tracking this node
|
|
*/
|
|
free(ivars->xd_node, M_DEVBUF);
|
|
free(ivars, M_DEVBUF);
|
|
return (0);
|
|
}
|
|
|
|
state = xenbus_read_driver_state(ivars->xd_node);
|
|
|
|
if (state != XenbusStateInitialising) {
|
|
/*
|
|
* Device is not new, so ignore it. This can
|
|
* happen if a device is going away after
|
|
* switching to Closed.
|
|
*/
|
|
free(ivars->xd_node, M_DEVBUF);
|
|
free(ivars, M_DEVBUF);
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Find the backend details
|
|
*/
|
|
error = xenbus_gather(XBT_NIL, ivars->xd_node,
|
|
"backend-id", "%i", &ivars->xd_otherend_id,
|
|
"backend", NULL, &ivars->xd_otherend_path,
|
|
NULL);
|
|
if (error)
|
|
return (error);
|
|
|
|
sx_init(&ivars->xd_lock, "xdlock");
|
|
ivars->xd_type = strdup(type, M_DEVBUF);
|
|
ivars->xd_state = XenbusStateInitialising;
|
|
|
|
statepath = malloc(strlen(ivars->xd_otherend_path)
|
|
+ strlen("/state") + 1, M_DEVBUF, M_WAITOK);
|
|
sprintf(statepath, "%s/state", ivars->xd_otherend_path);
|
|
|
|
ivars->xd_otherend_watch.node = statepath;
|
|
ivars->xd_otherend_watch.callback = xenbus_backend_changed;
|
|
|
|
child = device_add_child(dev, NULL, -1);
|
|
ivars->xd_dev = child;
|
|
device_set_ivars(child, ivars);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
xenbus_enumerate_type(device_t dev, const char *bus, const char *type)
|
|
{
|
|
char **dir;
|
|
unsigned int i, count;
|
|
int error;
|
|
|
|
error = xenbus_directory(XBT_NIL, bus, type, &count, &dir);
|
|
if (error)
|
|
return (error);
|
|
for (i = 0; i < count; i++)
|
|
xenbus_add_device(dev, bus, type, dir[i]);
|
|
|
|
free(dir, M_DEVBUF);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
xenbus_enumerate_bus(device_t dev, const char *bus)
|
|
{
|
|
char **dir;
|
|
unsigned int i, count;
|
|
int error;
|
|
|
|
error = xenbus_directory(XBT_NIL, bus, "", &count, &dir);
|
|
if (error)
|
|
return (error);
|
|
for (i = 0; i < count; i++) {
|
|
xenbus_enumerate_type(dev, bus, dir[i]);
|
|
}
|
|
free(dir, M_DEVBUF);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
xenbus_probe_children(device_t dev)
|
|
{
|
|
device_t *kids;
|
|
struct xenbus_device_ivars *ivars;
|
|
int i, count;
|
|
|
|
/*
|
|
* Probe any new devices and register watches for any that
|
|
* attach successfully. Since part of the protocol which
|
|
* establishes a connection with the other end is interrupt
|
|
* driven, we sleep until the device reaches a stable state
|
|
* (closed or connected).
|
|
*/
|
|
if (device_get_children(dev, &kids, &count) == 0) {
|
|
for (i = 0; i < count; i++) {
|
|
if (device_get_state(kids[i]) != DS_NOTPRESENT)
|
|
continue;
|
|
|
|
if (device_probe_and_attach(kids[i]))
|
|
continue;
|
|
ivars = device_get_ivars(kids[i]);
|
|
register_xenbus_watch(
|
|
&ivars->xd_otherend_watch);
|
|
sx_xlock(&ivars->xd_lock);
|
|
while (ivars->xd_state != XenbusStateClosed
|
|
&& ivars->xd_state != XenbusStateConnected)
|
|
sx_sleep(&ivars->xd_state, &ivars->xd_lock,
|
|
0, "xdattach", 0);
|
|
sx_xunlock(&ivars->xd_lock);
|
|
}
|
|
free(kids, M_TEMP);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
xenbus_probe_children_cb(void *arg, int pending)
|
|
{
|
|
device_t dev = (device_t) arg;
|
|
|
|
xenbus_probe_children(dev);
|
|
}
|
|
|
|
static void
|
|
xenbus_devices_changed(struct xenbus_watch *watch,
|
|
const char **vec, unsigned int len)
|
|
{
|
|
struct xenbus_softc *sc = (struct xenbus_softc *) watch;
|
|
device_t dev = sc->xs_dev;
|
|
char *node, *bus, *type, *id, *p;
|
|
|
|
node = strdup(vec[XS_WATCH_PATH], M_DEVBUF);;
|
|
p = strchr(node, '/');
|
|
if (!p)
|
|
goto out;
|
|
bus = node;
|
|
*p = 0;
|
|
type = p + 1;
|
|
|
|
p = strchr(type, '/');
|
|
if (!p)
|
|
goto out;
|
|
*p = 0;
|
|
id = p + 1;
|
|
|
|
p = strchr(id, '/');
|
|
if (p)
|
|
*p = 0;
|
|
|
|
xenbus_add_device(dev, bus, type, id);
|
|
taskqueue_enqueue(taskqueue_thread, &sc->xs_probechildren);
|
|
out:
|
|
free(node, M_DEVBUF);
|
|
}
|
|
|
|
static void
|
|
xenbus_attach_deferred(void *arg)
|
|
{
|
|
device_t dev = (device_t) arg;
|
|
struct xenbus_softc *sc = device_get_softc(dev);
|
|
int error;
|
|
|
|
error = xenbus_enumerate_bus(dev, "device");
|
|
if (error)
|
|
return;
|
|
xenbus_probe_children(dev);
|
|
|
|
sc->xs_dev = dev;
|
|
sc->xs_devicewatch.node = "device";
|
|
sc->xs_devicewatch.callback = xenbus_devices_changed;
|
|
|
|
TASK_INIT(&sc->xs_probechildren, 0, xenbus_probe_children_cb, dev);
|
|
|
|
register_xenbus_watch(&sc->xs_devicewatch);
|
|
|
|
config_intrhook_disestablish(&sc->xs_attachcb);
|
|
}
|
|
|
|
static int
|
|
xenbus_attach(device_t dev)
|
|
{
|
|
struct xenbus_softc *sc = device_get_softc(dev);
|
|
|
|
sc->xs_attachcb.ich_func = xenbus_attach_deferred;
|
|
sc->xs_attachcb.ich_arg = dev;
|
|
config_intrhook_establish(&sc->xs_attachcb);
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
xenbus_suspend(device_t dev)
|
|
{
|
|
int error;
|
|
|
|
DPRINTK("");
|
|
|
|
error = bus_generic_suspend(dev);
|
|
if (error)
|
|
return (error);
|
|
|
|
xs_suspend();
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
xenbus_resume(device_t dev)
|
|
{
|
|
device_t *kids;
|
|
struct xenbus_device_ivars *ivars;
|
|
int i, count, error;
|
|
char *statepath;
|
|
|
|
xb_init_comms();
|
|
xs_resume();
|
|
|
|
/*
|
|
* We must re-examine each device and find the new path for
|
|
* its backend.
|
|
*/
|
|
if (device_get_children(dev, &kids, &count) == 0) {
|
|
for (i = 0; i < count; i++) {
|
|
if (device_get_state(kids[i]) == DS_NOTPRESENT)
|
|
continue;
|
|
|
|
ivars = device_get_ivars(kids[i]);
|
|
|
|
unregister_xenbus_watch(
|
|
&ivars->xd_otherend_watch);
|
|
ivars->xd_state = XenbusStateInitialising;
|
|
|
|
/*
|
|
* Find the new backend details and
|
|
* re-register our watch.
|
|
*/
|
|
free(ivars->xd_otherend_path, M_DEVBUF);
|
|
error = xenbus_gather(XBT_NIL, ivars->xd_node,
|
|
"backend-id", "%i", &ivars->xd_otherend_id,
|
|
"backend", NULL, &ivars->xd_otherend_path,
|
|
NULL);
|
|
if (error)
|
|
return (error);
|
|
|
|
DEVICE_RESUME(kids[i]);
|
|
|
|
statepath = malloc(strlen(ivars->xd_otherend_path)
|
|
+ strlen("/state") + 1, M_DEVBUF, M_WAITOK);
|
|
sprintf(statepath, "%s/state", ivars->xd_otherend_path);
|
|
|
|
free(ivars->xd_otherend_watch.node, M_DEVBUF);
|
|
ivars->xd_otherend_watch.node = statepath;
|
|
register_xenbus_watch(
|
|
&ivars->xd_otherend_watch);
|
|
|
|
#if 0
|
|
/*
|
|
* Can't do this yet since we are running in
|
|
* the xenwatch thread and if we sleep here,
|
|
* we will stop delivering watch notifications
|
|
* and the device will never come back online.
|
|
*/
|
|
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);
|
|
}
|
|
|
|
static int
|
|
xenbus_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);
|
|
}
|
|
|
|
static int
|
|
xenbus_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);
|
|
}
|
|
|
|
static int
|
|
xenbus_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;
|
|
int error;
|
|
|
|
switch (index) {
|
|
case XENBUS_IVAR_STATE:
|
|
newstate = (enum xenbus_state) value;
|
|
sx_xlock(&ivars->xd_lock);
|
|
if (ivars->xd_state == newstate)
|
|
goto out;
|
|
|
|
error = xenbus_scanf(XBT_NIL, ivars->xd_node, "state",
|
|
NULL, "%d", &currstate);
|
|
if (error)
|
|
goto out;
|
|
|
|
error = xenbus_printf(XBT_NIL, ivars->xd_node, "state",
|
|
"%d", newstate);
|
|
if (error) {
|
|
if (newstate != XenbusStateClosing) /* Avoid looping */
|
|
xenbus_dev_fatal(dev, error, "writing new state");
|
|
goto out;
|
|
}
|
|
ivars->xd_state = newstate;
|
|
wakeup(&ivars->xd_state);
|
|
out:
|
|
sx_xunlock(&ivars->xd_lock);
|
|
return (0);
|
|
|
|
case XENBUS_IVAR_NODE:
|
|
case XENBUS_IVAR_TYPE:
|
|
case XENBUS_IVAR_OTHEREND_ID:
|
|
case XENBUS_IVAR_OTHEREND_PATH:
|
|
/*
|
|
* These variables are read-only.
|
|
*/
|
|
return (EINVAL);
|
|
}
|
|
|
|
return (ENOENT);
|
|
}
|
|
|
|
SYSCTL_NODE(_dev, OID_AUTO, xen, CTLFLAG_RD, NULL, "Xen");
|
|
SYSCTL_INT(_dev_xen, OID_AUTO, xsd_port, CTLFLAG_RD, &xen_store_evtchn, 0, "");
|
|
SYSCTL_ULONG(_dev_xen, OID_AUTO, xsd_kva, CTLFLAG_RD, (u_long *) &xen_store, 0, "");
|
|
|
|
static device_method_t xenbus_methods[] = {
|
|
/* Device interface */
|
|
DEVMETHOD(device_identify, xenbus_identify),
|
|
DEVMETHOD(device_probe, xenbus_probe),
|
|
DEVMETHOD(device_attach, xenbus_attach),
|
|
DEVMETHOD(device_detach, bus_generic_detach),
|
|
DEVMETHOD(device_shutdown, bus_generic_shutdown),
|
|
DEVMETHOD(device_suspend, xenbus_suspend),
|
|
DEVMETHOD(device_resume, xenbus_resume),
|
|
|
|
/* Bus interface */
|
|
DEVMETHOD(bus_print_child, xenbus_print_child),
|
|
DEVMETHOD(bus_read_ivar, xenbus_read_ivar),
|
|
DEVMETHOD(bus_write_ivar, xenbus_write_ivar),
|
|
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static char driver_name[] = "xenbus";
|
|
static driver_t xenbus_driver = {
|
|
driver_name,
|
|
xenbus_methods,
|
|
sizeof(struct xenbus_softc),
|
|
};
|
|
devclass_t xenbus_devclass;
|
|
|
|
#ifdef XENHVM
|
|
DRIVER_MODULE(xenbus, xenpci, xenbus_driver, xenbus_devclass, 0, 0);
|
|
#else
|
|
DRIVER_MODULE(xenbus, nexus, xenbus_driver, xenbus_devclass, 0, 0);
|
|
#endif
|