/* * xen paravirt block device backend * * (c) Gerd Hoffmann <kraxel@redhat.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; under version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see <http://www.gnu.org/licenses/>. */ #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <inttypes.h> #include <time.h> #include <fcntl.h> #include <errno.h> #include <sys/ioctl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/mman.h> #include <sys/uio.h> #include <xs.h> #include <xenctrl.h> #include <xen/io/xenbus.h> #include "hw.h" #include "block_int.h" #include "qemu-char.h" #include "xen_blkif.h" #include "xen_backend.h" /* ------------------------------------------------------------- */ static int syncwrite = 0; static int batch_maps = 0; static int max_requests = 32; static int use_aio = 1; /* ------------------------------------------------------------- */ #define BLOCK_SIZE 512 #define IOCB_COUNT (BLKIF_MAX_SEGMENTS_PER_REQUEST + 2) struct ioreq { blkif_request_t req; int16_t status; /* parsed request */ off_t start; QEMUIOVector v; int presync; int postsync; /* grant mapping */ uint32_t domids[BLKIF_MAX_SEGMENTS_PER_REQUEST]; uint32_t refs[BLKIF_MAX_SEGMENTS_PER_REQUEST]; int prot; void *page[BLKIF_MAX_SEGMENTS_PER_REQUEST]; void *pages; /* aio status */ int aio_inflight; int aio_errors; struct XenBlkDev *blkdev; QLIST_ENTRY(ioreq) list; }; struct XenBlkDev { struct XenDevice xendev; /* must be first */ char *params; char *mode; char *type; char *dev; char *devtype; const char *fileproto; const char *filename; int ring_ref; void *sring; int64_t file_blk; int64_t file_size; int protocol; blkif_back_rings_t rings; int more_work; int cnt_map; /* request lists */ QLIST_HEAD(inflight_head, ioreq) inflight; QLIST_HEAD(finished_head, ioreq) finished; QLIST_HEAD(freelist_head, ioreq) freelist; int requests_total; int requests_inflight; int requests_finished; /* qemu block driver */ DriveInfo *dinfo; BlockDriverState *bs; QEMUBH *bh; }; /* ------------------------------------------------------------- */ static struct ioreq *ioreq_start(struct XenBlkDev *blkdev) { struct ioreq *ioreq = NULL; if (QLIST_EMPTY(&blkdev->freelist)) { if (blkdev->requests_total >= max_requests) goto out; /* allocate new struct */ ioreq = qemu_mallocz(sizeof(*ioreq)); ioreq->blkdev = blkdev; blkdev->requests_total++; qemu_iovec_init(&ioreq->v, BLKIF_MAX_SEGMENTS_PER_REQUEST); } else { /* get one from freelist */ ioreq = QLIST_FIRST(&blkdev->freelist); QLIST_REMOVE(ioreq, list); qemu_iovec_reset(&ioreq->v); } QLIST_INSERT_HEAD(&blkdev->inflight, ioreq, list); blkdev->requests_inflight++; out: return ioreq; } static void ioreq_finish(struct ioreq *ioreq) { struct XenBlkDev *blkdev = ioreq->blkdev; QLIST_REMOVE(ioreq, list); QLIST_INSERT_HEAD(&blkdev->finished, ioreq, list); blkdev->requests_inflight--; blkdev->requests_finished++; } static void ioreq_release(struct ioreq *ioreq) { struct XenBlkDev *blkdev = ioreq->blkdev; QLIST_REMOVE(ioreq, list); memset(ioreq, 0, sizeof(*ioreq)); ioreq->blkdev = blkdev; QLIST_INSERT_HEAD(&blkdev->freelist, ioreq, list); blkdev->requests_finished--; } /* * translate request into iovec + start offset * do sanity checks along the way */ static int ioreq_parse(struct ioreq *ioreq) { struct XenBlkDev *blkdev = ioreq->blkdev; uintptr_t mem; size_t len; int i; xen_be_printf(&blkdev->xendev, 3, "op %d, nr %d, handle %d, id %" PRId64 ", sector %" PRId64 "\n", ioreq->req.operation, ioreq->req.nr_segments, ioreq->req.handle, ioreq->req.id, ioreq->req.sector_number); switch (ioreq->req.operation) { case BLKIF_OP_READ: ioreq->prot = PROT_WRITE; /* to memory */ break; case BLKIF_OP_WRITE_BARRIER: if (!syncwrite) ioreq->presync = ioreq->postsync = 1; /* fall through */ case BLKIF_OP_WRITE: ioreq->prot = PROT_READ; /* from memory */ if (syncwrite) ioreq->postsync = 1; break; default: xen_be_printf(&blkdev->xendev, 0, "error: unknown operation (%d)\n", ioreq->req.operation); goto err; }; if (ioreq->req.operation != BLKIF_OP_READ && blkdev->mode[0] != 'w') { xen_be_printf(&blkdev->xendev, 0, "error: write req for ro device\n"); goto err; } ioreq->start = ioreq->req.sector_number * blkdev->file_blk; for (i = 0; i < ioreq->req.nr_segments; i++) { if (i == BLKIF_MAX_SEGMENTS_PER_REQUEST) { xen_be_printf(&blkdev->xendev, 0, "error: nr_segments too big\n"); goto err; } if (ioreq->req.seg[i].first_sect > ioreq->req.seg[i].last_sect) { xen_be_printf(&blkdev->xendev, 0, "error: first > last sector\n"); goto err; } if (ioreq->req.seg[i].last_sect * BLOCK_SIZE >= XC_PAGE_SIZE) { xen_be_printf(&blkdev->xendev, 0, "error: page crossing\n"); goto err; } ioreq->domids[i] = blkdev->xendev.dom; ioreq->refs[i] = ioreq->req.seg[i].gref; mem = ioreq->req.seg[i].first_sect * blkdev->file_blk; len = (ioreq->req.seg[i].last_sect - ioreq->req.seg[i].first_sect + 1) * blkdev->file_blk; qemu_iovec_add(&ioreq->v, (void*)mem, len); } if (ioreq->start + ioreq->v.size > blkdev->file_size) { xen_be_printf(&blkdev->xendev, 0, "error: access beyond end of file\n"); goto err; } return 0; err: ioreq->status = BLKIF_RSP_ERROR; return -1; } static void ioreq_unmap(struct ioreq *ioreq) { int gnt = ioreq->blkdev->xendev.gnttabdev; int i; if (ioreq->v.niov == 0) return; if (batch_maps) { if (!ioreq->pages) return; if (xc_gnttab_munmap(gnt, ioreq->pages, ioreq->v.niov) != 0) xen_be_printf(&ioreq->blkdev->xendev, 0, "xc_gnttab_munmap failed: %s\n", strerror(errno)); ioreq->blkdev->cnt_map -= ioreq->v.niov; ioreq->pages = NULL; } else { for (i = 0; i < ioreq->v.niov; i++) { if (!ioreq->page[i]) continue; if (xc_gnttab_munmap(gnt, ioreq->page[i], 1) != 0) xen_be_printf(&ioreq->blkdev->xendev, 0, "xc_gnttab_munmap failed: %s\n", strerror(errno)); ioreq->blkdev->cnt_map--; ioreq->page[i] = NULL; } } } static int ioreq_map(struct ioreq *ioreq) { int gnt = ioreq->blkdev->xendev.gnttabdev; int i; if (ioreq->v.niov == 0) return 0; if (batch_maps) { ioreq->pages = xc_gnttab_map_grant_refs (gnt, ioreq->v.niov, ioreq->domids, ioreq->refs, ioreq->prot); if (ioreq->pages == NULL) { xen_be_printf(&ioreq->blkdev->xendev, 0, "can't map %d grant refs (%s, %d maps)\n", ioreq->v.niov, strerror(errno), ioreq->blkdev->cnt_map); return -1; } for (i = 0; i < ioreq->v.niov; i++) ioreq->v.iov[i].iov_base = ioreq->pages + i * XC_PAGE_SIZE + (uintptr_t)ioreq->v.iov[i].iov_base; ioreq->blkdev->cnt_map += ioreq->v.niov; } else { for (i = 0; i < ioreq->v.niov; i++) { ioreq->page[i] = xc_gnttab_map_grant_ref (gnt, ioreq->domids[i], ioreq->refs[i], ioreq->prot); if (ioreq->page[i] == NULL) { xen_be_printf(&ioreq->blkdev->xendev, 0, "can't map grant ref %d (%s, %d maps)\n", ioreq->refs[i], strerror(errno), ioreq->blkdev->cnt_map); ioreq_unmap(ioreq); return -1; } ioreq->v.iov[i].iov_base = ioreq->page[i] + (uintptr_t)ioreq->v.iov[i].iov_base; ioreq->blkdev->cnt_map++; } } return 0; } static int ioreq_runio_qemu_sync(struct ioreq *ioreq) { struct XenBlkDev *blkdev = ioreq->blkdev; int i, rc, len = 0; off_t pos; if (ioreq_map(ioreq) == -1) goto err; if (ioreq->presync) bdrv_flush(blkdev->bs); switch (ioreq->req.operation) { case BLKIF_OP_READ: pos = ioreq->start; for (i = 0; i < ioreq->v.niov; i++) { rc = bdrv_read(blkdev->bs, pos / BLOCK_SIZE, ioreq->v.iov[i].iov_base, ioreq->v.iov[i].iov_len / BLOCK_SIZE); if (rc != 0) { xen_be_printf(&blkdev->xendev, 0, "rd I/O error (%p, len %zd)\n", ioreq->v.iov[i].iov_base, ioreq->v.iov[i].iov_len); goto err; } len += ioreq->v.iov[i].iov_len; pos += ioreq->v.iov[i].iov_len; } break; case BLKIF_OP_WRITE: case BLKIF_OP_WRITE_BARRIER: pos = ioreq->start; for (i = 0; i < ioreq->v.niov; i++) { rc = bdrv_write(blkdev->bs, pos / BLOCK_SIZE, ioreq->v.iov[i].iov_base, ioreq->v.iov[i].iov_len / BLOCK_SIZE); if (rc != 0) { xen_be_printf(&blkdev->xendev, 0, "wr I/O error (%p, len %zd)\n", ioreq->v.iov[i].iov_base, ioreq->v.iov[i].iov_len); goto err; } len += ioreq->v.iov[i].iov_len; pos += ioreq->v.iov[i].iov_len; } break; default: /* unknown operation (shouldn't happen -- parse catches this) */ goto err; } if (ioreq->postsync) bdrv_flush(blkdev->bs); ioreq->status = BLKIF_RSP_OKAY; ioreq_unmap(ioreq); ioreq_finish(ioreq); return 0; err: ioreq->status = BLKIF_RSP_ERROR; return -1; } static void qemu_aio_complete(void *opaque, int ret) { struct ioreq *ioreq = opaque; if (ret != 0) { xen_be_printf(&ioreq->blkdev->xendev, 0, "%s I/O error\n", ioreq->req.operation == BLKIF_OP_READ ? "read" : "write"); ioreq->aio_errors++; } ioreq->aio_inflight--; if (ioreq->aio_inflight > 0) return; ioreq->status = ioreq->aio_errors ? BLKIF_RSP_ERROR : BLKIF_RSP_OKAY; ioreq_unmap(ioreq); ioreq_finish(ioreq); qemu_bh_schedule(ioreq->blkdev->bh); } static int ioreq_runio_qemu_aio(struct ioreq *ioreq) { struct XenBlkDev *blkdev = ioreq->blkdev; if (ioreq_map(ioreq) == -1) goto err; ioreq->aio_inflight++; if (ioreq->presync) bdrv_flush(blkdev->bs); /* FIXME: aio_flush() ??? */ switch (ioreq->req.operation) { case BLKIF_OP_READ: ioreq->aio_inflight++; bdrv_aio_readv(blkdev->bs, ioreq->start / BLOCK_SIZE, &ioreq->v, ioreq->v.size / BLOCK_SIZE, qemu_aio_complete, ioreq); break; case BLKIF_OP_WRITE: case BLKIF_OP_WRITE_BARRIER: ioreq->aio_inflight++; bdrv_aio_writev(blkdev->bs, ioreq->start / BLOCK_SIZE, &ioreq->v, ioreq->v.size / BLOCK_SIZE, qemu_aio_complete, ioreq); break; default: /* unknown operation (shouldn't happen -- parse catches this) */ goto err; } if (ioreq->postsync) bdrv_flush(blkdev->bs); /* FIXME: aio_flush() ??? */ qemu_aio_complete(ioreq, 0); return 0; err: ioreq->status = BLKIF_RSP_ERROR; return -1; } static int blk_send_response_one(struct ioreq *ioreq) { struct XenBlkDev *blkdev = ioreq->blkdev; int send_notify = 0; int have_requests = 0; blkif_response_t resp; void *dst; resp.id = ioreq->req.id; resp.operation = ioreq->req.operation; resp.status = ioreq->status; /* Place on the response ring for the relevant domain. */ switch (blkdev->protocol) { case BLKIF_PROTOCOL_NATIVE: dst = RING_GET_RESPONSE(&blkdev->rings.native, blkdev->rings.native.rsp_prod_pvt); break; case BLKIF_PROTOCOL_X86_32: dst = RING_GET_RESPONSE(&blkdev->rings.x86_32_part, blkdev->rings.x86_32_part.rsp_prod_pvt); break; case BLKIF_PROTOCOL_X86_64: dst = RING_GET_RESPONSE(&blkdev->rings.x86_64_part, blkdev->rings.x86_64_part.rsp_prod_pvt); break; default: dst = NULL; } memcpy(dst, &resp, sizeof(resp)); blkdev->rings.common.rsp_prod_pvt++; RING_PUSH_RESPONSES_AND_CHECK_NOTIFY(&blkdev->rings.common, send_notify); if (blkdev->rings.common.rsp_prod_pvt == blkdev->rings.common.req_cons) { /* * Tail check for pending requests. Allows frontend to avoid * notifications if requests are already in flight (lower * overheads and promotes batching). */ RING_FINAL_CHECK_FOR_REQUESTS(&blkdev->rings.common, have_requests); } else if (RING_HAS_UNCONSUMED_REQUESTS(&blkdev->rings.common)) { have_requests = 1; } if (have_requests) blkdev->more_work++; return send_notify; } /* walk finished list, send outstanding responses, free requests */ static void blk_send_response_all(struct XenBlkDev *blkdev) { struct ioreq *ioreq; int send_notify = 0; while (!QLIST_EMPTY(&blkdev->finished)) { ioreq = QLIST_FIRST(&blkdev->finished); send_notify += blk_send_response_one(ioreq); ioreq_release(ioreq); } if (send_notify) xen_be_send_notify(&blkdev->xendev); } static int blk_get_request(struct XenBlkDev *blkdev, struct ioreq *ioreq, RING_IDX rc) { switch (blkdev->protocol) { case BLKIF_PROTOCOL_NATIVE: memcpy(&ioreq->req, RING_GET_REQUEST(&blkdev->rings.native, rc), sizeof(ioreq->req)); break; case BLKIF_PROTOCOL_X86_32: blkif_get_x86_32_req(&ioreq->req, RING_GET_REQUEST(&blkdev->rings.x86_32_part, rc)); break; case BLKIF_PROTOCOL_X86_64: blkif_get_x86_64_req(&ioreq->req, RING_GET_REQUEST(&blkdev->rings.x86_64_part, rc)); break; } return 0; } static void blk_handle_requests(struct XenBlkDev *blkdev) { RING_IDX rc, rp; struct ioreq *ioreq; blkdev->more_work = 0; rc = blkdev->rings.common.req_cons; rp = blkdev->rings.common.sring->req_prod; xen_rmb(); /* Ensure we see queued requests up to 'rp'. */ if (use_aio) blk_send_response_all(blkdev); while (rc != rp) { /* pull request from ring */ if (RING_REQUEST_CONS_OVERFLOW(&blkdev->rings.common, rc)) break; ioreq = ioreq_start(blkdev); if (ioreq == NULL) { blkdev->more_work++; break; } blk_get_request(blkdev, ioreq, rc); blkdev->rings.common.req_cons = ++rc; /* parse them */ if (ioreq_parse(ioreq) != 0) { if (blk_send_response_one(ioreq)) xen_be_send_notify(&blkdev->xendev); ioreq_release(ioreq); continue; } if (use_aio) { /* run i/o in aio mode */ ioreq_runio_qemu_aio(ioreq); } else { /* run i/o in sync mode */ ioreq_runio_qemu_sync(ioreq); } } if (!use_aio) blk_send_response_all(blkdev); if (blkdev->more_work && blkdev->requests_inflight < max_requests) qemu_bh_schedule(blkdev->bh); } /* ------------------------------------------------------------- */ static void blk_bh(void *opaque) { struct XenBlkDev *blkdev = opaque; blk_handle_requests(blkdev); } static void blk_alloc(struct XenDevice *xendev) { struct XenBlkDev *blkdev = container_of(xendev, struct XenBlkDev, xendev); QLIST_INIT(&blkdev->inflight); QLIST_INIT(&blkdev->finished); QLIST_INIT(&blkdev->freelist); blkdev->bh = qemu_bh_new(blk_bh, blkdev); if (xen_mode != XEN_EMULATE) batch_maps = 1; } static int blk_init(struct XenDevice *xendev) { struct XenBlkDev *blkdev = container_of(xendev, struct XenBlkDev, xendev); int index, mode, qflags, have_barriers, info = 0; char *h; /* read xenstore entries */ if (blkdev->params == NULL) { blkdev->params = xenstore_read_be_str(&blkdev->xendev, "params"); h = strchr(blkdev->params, ':'); if (h != NULL) { blkdev->fileproto = blkdev->params; blkdev->filename = h+1; *h = 0; } else { blkdev->fileproto = "<unset>"; blkdev->filename = blkdev->params; } } if (blkdev->mode == NULL) blkdev->mode = xenstore_read_be_str(&blkdev->xendev, "mode"); if (blkdev->type == NULL) blkdev->type = xenstore_read_be_str(&blkdev->xendev, "type"); if (blkdev->dev == NULL) blkdev->dev = xenstore_read_be_str(&blkdev->xendev, "dev"); if (blkdev->devtype == NULL) blkdev->devtype = xenstore_read_be_str(&blkdev->xendev, "device-type"); /* do we have all we need? */ if (blkdev->params == NULL || blkdev->mode == NULL || blkdev->type == NULL || blkdev->dev == NULL) return -1; /* read-only ? */ if (strcmp(blkdev->mode, "w") == 0) { mode = O_RDWR; qflags = BDRV_O_RDWR; } else { mode = O_RDONLY; qflags = BDRV_O_RDONLY; info |= VDISK_READONLY; } /* cdrom ? */ if (blkdev->devtype && !strcmp(blkdev->devtype, "cdrom")) info |= VDISK_CDROM; /* init qemu block driver */ index = (blkdev->xendev.dev - 202 * 256) / 16; blkdev->dinfo = drive_get(IF_XEN, 0, index); if (!blkdev->dinfo) { /* setup via xenbus -> create new block driver instance */ xen_be_printf(&blkdev->xendev, 2, "create new bdrv (xenbus setup)\n"); blkdev->bs = bdrv_new(blkdev->dev); if (blkdev->bs) { if (bdrv_open2(blkdev->bs, blkdev->filename, qflags, bdrv_find_format(blkdev->fileproto)) != 0) { bdrv_delete(blkdev->bs); blkdev->bs = NULL; } } if (!blkdev->bs) return -1; } else { /* setup via qemu cmdline -> already setup for us */ xen_be_printf(&blkdev->xendev, 2, "get configured bdrv (cmdline setup)\n"); blkdev->bs = blkdev->dinfo->bdrv; } blkdev->file_blk = BLOCK_SIZE; blkdev->file_size = bdrv_getlength(blkdev->bs); if (blkdev->file_size < 0) { xen_be_printf(&blkdev->xendev, 1, "bdrv_getlength: %d (%s) | drv %s\n", (int)blkdev->file_size, strerror(-blkdev->file_size), blkdev->bs->drv ? blkdev->bs->drv->format_name : "-"); blkdev->file_size = 0; } have_barriers = blkdev->bs->drv && blkdev->bs->drv->bdrv_flush ? 1 : 0; xen_be_printf(xendev, 1, "type \"%s\", fileproto \"%s\", filename \"%s\"," " size %" PRId64 " (%" PRId64 " MB)\n", blkdev->type, blkdev->fileproto, blkdev->filename, blkdev->file_size, blkdev->file_size >> 20); /* fill info */ xenstore_write_be_int(&blkdev->xendev, "feature-barrier", have_barriers); xenstore_write_be_int(&blkdev->xendev, "info", info); xenstore_write_be_int(&blkdev->xendev, "sector-size", blkdev->file_blk); xenstore_write_be_int(&blkdev->xendev, "sectors", blkdev->file_size / blkdev->file_blk); return 0; } static int blk_connect(struct XenDevice *xendev) { struct XenBlkDev *blkdev = container_of(xendev, struct XenBlkDev, xendev); if (xenstore_read_fe_int(&blkdev->xendev, "ring-ref", &blkdev->ring_ref) == -1) return -1; if (xenstore_read_fe_int(&blkdev->xendev, "event-channel", &blkdev->xendev.remote_port) == -1) return -1; blkdev->protocol = BLKIF_PROTOCOL_NATIVE; if (blkdev->xendev.protocol) { if (strcmp(blkdev->xendev.protocol, XEN_IO_PROTO_ABI_X86_32) == 0) blkdev->protocol = BLKIF_PROTOCOL_X86_32; if (strcmp(blkdev->xendev.protocol, XEN_IO_PROTO_ABI_X86_64) == 0) blkdev->protocol = BLKIF_PROTOCOL_X86_64; } blkdev->sring = xc_gnttab_map_grant_ref(blkdev->xendev.gnttabdev, blkdev->xendev.dom, blkdev->ring_ref, PROT_READ | PROT_WRITE); if (!blkdev->sring) return -1; blkdev->cnt_map++; switch (blkdev->protocol) { case BLKIF_PROTOCOL_NATIVE: { blkif_sring_t *sring_native = blkdev->sring; BACK_RING_INIT(&blkdev->rings.native, sring_native, XC_PAGE_SIZE); break; } case BLKIF_PROTOCOL_X86_32: { blkif_x86_32_sring_t *sring_x86_32 = blkdev->sring; BACK_RING_INIT(&blkdev->rings.x86_32_part, sring_x86_32, XC_PAGE_SIZE); break; } case BLKIF_PROTOCOL_X86_64: { blkif_x86_64_sring_t *sring_x86_64 = blkdev->sring; BACK_RING_INIT(&blkdev->rings.x86_64_part, sring_x86_64, XC_PAGE_SIZE); break; } } xen_be_bind_evtchn(&blkdev->xendev); xen_be_printf(&blkdev->xendev, 1, "ok: proto %s, ring-ref %d, " "remote port %d, local port %d\n", blkdev->xendev.protocol, blkdev->ring_ref, blkdev->xendev.remote_port, blkdev->xendev.local_port); return 0; } static void blk_disconnect(struct XenDevice *xendev) { struct XenBlkDev *blkdev = container_of(xendev, struct XenBlkDev, xendev); if (blkdev->bs) { if (!blkdev->dinfo) { /* close/delete only if we created it ourself */ bdrv_close(blkdev->bs); bdrv_delete(blkdev->bs); } blkdev->bs = NULL; } xen_be_unbind_evtchn(&blkdev->xendev); if (blkdev->sring) { xc_gnttab_munmap(blkdev->xendev.gnttabdev, blkdev->sring, 1); blkdev->cnt_map--; blkdev->sring = NULL; } } static int blk_free(struct XenDevice *xendev) { struct XenBlkDev *blkdev = container_of(xendev, struct XenBlkDev, xendev); struct ioreq *ioreq; while (!QLIST_EMPTY(&blkdev->freelist)) { ioreq = QLIST_FIRST(&blkdev->freelist); QLIST_REMOVE(ioreq, list); qemu_iovec_destroy(&ioreq->v); qemu_free(ioreq); } qemu_free(blkdev->params); qemu_free(blkdev->mode); qemu_free(blkdev->type); qemu_free(blkdev->dev); qemu_free(blkdev->devtype); qemu_bh_delete(blkdev->bh); return 0; } static void blk_event(struct XenDevice *xendev) { struct XenBlkDev *blkdev = container_of(xendev, struct XenBlkDev, xendev); qemu_bh_schedule(blkdev->bh); } struct XenDevOps xen_blkdev_ops = { .size = sizeof(struct XenBlkDev), .flags = DEVOPS_FLAG_NEED_GNTDEV, .alloc = blk_alloc, .init = blk_init, .connect = blk_connect, .disconnect = blk_disconnect, .event = blk_event, .free = blk_free, };