aboutsummaryrefslogtreecommitdiffstats
path: root/roms/u-boot/drivers/xen/pvblock.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/u-boot/drivers/xen/pvblock.c')
-rw-r--r--roms/u-boot/drivers/xen/pvblock.c868
1 files changed, 868 insertions, 0 deletions
diff --git a/roms/u-boot/drivers/xen/pvblock.c b/roms/u-boot/drivers/xen/pvblock.c
new file mode 100644
index 000000000..1c5d039ef
--- /dev/null
+++ b/roms/u-boot/drivers/xen/pvblock.c
@@ -0,0 +1,868 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) 2007-2008 Samuel Thibault.
+ * (C) Copyright 2020 EPAM Systems Inc.
+ */
+#include <blk.h>
+#include <common.h>
+#include <dm.h>
+#include <dm/device-internal.h>
+#include <malloc.h>
+#include <part.h>
+
+#include <asm/armv8/mmu.h>
+#include <asm/global_data.h>
+#include <asm/io.h>
+#include <asm/xen/system.h>
+
+#include <linux/bug.h>
+#include <linux/compat.h>
+
+#include <xen/events.h>
+#include <xen/gnttab.h>
+#include <xen/hvm.h>
+#include <xen/xenbus.h>
+
+#include <xen/interface/io/ring.h>
+#include <xen/interface/io/blkif.h>
+#include <xen/interface/io/protocols.h>
+
+#define DRV_NAME "pvblock"
+#define DRV_NAME_BLK "pvblock_blk"
+
+#define O_RDONLY 00
+#define O_RDWR 02
+#define WAIT_RING_TO_MS 10
+
+struct blkfront_info {
+ u64 sectors;
+ unsigned int sector_size;
+ int mode;
+ int info;
+ int barrier;
+ int flush;
+};
+
+/**
+ * struct blkfront_dev - Struct representing blkfront device
+ * @dom: Domain id
+ * @ring: Front_ring structure
+ * @ring_ref: The grant reference, allowing us to grant access
+ * to the ring to the other end/domain
+ * @evtchn: Event channel used to signal ring events
+ * @handle: Events handle
+ * @nodename: Device XenStore path in format "device/vbd/" + @devid
+ * @backend: Backend XenStore path
+ * @info: Private data
+ * @devid: Device id
+ */
+struct blkfront_dev {
+ domid_t dom;
+
+ struct blkif_front_ring ring;
+ grant_ref_t ring_ref;
+ evtchn_port_t evtchn;
+ blkif_vdev_t handle;
+
+ char *nodename;
+ char *backend;
+ struct blkfront_info info;
+ unsigned int devid;
+ u8 *bounce_buffer;
+};
+
+struct blkfront_plat {
+ unsigned int devid;
+};
+
+/**
+ * struct blkfront_aiocb - AIO сontrol block
+ * @aio_dev: Blockfront device
+ * @aio_buf: Memory buffer, which must be sector-aligned for
+ * @aio_dev sector
+ * @aio_nbytes: Size of AIO, which must be less than @aio_dev
+ * sector-sized amounts
+ * @aio_offset: Offset, which must not go beyond @aio_dev
+ * sector-aligned location
+ * @data: Data used to receiving response from ring
+ * @gref: Array of grant references
+ * @n: Number of segments
+ * @aio_cb: Represents one I/O request.
+ */
+struct blkfront_aiocb {
+ struct blkfront_dev *aio_dev;
+ u8 *aio_buf;
+ size_t aio_nbytes;
+ off_t aio_offset;
+ void *data;
+
+ grant_ref_t gref[BLKIF_MAX_SEGMENTS_PER_REQUEST];
+ int n;
+
+ void (*aio_cb)(struct blkfront_aiocb *aiocb, int ret);
+};
+
+static void blkfront_sync(struct blkfront_dev *dev);
+
+static void free_blkfront(struct blkfront_dev *dev)
+{
+ mask_evtchn(dev->evtchn);
+ free(dev->backend);
+
+ gnttab_end_access(dev->ring_ref);
+ free(dev->ring.sring);
+
+ unbind_evtchn(dev->evtchn);
+
+ free(dev->bounce_buffer);
+ free(dev->nodename);
+ free(dev);
+}
+
+static int init_blkfront(unsigned int devid, struct blkfront_dev *dev)
+{
+ xenbus_transaction_t xbt;
+ char *err = NULL;
+ char *message = NULL;
+ struct blkif_sring *s;
+ int retry = 0;
+ char *msg = NULL;
+ char *c;
+ char nodename[32];
+ char path[ARRAY_SIZE(nodename) + strlen("/backend-id") + 1];
+
+ sprintf(nodename, "device/vbd/%d", devid);
+
+ memset(dev, 0, sizeof(*dev));
+ dev->nodename = strdup(nodename);
+ dev->devid = devid;
+
+ snprintf(path, sizeof(path), "%s/backend-id", nodename);
+ dev->dom = xenbus_read_integer(path);
+ evtchn_alloc_unbound(dev->dom, NULL, dev, &dev->evtchn);
+
+ s = (struct blkif_sring *)memalign(PAGE_SIZE, PAGE_SIZE);
+ if (!s) {
+ printf("Failed to allocate shared ring\n");
+ goto error;
+ }
+
+ SHARED_RING_INIT(s);
+ FRONT_RING_INIT(&dev->ring, s, PAGE_SIZE);
+
+ dev->ring_ref = gnttab_grant_access(dev->dom, virt_to_pfn(s), 0);
+
+again:
+ err = xenbus_transaction_start(&xbt);
+ if (err) {
+ printf("starting transaction\n");
+ free(err);
+ }
+
+ err = xenbus_printf(xbt, nodename, "ring-ref", "%u", dev->ring_ref);
+ if (err) {
+ message = "writing ring-ref";
+ goto abort_transaction;
+ }
+ err = xenbus_printf(xbt, nodename, "event-channel", "%u", dev->evtchn);
+ if (err) {
+ message = "writing event-channel";
+ goto abort_transaction;
+ }
+ err = xenbus_printf(xbt, nodename, "protocol", "%s",
+ XEN_IO_PROTO_ABI_NATIVE);
+ if (err) {
+ message = "writing protocol";
+ goto abort_transaction;
+ }
+
+ snprintf(path, sizeof(path), "%s/state", nodename);
+ err = xenbus_switch_state(xbt, path, XenbusStateConnected);
+ if (err) {
+ message = "switching state";
+ goto abort_transaction;
+ }
+
+ err = xenbus_transaction_end(xbt, 0, &retry);
+ free(err);
+ if (retry) {
+ goto again;
+ printf("completing transaction\n");
+ }
+
+ goto done;
+
+abort_transaction:
+ free(err);
+ err = xenbus_transaction_end(xbt, 1, &retry);
+ printf("Abort transaction %s\n", message);
+ goto error;
+
+done:
+ snprintf(path, sizeof(path), "%s/backend", nodename);
+ msg = xenbus_read(XBT_NIL, path, &dev->backend);
+ if (msg) {
+ printf("Error %s when reading the backend path %s\n",
+ msg, path);
+ goto error;
+ }
+
+ dev->handle = strtoul(strrchr(nodename, '/') + 1, NULL, 0);
+
+ {
+ XenbusState state;
+ char path[strlen(dev->backend) +
+ strlen("/feature-flush-cache") + 1];
+
+ snprintf(path, sizeof(path), "%s/mode", dev->backend);
+ msg = xenbus_read(XBT_NIL, path, &c);
+ if (msg) {
+ printf("Error %s when reading the mode\n", msg);
+ goto error;
+ }
+ if (*c == 'w')
+ dev->info.mode = O_RDWR;
+ else
+ dev->info.mode = O_RDONLY;
+ free(c);
+
+ snprintf(path, sizeof(path), "%s/state", dev->backend);
+
+ msg = NULL;
+ state = xenbus_read_integer(path);
+ while (!msg && state < XenbusStateConnected)
+ msg = xenbus_wait_for_state_change(path, &state);
+ if (msg || state != XenbusStateConnected) {
+ printf("backend not available, state=%d\n", state);
+ goto error;
+ }
+
+ snprintf(path, sizeof(path), "%s/info", dev->backend);
+ dev->info.info = xenbus_read_integer(path);
+
+ snprintf(path, sizeof(path), "%s/sectors", dev->backend);
+ /*
+ * FIXME: read_integer returns an int, so disk size
+ * limited to 1TB for now
+ */
+ dev->info.sectors = xenbus_read_integer(path);
+
+ snprintf(path, sizeof(path), "%s/sector-size", dev->backend);
+ dev->info.sector_size = xenbus_read_integer(path);
+
+ snprintf(path, sizeof(path), "%s/feature-barrier",
+ dev->backend);
+ dev->info.barrier = xenbus_read_integer(path);
+
+ snprintf(path, sizeof(path), "%s/feature-flush-cache",
+ dev->backend);
+ dev->info.flush = xenbus_read_integer(path);
+ }
+ unmask_evtchn(dev->evtchn);
+
+ dev->bounce_buffer = memalign(dev->info.sector_size,
+ dev->info.sector_size);
+ if (!dev->bounce_buffer) {
+ printf("Failed to allocate bouncing buffer\n");
+ goto error;
+ }
+
+ debug("%llu sectors of %u bytes, bounce buffer at %p\n",
+ dev->info.sectors, dev->info.sector_size,
+ dev->bounce_buffer);
+
+ return 0;
+
+error:
+ free(msg);
+ free(err);
+ free_blkfront(dev);
+ return -ENODEV;
+}
+
+static void shutdown_blkfront(struct blkfront_dev *dev)
+{
+ char *err = NULL, *err2;
+ XenbusState state;
+
+ char path[strlen(dev->backend) + strlen("/state") + 1];
+ char nodename[strlen(dev->nodename) + strlen("/event-channel") + 1];
+
+ debug("Close " DRV_NAME ", device ID %d\n", dev->devid);
+
+ blkfront_sync(dev);
+
+ snprintf(path, sizeof(path), "%s/state", dev->backend);
+ snprintf(nodename, sizeof(nodename), "%s/state", dev->nodename);
+
+ err = xenbus_switch_state(XBT_NIL, nodename, XenbusStateClosing);
+ if (err) {
+ printf("%s: error changing state to %d: %s\n", __func__,
+ XenbusStateClosing, err);
+ goto close;
+ }
+
+ state = xenbus_read_integer(path);
+ while (!err && state < XenbusStateClosing)
+ err = xenbus_wait_for_state_change(path, &state);
+ free(err);
+
+ err = xenbus_switch_state(XBT_NIL, nodename, XenbusStateClosed);
+ if (err) {
+ printf("%s: error changing state to %d: %s\n", __func__,
+ XenbusStateClosed, err);
+ goto close;
+ }
+
+ state = xenbus_read_integer(path);
+ while (state < XenbusStateClosed) {
+ err = xenbus_wait_for_state_change(path, &state);
+ free(err);
+ }
+
+ err = xenbus_switch_state(XBT_NIL, nodename, XenbusStateInitialising);
+ if (err) {
+ printf("%s: error changing state to %d: %s\n", __func__,
+ XenbusStateInitialising, err);
+ goto close;
+ }
+
+ state = xenbus_read_integer(path);
+ while (!err &&
+ (state < XenbusStateInitWait || state >= XenbusStateClosed))
+ err = xenbus_wait_for_state_change(path, &state);
+
+close:
+ free(err);
+
+ snprintf(nodename, sizeof(nodename), "%s/ring-ref", dev->nodename);
+ err2 = xenbus_rm(XBT_NIL, nodename);
+ free(err2);
+ snprintf(nodename, sizeof(nodename), "%s/event-channel", dev->nodename);
+ err2 = xenbus_rm(XBT_NIL, nodename);
+ free(err2);
+
+ if (!err)
+ free_blkfront(dev);
+}
+
+/**
+ * blkfront_aio_poll() - AIO polling function.
+ * @dev: Blkfront device
+ *
+ * Here we receive response from the ring and check its status. This happens
+ * until we read all data from the ring. We read the data from consumed pointer
+ * to the response pointer. Then increase consumed pointer to make it clear that
+ * the data has been read.
+ *
+ * Return: Number of consumed bytes.
+ */
+static int blkfront_aio_poll(struct blkfront_dev *dev)
+{
+ RING_IDX rp, cons;
+ struct blkif_response *rsp;
+ int more;
+ int nr_consumed;
+
+moretodo:
+ rp = dev->ring.sring->rsp_prod;
+ rmb(); /* Ensure we see queued responses up to 'rp'. */
+ cons = dev->ring.rsp_cons;
+
+ nr_consumed = 0;
+ while ((cons != rp)) {
+ struct blkfront_aiocb *aiocbp;
+ int status;
+
+ rsp = RING_GET_RESPONSE(&dev->ring, cons);
+ nr_consumed++;
+
+ aiocbp = (void *)(uintptr_t)rsp->id;
+ status = rsp->status;
+
+ switch (rsp->operation) {
+ case BLKIF_OP_READ:
+ case BLKIF_OP_WRITE:
+ {
+ int j;
+
+ if (status != BLKIF_RSP_OKAY)
+ printf("%s error %d on %s at offset %llu, num bytes %llu\n",
+ rsp->operation == BLKIF_OP_READ ?
+ "read" : "write",
+ status, aiocbp->aio_dev->nodename,
+ (unsigned long long)aiocbp->aio_offset,
+ (unsigned long long)aiocbp->aio_nbytes);
+
+ for (j = 0; j < aiocbp->n; j++)
+ gnttab_end_access(aiocbp->gref[j]);
+
+ break;
+ }
+
+ case BLKIF_OP_WRITE_BARRIER:
+ if (status != BLKIF_RSP_OKAY)
+ printf("write barrier error %d\n", status);
+ break;
+ case BLKIF_OP_FLUSH_DISKCACHE:
+ if (status != BLKIF_RSP_OKAY)
+ printf("flush error %d\n", status);
+ break;
+
+ default:
+ printf("unrecognized block operation %d response (status %d)\n",
+ rsp->operation, status);
+ break;
+ }
+
+ dev->ring.rsp_cons = ++cons;
+ /* Nota: callback frees aiocbp itself */
+ if (aiocbp && aiocbp->aio_cb)
+ aiocbp->aio_cb(aiocbp, status ? -EIO : 0);
+ if (dev->ring.rsp_cons != cons)
+ /* We reentered, we must not continue here */
+ break;
+ }
+
+ RING_FINAL_CHECK_FOR_RESPONSES(&dev->ring, more);
+ if (more)
+ goto moretodo;
+
+ return nr_consumed;
+}
+
+static void blkfront_wait_slot(struct blkfront_dev *dev)
+{
+ /* Wait for a slot */
+ if (RING_FULL(&dev->ring)) {
+ while (true) {
+ blkfront_aio_poll(dev);
+ if (!RING_FULL(&dev->ring))
+ break;
+ wait_event_timeout(NULL, !RING_FULL(&dev->ring),
+ WAIT_RING_TO_MS);
+ }
+ }
+}
+
+/**
+ * blkfront_aio_poll() - Issue an aio.
+ * @aiocbp: AIO control block structure
+ * @write: Describes is it read or write operation
+ * 0 - read
+ * 1 - write
+ *
+ * We check whether the AIO parameters meet the requirements of the device.
+ * Then receive request from ring and define its arguments. After this we
+ * grant access to the grant references. The last step is notifying about AIO
+ * via event channel.
+ */
+static void blkfront_aio(struct blkfront_aiocb *aiocbp, int write)
+{
+ struct blkfront_dev *dev = aiocbp->aio_dev;
+ struct blkif_request *req;
+ RING_IDX i;
+ int notify;
+ int n, j;
+ uintptr_t start, end;
+
+ /* Can't io at non-sector-aligned location */
+ BUG_ON(aiocbp->aio_offset & (dev->info.sector_size - 1));
+ /* Can't io non-sector-sized amounts */
+ BUG_ON(aiocbp->aio_nbytes & (dev->info.sector_size - 1));
+ /* Can't io non-sector-aligned buffer */
+ BUG_ON(((uintptr_t)aiocbp->aio_buf & (dev->info.sector_size - 1)));
+
+ start = (uintptr_t)aiocbp->aio_buf & PAGE_MASK;
+ end = ((uintptr_t)aiocbp->aio_buf + aiocbp->aio_nbytes +
+ PAGE_SIZE - 1) & PAGE_MASK;
+ n = (end - start) / PAGE_SIZE;
+ aiocbp->n = n;
+
+ BUG_ON(n > BLKIF_MAX_SEGMENTS_PER_REQUEST);
+
+ blkfront_wait_slot(dev);
+ i = dev->ring.req_prod_pvt;
+ req = RING_GET_REQUEST(&dev->ring, i);
+
+ req->operation = write ? BLKIF_OP_WRITE : BLKIF_OP_READ;
+ req->nr_segments = n;
+ req->handle = dev->handle;
+ req->id = (uintptr_t)aiocbp;
+ req->sector_number = aiocbp->aio_offset / dev->info.sector_size;
+
+ for (j = 0; j < n; j++) {
+ req->seg[j].first_sect = 0;
+ req->seg[j].last_sect = PAGE_SIZE / dev->info.sector_size - 1;
+ }
+ req->seg[0].first_sect = ((uintptr_t)aiocbp->aio_buf & ~PAGE_MASK) /
+ dev->info.sector_size;
+ req->seg[n - 1].last_sect = (((uintptr_t)aiocbp->aio_buf +
+ aiocbp->aio_nbytes - 1) & ~PAGE_MASK) / dev->info.sector_size;
+ for (j = 0; j < n; j++) {
+ uintptr_t data = start + j * PAGE_SIZE;
+
+ if (!write) {
+ /* Trigger CoW if needed */
+ *(char *)(data + (req->seg[j].first_sect *
+ dev->info.sector_size)) = 0;
+ barrier();
+ }
+ req->seg[j].gref = gnttab_grant_access(dev->dom,
+ virt_to_pfn((void *)data),
+ write);
+ aiocbp->gref[j] = req->seg[j].gref;
+ }
+
+ dev->ring.req_prod_pvt = i + 1;
+
+ wmb();
+ RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&dev->ring, notify);
+
+ if (notify)
+ notify_remote_via_evtchn(dev->evtchn);
+}
+
+static void blkfront_aio_cb(struct blkfront_aiocb *aiocbp, int ret)
+{
+ aiocbp->data = (void *)1;
+ aiocbp->aio_cb = NULL;
+}
+
+static void blkfront_io(struct blkfront_aiocb *aiocbp, int write)
+{
+ aiocbp->aio_cb = blkfront_aio_cb;
+ blkfront_aio(aiocbp, write);
+ aiocbp->data = NULL;
+
+ while (true) {
+ blkfront_aio_poll(aiocbp->aio_dev);
+ if (aiocbp->data)
+ break;
+ cpu_relax();
+ }
+}
+
+static void blkfront_push_operation(struct blkfront_dev *dev, u8 op,
+ uint64_t id)
+{
+ struct blkif_request *req;
+ int notify, i;
+
+ blkfront_wait_slot(dev);
+ i = dev->ring.req_prod_pvt;
+ req = RING_GET_REQUEST(&dev->ring, i);
+ req->operation = op;
+ req->nr_segments = 0;
+ req->handle = dev->handle;
+ req->id = id;
+ req->sector_number = 0;
+ dev->ring.req_prod_pvt = i + 1;
+ wmb();
+ RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&dev->ring, notify);
+ if (notify)
+ notify_remote_via_evtchn(dev->evtchn);
+}
+
+static void blkfront_sync(struct blkfront_dev *dev)
+{
+ if (dev->info.mode == O_RDWR) {
+ if (dev->info.barrier == 1)
+ blkfront_push_operation(dev,
+ BLKIF_OP_WRITE_BARRIER, 0);
+
+ if (dev->info.flush == 1)
+ blkfront_push_operation(dev,
+ BLKIF_OP_FLUSH_DISKCACHE, 0);
+ }
+
+ while (true) {
+ blkfront_aio_poll(dev);
+ if (RING_FREE_REQUESTS(&dev->ring) == RING_SIZE(&dev->ring))
+ break;
+ cpu_relax();
+ }
+}
+
+/**
+ * pvblock_iop() - Issue an aio.
+ * @udev: Pvblock device
+ * @blknr: Block number to read from / write to
+ * @blkcnt: Amount of blocks to read / write
+ * @buffer: Memory buffer with data to be read / write
+ * @write: Describes is it read or write operation
+ * 0 - read
+ * 1 - write
+ *
+ * Depending on the operation - reading or writing, data is read / written from the
+ * specified address (@buffer) to the sector (@blknr).
+ */
+static ulong pvblock_iop(struct udevice *udev, lbaint_t blknr,
+ lbaint_t blkcnt, void *buffer, int write)
+{
+ struct blkfront_dev *blk_dev = dev_get_priv(udev);
+ struct blk_desc *desc = dev_get_uclass_plat(udev);
+ struct blkfront_aiocb aiocb;
+ lbaint_t blocks_todo;
+ bool unaligned;
+
+ if (blkcnt == 0)
+ return 0;
+
+ if ((blknr + blkcnt) > desc->lba) {
+ printf(DRV_NAME ": block number 0x" LBAF " exceeds max(0x" LBAF ")\n",
+ blknr + blkcnt, desc->lba);
+ return 0;
+ }
+
+ unaligned = (uintptr_t)buffer & (blk_dev->info.sector_size - 1);
+
+ aiocb.aio_dev = blk_dev;
+ aiocb.aio_offset = blknr * desc->blksz;
+ aiocb.aio_cb = NULL;
+ aiocb.data = NULL;
+ blocks_todo = blkcnt;
+ do {
+ aiocb.aio_buf = unaligned ? blk_dev->bounce_buffer : buffer;
+
+ if (write && unaligned)
+ memcpy(blk_dev->bounce_buffer, buffer, desc->blksz);
+
+ aiocb.aio_nbytes = unaligned ? desc->blksz :
+ min((size_t)(BLKIF_MAX_SEGMENTS_PER_REQUEST * PAGE_SIZE),
+ (size_t)(blocks_todo * desc->blksz));
+
+ blkfront_io(&aiocb, write);
+
+ if (!write && unaligned)
+ memcpy(buffer, blk_dev->bounce_buffer, desc->blksz);
+
+ aiocb.aio_offset += aiocb.aio_nbytes;
+ buffer += aiocb.aio_nbytes;
+ blocks_todo -= aiocb.aio_nbytes / desc->blksz;
+ } while (blocks_todo > 0);
+
+ return blkcnt;
+}
+
+ulong pvblock_blk_read(struct udevice *udev, lbaint_t blknr, lbaint_t blkcnt,
+ void *buffer)
+{
+ return pvblock_iop(udev, blknr, blkcnt, buffer, 0);
+}
+
+ulong pvblock_blk_write(struct udevice *udev, lbaint_t blknr, lbaint_t blkcnt,
+ const void *buffer)
+{
+ return pvblock_iop(udev, blknr, blkcnt, (void *)buffer, 1);
+}
+
+static int pvblock_blk_bind(struct udevice *udev)
+{
+ struct blk_desc *desc = dev_get_uclass_plat(udev);
+ int devnum;
+
+ desc->if_type = IF_TYPE_PVBLOCK;
+ /*
+ * Initialize the devnum to -ENODEV. This is to make sure that
+ * blk_next_free_devnum() works as expected, since the default
+ * value 0 is a valid devnum.
+ */
+ desc->devnum = -ENODEV;
+ devnum = blk_next_free_devnum(IF_TYPE_PVBLOCK);
+ if (devnum < 0)
+ return devnum;
+ desc->devnum = devnum;
+ desc->part_type = PART_TYPE_UNKNOWN;
+ desc->bdev = udev;
+
+ strncpy(desc->vendor, "Xen", sizeof(desc->vendor));
+ strncpy(desc->revision, "1", sizeof(desc->revision));
+ strncpy(desc->product, "Virtual disk", sizeof(desc->product));
+
+ return 0;
+}
+
+static int pvblock_blk_probe(struct udevice *udev)
+{
+ struct blkfront_dev *blk_dev = dev_get_priv(udev);
+ struct blkfront_plat *plat = dev_get_plat(udev);
+ struct blk_desc *desc = dev_get_uclass_plat(udev);
+ int ret, devid;
+
+ devid = plat->devid;
+ free(plat);
+
+ ret = init_blkfront(devid, blk_dev);
+ if (ret < 0)
+ return ret;
+
+ desc->blksz = blk_dev->info.sector_size;
+ desc->lba = blk_dev->info.sectors;
+ desc->log2blksz = LOG2(blk_dev->info.sector_size);
+
+ return 0;
+}
+
+static int pvblock_blk_remove(struct udevice *udev)
+{
+ struct blkfront_dev *blk_dev = dev_get_priv(udev);
+
+ shutdown_blkfront(blk_dev);
+ return 0;
+}
+
+static const struct blk_ops pvblock_blk_ops = {
+ .read = pvblock_blk_read,
+ .write = pvblock_blk_write,
+};
+
+U_BOOT_DRIVER(pvblock_blk) = {
+ .name = DRV_NAME_BLK,
+ .id = UCLASS_BLK,
+ .ops = &pvblock_blk_ops,
+ .bind = pvblock_blk_bind,
+ .probe = pvblock_blk_probe,
+ .remove = pvblock_blk_remove,
+ .priv_auto = sizeof(struct blkfront_dev),
+ .flags = DM_FLAG_OS_PREPARE,
+};
+
+/*******************************************************************************
+ * Para-virtual block device class
+ *******************************************************************************/
+
+typedef int (*enum_vbd_callback)(struct udevice *parent, unsigned int devid);
+
+static int on_new_vbd(struct udevice *parent, unsigned int devid)
+{
+ struct driver_info info;
+ struct udevice *udev;
+ struct blkfront_plat *plat;
+ int ret;
+
+ debug("New " DRV_NAME_BLK ", device ID %d\n", devid);
+
+ plat = malloc(sizeof(struct blkfront_plat));
+ if (!plat) {
+ printf("Failed to allocate platform data\n");
+ return -ENOMEM;
+ }
+
+ plat->devid = devid;
+
+ info.name = DRV_NAME_BLK;
+ info.plat = plat;
+
+ ret = device_bind_by_name(parent, false, &info, &udev);
+ if (ret < 0) {
+ printf("Failed to bind " DRV_NAME_BLK " to device with ID %d, ret: %d\n",
+ devid, ret);
+ free(plat);
+ }
+ return ret;
+}
+
+static int xenbus_enumerate_vbd(struct udevice *udev, enum_vbd_callback clb)
+{
+ char **dirs, *msg;
+ int i, ret;
+
+ msg = xenbus_ls(XBT_NIL, "device/vbd", &dirs);
+ if (msg) {
+ printf("Failed to read device/vbd directory: %s\n", msg);
+ free(msg);
+ return -ENODEV;
+ }
+
+ for (i = 0; dirs[i]; i++) {
+ int devid;
+
+ sscanf(dirs[i], "%d", &devid);
+ ret = clb(udev, devid);
+ if (ret < 0)
+ goto fail;
+
+ free(dirs[i]);
+ }
+ ret = 0;
+
+fail:
+ for (; dirs[i]; i++)
+ free(dirs[i]);
+ free(dirs);
+ return ret;
+}
+
+static void print_pvblock_devices(void)
+{
+ struct udevice *udev;
+ bool first = true;
+ const char *class_name;
+
+ class_name = uclass_get_name(UCLASS_PVBLOCK);
+ for (blk_first_device(IF_TYPE_PVBLOCK, &udev); udev;
+ blk_next_device(&udev), first = false) {
+ struct blk_desc *desc = dev_get_uclass_plat(udev);
+
+ if (!first)
+ puts(", ");
+ printf("%s: %d", class_name, desc->devnum);
+ }
+ printf("\n");
+}
+
+void pvblock_init(void)
+{
+ struct driver_info info;
+ struct udevice *udev;
+ struct uclass *uc;
+ int ret;
+
+ /*
+ * At this point Xen drivers have already initialized,
+ * so we can instantiate the class driver and enumerate
+ * virtual block devices.
+ */
+ info.name = DRV_NAME;
+ ret = device_bind_by_name(gd->dm_root, false, &info, &udev);
+ if (ret < 0)
+ printf("Failed to bind " DRV_NAME ", ret: %d\n", ret);
+
+ /* Bootstrap virtual block devices class driver */
+ ret = uclass_get(UCLASS_PVBLOCK, &uc);
+ if (ret)
+ return;
+ uclass_foreach_dev_probe(UCLASS_PVBLOCK, udev);
+
+ print_pvblock_devices();
+}
+
+static int pvblock_probe(struct udevice *udev)
+{
+ struct uclass *uc;
+ int ret;
+
+ if (xenbus_enumerate_vbd(udev, on_new_vbd) < 0)
+ return -ENODEV;
+
+ ret = uclass_get(UCLASS_BLK, &uc);
+ if (ret)
+ return ret;
+ uclass_foreach_dev_probe(UCLASS_BLK, udev) {
+ if (_ret)
+ return _ret;
+ };
+ return 0;
+}
+
+U_BOOT_DRIVER(pvblock_drv) = {
+ .name = DRV_NAME,
+ .id = UCLASS_PVBLOCK,
+ .probe = pvblock_probe,
+};
+
+UCLASS_DRIVER(pvblock) = {
+ .name = DRV_NAME,
+ .id = UCLASS_PVBLOCK,
+};