#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: xbdback_xenbus.c,v 1.108 2026/03/10 21:59:29 andvar Exp $");
#include <sys/buf.h>
#include <sys/condvar.h>
#include <sys/conf.h>
#include <sys/disk.h>
#include <sys/device.h>
#include <sys/fcntl.h>
#include <sys/kauth.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/kthread.h>
#include <sys/mutex.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/vnode.h>
#include <xen/intr.h>
#include <xen/hypervisor.h>
#include <xen/xen.h>
#include <xen/xen_shm.h>
#include <xen/evtchn.h>
#include <xen/xenbus.h>
#include <xen/xenring.h>
#include <xen/include/public/io/protocols.h>
#ifdef XENDEBUG_VBD
#define XENPRINTF(x) printf x
#else
#define XENPRINTF(x)
#endif
#define BLKIF_RING_SIZE __CONST_RING_SIZE(blkif, PAGE_SIZE)
#define VBD_BSIZE 512
#define VBD_MAXSECT ((PAGE_SIZE / VBD_BSIZE) - 1)
#define VBD_VA_SIZE MAXPHYS
#define VBD_MAX_INDIRECT_SEGMENTS (VBD_VA_SIZE >> PAGE_SHIFT)
CTASSERT(XENSHM_MAX_PAGES_PER_REQUEST >= VBD_MAX_INDIRECT_SEGMENTS);
struct xbdback_instance;
typedef enum {WAITING, RUN, DISCONNECTING, DISCONNECTED} xbdback_state_t;
typedef void *(* xbdback_cont_t)(struct xbdback_instance *, void *);
enum xbdi_proto {
XBDIP_NATIVE,
XBDIP_32,
XBDIP_64
};
struct xbdback_va {
SLIST_ENTRY(xbdback_va) xv_next;
vaddr_t xv_vaddr;
};
struct xbdback_io {
SLIST_ENTRY(xbdback_io) xio_next;
struct xbdback_instance *xio_xbdi;
blkif_request_t xio_xen_req;
struct blkif_request_segment *xio_seg;
bus_dmamap_t xio_seg_dmamap;
union {
struct {
struct buf xio_buf;
vaddr_t xio_vaddr;
struct xbdback_va *xio_xv;
vaddr_t xio_start_offset;
grant_ref_t xio_gref[VBD_MAX_INDIRECT_SEGMENTS];
grant_handle_t xio_gh[VBD_MAX_INDIRECT_SEGMENTS];
bool xio_need_bounce;
} xio_rw;
} u;
};
#define xio_buf u.xio_rw.xio_buf
#define xio_vaddr u.xio_rw.xio_vaddr
#define xio_start_offset u.xio_rw.xio_start_offset
#define xio_xv u.xio_rw.xio_xv
#define xio_gref u.xio_rw.xio_gref
#define xio_gh u.xio_rw.xio_gh
#define xio_need_bounce u.xio_rw.xio_need_bounce
struct xbdback_instance {
SLIST_ENTRY(xbdback_instance) next;
struct xenbus_device *xbdi_xbusd;
struct xenbus_watch xbdi_watch;
domid_t xbdi_domid;
uint32_t xbdi_handle;
char xbdi_name[16];
kmutex_t xbdi_lock;
kcondvar_t xbdi_cv;
xbdback_state_t xbdi_status;
struct xbdback_io xbdi_io[BLKIF_RING_SIZE];
SLIST_HEAD(, xbdback_io) xbdi_io_free;
struct xbdback_va xbdi_va[BLKIF_RING_SIZE];
SLIST_HEAD(, xbdback_va) xbdi_va_free;
struct blkif_request_segment *xbdi_segs;
vaddr_t xbdi_bouncebuf;
int xbdi_bouncebuf_use;
dev_t xbdi_dev;
const struct bdevsw *xbdi_bdevsw;
struct vnode *xbdi_vp;
uint64_t xbdi_size;
bool xbdi_ro;
unsigned int xbdi_evtchn;
struct intrhand *xbdi_ih;
blkif_back_ring_proto_t xbdi_ring;
enum xbdi_proto xbdi_proto;
grant_handle_t xbdi_ring_handle;
vaddr_t xbdi_ring_va;
int xbdi_refcnt;
RING_IDX xbdi_req_prod;
xbdback_cont_t xbdi_cont;
xbdback_cont_t xbdi_cont_restart;
void *xbdi_cont_restart_obj;
uint xbdi_pendingreqs;
struct timeval xbdi_lasterr_time;
};
#define xbdi_get(xbdip) \
do { \
KASSERT(mutex_owned(&xbdip->xbdi_lock)); \
(xbdip)->xbdi_refcnt++; \
} while (0)
#define xbdi_put(xbdip) \
do { \
KASSERT(mutex_owned(&xbdip->xbdi_lock)); \
if (--((xbdip)->xbdi_refcnt) == 0) \
xbdback_finish_disconnect(xbdip); \
} while (0)
static SLIST_HEAD(, xbdback_instance) xbdback_instances;
static kmutex_t xbdback_lock;
static const struct timeval xbdback_err_intvl = { 1, 0 };
void xbdbackattach(int);
static int xbdback_xenbus_create(struct xenbus_device *);
static int xbdback_xenbus_destroy(void *);
static void xbdback_frontend_changed(void *, XenbusState);
static void xbdback_backend_changed(struct xenbus_watch *,
const char **, unsigned int);
static int xbdback_evthandler(void *);
static int xbdback_connect(struct xbdback_instance *);
static void xbdback_disconnect(struct xbdback_instance *);
static void xbdback_finish_disconnect(struct xbdback_instance *);
static bool xbdif_lookup(domid_t, uint32_t);
static void *xbdback_co_main(struct xbdback_instance *, void *);
static void *xbdback_co_main_loop(struct xbdback_instance *, void *);
static void *xbdback_co_main_incr(struct xbdback_instance *, void *);
static void *xbdback_co_main_done2(struct xbdback_instance *, void *);
static void *xbdback_co_cache_flush(struct xbdback_instance *, void *);
static void *xbdback_co_io(struct xbdback_instance *, void *);
static void *xbdback_co_io_gotio(struct xbdback_instance *, void *);
static void *xbdback_co_do_io(struct xbdback_instance *, void *);
static void xbdback_io_error(struct xbdback_io *, int);
static void xbdback_iodone(struct buf *);
static void xbdback_iodone_locked(struct xbdback_instance *,
struct xbdback_io *, struct buf *);
static void xbdback_send_reply(struct xbdback_instance *, uint64_t , int , int);
static int xbdback_map_shm(struct xbdback_io *);
static void xbdback_unmap_shm(struct xbdback_io *);
static struct xbdback_io *xbdback_io_get(struct xbdback_instance *);
static void xbdback_io_put(struct xbdback_instance *, struct xbdback_io *);
static void xbdback_thread(void *);
static void xbdback_wakeup_thread(struct xbdback_instance *);
static void xbdback_trampoline(struct xbdback_instance *, void *);
static struct xenbus_backend_driver xbd_backend_driver = {
.xbakd_create = xbdback_xenbus_create,
.xbakd_type = "vbd"
};
void
xbdbackattach(int n)
{
XENPRINTF(("xbdbackattach\n"));
SLIST_INIT(&xbdback_instances);
mutex_init(&xbdback_lock, MUTEX_DEFAULT, IPL_NONE);
xenbus_backend_register(&xbd_backend_driver);
}
static int
xbdback_xenbus_create(struct xenbus_device *xbusd)
{
struct xbdback_instance *xbdi;
long domid, handle;
int error, i;
int segalloc = 0;
char *ep;
if ((error = xenbus_read_ul(NULL, xbusd->xbusd_path,
"frontend-id", &domid, 10)) != 0) {
aprint_error("xbdback: can't read %s/frontend-id: %d\n",
xbusd->xbusd_path, error);
return error;
}
for (i = strlen(xbusd->xbusd_path); i > 0; i--) {
if (xbusd->xbusd_path[i] == '/')
break;
}
if (i == 0) {
aprint_error("xbdback: can't parse %s\n",
xbusd->xbusd_path);
return EFTYPE;
}
handle = strtoul(&xbusd->xbusd_path[i+1], &ep, 10);
if (*ep != '\0') {
aprint_error("xbdback: can't parse %s\n",
xbusd->xbusd_path);
return EFTYPE;
}
xbdi = kmem_zalloc(sizeof(*xbdi), KM_SLEEP);
xbdi->xbdi_domid = domid;
xbdi->xbdi_handle = handle;
snprintf(xbdi->xbdi_name, sizeof(xbdi->xbdi_name), "xbdb%di%d",
xbdi->xbdi_domid, xbdi->xbdi_handle);
mutex_enter(&xbdback_lock);
if (xbdif_lookup(domid, handle)) {
mutex_exit(&xbdback_lock);
kmem_free(xbdi, sizeof(*xbdi));
return EEXIST;
}
SLIST_INSERT_HEAD(&xbdback_instances, xbdi, next);
mutex_exit(&xbdback_lock);
xbdi->xbdi_status = DISCONNECTED;
mutex_init(&xbdi->xbdi_lock, MUTEX_DEFAULT, IPL_BIO);
cv_init(&xbdi->xbdi_cv, xbdi->xbdi_name);
mutex_enter(&xbdi->xbdi_lock);
xbdi_get(xbdi);
mutex_exit(&xbdi->xbdi_lock);
xbusd->xbusd_u.b.b_cookie = xbdi;
xbusd->xbusd_u.b.b_detach = xbdback_xenbus_destroy;
xbusd->xbusd_otherend_changed = xbdback_frontend_changed;
xbdi->xbdi_xbusd = xbusd;
SLIST_INIT(&xbdi->xbdi_va_free);
for (i = 0; i < BLKIF_RING_SIZE; i++) {
xbdi->xbdi_va[i].xv_vaddr = uvm_km_alloc(kernel_map,
VBD_VA_SIZE, 0, UVM_KMF_VAONLY|UVM_KMF_WAITVA);
SLIST_INSERT_HEAD(&xbdi->xbdi_va_free, &xbdi->xbdi_va[i],
xv_next);
}
CTASSERT(sizeof(struct blkif_request_segment) * VBD_MAX_INDIRECT_SEGMENTS == 128);
xbdi->xbdi_segs = (void *)uvm_km_alloc(kernel_map, round_page(
sizeof(struct blkif_request_segment) * VBD_MAX_INDIRECT_SEGMENTS * BLKIF_RING_SIZE),
PAGE_SIZE, UVM_KMF_WIRED | UVM_KMF_WAITVA);
SLIST_INIT(&xbdi->xbdi_io_free);
for (i = 0; i < BLKIF_RING_SIZE; i++) {
struct xbdback_io *xbd_io = &xbdi->xbdi_io[i];
xbd_io->xio_seg =
&xbdi->xbdi_segs[i * VBD_MAX_INDIRECT_SEGMENTS];
error = bus_dmamap_create(xbdi->xbdi_xbusd->xbusd_dmat,
PAGE_SIZE, 1, PAGE_SIZE, PAGE_SIZE,
BUS_DMA_WAITOK | BUS_DMA_ALLOCNOW,
&xbd_io->xio_seg_dmamap);
if (error != 0) {
printf("%s: can't create dma map for indirect segments %d\n",
xbdi->xbdi_name, i);
goto fail;
}
error = bus_dmamap_load(xbdi->xbdi_xbusd->xbusd_dmat,
xbd_io->xio_seg_dmamap, xbd_io->xio_seg,
sizeof(struct blkif_request_segment) * VBD_MAX_INDIRECT_SEGMENTS,
NULL, BUS_DMA_WAITOK);
if (error != 0) {
printf("%s: can't load dma map for indirect segments %d @%p (%d, %zu)\n",
xbdi->xbdi_name, i, xbd_io->xio_seg, error, sizeof(xbd_io->xio_seg));
bus_dmamap_destroy(xbdi->xbdi_xbusd->xbusd_dmat,
xbd_io->xio_seg_dmamap);
goto fail;
}
KASSERT(xbd_io->xio_seg_dmamap->dm_nsegs == 1);
segalloc = i;
SLIST_INSERT_HEAD(&xbdi->xbdi_io_free, xbd_io, xio_next);
}
error = xenbus_watch_path2(xbusd, xbusd->xbusd_path, "physical-device",
&xbdi->xbdi_watch, xbdback_backend_changed);
if (error) {
printf("failed to watch on %s/physical-device: %d\n",
xbusd->xbusd_path, error);
goto fail;
}
xbdi->xbdi_watch.xbw_dev = xbusd;
error = xenbus_switch_state(xbusd, NULL, XenbusStateInitWait);
if (error) {
printf("failed to switch state on %s: %d\n",
xbusd->xbusd_path, error);
goto fail2;
}
xbdi->xbdi_bouncebuf = uvm_km_alloc(kernel_map, MAXPHYS, PAGE_SIZE,
UVM_KMF_WIRED | UVM_KMF_WAITVA);
return 0;
fail2:
unregister_xenbus_watch(&xbdi->xbdi_watch);
fail:
for (i = 0; i < segalloc; i++) {
struct xbdback_io *xbd_io = &xbdi->xbdi_io[i];
bus_dmamap_unload(xbdi->xbdi_xbusd->xbusd_dmat,
xbd_io->xio_seg_dmamap);
bus_dmamap_destroy(xbdi->xbdi_xbusd->xbusd_dmat,
xbd_io->xio_seg_dmamap);
}
mutex_enter(&xbdback_lock);
SLIST_REMOVE(&xbdback_instances, xbdi, xbdback_instance, next);
mutex_exit(&xbdback_lock);
kmem_free(xbdi, sizeof(*xbdi));
return error;
}
static int
xbdback_xenbus_destroy(void *arg)
{
struct xbdback_instance *xbdi = arg;
XENPRINTF(("xbdback_xenbus_destroy state %d\n", xbdi->xbdi_status));
xbdback_disconnect(xbdi);
if (xbdi->xbdi_watch.node)
xenbus_unwatch_path(&xbdi->xbdi_watch);
if (xbdi->xbdi_ring_handle) {
xen_shm_unmap(xbdi->xbdi_ring_va, 1, &xbdi->xbdi_ring_handle);
}
if (xbdi->xbdi_ring_va != 0) {
uvm_km_free(kernel_map, xbdi->xbdi_ring_va,
PAGE_SIZE, UVM_KMF_VAONLY);
}
if (xbdi->xbdi_size) {
const char *name;
struct dkwedge_info wi;
if (getdiskinfo(xbdi->xbdi_vp, &wi) == 0)
name = wi.dkw_devname;
else
name = "*unknown*";
printf("xbd backend: detach device %s for domain %d\n",
name, xbdi->xbdi_domid);
vn_close(xbdi->xbdi_vp, FREAD, NOCRED);
}
mutex_enter(&xbdback_lock);
SLIST_REMOVE(&xbdback_instances, xbdi, xbdback_instance, next);
mutex_exit(&xbdback_lock);
for (int i = 0; i < BLKIF_RING_SIZE; i++) {
struct xbdback_io *xbd_io = &xbdi->xbdi_io[i];
bus_dmamap_unload(xbdi->xbdi_xbusd->xbusd_dmat,
xbd_io->xio_seg_dmamap);
bus_dmamap_destroy(xbdi->xbdi_xbusd->xbusd_dmat,
xbd_io->xio_seg_dmamap);
if (xbdi->xbdi_va[i].xv_vaddr != 0) {
uvm_km_free(kernel_map, xbdi->xbdi_va[i].xv_vaddr,
VBD_VA_SIZE, UVM_KMF_VAONLY);
xbdi->xbdi_va[i].xv_vaddr = 0;
}
}
mutex_destroy(&xbdi->xbdi_lock);
cv_destroy(&xbdi->xbdi_cv);
kmem_free(xbdi, sizeof(*xbdi));
return 0;
}
static int
xbdback_connect(struct xbdback_instance *xbdi)
{
int err;
evtchn_op_t evop;
grant_ref_t gring_ref;
u_long ring_ref, revtchn;
char xsproto[32];
const char *proto;
struct xenbus_device *xbusd = xbdi->xbdi_xbusd;
XENPRINTF(("xbdback %s: connect\n", xbusd->xbusd_path));
err = xenbus_read_ul(NULL, xbusd->xbusd_otherend,
"ring-ref", &ring_ref, 10);
if (err) {
xenbus_dev_fatal(xbusd, err, "reading %s/ring-ref",
xbusd->xbusd_otherend);
return -1;
}
XENPRINTF(("xbdback %s: connect ring-ref %lu\n", xbusd->xbusd_path, ring_ref));
err = xenbus_read_ul(NULL, xbusd->xbusd_otherend,
"event-channel", &revtchn, 10);
if (err) {
xenbus_dev_fatal(xbusd, err, "reading %s/event-channel",
xbusd->xbusd_otherend);
return -1;
}
XENPRINTF(("xbdback %s: connect revtchn %lu\n", xbusd->xbusd_path, revtchn));
err = xenbus_read(NULL, xbusd->xbusd_otherend, "protocol",
xsproto, sizeof(xsproto));
if (err) {
xbdi->xbdi_proto = XBDIP_NATIVE;
proto = "unspecified";
XENPRINTF(("xbdback %s: connect no xsproto\n", xbusd->xbusd_path));
} else {
XENPRINTF(("xbdback %s: connect xsproto %s\n", xbusd->xbusd_path, xsproto));
if (strcmp(xsproto, XEN_IO_PROTO_ABI_NATIVE) == 0) {
xbdi->xbdi_proto = XBDIP_NATIVE;
proto = XEN_IO_PROTO_ABI_NATIVE;
} else if (strcmp(xsproto, XEN_IO_PROTO_ABI_X86_32) == 0) {
xbdi->xbdi_proto = XBDIP_32;
proto = XEN_IO_PROTO_ABI_X86_32;
} else if (strcmp(xsproto, XEN_IO_PROTO_ABI_X86_64) == 0) {
xbdi->xbdi_proto = XBDIP_64;
proto = XEN_IO_PROTO_ABI_X86_64;
} else {
aprint_error("xbd domain %d: unknown proto %s\n",
xbdi->xbdi_domid, xsproto);
return -1;
}
}
xbdi->xbdi_ring_va = uvm_km_alloc(kernel_map, PAGE_SIZE, 0,
UVM_KMF_VAONLY);
if (xbdi->xbdi_ring_va == 0) {
xenbus_dev_fatal(xbusd, ENOMEM,
"can't get VA for ring", xbusd->xbusd_otherend);
return -1;
}
XENPRINTF(("xbdback %s: connect va 0x%" PRIxVADDR "\n", xbusd->xbusd_path, xbdi->xbdi_ring_va));
gring_ref = ring_ref;
if (xen_shm_map(1, xbdi->xbdi_domid, &gring_ref, xbdi->xbdi_ring_va,
&xbdi->xbdi_ring_handle, 0) != 0) {
aprint_error("xbdback %s: can't map grant ref\n",
xbusd->xbusd_path);
xenbus_dev_fatal(xbusd, EINVAL,
"can't map ring", xbusd->xbusd_otherend);
goto err1;
}
XENPRINTF(("xbdback %s: connect grhandle %d\n", xbusd->xbusd_path, xbdi->xbdi_ring_handle));
switch(xbdi->xbdi_proto) {
case XBDIP_NATIVE:
{
blkif_sring_t *sring = (void *)xbdi->xbdi_ring_va;
BACK_RING_INIT(&xbdi->xbdi_ring.ring_n, sring, PAGE_SIZE);
break;
}
case XBDIP_32:
{
blkif_x86_32_sring_t *sring = (void *)xbdi->xbdi_ring_va;
BACK_RING_INIT(&xbdi->xbdi_ring.ring_32, sring, PAGE_SIZE);
break;
}
case XBDIP_64:
{
blkif_x86_64_sring_t *sring = (void *)xbdi->xbdi_ring_va;
BACK_RING_INIT(&xbdi->xbdi_ring.ring_64, sring, PAGE_SIZE);
break;
}
}
evop.cmd = EVTCHNOP_bind_interdomain;
evop.u.bind_interdomain.remote_dom = xbdi->xbdi_domid;
evop.u.bind_interdomain.remote_port = revtchn;
err = HYPERVISOR_event_channel_op(&evop);
if (err) {
aprint_error("blkback %s: "
"can't get event channel: %d\n",
xbusd->xbusd_otherend, err);
xenbus_dev_fatal(xbusd, err,
"can't bind event channel", xbusd->xbusd_otherend);
goto err2;
}
xbdi->xbdi_evtchn = evop.u.bind_interdomain.local_port;
XENPRINTF(("xbdback %s: connect evchannel %d\n", xbusd->xbusd_path, xbdi->xbdi_evtchn));
xbdi->xbdi_ih = xen_intr_establish_xname(-1, &xen_pic,
xbdi->xbdi_evtchn, IST_LEVEL, IPL_BIO, xbdback_evthandler, xbdi,
true, xbdi->xbdi_name);
KASSERT(xbdi->xbdi_ih != NULL);
aprint_verbose("xbd backend domain %d handle %#x (%d) "
"using event channel %d, protocol %s\n", xbdi->xbdi_domid,
xbdi->xbdi_handle, xbdi->xbdi_handle, xbdi->xbdi_evtchn, proto);
xbdi->xbdi_status = WAITING;
hypervisor_unmask_event(xbdi->xbdi_evtchn);
hypervisor_notify_via_evtchn(xbdi->xbdi_evtchn);
if (kthread_create(PRI_NONE, KTHREAD_MPSAFE, NULL,
xbdback_thread, xbdi, NULL, "%s", xbdi->xbdi_name) == 0)
return 0;
err2:
xen_shm_unmap(xbdi->xbdi_ring_va, 1, &xbdi->xbdi_ring_handle);
err1:
uvm_km_free(kernel_map, xbdi->xbdi_ring_va, PAGE_SIZE, UVM_KMF_VAONLY);
return -1;
}
static void
xbdback_disconnect(struct xbdback_instance *xbdi)
{
mutex_enter(&xbdi->xbdi_lock);
if (xbdi->xbdi_status == DISCONNECTED) {
mutex_exit(&xbdi->xbdi_lock);
return;
}
hypervisor_mask_event(xbdi->xbdi_evtchn);
xbdi->xbdi_status = DISCONNECTING;
cv_signal(&xbdi->xbdi_cv);
while (xbdi->xbdi_status != DISCONNECTED)
cv_wait(&xbdi->xbdi_cv, &xbdi->xbdi_lock);
mutex_exit(&xbdi->xbdi_lock);
xen_intr_disestablish(xbdi->xbdi_ih);
xenbus_switch_state(xbdi->xbdi_xbusd, NULL, XenbusStateClosing);
}
static void
xbdback_frontend_changed(void *arg, XenbusState new_state)
{
struct xbdback_instance *xbdi = arg;
struct xenbus_device *xbusd = xbdi->xbdi_xbusd;
XENPRINTF(("xbdback %s: new state %d\n", xbusd->xbusd_path, new_state));
switch(new_state) {
case XenbusStateInitialising:
break;
case XenbusStateInitialised:
case XenbusStateConnected:
if (xbdi->xbdi_status == WAITING || xbdi->xbdi_status == RUN)
break;
xbdback_connect(xbdi);
break;
case XenbusStateClosing:
xbdback_disconnect(xbdi);
break;
case XenbusStateClosed:
panic("xbdback_frontend_changed: closed\n");
case XenbusStateUnknown:
case XenbusStateInitWait:
default:
aprint_error("xbdback %s: invalid frontend state %d\n",
xbusd->xbusd_path, new_state);
}
return;
}
static void
xbdback_backend_changed(struct xenbus_watch *watch,
const char **vec, unsigned int len)
{
struct xenbus_device *xbusd = watch->xbw_dev;
struct xbdback_instance *xbdi = xbusd->xbusd_u.b.b_cookie;
int err;
long dev;
char mode[32];
struct xenbus_transaction *xbt;
const char *devname;
int major;
err = xenbus_read_ul(NULL, xbusd->xbusd_path, "physical-device",
&dev, 10);
if (err)
return;
if (xbdi->xbdi_vp != NULL) {
if (xbdi->xbdi_status == WAITING || xbdi->xbdi_status == RUN) {
if (xbdi->xbdi_dev != dev) {
printf("xbdback %s: changing physical device "
"from %#"PRIx64" to %#lx not supported\n",
xbusd->xbusd_path, xbdi->xbdi_dev, dev);
}
}
return;
}
xbdi->xbdi_dev = dev;
err = xenbus_read(NULL, xbusd->xbusd_path, "mode", mode, sizeof(mode));
if (err) {
printf("xbdback: failed to read %s/mode: %d\n",
xbusd->xbusd_path, err);
return;
}
if (mode[0] == 'w')
xbdi->xbdi_ro = false;
else
xbdi->xbdi_ro = true;
major = major(xbdi->xbdi_dev);
devname = devsw_blk2name(major);
if (devname == NULL) {
printf("xbdback %s: unknown device 0x%"PRIx64"\n",
xbusd->xbusd_path, xbdi->xbdi_dev);
return;
}
xbdi->xbdi_bdevsw = bdevsw_lookup(xbdi->xbdi_dev);
if (xbdi->xbdi_bdevsw == NULL) {
printf("xbdback %s: no bdevsw for device 0x%"PRIx64"\n",
xbusd->xbusd_path, xbdi->xbdi_dev);
return;
}
err = bdevvp(xbdi->xbdi_dev, &xbdi->xbdi_vp);
if (err) {
printf("xbdback %s: can't open device 0x%"PRIx64": %d\n",
xbusd->xbusd_path, xbdi->xbdi_dev, err);
return;
}
err = vn_lock(xbdi->xbdi_vp, LK_EXCLUSIVE | LK_RETRY);
if (err) {
printf("xbdback %s: can't vn_lock device 0x%"PRIx64": %d\n",
xbusd->xbusd_path, xbdi->xbdi_dev, err);
vrele(xbdi->xbdi_vp);
return;
}
err = VOP_OPEN(xbdi->xbdi_vp, FREAD, NOCRED);
if (err) {
printf("xbdback %s: can't VOP_OPEN device 0x%"PRIx64": %d\n",
xbusd->xbusd_path, xbdi->xbdi_dev, err);
vput(xbdi->xbdi_vp);
return;
}
VOP_UNLOCK(xbdi->xbdi_vp);
struct dkwedge_info wi;
if ((err = getdiskinfo(xbdi->xbdi_vp, &wi)) == 0) {
xbdi->xbdi_size = wi.dkw_size;
printf("xbd backend: attach device %s (size %" PRIu64 ") "
"for domain %d\n", wi.dkw_devname, xbdi->xbdi_size,
xbdi->xbdi_domid);
} else {
printf("xbdback %s: can't DIOCGWEDGEINFO device "
"0x%"PRIx64": %d\n", xbusd->xbusd_path,
xbdi->xbdi_dev, err);
xbdi->xbdi_size = xbdi->xbdi_dev = 0;
vn_close(xbdi->xbdi_vp, FREAD, NOCRED);
xbdi->xbdi_vp = NULL;
return;
}
again:
xbt = xenbus_transaction_start();
if (xbt == NULL) {
printf("xbdback %s: can't start transaction\n",
xbusd->xbusd_path);
return;
}
err = xenbus_printf(xbt, xbusd->xbusd_path, "sectors", "%" PRIu64 ,
xbdi->xbdi_size);
if (err) {
printf("xbdback: failed to write %s/sectors: %d\n",
xbusd->xbusd_path, err);
goto abort;
}
err = xenbus_printf(xbt, xbusd->xbusd_path, "info", "%u",
xbdi->xbdi_ro ? VDISK_READONLY : 0);
if (err) {
printf("xbdback: failed to write %s/info: %d\n",
xbusd->xbusd_path, err);
goto abort;
}
err = xenbus_printf(xbt, xbusd->xbusd_path, "sector-size", "%lu",
(u_long)DEV_BSIZE);
if (err) {
printf("xbdback: failed to write %s/sector-size: %d\n",
xbusd->xbusd_path, err);
goto abort;
}
err = xenbus_printf(xbt, xbusd->xbusd_path, "feature-flush-cache",
"%u", 1);
if (err) {
printf("xbdback: failed to write %s/feature-flush-cache: %d\n",
xbusd->xbusd_path, err);
goto abort;
}
err = xenbus_printf(xbt, xbusd->xbusd_path,
"feature-max-indirect-segments", "%u", VBD_MAX_INDIRECT_SEGMENTS);
if (err) {
printf("xbdback: failed to write %s/feature-indirect: %d\n",
xbusd->xbusd_path, err);
goto abort;
}
err = xenbus_transaction_end(xbt, 0);
if (err == EAGAIN)
goto again;
if (err) {
printf("xbdback %s: can't end transaction: %d\n",
xbusd->xbusd_path, err);
}
err = xenbus_switch_state(xbusd, NULL, XenbusStateConnected);
if (err) {
printf("xbdback %s: can't switch state: %d\n",
xbusd->xbusd_path, err);
}
return;
abort:
xenbus_transaction_end(xbt, 1);
}
static void
xbdback_finish_disconnect(struct xbdback_instance *xbdi)
{
KASSERT(mutex_owned(&xbdi->xbdi_lock));
KASSERT(xbdi->xbdi_status == DISCONNECTING);
xbdi->xbdi_status = DISCONNECTED;
cv_broadcast(&xbdi->xbdi_cv);
}
static bool
xbdif_lookup(domid_t dom , uint32_t handle)
{
struct xbdback_instance *xbdi;
bool found = false;
KASSERT(mutex_owned(&xbdback_lock));
SLIST_FOREACH(xbdi, &xbdback_instances, next) {
if (xbdi->xbdi_domid == dom && xbdi->xbdi_handle == handle) {
found = true;
break;
}
}
return found;
}
static int
xbdback_evthandler(void *arg)
{
struct xbdback_instance *xbdi = arg;
XENPRINTF(("xbdback_evthandler domain %d: cont %p\n",
xbdi->xbdi_domid, xbdi->xbdi_cont));
mutex_enter(&xbdi->xbdi_lock);
xbdback_wakeup_thread(xbdi);
mutex_exit(&xbdi->xbdi_lock);
return 1;
}
static void
xbdback_thread(void *arg)
{
struct xbdback_instance *xbdi = arg;
void *obj;
mutex_enter(&xbdi->xbdi_lock);
for (;;) {
switch (xbdi->xbdi_status) {
case WAITING:
cv_wait(&xbdi->xbdi_cv, &xbdi->xbdi_lock);
break;
case RUN:
xbdi->xbdi_status = WAITING;
obj = xbdi;
if (xbdi->xbdi_cont_restart != NULL) {
KASSERT(xbdi->xbdi_cont == NULL);
xbdi->xbdi_cont = xbdi->xbdi_cont_restart;
obj = xbdi->xbdi_cont_restart_obj;
xbdi->xbdi_cont_restart = NULL;
xbdi->xbdi_cont_restart_obj = NULL;
}
if (xbdi->xbdi_cont == NULL) {
xbdi->xbdi_cont = xbdback_co_main;
}
xbdback_trampoline(xbdi, obj);
break;
case DISCONNECTING:
if (xbdi->xbdi_pendingreqs > 0) {
cv_wait(&xbdi->xbdi_cv, &xbdi->xbdi_lock);
continue;
}
xbdi_put(xbdi);
KASSERT(xbdi->xbdi_refcnt == 0);
goto out;
default:
panic("%s: invalid state %d",
xbdi->xbdi_name, xbdi->xbdi_status);
}
}
out:
mutex_exit(&xbdi->xbdi_lock);
kthread_exit(0);
}
static void *
xbdback_co_main(struct xbdback_instance *xbdi, void *obj)
{
(void)obj;
xbdi->xbdi_req_prod = xbdi->xbdi_ring.ring_n.sring->req_prod;
xen_rmb();
xbdi->xbdi_cont = xbdback_co_main_loop;
return xbdi;
}
static void *
xbdback_co_main_loop(struct xbdback_instance *xbdi, void *obj __unused)
{
blkif_request_t *req, *reqn;
blkif_x86_32_request_t *req32;
blkif_x86_64_request_t *req64;
blkif_request_indirect_t *rinn;
blkif_x86_32_request_indirect_t *rin32;
blkif_x86_64_request_indirect_t *rin64;
if (xbdi->xbdi_ring.ring_n.req_cons != xbdi->xbdi_req_prod) {
struct xbdback_io *xbd_io = xbdback_io_get(xbdi);
uint8_t real_op = 0xff;
if (xbd_io == NULL) {
xbdi->xbdi_cont = NULL;
return NULL;
}
memset(&xbd_io->u, 0, sizeof(xbd_io->u));
buf_init(&xbd_io->xio_buf);
xbd_io->xio_xbdi = xbdi;
req = &xbd_io->xio_xen_req;
memset(req, 0, sizeof(*req));
switch(xbdi->xbdi_proto) {
case XBDIP_NATIVE:
reqn = RING_GET_REQUEST(&xbdi->xbdi_ring.ring_n,
xbdi->xbdi_ring.ring_n.req_cons);
real_op = req->operation = reqn->operation;
if (real_op == BLKIF_OP_INDIRECT) {
rinn = (blkif_request_indirect_t *)reqn;
real_op = rinn->indirect_op;
}
req->id = reqn->id;
break;
case XBDIP_32:
req32 = RING_GET_REQUEST(&xbdi->xbdi_ring.ring_32,
xbdi->xbdi_ring.ring_n.req_cons);
real_op = req->operation = req32->operation;
if (real_op == BLKIF_OP_INDIRECT) {
rin32 = (blkif_x86_32_request_indirect_t*)req32;
real_op = rin32->indirect_op;
}
req->id = req32->id;
break;
case XBDIP_64:
req64 = RING_GET_REQUEST(&xbdi->xbdi_ring.ring_64,
xbdi->xbdi_ring.ring_n.req_cons);
real_op = req->operation = req64->operation;
if (real_op == BLKIF_OP_INDIRECT) {
rin64 = (blkif_x86_64_request_indirect_t*)req64;
real_op = rin64->indirect_op;
}
req->id = req64->id;
break;
}
__insn_barrier();
XENPRINTF(("xbdback op %d req_cons 0x%x req_prod 0x%x "
"resp_prod 0x%x id %" PRIu64 "\n", req->operation,
xbdi->xbdi_ring.ring_n.req_cons,
xbdi->xbdi_req_prod,
xbdi->xbdi_ring.ring_n.rsp_prod_pvt,
req->id));
switch (req->operation) {
case BLKIF_OP_INDIRECT:
if (real_op != BLKIF_OP_READ &&
real_op != BLKIF_OP_WRITE) {
if (ratecheck(&xbdi->xbdi_lasterr_time,
&xbdback_err_intvl)) {
printf("%s: unknown ind operation %d\n",
xbdi->xbdi_name,
real_op);
}
goto fail;
}
case BLKIF_OP_READ:
case BLKIF_OP_WRITE:
xbdi->xbdi_cont = xbdback_co_io;
return xbd_io;
case BLKIF_OP_FLUSH_DISKCACHE:
xbdi->xbdi_cont = xbdback_co_cache_flush;
return xbd_io;
default:
if (ratecheck(&xbdi->xbdi_lasterr_time,
&xbdback_err_intvl)) {
printf("%s: unknown operation %d\n",
xbdi->xbdi_name, req->operation);
}
fail:
xbdback_send_reply(xbdi, req->id, real_op,
BLKIF_RSP_ERROR);
xbdi->xbdi_cont = xbdback_co_main_incr;
return xbdi;
}
} else {
xbdi->xbdi_cont = xbdback_co_main_done2;
return xbdi;
}
}
static void *
xbdback_co_main_incr(struct xbdback_instance *xbdi, void *obj __unused)
{
KASSERT(mutex_owned(&xbdi->xbdi_lock));
blkif_back_ring_t *ring = &xbdi->xbdi_ring.ring_n;
ring->req_cons++;
if (xbdi->xbdi_status == DISCONNECTING)
xbdi->xbdi_cont = NULL;
else
xbdi->xbdi_cont = xbdback_co_main_loop;
return xbdi;
}
static void *
xbdback_co_main_done2(struct xbdback_instance *xbdi, void *obj)
{
int work_to_do;
xen_wmb();
RING_FINAL_CHECK_FOR_REQUESTS(&xbdi->xbdi_ring.ring_n, work_to_do);
if (work_to_do)
xbdi->xbdi_cont = xbdback_co_main;
else
xbdi->xbdi_cont = NULL;
return xbdi;
}
static void *
xbdback_co_cache_flush(struct xbdback_instance *xbdi, void *obj)
{
struct xbdback_io *xbd_io = obj;
KASSERT(xbd_io->xio_xen_req.operation == BLKIF_OP_FLUSH_DISKCACHE);
if (xbdi->xbdi_pendingreqs > 0) {
xbdi->xbdi_cont_restart = xbdback_co_cache_flush;
xbdi->xbdi_cont_restart_obj = xbd_io;
xbdi->xbdi_cont = NULL;
return NULL;
}
xbdi_get(xbdi);
xbdi->xbdi_cont = xbdback_co_do_io;
return xbd_io;
}
static void *
xbdback_co_io(struct xbdback_instance *xbdi, void *obj)
{
int i, error;
blkif_request_t *req, *reqn;
blkif_x86_32_request_t *req32;
blkif_x86_64_request_t *req64;
blkif_request_indirect_t *rinn;
blkif_x86_32_request_indirect_t *rin32;
blkif_x86_64_request_indirect_t *rin64;
const char *errstr;
struct xbdback_io *xbd_io = obj;
grant_ref_t in_gntref = 0;
req = &xbd_io->xio_xen_req;
KASSERT(req->operation == BLKIF_OP_READ ||
req->operation == BLKIF_OP_WRITE ||
req->operation == BLKIF_OP_INDIRECT);
switch (xbdi->xbdi_proto) {
case XBDIP_NATIVE:
reqn = RING_GET_REQUEST(&xbdi->xbdi_ring.ring_n,
xbdi->xbdi_ring.ring_n.req_cons);
req->handle = reqn->handle;
req->sector_number = reqn->sector_number;
if (reqn->operation == BLKIF_OP_INDIRECT) {
rinn = (blkif_request_indirect_t *)reqn;
req->operation = rinn->indirect_op;
req->nr_segments = (uint8_t)rinn->nr_segments;
if (req->nr_segments > VBD_MAX_INDIRECT_SEGMENTS) {
errstr = "too many indirect segments";
goto bad_segments;
}
in_gntref = rinn->indirect_grefs[0];
} else {
req->nr_segments = reqn->nr_segments;
if (req->nr_segments > BLKIF_MAX_SEGMENTS_PER_REQUEST) {
errstr = "too many segments";
goto bad_segments;
}
for (i = 0; i < req->nr_segments; i++)
xbd_io->xio_seg[i] = reqn->seg[i];
}
break;
case XBDIP_32:
req32 = RING_GET_REQUEST(&xbdi->xbdi_ring.ring_32,
xbdi->xbdi_ring.ring_n.req_cons);
req->handle = req32->handle;
req->sector_number = req32->sector_number;
if (req32->operation == BLKIF_OP_INDIRECT) {
rin32 = (blkif_x86_32_request_indirect_t *)req32;
req->operation = rin32->indirect_op;
req->nr_segments = (uint8_t)rin32->nr_segments;
if (req->nr_segments > VBD_MAX_INDIRECT_SEGMENTS) {
errstr = "too many indirect segments";
goto bad_segments;
}
in_gntref = rin32->indirect_grefs[0];
} else {
req->nr_segments = req32->nr_segments;
if (req->nr_segments > BLKIF_MAX_SEGMENTS_PER_REQUEST) {
errstr = "too many segments";
goto bad_segments;
}
for (i = 0; i < req->nr_segments; i++)
xbd_io->xio_seg[i] = req32->seg[i];
}
break;
case XBDIP_64:
req64 = RING_GET_REQUEST(&xbdi->xbdi_ring.ring_64,
xbdi->xbdi_ring.ring_n.req_cons);
req->handle = req64->handle;
req->sector_number = req64->sector_number;
if (req64->operation == BLKIF_OP_INDIRECT) {
rin64 = (blkif_x86_64_request_indirect_t *)req64;
req->nr_segments = (uint8_t)rin64->nr_segments;
if (req->nr_segments > VBD_MAX_INDIRECT_SEGMENTS) {
errstr = "too many indirect segments";
goto bad_segments;
}
in_gntref = rin64->indirect_grefs[0];
} else {
req->nr_segments = req64->nr_segments;
if (req->nr_segments > BLKIF_MAX_SEGMENTS_PER_REQUEST) {
errstr = "too many segments";
goto bad_segments;
}
for (i = 0; i < req->nr_segments; i++)
xbd_io->xio_seg[i] = req64->seg[i];
}
break;
}
if (req->operation == BLKIF_OP_WRITE) {
if (xbdi->xbdi_ro) {
error = EROFS;
goto end;
}
}
if (req->nr_segments < 1) {
errstr = "invalid number of segments";
goto bad_segments;
}
if (in_gntref) {
gnttab_copy_t gop;
paddr_t ma;
gop.flags = GNTCOPY_source_gref;
gop.len = req->nr_segments
* sizeof(struct blkif_request_segment);
gop.source.u.ref = in_gntref;
gop.source.offset = 0;
gop.source.domid = xbdi->xbdi_domid;
ma = xbd_io->xio_seg_dmamap->dm_segs[0].ds_addr;
gop.dest.offset = ma & PAGE_MASK;
gop.dest.domid = DOMID_SELF;
gop.dest.u.gmfn = ma >> PAGE_SHIFT;
if (HYPERVISOR_grant_table_op(GNTTABOP_copy, &gop, 1) != 0) {
errstr = "GNTTABOP_copy failed";
goto bad_segments;
}
}
xbdi_get(xbdi);
xbdi->xbdi_cont = xbdback_co_io_gotio;
return xbd_io;
bad_segments:
if (ratecheck(&xbdi->xbdi_lasterr_time, &xbdback_err_intvl)) {
printf("%s: %s\n", xbdi->xbdi_name, errstr);
}
error = EINVAL;
end:
xbdback_send_reply(xbdi, req->id, req->operation,
(error == EROFS) ? BLKIF_RSP_EOPNOTSUPP : BLKIF_RSP_ERROR);
xbdi->xbdi_cont = xbdback_co_main_incr;
return xbdi;
}
static void *
xbdback_co_io_gotio(struct xbdback_instance *xbdi, void *obj)
{
struct xbdback_io *xbd_io = obj;
int buf_flags;
size_t bcount;
blkif_request_t *req = &xbd_io->xio_xen_req;
uint8_t last_sect;
int error;
KASSERT(mutex_owned(&xbdi->xbdi_lock));
KASSERT(xbdi->xbdi_refcnt > 0);
bcount = 0;
for (int i = 0; i < req->nr_segments; i++) {
struct blkif_request_segment *seg = &xbd_io->xio_seg[i];
if (seg->last_sect > VBD_MAXSECT ||
seg->first_sect > VBD_MAXSECT) {
if (ratecheck(&xbdi->xbdi_lasterr_time,
&xbdback_err_intvl)) {
printf("%s: invalid segment sectors %d %d\n",
xbdi->xbdi_name,
seg->first_sect, seg->last_sect);
}
xbdi->xbdi_pendingreqs++;
xbdback_io_error(xbd_io, EINVAL);
xbdi->xbdi_cont = xbdback_co_main_incr;
return xbdi;
}
if (i > 0) {
if (last_sect != VBD_MAXSECT ||
seg->first_sect != 0) {
xbd_io->xio_need_bounce = 1;
}
}
last_sect = seg->last_sect;
xbd_io->xio_gref[i] = seg->gref;
bcount += (seg->last_sect - seg->first_sect + 1)
* VBD_BSIZE;
}
xbd_io->xio_start_offset = xbd_io->xio_seg[0].first_sect * VBD_BSIZE;
KASSERT(bcount <= MAXPHYS);
KASSERT(xbd_io->xio_start_offset < PAGE_SIZE);
KASSERT(bcount + xbd_io->xio_start_offset <= VBD_VA_SIZE);
if (req->operation == BLKIF_OP_WRITE) {
buf_flags = B_WRITE;
} else {
buf_flags = B_READ;
}
xbd_io->xio_buf.b_flags = buf_flags;
xbd_io->xio_buf.b_cflags = 0;
xbd_io->xio_buf.b_oflags = 0;
xbd_io->xio_buf.b_iodone = xbdback_iodone;
xbd_io->xio_buf.b_proc = NULL;
xbd_io->xio_buf.b_vp = xbdi->xbdi_vp;
xbd_io->xio_buf.b_objlock = xbdi->xbdi_vp->v_interlock;
xbd_io->xio_buf.b_dev = xbdi->xbdi_dev;
xbd_io->xio_buf.b_blkno = req->sector_number;
xbd_io->xio_buf.b_bcount = bcount;
if (__predict_false(xbd_io->xio_need_bounce)) {
if (__predict_false(xbdi->xbdi_bouncebuf_use)) {
KASSERT(xbdi->xbdi_pendingreqs > 1);
xbdi->xbdi_cont_restart = xbdback_co_io_gotio;
xbdi->xbdi_cont_restart_obj = xbd_io;
xbdi->xbdi_cont = NULL;
return NULL;
}
xbdi->xbdi_bouncebuf_use++;
KASSERT(xbdi->xbdi_bouncebuf_use == 1);
xbd_io->xio_buf.b_data = (void *)xbdi->xbdi_bouncebuf;
}
xbdi->xbdi_pendingreqs++;
if ((error = xbdback_map_shm(xbd_io)) != 0) {
xbdback_io_error(xbd_io, error);
xbdi->xbdi_cont = xbdback_co_main_incr;
return xbdi;
}
if (__predict_true(xbd_io->xio_need_bounce == 0)) {
xbd_io->xio_buf.b_data = (void *)
(xbd_io->xio_vaddr + xbd_io->xio_start_offset);
}
xbd_io->xio_buf.b_private = xbd_io;
xbdi->xbdi_cont = xbdback_co_do_io;
return xbd_io;
}
static void
xbdback_io_error(struct xbdback_io *xbd_io, int error)
{
KASSERT(mutex_owned(&xbd_io->xio_xbdi->xbdi_lock));
struct buf *bp = &xbd_io->xio_buf;
bp->b_error = error;
xbdback_iodone_locked(xbd_io->xio_xbdi, xbd_io, bp);
}
static void *
xbdback_co_do_io(struct xbdback_instance *xbdi, void *obj)
{
struct xbdback_io *xbd_io = obj;
blkif_request_t *req = &xbd_io->xio_xen_req;
KASSERT(xbdi->xbdi_refcnt > 0);
switch (req->operation) {
case BLKIF_OP_FLUSH_DISKCACHE:
{
int error;
int force = 1;
KASSERT(mutex_owned(&xbdi->xbdi_lock));
mutex_exit(&xbdi->xbdi_lock);
error = VOP_IOCTL(xbdi->xbdi_vp, DIOCCACHESYNC, &force, FWRITE,
kauth_cred_get());
mutex_enter(&xbdi->xbdi_lock);
if (error) {
aprint_error("xbdback %s: DIOCCACHESYNC returned %d\n",
xbdi->xbdi_xbusd->xbusd_path, error);
if (error == EOPNOTSUPP || error == ENOTTY)
error = BLKIF_RSP_EOPNOTSUPP;
else
error = BLKIF_RSP_ERROR;
} else
error = BLKIF_RSP_OKAY;
xbdback_send_reply(xbdi, req->id, req->operation, error);
xbdback_io_put(xbdi, xbd_io);
xbdi_put(xbdi);
xbdi->xbdi_cont = xbdback_co_main_incr;
return xbdi;
}
case BLKIF_OP_READ:
case BLKIF_OP_WRITE:
if (__predict_false(xbd_io->xio_need_bounce) &&
req->operation == BLKIF_OP_WRITE) {
vaddr_t boffset = 0;
for (int i = 0; i < req->nr_segments; i++) {
struct blkif_request_segment *seg =
&xbd_io->xio_seg[i];
vaddr_t segoffset = seg->first_sect * VBD_BSIZE;
size_t segbcount =
(seg->last_sect - seg->first_sect + 1) *
VBD_BSIZE;
KASSERT(segoffset + segbcount <= PAGE_SIZE);
KASSERT(boffset + segbcount < MAXPHYS);
segoffset += PAGE_SIZE * i;
memcpy(
(void *)(xbdi->xbdi_bouncebuf + boffset),
(void *)(xbd_io->xio_vaddr + segoffset),
segbcount);
boffset += segbcount;
}
}
KASSERT(mutex_owned(&xbdi->xbdi_lock));
mutex_exit(&xbdi->xbdi_lock);
if ((xbd_io->xio_buf.b_flags & B_READ) == 0) {
mutex_enter(xbd_io->xio_buf.b_vp->v_interlock);
xbd_io->xio_buf.b_vp->v_numoutput++;
mutex_exit(xbd_io->xio_buf.b_vp->v_interlock);
}
bdev_strategy(&xbd_io->xio_buf);
mutex_enter(&xbdi->xbdi_lock);
xbdi->xbdi_cont = xbdback_co_main_incr;
return xbdi;
default:
panic("xbdback_co_do_io: unsupported operation %d",
req->operation);
}
}
static void
xbdback_iodone(struct buf *bp)
{
struct xbdback_io *xbd_io;
struct xbdback_instance *xbdi;
xbd_io = bp->b_private;
KASSERT(bp == &xbd_io->xio_buf);
xbdi = xbd_io->xio_xbdi;
mutex_enter(&xbdi->xbdi_lock);
xbdback_iodone_locked(xbdi, xbd_io, bp);
mutex_exit(&xbdi->xbdi_lock);
}
static void
xbdback_iodone_locked(struct xbdback_instance *xbdi, struct xbdback_io *xbd_io,
struct buf *bp)
{
int status;
blkif_request_t *req = &xbd_io->xio_xen_req;
XENPRINTF(("xbdback_io domain %d: iodone ptr 0x%lx\n",
xbdi->xbdi_domid, (long)xbd_io));
KASSERT(mutex_owned(&xbdi->xbdi_lock));
KASSERT(bp->b_error != 0 || xbd_io->xio_xv != NULL);
if (__predict_false(xbd_io->xio_need_bounce)) {
KASSERT(xbd_io->xio_buf.b_data == (void *)xbdi->xbdi_bouncebuf);
KASSERT(req->operation == BLKIF_OP_WRITE ||
req->operation == BLKIF_OP_READ);
if (req->operation == BLKIF_OP_READ && bp->b_error == 0) {
vaddr_t boffset = 0;
for (int i = 0; i < req->nr_segments; i++) {
struct blkif_request_segment *seg =
&xbd_io->xio_seg[i];
vaddr_t segoffset = seg->first_sect * VBD_BSIZE;
size_t segbcount =
(seg->last_sect - seg->first_sect + 1) *
VBD_BSIZE;
KASSERT(segoffset + segbcount <= PAGE_SIZE);
KASSERT(boffset + segbcount < MAXPHYS);
segoffset += PAGE_SIZE * i;
memcpy(
(void *)(xbd_io->xio_vaddr + segoffset),
(void *)(xbdi->xbdi_bouncebuf + boffset),
segbcount);
boffset += segbcount;
}
}
KASSERT(xbdi->xbdi_bouncebuf_use == 1);
xbdi->xbdi_bouncebuf_use--;
}
if (xbd_io->xio_xv != NULL)
xbdback_unmap_shm(xbd_io);
if (bp->b_error != 0) {
printf("xbd IO domain %d: error %d\n",
xbdi->xbdi_domid, bp->b_error);
status = BLKIF_RSP_ERROR;
} else
status = BLKIF_RSP_OKAY;
xbdback_send_reply(xbdi, req->id, req->operation, status);
xbdi_put(xbdi);
KASSERT(xbdi->xbdi_pendingreqs > 0);
xbdi->xbdi_pendingreqs--;
buf_destroy(&xbd_io->xio_buf);
xbdback_io_put(xbdi, xbd_io);
xbdback_wakeup_thread(xbdi);
}
static void
xbdback_wakeup_thread(struct xbdback_instance *xbdi)
{
KASSERT(mutex_owned(&xbdi->xbdi_lock));
if (xbdi->xbdi_status == WAITING)
xbdi->xbdi_status = RUN;
cv_signal(&xbdi->xbdi_cv);
}
static void
xbdback_send_reply(struct xbdback_instance *xbdi, uint64_t id,
int op, int status)
{
blkif_response_t *resp_n;
blkif_x86_32_response_t *resp32;
blkif_x86_64_response_t *resp64;
int notify;
KASSERT(mutex_owned(&xbdi->xbdi_lock));
switch (xbdi->xbdi_proto) {
case XBDIP_NATIVE:
resp_n = RING_GET_RESPONSE(&xbdi->xbdi_ring.ring_n,
xbdi->xbdi_ring.ring_n.rsp_prod_pvt);
resp_n->id = id;
resp_n->operation = op;
resp_n->status = status;
break;
case XBDIP_32:
resp32 = RING_GET_RESPONSE(&xbdi->xbdi_ring.ring_32,
xbdi->xbdi_ring.ring_n.rsp_prod_pvt);
resp32->id = id;
resp32->operation = op;
resp32->status = status;
break;
case XBDIP_64:
resp64 = RING_GET_RESPONSE(&xbdi->xbdi_ring.ring_64,
xbdi->xbdi_ring.ring_n.rsp_prod_pvt);
resp64->id = id;
resp64->operation = op;
resp64->status = status;
break;
}
xbdi->xbdi_ring.ring_n.rsp_prod_pvt++;
RING_PUSH_RESPONSES_AND_CHECK_NOTIFY(&xbdi->xbdi_ring.ring_n, notify);
if (notify) {
XENPRINTF(("xbdback_send_reply notify %d\n", xbdi->xbdi_domid));
hypervisor_notify_via_evtchn(xbdi->xbdi_evtchn);
}
}
static int
xbdback_map_shm(struct xbdback_io *xbd_io)
{
struct xbdback_instance *xbdi = xbd_io->xio_xbdi;
blkif_request_t *req = &xbd_io->xio_xen_req;
int error;
#ifdef XENDEBUG_VBD
int i;
printf("xbdback_map_shm map grant ");
for (i = 0; i < req->nr_segments; i++) {
printf("%u ", (u_int)xbd_io->xio_gref[i]);
}
#endif
KASSERT(mutex_owned(&xbdi->xbdi_lock));
KASSERT(xbd_io->xio_xv == NULL);
xbd_io->xio_xv = SLIST_FIRST(&xbdi->xbdi_va_free);
KASSERT(xbd_io->xio_xv != NULL);
SLIST_REMOVE_HEAD(&xbdi->xbdi_va_free, xv_next);
xbd_io->xio_vaddr = xbd_io->xio_xv->xv_vaddr;
error = xen_shm_map(req->nr_segments, xbdi->xbdi_domid,
xbd_io->xio_gref, xbd_io->xio_vaddr, xbd_io->xio_gh,
(req->operation == BLKIF_OP_WRITE) ? XSHM_RO : 0);
switch(error) {
case 0:
#ifdef XENDEBUG_VBD
printf("handle");
for (i = 0; i < req->nr_segments; i++) {
printf(" %u ", (u_int)xbd_io->xio_gh[i]);
}
printf("\n");
#endif
return 0;
default:
SLIST_INSERT_HEAD(&xbdi->xbdi_va_free, xbd_io->xio_xv, xv_next);
xbd_io->xio_xv = NULL;
return error;
}
}
static void
xbdback_unmap_shm(struct xbdback_io *xbd_io)
{
struct xbdback_instance *xbdi = xbd_io->xio_xbdi;
blkif_request_t *req = &xbd_io->xio_xen_req;
#ifdef XENDEBUG_VBD
int i;
printf("xbdback_unmap_shm handle ");
for (i = 0; i < req->nr_segments; i++) {
printf("%u ", (u_int)xbd_io->xio_gh[i]);
}
printf("\n");
#endif
KASSERT(xbd_io->xio_xv != NULL);
xen_shm_unmap(xbd_io->xio_vaddr, req->nr_segments,
xbd_io->xio_gh);
SLIST_INSERT_HEAD(&xbdi->xbdi_va_free, xbd_io->xio_xv, xv_next);
xbd_io->xio_xv = NULL;
xbd_io->xio_vaddr = -1;
}
static struct xbdback_io *
xbdback_io_get(struct xbdback_instance *xbdi)
{
struct xbdback_io *xbd_io = SLIST_FIRST(&xbdi->xbdi_io_free);
SLIST_REMOVE_HEAD(&xbdi->xbdi_io_free, xio_next);
return xbd_io;
}
static void
xbdback_io_put(struct xbdback_instance *xbdi, struct xbdback_io *xbd_io)
{
KASSERT(xbd_io->xio_xv == NULL);
KASSERT(xbd_io != NULL);
SLIST_INSERT_HEAD(&xbdi->xbdi_io_free, xbd_io, xio_next);
}
static void
xbdback_trampoline(struct xbdback_instance *xbdi, void *obj)
{
xbdback_cont_t cont;
while(obj != NULL && xbdi->xbdi_cont != NULL) {
KASSERT(xbdi->xbdi_cont_restart == NULL);
KASSERT(xbdi->xbdi_cont_restart_obj == NULL);
cont = xbdi->xbdi_cont;
#ifdef DIAGNOSTIC
xbdi->xbdi_cont = (xbdback_cont_t)0xDEADBEEF;
#endif
obj = (*cont)(xbdi, obj);
#ifdef DIAGNOSTIC
if (xbdi->xbdi_cont == (xbdback_cont_t)0xDEADBEEF) {
printf("xbdback_trampoline: 0x%lx didn't set "
"xbdi->xbdi_cont!\n", (long)cont);
panic("xbdback_trampoline: bad continuation");
}
if (xbdi->xbdi_cont_restart != NULL ||
xbdi->xbdi_cont_restart_obj != NULL) {
KASSERT(xbdi->xbdi_cont_restart != NULL);
KASSERT(xbdi->xbdi_cont_restart_obj != NULL);
KASSERT(xbdi->xbdi_cont == NULL);
KASSERT(obj == NULL);
}
#endif
}
}