|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [Xen-devel] [PATCH 5 of 7 v5] blktap3/tapback: Introduce front-end XenStore path handler
This patch introduces the handler executed when the front-end XenStore path of
a VBD gets modified. This is done when the front-end switches state. The core
of this functionality is connecting/disconnecting the tapdisk to/from the
shared ring.
When the front-end goes to Initialised or Connected state, the daemon reads all
the necessary information from XenStore provided by the front-end, initiates
the shared ring creation (the actual creation is performed by a library that
will be introduced by another patch series), and instructs the tapdisk
designated to serve this VBD to connect to the ring. Finally, it communicates
to the front-end all the necessary disk parameters.
When the front-end switches to Closed, the tapdisk is disconnected from the
shared ring.
Signed-off-by: Thanos Makatos <thanos.makatos@xxxxxxxxxx>
---
Changed since v2:
* Documented the frontend_state_change_map array of callbacks.
* Minor code and whitespace clean up.
* Added some debug prints.
* Don't fail if the front-end doesn't tell us whether or not it supports
persistent grants, just assume it doesn't.
Changed since v3:
* Pass the type:/path/to/file to tap_ctl_connect_xenblkif instead of the
minor number.
Changed since v4:
* Fix signs in return values.
diff --git a/tools/blktap3/include/blktap3.h b/tools/blktap3/include/blktap3.h
--- a/tools/blktap3/include/blktap3.h
+++ b/tools/blktap3/include/blktap3.h
@@ -46,4 +46,19 @@
TAILQ_REMOVE(src, node, entry); \
TAILQ_INSERT_TAIL(dst, node, entry);
+/**
+ * Block I/O protocol
+ *
+ * Taken from linux/drivers/block/xen-blkback/common.h so that blkfront can
+ * work both with blktap2 and blktap3.
+ *
+ * TODO linux/drivers/block/xen-blkback/common.h contains other definitions
+ * necessary for allowing tapdisk3 to talk directly to blkfront. Find a way to
+ * use the definitions from there.
+ */
+enum blkif_protocol {
+ BLKIF_PROTOCOL_NATIVE = 1,
+ BLKIF_PROTOCOL_X86_32 = 2,
+ BLKIF_PROTOCOL_X86_64 = 3,
+};
#endif /* __BLKTAP_3_H__ */
diff --git a/tools/blktap3/tapback/frontend.c b/tools/blktap3/tapback/frontend.c
new file mode 100644
--- /dev/null
+++ b/tools/blktap3/tapback/frontend.c
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2012 Citrix Ltd.
+ *
+ * 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; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ *
+ * This file contains the handler executed when the front-end XenStore path of
+ * a VBD gets modified.
+ */
+
+#include "tapback.h"
+#include "xenstore.h"
+
+#include <xen/io/protocols.h>
+
+/**
+ * Switches the back-end state of the device by writing to XenStore.
+ *
+ * @param device the VBD
+ * @param state the state to switch to
+ * @returns 0 on success, an error code otherwise
+ */
+static int
+tapback_device_switch_state(vbd_t * const device,
+ const XenbusState state)
+{
+ int err;
+
+ assert(device);
+
+ /*
+ * TODO Ensure @state contains a legitimate XenbusState value.
+ * TODO Check for valid state transitions?
+ */
+
+ err = -tapback_device_printf(device, "state", false, "%u", state);
+ if (err) {
+ WARN("failed to switch back-end state to %s: %s\n",
+ XenbusState2str(state), strerror(err));
+ } else
+ DBG("switched back-end state to %s\n", XenbusState2str(state));
+ return err;
+}
+
+/**
+ * Core functions that instructs the tapdisk to connect to the shared ring (if
+ * not already connected) and communicates essential information to the
+ * front-end.
+ *
+ * If the tapdisk is not already connected, all the necessary information is
+ * read from XenStore and the tapdisk gets connected using this information.
+ *
+ * TODO Why should this function be called on an already connected VBD? Why
+ * re-write the sector size etc. in XenStore for an already connected VBD?
+ * TODO rename function (no blkback, not only connects to tapdisk)
+ *
+ * @param xbdev the VBD the tapdisk should connect to
+ * @param state unused
+ * @returns 0 on success, an error code otherwise
+ *
+ * XXX Only called by blkback_frontend_changed, when the front-end switches to
+ * Initialised and Connected.
+ */
+static int
+blkback_connect_tap(vbd_t * const bdev,
+ const XenbusState state __attribute__((unused)))
+{
+ evtchn_port_t port = 0;
+ grant_ref_t *gref = NULL;
+ int err = 0;
+ char *proto_str = NULL;
+ char *persistent_grants_str = NULL;
+
+ assert(bdev);
+
+ if (bdev->connected) {
+ DBG("front-end already connected to tapdisk.\n");
+ } else {
+ /*
+ * TODO How can we make sure we're not missing a node written by the
+ * front-end? Use xs_directory?
+ */
+ int nr_pages = 0, proto = 0, order = 0;
+ bool persistent_grants = false;
+
+ if (1 != tapback_device_scanf_otherend(bdev, "ring-page-order", "%d",
+ &order))
+ order = 0;
+
+ nr_pages = 1 << order;
+
+ if (!(gref = calloc(nr_pages, sizeof(grant_ref_t)))) {
+ WARN("Failed to allocate memory for grant refs.\n");
+ err = ENOMEM;
+ goto fail;
+ }
+
+ /*
+ * Read the grant references.
+ */
+ if (order) {
+ int i = 0;
+ /*
+ * +10 is for INT_MAX, +1 for NULL termination
+ */
+ static const size_t len = sizeof(RING_REF) + 10 + 1;
+ char ring_ref[len];
+ for (i = 0; i < nr_pages; i++) {
+ if (snprintf(ring_ref, len, "%s%d", RING_REF, i) >= len) {
+ DBG("error printing to buffer\n");
+ err = EINVAL;
+ goto fail;
+ }
+ if (1 != tapback_device_scanf_otherend(bdev, ring_ref, "%u",
+ &gref[i])) {
+ WARN("Failed to read grant ref 0x%x.\n", i);
+ err = ENOENT;
+ goto fail;
+ }
+ }
+ } else {
+ if (1 != tapback_device_scanf_otherend(bdev, RING_REF, "%u",
+ &gref[0])) {
+ WARN("Failed to read grant ref.\n");
+ err = ENOENT;
+ goto fail;
+ }
+ }
+
+ /*
+ * Read the event channel.
+ */
+ if (1 != tapback_device_scanf_otherend(bdev, "event-channel", "%u",
+ &port)) {
+ WARN("Failed to read event channel.\n");
+ err = ENOENT;
+ goto fail;
+ }
+
+ /*
+ * Read the guest VM's ABI.
+ */
+ if (!(proto_str = tapback_device_read_otherend(bdev, "protocol")))
+ proto = BLKIF_PROTOCOL_NATIVE;
+ else if (!strcmp(proto_str, XEN_IO_PROTO_ABI_X86_32))
+ proto = BLKIF_PROTOCOL_X86_32;
+ else if (!strcmp(proto_str, XEN_IO_PROTO_ABI_X86_64))
+ proto = BLKIF_PROTOCOL_X86_64;
+ else {
+ WARN("unsupported protocol %s\n", proto_str);
+ err = EINVAL;
+ goto fail;
+ }
+
+ /*
+ * Does the front-end support persistent grants?
+ */
+ persistent_grants_str = tapback_device_read_otherend(bdev,
+ FEAT_PERSIST);
+ if (persistent_grants_str) {
+ if (!strcmp(persistent_grants_str, "0"))
+ persistent_grants = false;
+ else if (!strcmp(persistent_grants_str, "1"))
+ persistent_grants = true;
+ else {
+ WARN("invalid %s value: %s\n", FEAT_PERSIST,
+ persistent_grants_str);
+ err = EINVAL;
+ goto fail;
+ }
+ }
+ else
+ DBG("front-end doesn't support persistent grants\n");
+
+ /*
+ * persistent grants are not yet supported
+ */
+ if (persistent_grants)
+ WARN("front-end supports persistent grants but we don't\n");
+
+ /*
+ * Create the shared ring and ask the tapdisk to connect to it.
+ */
+ if ((err = tap_ctl_connect_xenblkif(bdev->tap.pid, bdev->params,
+ bdev->domid, bdev->devid, gref, order, port, proto,
+ NULL))) {
+ WARN("tapdisk failed to connect to the shared ring: %s\n",
+ strerror(err));
+ goto fail;
+ }
+ DBG("tapdisk pid=%d, path=%s connected to shared ring\n",
+ bdev->tap.pid, bdev->path);
+
+ bdev->connected = true;
+ }
+
+ /*
+ * Write the number of sectors, sector size, and info to the back-end path
+ * in XenStore so that the front-end creates a VBD with the appropriate
+ * characteristics.
+ */
+ if ((err = tapback_device_printf(bdev, "sector-size", true, "%u",
+ bdev->sector_size))) {
+ WARN("Failed to write sector-size.\n");
+ goto fail;
+ }
+
+ if ((err = tapback_device_printf(bdev, "sectors", true, "%llu",
+ bdev->sectors))) {
+ WARN("Failed to write sectors.\n");
+ goto fail;
+ }
+
+ if ((err = tapback_device_printf(bdev, "info", true, "%u", bdev->info))) {
+ WARN("Failed to write info.\n");
+ goto fail;
+ }
+
+ if ((err = tapback_device_switch_state(bdev, XenbusStateConnected))) {
+ WARN("failed to switch back-end state to connected: %s\n",
+ strerror(err));
+ }
+
+fail:
+ if (err && bdev->connected) {
+ const int err2 = -tap_ctl_disconnect_xenblkif(bdev->tap.pid,
+ bdev->domid, bdev->devid, NULL);
+ if (err2) {
+ WARN("error disconnecting tapdisk from the shared ring (error "
+ "ignored): %s\n", strerror(err2));
+ }
+
+ bdev->connected = false;
+ }
+
+ free(gref);
+ free(proto_str);
+ free(persistent_grants_str);
+
+ return err;
+}
+
+/**
+ * Callback that is executed when the front-end goes to StateClosed.
+ *
+ * Instructs the tapdisk to disconnect itself from the shared ring and switches
+ * the back-end state to StateClosed.
+ *
+ * @param xbdev the VBD whose tapdisk should be disconnected
+ * @param state unused
+ * @returns 0 on success, an error code otherwise
+ *
+ * XXX Only called by blkback_frontend_changed.
+ */
+static inline int
+backend_close(vbd_t * const bdev,
+ const XenbusState state __attribute__((unused)))
+{
+ int err = 0;
+
+ assert(bdev);
+
+ if (!bdev->connected) {
+ /*
+ * TODO Is this safe? Shouldn't we report an error?
+ */
+ DBG("tapdisk not connected\n");
+ return 0;
+ }
+
+ DBG("disconnecting domid=%d devid=%d from tapdisk pid=%d %s:%s\n",
+ bdev->domid, bdev->devid, bdev->tap.pid, bdev->type, bdev->path);
+
+ if ((err = -tap_ctl_disconnect_xenblkif(bdev->tap.pid, bdev->domid,
+ bdev->devid, NULL))){
+
+ /*
+ * TODO I don't see how tap_ctl_disconnect_xenblkif can return
+ * ESRCH, so this is probably wrong. Probably there's another error
+ * code indicating that there's no tapdisk process.
+ */
+ if (errno == ESRCH) {
+ WARN("tapdisk not running\n");
+ } else {
+ WARN("error disconnecting tapdisk from front-end: %s\n",
+ strerror(err));
+ return err;
+ }
+ }
+
+ bdev->connected = false;
+
+ return tapback_device_switch_state(bdev, XenbusStateClosed);
+}
+
+/**
+ * Acts on changes in the front-end state.
+ *
+ * TODO The back-end blindly follows the front-ends state transitions, should
+ * we check whether unexpected transitions are performed?
+ *
+ * @param xbdev the VBD whose front-end state changed
+ * @param state the new state
+ * @returns 0 on success, an error code otherwise
+ *
+ * XXX Only called by tapback_device_check_front-end_state.
+ */
+static inline int
+blkback_frontend_changed(vbd_t * const xbdev, const XenbusState state)
+{
+ /*
+ * XXX The size of the array (9) comes from the XenbusState enum.
+ *
+ * TODO Send a patch that adds XenbusStateMin, XenbusStateMax,
+ * XenbusStateInvalid and in the XenbusState enum (located in xenbus.h).
+ *
+ * The front-end's state is used as the array index. Each element contains
+ * a call-back function to be executed in response, and an optional state
+ * for the back-end to switch to.
+ */
+ struct frontend_state_change {
+ int (*fn)(vbd_t * const, const XenbusState);
+ const XenbusState state;
+ } static const frontend_state_change_map[] = {
+ [XenbusStateUnknown] = {NULL, 0},
+ [XenbusStateInitialising]
+ = {tapback_device_switch_state, XenbusStateInitWait},
+ [XenbusStateInitWait] = {NULL, 0},
+
+ /* blkback_connect_tap swicthes back-end state to Connected */
+ [XenbusStateInitialised] = {blkback_connect_tap, 0},
+ [XenbusStateConnected] = {blkback_connect_tap, 0},
+
+ [XenbusStateClosing]
+ = {tapback_device_switch_state, XenbusStateClosing},
+ [XenbusStateClosed] = {backend_close, 0},
+ [XenbusStateReconfiguring] = {NULL, 0},
+ [XenbusStateReconfigured] = {NULL, 0}
+ };
+
+ assert(xbdev);
+ assert(state >= XenbusStateUnknown && state <= XenbusStateReconfigured);
+
+ DBG("front-end domid=%d, devid=%s went into state %s\n",
+ xbdev->domid, xbdev->name, XenbusState2str(state));
+
+ if (frontend_state_change_map[state].fn)
+ return frontend_state_change_map[state].fn(xbdev,
+ frontend_state_change_map[state].state);
+ else
+ DBG("ignoring front-end's domid=%d, devid=%s transition to state %s\n",
+ xbdev->domid, xbdev->name, XenbusState2str(state));
+ return 0;
+}
+
+int
+tapback_backend_handle_otherend_watch(const char * const path)
+{
+ vbd_t *device = NULL;
+ int err = 0, state = 0;
+ char *s = NULL, *end = NULL;
+
+ assert(path);
+
+ /*
+ * Find the device that has the same front-end state path.
+ *
+ * There should definitely be such a device in our list, otherwise this
+ * function would not have executed at all, since we would not be waiting
+ * on that XenStore path. The XenStore path we wait for is:
+ * /local/domain/<domid>/device/vbd/<devname>/state. In order to watch this
+ * path, it means that we have received a device create request, so the
+ * device will be there.
+ *
+ * TODO Instead of this linear search we could do better (hash table etc).
+ */
+ tapback_backend_find_device(device,
+ !strcmp(device->frontend_state_path, path));
+ if (!device) {
+ WARN("path \'%s\' does not correspond to a known device\n", path);
+ return ENODEV;
+ }
+
+ DBG("device: domid=%d name=%s\n", device->domid, device->name);
+
+ /*
+ * Read the new front-end's state.
+ */
+ if (!(s = tapback_xs_read(blktap3_daemon.xs, blktap3_daemon.xst, "%s",
+ device->frontend_state_path))) {
+ err = errno;
+ goto fail;
+ }
+ state = strtol(s, &end, 0);
+ if (*end != 0 || end == s) {
+ err = EINVAL;
+ goto fail;
+ }
+
+ err = blkback_frontend_changed(device, state);
+
+fail:
+ free(s);
+ return err;
+}
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxx
http://lists.xen.org/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |