xvdi.c revision 1ca30e39ba109652a7cbb9fa4d158cc78d0fbbad
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Xen virtual device driver interfaces
*/
/*
* todo:
* + name space clean up:
* xvdi_* - public xen interfaces, for use by all leaf drivers
* xd_* - public xen data structures
* i_xvdi_* - implementation private functions
* + add mdb dcmds to dump ring status
* + convert (xendev_ring_t *) into xvdi_ring_handle_t
*/
#include <vm/seg_kmem.h>
#include <sys/autoconf.h>
#include <sys/ddi_impldefs.h>
#include <sys/ddi_subrdefs.h>
#include <sys/bootconf.h>
#include <sys/bootsvcs.h>
#include <sys/bootinfo.h>
#ifdef XPV_HVM_DRIVER
#include <sys/xpv_support.h>
#include <sys/hypervisor.h>
#include <public/grant_table.h>
#include <public/event_channel.h>
#else /* XPV_HVM_DRIVER */
#include <sys/hypervisor.h>
#include <sys/evtchn_impl.h>
#endif /* XPV_HVM_DRIVER */
static void xvdi_ring_init_sring(xendev_ring_t *);
#ifndef XPV_HVM_DRIVER
#endif
static int i_xvdi_add_watches(dev_info_t *);
static void i_xvdi_rem_watches(dev_info_t *);
static int i_xvdi_add_watch_oestate(dev_info_t *);
static void i_xvdi_rem_watch_oestate(dev_info_t *);
static void i_xvdi_oestate_handler(void *);
static int i_xvdi_add_watch_hpstate(dev_info_t *);
static void i_xvdi_rem_watch_hpstate(dev_info_t *);
static void i_xvdi_hpstate_cb(struct xenbus_watch *, const char **,
unsigned int);
static void i_xvdi_hpstate_handler(void *);
static int i_xvdi_add_watch_bepath(dev_info_t *);
static void i_xvdi_rem_watch_bepath(dev_info_t *);
static void i_xvdi_bepath_cb(struct xenbus_watch *, const char **,
unsigned in);
static void xendev_offline_device(void *);
static void i_xvdi_probe_path_cb(struct xenbus_watch *, const char **,
unsigned int);
static void i_xvdi_probe_path_handler(void *);
typedef struct xd_cfg {
char *xsdev;
char *xs_path_fe;
char *xs_path_be;
char *node_fe;
char *node_be;
char *device_type;
int xd_ipl;
int flags;
} i_xd_cfg_t;
static i_xd_cfg_t xdci[] = {
NULL, 0, XD_DOM_ALL, },
NULL, 0, XD_DOM_ALL, },
NULL, 0, XD_DOM_ALL, },
NULL, 0, XD_DOM_ZERO, },
NULL, 0, XD_DOM_ZERO, },
};
/*
* Xen device channel device access and DMA attributes
*/
static ddi_device_acc_attr_t xendev_dc_accattr = {
};
static ddi_dma_attr_t xendev_dc_dmaattr = {
DMA_ATTR_V0, /* version of this structure */
0, /* lowest usable address */
0xffffffffffffffffULL, /* highest usable address */
0x7fffffff, /* maximum DMAable byte count */
MMU_PAGESIZE, /* alignment in bytes */
0x7ff, /* bitmap of burst sizes */
1, /* minimum transfer */
0xffffffffU, /* maximum transfer */
0xffffffffffffffffULL, /* maximum segment length */
1, /* maximum number of segments */
1, /* granularity */
0, /* flags (reserved) */
};
#define XVDI_DBG_STATE 0x01
#define XVDI_DBG_PROBE 0x02
#ifdef DEBUG
int i_xvdi_debug = 0;
{ \
if (i_xvdi_debug & (flag)) \
}
#else
#endif /* DEBUG */
static i_xd_cfg_t *
{
int i;
return (xdcp);
return (NULL);
}
int
{
int vdevnum;
struct xendev_ppd *pdp;
char xsnamebuf[TYPICALMAXPATHLEN];
char *xsname;
/*
* devices that do not need to interact with xenstore
*/
"unit-address", "0");
if (devcls == XEN_CONSOLE)
"pm-hardware-state", "needs-suspend-resume");
return (DDI_SUCCESS);
}
/*
* PV devices that need to probe xenstore
*/
"pm-hardware-state", "needs-suspend-resume");
if (!backend)
else
/* Don't try to init a dev that may be closing */
return (DDI_FAILURE);
}
"cannot add watches for %s", xsname);
return (DDI_FAILURE);
}
/*
* frontend device will use "unit-addr" as
* the bus address, which will be set here
*/
if (!backend) {
void *prop_str;
switch (devcls) {
case XEN_VNET:
&prop_len) == 0) {
(void) ndi_prop_update_string(DDI_DEV_T_NONE,
}
&addr) == 0) {
(void) ndi_prop_update_string(DDI_DEV_T_NONE,
}
break;
case XEN_VBLK:
(void) ndi_prop_update_string(DDI_DEV_T_NONE,
}
#ifdef XPV_HVM_DRIVER
/*
* The mapping between the 'dev' name and the
* device ID maintained by Xenstore has to be
* tracked explicitly in HVM domains.
*/
(void) ndi_prop_update_string(DDI_DEV_T_NONE,
}
#endif /* XPV_HVM_DRIVER */
break;
default:
break;
}
}
return (DDI_SUCCESS);
}
void
{
/* Remove any registered callbacks. */
/* Remove any registered watches. */
/* tell other end to close */
}
}
/*
* Bind the event channel for this device instance.
* Currently we only support one evtchn per device instance.
*/
int
{
struct xendev_ppd *pdp;
int r;
if (!DOMAIN_IS_INITDOMAIN(xen_info)) {
} else {
return (DDI_SUCCESS);
}
} else {
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
}
#ifndef XPV_HVM_DRIVER
#endif
return (DDI_SUCCESS);
}
/*
* Allocate an event channel for this device instance.
* Currently we only support one evtchn per device instance.
*/
int
{
struct xendev_ppd *pdp;
int rv;
if (!DOMAIN_IS_INITDOMAIN(xen_info)) {
} else {
return (DDI_SUCCESS);
}
} else {
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
}
#ifndef XPV_HVM_DRIVER
#endif
return (DDI_SUCCESS);
}
/*
* Unbind the event channel for this device instance.
* Currently we only support one evtchn per device instance.
*/
void
{
struct xendev_ppd *pdp;
#ifndef XPV_HVM_DRIVER
#endif
}
}
#ifndef XPV_HVM_DRIVER
/*
* Map an inter-domain communication ring for a virtual device.
* This is used by backend drivers.
*/
int
{
int err;
char errstr[] = "mapping in ring buffer";
/* alloc va in backend dom for ring buffer */
0, 0, 0, 0, VM_SLEEP);
/* map in ring page */
if (err) {
goto errout1;
}
goto errout2;
}
/*
* init an acc handle and associate it w/ this ring
* this is only for backend drivers. we get the memory by calling
* vmem_xalloc(), instead of calling any ddi function, so we have
* to init an acc handle by ourselves
*/
/* init backend ring */
return (DDI_SUCCESS);
/* unmap ring page */
return (DDI_FAILURE);
}
/*
* Unmap a ring for a virtual device.
* This is used by backend drivers.
*/
void
{
}
#endif /* XPV_HVM_DRIVER */
/*
* Re-initialise an inter-domain communications ring for the backend domain.
* ring will be re-initialized after re-grant succeed
* ring will be freed if fails to re-grant access to backend domain
* so, don't keep useful data in the ring
* used only in frontend driver
*/
static void
{
/* init frontend ring */
}
/*
* allocate Xen inter-domain communications ring for Xen virtual devices
* used only in frontend driver
* if *ringpp is not NULL, we'll simply re-init it
*/
int
{
if (*ringpp) {
return (DDI_SUCCESS);
}
/*
* Allocate page for this ring buffer
*/
goto err;
goto err;
}
goto err;
}
goto err;
}
/* init frontend ring */
return (DDI_SUCCESS);
err:
return (DDI_FAILURE);
}
/*
* Release ring buffers allocated for Xen devices
* used for frontend driver
*/
void
{
}
{
char xsnamebuf[TYPICALMAXPATHLEN];
unsigned int tlen;
int ret;
if (!backend) {
} else {
}
} else {
}
/* Must have a driver to use. */
return (NULL);
/*
* We need to check the state of this device before we go
* further, otherwise we'll end up with a dead loop if
* anything goes wrong.
*/
return (NULL);
/*
* Driver binding uses the compatible property _before_ the
* node name, so we set the node name to the 'model' of the
* device (i.e. 'xnb' or 'xdb') and, if 'type' is present,
* encode both the model and the type in a compatible property
* (i.e. 'xnb,netfront' or 'xnb,SUNW_mac'). This allows a
* driver binding based on the <model,type> pair _before_ a
* binding based on the node name.
*/
== 0)) {
char *c[1];
}
if (i_ddi_devi_attached(parent))
else
if (ret != NDI_SUCCESS)
return (dip);
}
/*
* xendev_enum_class()
*/
void
{
return;
return;
int circ;
/*
* Don't need to probe this kind of device from the
* store, just create one if it doesn't exist.
*/
== NULL)
} else {
/*
* Probe this kind of device from the store, both
* frontend and backend.
*/
}
}
/*
* xendev_enum_all()
*/
void
{
int i;
/*
* Dom0 relies on watchpoints to create non-soft
* devices - don't attempt to iterate over the store.
*/
continue;
/*
* If the store is not yet available, don't attempt to
* iterate.
*/
continue;
}
}
{
int i;
/*
* This relies on the convention that variants of a base
* driver share the same prefix and that there are no drivers
* which share a common prefix with the name of any other base
* drivers.
*
* So for a base driver 'xnb' (which is the name listed in
* xdci) the variants all begin with the string 'xnb' (in fact
* they are 'xnbe', 'xnbo' and 'xnbu') and there are no other
* base drivers which have the prefix 'xnb'.
*/
}
return (XEN_INVAL);
}
int
{
}
/*
* Determine if a devinfo instance exists of a particular device
* class, domain and xenstore virtual device number.
*/
{
switch (devclass) {
case XEN_CONSOLE:
case XEN_XENBUS:
case XEN_DOMCAPS:
case XEN_BALLOON:
case XEN_EVTCHN:
case XEN_PRIVCMD:
/* Console and soft devices have no vdev. */
break;
default:
break;
}
continue;
continue;
&ndevcls) != DDI_PROP_SUCCESS)
continue;
} else {
}
return (dip);
}
return (NULL);
}
int
{
}
int
{
return (pdp->xd_vdevnum);
}
char *
{
}
char *
{
return (NULL);
}
struct xenbus_device *
{
}
{
return ((domid_t)-1);
}
void
{
}
void
{
}
static void
i_xvdi_oestate_handler(void *arg)
{
/* send notification to driver */
&evc) == DDI_SUCCESS) {
}
} else {
/*
* take default action, if driver hasn't registered its
* event handler yet
*/
if (oestate == XenbusStateClosing) {
} else if (oestate == XenbusStateClosed) {
}
}
/*
* We'll try to remove the devinfo node of this device if the
* other end has closed.
*/
if (oestate == XenbusStateClosed)
}
static void
i_xvdi_hpstate_handler(void *arg)
{
char *hp_status;
unsigned int hpl;
return;
}
}
void
{
struct xendev_ppd *pdp;
}
static void
{
unsigned int bel;
/*
* If the backend is not the same as that we already stored,
* re-set our watch for its' state.
*/
(void) i_xvdi_add_watch_oestate(dip);
}
}
static int
{
/*
* Create taskq for delivering other end state change event to
* this device later.
*
* Set nthreads to 1 to make sure that events can be delivered
* in order.
*
* Note: It is _not_ guaranteed that driver can see every
* xenstore change under the path that it is watching. If two
* changes happen consecutively in a very short amount of
* time, it is likely that the driver will see only the last
* one.
*/
return (DDI_FAILURE);
/*
* Watch for changes to the XenbusState of otherend.
*/
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static void
{
struct xendev_ppd *pdp;
struct xenbus_device *dev;
/* Unwatch for changes to XenbusState of otherend */
}
/* make sure no event handler is running */
}
/* clean up */
}
static int
{
/*
* Create taskq for delivering hotplug status change event to
* this device later.
*
* Set nthreads to 1 to make sure that events can be delivered
* in order.
*
* Note: It is _not_ guaranteed that driver can see every
* hotplug status change under the path that it is
* watching. If two changes happen consecutively in a very
* short amount of time, it is likely that the driver only
* sees the last one.
*/
return (DDI_FAILURE);
char *path;
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
static void
{
struct xendev_ppd *pdp;
/* Unwatch for changes to "hotplug-status" node for backend device. */
}
/* Make sure no event handler is running. */
}
/* Clean up. */
}
}
static int
{
return (DDI_FAILURE);
}
/*
* Frontend devices must watch for the backend path
* changing.
*/
goto unwatch_and_fail;
} else {
/*
* Backend devices must watch for hotplug events.
*/
goto unwatch_and_fail;
}
return (DDI_SUCCESS);
return (DDI_FAILURE);
}
static void
{
else
}
static int
{
/*
* Frontend devices need to watch for the backend path changing.
*/
char *path;
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
static void
{
}
}
int
{
int rv;
struct xendev_ppd *pdp;
"xvdi_switch_state: dip 0x%p moves to %d",
if (rv > 0)
return (rv);
}
/*
* Notify hotplug script running in userland
*/
int
{
struct xendev_ppd *pdp;
int err;
if (err != DDI_SUCCESS)
goto failure;
if (err != DDI_SUCCESS)
goto failure;
if (err != DDI_SUCCESS)
goto failure;
if (err != DDI_SUCCESS)
goto failure;
if (err != DDI_SUCCESS)
goto failure;
if (err != DDI_SUCCESS)
goto failure;
switch (hpc) {
case XEN_HP_ADD:
break;
case XEN_HP_REMOVE:
break;
default:
err = DDI_FAILURE;
goto failure;
}
return (err);
}
/* ARGSUSED */
static void
unsigned int len)
{
char *path;
if (xendev_dip == NULL)
}
static void
i_xvdi_watch_device(char *path)
{
struct xenbus_watch *w;
w = kmem_zalloc(sizeof (*w), KM_SLEEP);
w->callback = &i_xvdi_probe_path_cb;
if (register_xenbus_watch(w) != 0) {
"cannot set watch on %s", path);
kmem_free(w, sizeof (*w));
return;
}
}
void
{
int devclass;
/*
* Watch for devices being created in the store.
*/
if (newstate == XENSTORE_DOWN)
return;
}
}
/*
* Iterate over the store looking for backend devices to create.
*/
static void
{
char **domains;
unsigned int ndomains;
int ldomains, i;
return;
}
}
/*
* Iterate over the store looking for frontend devices to create.
*/
static void
{
}
static void
char *domain)
{
char **devices;
unsigned int ndevices;
dom = DOMID_SELF;
domain_path = "";
} else {
}
return;
int vdev;
== NULL)
}
}
/*
* Leaf drivers should call this in their detach() routine during suspend.
*/
void
{
}
/*
* Leaf drivers should call this in their attach() routine during resume.
*/
int
{
return (i_xvdi_add_watches(dip));
}
/*
* Add event handler for the leaf driver
* to handle event triggered by the change in xenstore
*/
int
{
return (DDI_FAILURE);
}
} else {
/* Unsupported watch. */
return (DDI_FAILURE);
}
/*
* No event handler provided, take default action to handle
* event.
*/
if (evthandler == NULL) {
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
!= DDI_SUCCESS) {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* Remove event handler for the leaf driver and unwatch xenstore
* so, driver will not be notified when xenstore entry changed later
*/
void
{
struct xendev_ppd *pdp;
} else {
return;
}
}
}
(void) ddi_remove_event_handler(oeid);
(void) ddi_remove_event_handler(hpid);
}
/*
* common ring interfaces
*/
unsigned int
{
if (ringp->xr_frontend) {
return (GET_RING_SIZE(ringp) -
} else {
return (GET_RING_SIZE(ringp) -
}
}
int
{
}
int
{
return (frp->req_prod_pvt !=
}
int
{
}
/* NOTE: req_event will be increased as needed */
void *
{
if (ringp->xr_frontend) {
/* for frontend ring */
else
return (NULL);
} else {
/* for backend ring */
/* RING_FINAL_CHECK_FOR_REQUESTS() */
else {
membar_enter();
return (GET_RING_ENTRY_BE(ringp,
else
return (NULL);
}
}
}
int
{
/* only frontend should be able to push request */
/* RING_PUSH_REQUEST_AND_CHECK_NOTIFY() */
membar_enter();
}
/* NOTE: rsp_event will be increased as needed */
void *
{
if (!ringp->xr_frontend) {
/* for backend ring */
} else {
/* for frontend ring */
/* RING_FINAL_CHECK_FOR_RESPONSES() */
else {
membar_enter();
return (GET_RING_ENTRY_FE(ringp,
else
return (NULL);
}
}
}
int
{
/* only backend should be able to push response */
/* RING_PUSH_RESPONSE_AND_CHECK_NOTIFY() */
membar_enter();
}
static void
{
int i;
/* shared ring initialization */
}
static void
{
xfrp->req_prod_pvt = 0;
}
#ifndef XPV_HVM_DRIVER
static void
{
xbrp->rsp_prod_pvt = 0;
ringp->xr_frontend = 0;
}
#endif /* XPV_HVM_DRIVER */
static void
xendev_offline_device(void *arg)
{
char devname[MAXNAMELEN] = {0};
/*
* This is currently the only chance to delete a devinfo node, which
* is _not_ always successful.
*/
}
static void
{
/*
* Don't trigger two consecutive ndi_devi_offline on the same
* dip.
*/
if ((oestate == XenbusStateClosed) &&
return;
}
/*ARGSUSED*/
static void
unsigned int len)
{
}
static void
i_xvdi_probe_path_handler(void *arg)
{
== 0)) {
break;
}
== 0)) {
break;
}
}
if (p == NULL) {
"unexpected path prefix in %s", path);
goto done;
}
if (frontend) {
dom = DOMID_SELF;
"i_xvdi_probe_path_handler: "
"cannot parse frontend path %s",
path);
goto done;
}
} else {
"i_xvdi_probe_path_handler: "
"cannot parse backend path %s",
path);
goto done;
}
}
/*
* This is an oxymoron, so indicates a bogus configuration we
* must check for.
*/
"invalid path %s", path);
goto done;
}
parent = xendev_dip;
"i_xvdi_probe_path_handler: create for %s", path);
} else {
"i_xvdi_probe_path_handler: %s already exists", path);
}
done:
}