summaryrefslogtreecommitdiffstats
path: root/src/spdk/module/bdev/ocf/vbdev_ocf.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/spdk/module/bdev/ocf/vbdev_ocf.c')
-rw-r--r--src/spdk/module/bdev/ocf/vbdev_ocf.c1775
1 files changed, 1775 insertions, 0 deletions
diff --git a/src/spdk/module/bdev/ocf/vbdev_ocf.c b/src/spdk/module/bdev/ocf/vbdev_ocf.c
new file mode 100644
index 000000000..4997772cd
--- /dev/null
+++ b/src/spdk/module/bdev/ocf/vbdev_ocf.c
@@ -0,0 +1,1775 @@
+/*-
+ * BSD LICENSE
+ *
+ * Copyright (c) Intel Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <ocf/ocf.h>
+#include <ocf/ocf_types.h>
+#include <ocf/ocf_mngt.h>
+
+#include "ctx.h"
+#include "data.h"
+#include "volume.h"
+#include "utils.h"
+#include "vbdev_ocf.h"
+
+#include "spdk/bdev_module.h"
+#include "spdk/conf.h"
+#include "spdk/thread.h"
+#include "spdk/string.h"
+#include "spdk_internal/log.h"
+#include "spdk/cpuset.h"
+
+static struct spdk_bdev_module ocf_if;
+
+static TAILQ_HEAD(, vbdev_ocf) g_ocf_vbdev_head
+ = TAILQ_HEAD_INITIALIZER(g_ocf_vbdev_head);
+
+static TAILQ_HEAD(, examining_bdev) g_ocf_examining_bdevs_head
+ = TAILQ_HEAD_INITIALIZER(g_ocf_examining_bdevs_head);
+
+bool g_fini_started = false;
+
+/* Structure for keeping list of bdevs that are claimed but not used yet */
+struct examining_bdev {
+ struct spdk_bdev *bdev;
+ TAILQ_ENTRY(examining_bdev) tailq;
+};
+
+/* Add bdev to list of claimed */
+static void
+examine_start(struct spdk_bdev *bdev)
+{
+ struct examining_bdev *entry = malloc(sizeof(*entry));
+
+ assert(entry);
+ entry->bdev = bdev;
+ TAILQ_INSERT_TAIL(&g_ocf_examining_bdevs_head, entry, tailq);
+}
+
+/* Find bdev on list of claimed bdevs, then remove it,
+ * if it was the last one on list then report examine done */
+static void
+examine_done(int status, struct vbdev_ocf *vbdev, void *cb_arg)
+{
+ struct spdk_bdev *bdev = cb_arg;
+ struct examining_bdev *entry, *safe, *found = NULL;
+
+ TAILQ_FOREACH_SAFE(entry, &g_ocf_examining_bdevs_head, tailq, safe) {
+ if (entry->bdev == bdev) {
+ if (found) {
+ goto remove;
+ } else {
+ found = entry;
+ }
+ }
+ }
+
+ assert(found);
+ spdk_bdev_module_examine_done(&ocf_if);
+
+remove:
+ TAILQ_REMOVE(&g_ocf_examining_bdevs_head, found, tailq);
+ free(found);
+}
+
+/* Free allocated strings and structure itself
+ * Used at shutdown only */
+static void
+free_vbdev(struct vbdev_ocf *vbdev)
+{
+ if (!vbdev) {
+ return;
+ }
+
+ free(vbdev->name);
+ free(vbdev->cache.name);
+ free(vbdev->core.name);
+ free(vbdev);
+}
+
+/* Get existing cache base
+ * that is attached to other vbdev */
+static struct vbdev_ocf_base *
+get_other_cache_base(struct vbdev_ocf_base *base)
+{
+ struct vbdev_ocf *vbdev;
+
+ TAILQ_FOREACH(vbdev, &g_ocf_vbdev_head, tailq) {
+ if (&vbdev->cache == base || !vbdev->cache.attached) {
+ continue;
+ }
+ if (!strcmp(vbdev->cache.name, base->name)) {
+ return &vbdev->cache;
+ }
+ }
+
+ return NULL;
+}
+
+static bool
+is_ocf_cache_running(struct vbdev_ocf *vbdev)
+{
+ if (vbdev->cache.attached && vbdev->ocf_cache) {
+ return ocf_cache_is_running(vbdev->ocf_cache);
+ }
+ return false;
+}
+
+/* Get existing OCF cache instance
+ * that is started by other vbdev */
+static ocf_cache_t
+get_other_cache_instance(struct vbdev_ocf *vbdev)
+{
+ struct vbdev_ocf *cmp;
+
+ TAILQ_FOREACH(cmp, &g_ocf_vbdev_head, tailq) {
+ if (cmp->state.doing_finish || cmp == vbdev) {
+ continue;
+ }
+ if (strcmp(cmp->cache.name, vbdev->cache.name)) {
+ continue;
+ }
+ if (is_ocf_cache_running(cmp)) {
+ return cmp->ocf_cache;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+_remove_base_bdev(void *ctx)
+{
+ struct spdk_bdev_desc *desc = ctx;
+
+ spdk_bdev_close(desc);
+}
+
+/* Close and unclaim base bdev */
+static void
+remove_base_bdev(struct vbdev_ocf_base *base)
+{
+ if (base->attached) {
+ if (base->management_channel) {
+ spdk_put_io_channel(base->management_channel);
+ }
+
+ spdk_bdev_module_release_bdev(base->bdev);
+ /* Close the underlying bdev on its same opened thread. */
+ if (base->thread && base->thread != spdk_get_thread()) {
+ spdk_thread_send_msg(base->thread, _remove_base_bdev, base->desc);
+ } else {
+ spdk_bdev_close(base->desc);
+ }
+ base->attached = false;
+ }
+}
+
+/* Finish unregister operation */
+static void
+unregister_finish(struct vbdev_ocf *vbdev)
+{
+ spdk_bdev_destruct_done(&vbdev->exp_bdev, vbdev->state.stop_status);
+ ocf_mngt_cache_put(vbdev->ocf_cache);
+ vbdev_ocf_cache_ctx_put(vbdev->cache_ctx);
+ vbdev_ocf_mngt_continue(vbdev, 0);
+}
+
+static void
+close_core_bdev(struct vbdev_ocf *vbdev)
+{
+ remove_base_bdev(&vbdev->core);
+ vbdev_ocf_mngt_continue(vbdev, 0);
+}
+
+static void
+remove_core_cmpl(void *priv, int error)
+{
+ struct vbdev_ocf *vbdev = priv;
+
+ ocf_mngt_cache_unlock(vbdev->ocf_cache);
+ vbdev_ocf_mngt_continue(vbdev, error);
+}
+
+/* Try to lock cache, then remove core */
+static void
+remove_core_cache_lock_cmpl(ocf_cache_t cache, void *priv, int error)
+{
+ struct vbdev_ocf *vbdev = (struct vbdev_ocf *)priv;
+
+ if (error) {
+ SPDK_ERRLOG("Error %d, can not lock cache instance %s\n",
+ error, vbdev->name);
+ vbdev_ocf_mngt_continue(vbdev, error);
+ return;
+ }
+
+ ocf_mngt_cache_remove_core(vbdev->ocf_core, remove_core_cmpl, vbdev);
+}
+
+/* Detach core base */
+static void
+detach_core(struct vbdev_ocf *vbdev)
+{
+ if (is_ocf_cache_running(vbdev)) {
+ ocf_mngt_cache_lock(vbdev->ocf_cache, remove_core_cache_lock_cmpl, vbdev);
+ } else {
+ vbdev_ocf_mngt_continue(vbdev, 0);
+ }
+}
+
+static void
+close_cache_bdev(struct vbdev_ocf *vbdev)
+{
+ remove_base_bdev(&vbdev->cache);
+ vbdev_ocf_mngt_continue(vbdev, 0);
+}
+
+/* Detach cache base */
+static void
+detach_cache(struct vbdev_ocf *vbdev)
+{
+ vbdev->state.stop_status = vbdev->mngt_ctx.status;
+
+ /* If some other vbdev references this cache bdev,
+ * we detach this only by changing the flag, without actual close */
+ if (get_other_cache_base(&vbdev->cache)) {
+ vbdev->cache.attached = false;
+ }
+
+ vbdev_ocf_mngt_continue(vbdev, 0);
+}
+
+static void
+stop_vbdev_cmpl(ocf_cache_t cache, void *priv, int error)
+{
+ struct vbdev_ocf *vbdev = priv;
+
+ vbdev_ocf_queue_put(vbdev->cache_ctx->mngt_queue);
+ ocf_mngt_cache_unlock(cache);
+
+ vbdev_ocf_mngt_continue(vbdev, error);
+}
+
+/* Try to lock cache, then stop it */
+static void
+stop_vbdev_cache_lock_cmpl(ocf_cache_t cache, void *priv, int error)
+{
+ struct vbdev_ocf *vbdev = (struct vbdev_ocf *)priv;
+
+ if (error) {
+ SPDK_ERRLOG("Error %d, can not lock cache instance %s\n",
+ error, vbdev->name);
+ vbdev_ocf_mngt_continue(vbdev, error);
+ return;
+ }
+
+ ocf_mngt_cache_stop(vbdev->ocf_cache, stop_vbdev_cmpl, vbdev);
+}
+
+/* Stop OCF cache object
+ * vbdev_ocf is not operational after this */
+static void
+stop_vbdev(struct vbdev_ocf *vbdev)
+{
+ if (!is_ocf_cache_running(vbdev)) {
+ vbdev_ocf_mngt_continue(vbdev, 0);
+ return;
+ }
+
+ if (!g_fini_started && get_other_cache_instance(vbdev)) {
+ SPDK_NOTICELOG("Not stopping cache instance '%s'"
+ " because it is referenced by other OCF bdev\n",
+ vbdev->cache.name);
+ vbdev_ocf_mngt_continue(vbdev, 0);
+ return;
+ }
+
+ ocf_mngt_cache_lock(vbdev->ocf_cache, stop_vbdev_cache_lock_cmpl, vbdev);
+}
+
+static void
+flush_vbdev_cmpl(ocf_cache_t cache, void *priv, int error)
+{
+ struct vbdev_ocf *vbdev = priv;
+
+ ocf_mngt_cache_unlock(cache);
+ vbdev_ocf_mngt_continue(vbdev, error);
+}
+
+static void
+flush_vbdev_cache_lock_cmpl(ocf_cache_t cache, void *priv, int error)
+{
+ struct vbdev_ocf *vbdev = (struct vbdev_ocf *)priv;
+
+ if (error) {
+ SPDK_ERRLOG("Error %d, can not lock cache instance %s\n",
+ error, vbdev->name);
+ vbdev_ocf_mngt_continue(vbdev, error);
+ return;
+ }
+
+ ocf_mngt_cache_flush(vbdev->ocf_cache, flush_vbdev_cmpl, vbdev);
+}
+
+static void
+flush_vbdev(struct vbdev_ocf *vbdev)
+{
+ if (!is_ocf_cache_running(vbdev)) {
+ vbdev_ocf_mngt_continue(vbdev, -EINVAL);
+ return;
+ }
+
+ ocf_mngt_cache_lock(vbdev->ocf_cache, flush_vbdev_cache_lock_cmpl, vbdev);
+}
+
+/* Procedures called during dirty unregister */
+vbdev_ocf_mngt_fn unregister_path_dirty[] = {
+ flush_vbdev,
+ stop_vbdev,
+ detach_cache,
+ close_cache_bdev,
+ detach_core,
+ close_core_bdev,
+ unregister_finish,
+ NULL
+};
+
+/* Procedures called during clean unregister */
+vbdev_ocf_mngt_fn unregister_path_clean[] = {
+ flush_vbdev,
+ detach_core,
+ close_core_bdev,
+ stop_vbdev,
+ detach_cache,
+ close_cache_bdev,
+ unregister_finish,
+ NULL
+};
+
+/* Start asynchronous management operation using unregister_path */
+static void
+unregister_cb(void *opaque)
+{
+ struct vbdev_ocf *vbdev = opaque;
+ vbdev_ocf_mngt_fn *unregister_path;
+ int rc;
+
+ unregister_path = vbdev->state.doing_clean_delete ?
+ unregister_path_clean : unregister_path_dirty;
+
+ rc = vbdev_ocf_mngt_start(vbdev, unregister_path, NULL, NULL);
+ if (rc) {
+ SPDK_ERRLOG("Unable to unregister OCF bdev: %d\n", rc);
+ spdk_bdev_destruct_done(&vbdev->exp_bdev, rc);
+ }
+}
+
+/* Clean remove case - remove core and then cache, this order
+ * will remove instance permanently */
+static void
+_vbdev_ocf_destruct_clean(struct vbdev_ocf *vbdev)
+{
+ if (vbdev->core.attached) {
+ detach_core(vbdev);
+ close_core_bdev(vbdev);
+ }
+
+ if (vbdev->cache.attached) {
+ detach_cache(vbdev);
+ close_cache_bdev(vbdev);
+ }
+}
+
+/* Dirty shutdown/hot remove case - remove cache and then core, this order
+ * will allow us to recover this instance in the future */
+static void
+_vbdev_ocf_destruct_dirty(struct vbdev_ocf *vbdev)
+{
+ if (vbdev->cache.attached) {
+ detach_cache(vbdev);
+ close_cache_bdev(vbdev);
+ }
+
+ if (vbdev->core.attached) {
+ detach_core(vbdev);
+ close_core_bdev(vbdev);
+ }
+}
+
+/* Unregister io device with callback to unregister_cb
+ * This function is called during spdk_bdev_unregister */
+static int
+vbdev_ocf_destruct(void *opaque)
+{
+ struct vbdev_ocf *vbdev = opaque;
+
+ if (vbdev->state.doing_finish) {
+ return -EALREADY;
+ }
+
+ if (vbdev->state.starting && !vbdev->state.started) {
+ /* Prevent before detach cache/core during register path of
+ this bdev */
+ return -EBUSY;
+ }
+
+ vbdev->state.doing_finish = true;
+
+ if (vbdev->state.started) {
+ spdk_io_device_unregister(vbdev, unregister_cb);
+ /* Return 1 because unregister is delayed */
+ return 1;
+ }
+
+ if (vbdev->state.doing_clean_delete) {
+ _vbdev_ocf_destruct_clean(vbdev);
+ } else {
+ _vbdev_ocf_destruct_dirty(vbdev);
+ }
+
+ return 0;
+}
+
+/* Stop OCF cache and unregister SPDK bdev */
+int
+vbdev_ocf_delete(struct vbdev_ocf *vbdev, void (*cb)(void *, int), void *cb_arg)
+{
+ int rc = 0;
+
+ if (vbdev->state.started) {
+ spdk_bdev_unregister(&vbdev->exp_bdev, cb, cb_arg);
+ } else {
+ rc = vbdev_ocf_destruct(vbdev);
+ if (rc == 0 && cb) {
+ cb(cb_arg, 0);
+ }
+ }
+
+ return rc;
+}
+
+/* Remove cores permanently and then stop OCF cache and unregister SPDK bdev */
+int
+vbdev_ocf_delete_clean(struct vbdev_ocf *vbdev, void (*cb)(void *, int),
+ void *cb_arg)
+{
+ vbdev->state.doing_clean_delete = true;
+
+ return vbdev_ocf_delete(vbdev, cb, cb_arg);
+}
+
+
+/* If vbdev is online, return its object */
+struct vbdev_ocf *
+vbdev_ocf_get_by_name(const char *name)
+{
+ struct vbdev_ocf *vbdev;
+
+ if (name == NULL) {
+ assert(false);
+ return NULL;
+ }
+
+ TAILQ_FOREACH(vbdev, &g_ocf_vbdev_head, tailq) {
+ if (vbdev->name == NULL || vbdev->state.doing_finish) {
+ continue;
+ }
+ if (strcmp(vbdev->name, name) == 0) {
+ return vbdev;
+ }
+ }
+ return NULL;
+}
+
+/* Return matching base if parent vbdev is online */
+struct vbdev_ocf_base *
+vbdev_ocf_get_base_by_name(const char *name)
+{
+ struct vbdev_ocf *vbdev;
+
+ if (name == NULL) {
+ assert(false);
+ return NULL;
+ }
+
+ TAILQ_FOREACH(vbdev, &g_ocf_vbdev_head, tailq) {
+ if (vbdev->state.doing_finish) {
+ continue;
+ }
+
+ if (vbdev->cache.name && strcmp(vbdev->cache.name, name) == 0) {
+ return &vbdev->cache;
+ }
+ if (vbdev->core.name && strcmp(vbdev->core.name, name) == 0) {
+ return &vbdev->core;
+ }
+ }
+ return NULL;
+}
+
+/* Execute fn for each OCF device that is online or waits for base devices */
+void
+vbdev_ocf_foreach(vbdev_ocf_foreach_fn fn, void *ctx)
+{
+ struct vbdev_ocf *vbdev;
+
+ assert(fn != NULL);
+
+ TAILQ_FOREACH(vbdev, &g_ocf_vbdev_head, tailq) {
+ if (!vbdev->state.doing_finish) {
+ fn(vbdev, ctx);
+ }
+ }
+}
+
+/* Called from OCF when SPDK_IO is completed */
+static void
+vbdev_ocf_io_submit_cb(struct ocf_io *io, int error)
+{
+ struct spdk_bdev_io *bdev_io = io->priv1;
+
+ if (error == 0) {
+ spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_SUCCESS);
+ } else if (error == -ENOMEM) {
+ spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_NOMEM);
+ } else {
+ spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_FAILED);
+ }
+
+ ocf_io_put(io);
+}
+
+/* Configure io parameters and send it to OCF */
+static int
+io_submit_to_ocf(struct spdk_bdev_io *bdev_io, struct ocf_io *io)
+{
+ switch (bdev_io->type) {
+ case SPDK_BDEV_IO_TYPE_WRITE:
+ case SPDK_BDEV_IO_TYPE_READ:
+ ocf_core_submit_io(io);
+ return 0;
+ case SPDK_BDEV_IO_TYPE_FLUSH:
+ ocf_core_submit_flush(io);
+ return 0;
+ case SPDK_BDEV_IO_TYPE_UNMAP:
+ ocf_core_submit_discard(io);
+ return 0;
+ case SPDK_BDEV_IO_TYPE_RESET:
+ case SPDK_BDEV_IO_TYPE_WRITE_ZEROES:
+ default:
+ SPDK_ERRLOG("Unsupported IO type: %d\n", bdev_io->type);
+ return -EINVAL;
+ }
+}
+
+/* Submit SPDK-IO to OCF */
+static void
+io_handle(struct spdk_io_channel *ch, struct spdk_bdev_io *bdev_io)
+{
+ struct vbdev_ocf *vbdev = bdev_io->bdev->ctxt;
+ struct ocf_io *io = NULL;
+ struct bdev_ocf_data *data = NULL;
+ struct vbdev_ocf_qctx *qctx = spdk_io_channel_get_ctx(ch);
+ uint64_t len = bdev_io->u.bdev.num_blocks * bdev_io->bdev->blocklen;
+ uint64_t offset = bdev_io->u.bdev.offset_blocks * bdev_io->bdev->blocklen;
+ int dir, flags = 0;
+ int err;
+
+ switch (bdev_io->type) {
+ case SPDK_BDEV_IO_TYPE_READ:
+ dir = OCF_READ;
+ break;
+ case SPDK_BDEV_IO_TYPE_WRITE:
+ dir = OCF_WRITE;
+ break;
+ case SPDK_BDEV_IO_TYPE_FLUSH:
+ dir = OCF_WRITE;
+ break;
+ case SPDK_BDEV_IO_TYPE_UNMAP:
+ dir = OCF_WRITE;
+ break;
+ default:
+ err = -EINVAL;
+ goto fail;
+ }
+
+ if (bdev_io->type == SPDK_BDEV_IO_TYPE_FLUSH) {
+ flags = OCF_WRITE_FLUSH;
+ }
+
+ io = ocf_core_new_io(vbdev->ocf_core, qctx->queue, offset, len, dir, 0, flags);
+ if (!io) {
+ err = -ENOMEM;
+ goto fail;
+ }
+
+ data = vbdev_ocf_data_from_spdk_io(bdev_io);
+ if (!data) {
+ err = -ENOMEM;
+ goto fail;
+ }
+
+ err = ocf_io_set_data(io, data, 0);
+ if (err) {
+ goto fail;
+ }
+
+ ocf_io_set_cmpl(io, bdev_io, NULL, vbdev_ocf_io_submit_cb);
+
+ err = io_submit_to_ocf(bdev_io, io);
+ if (err) {
+ goto fail;
+ }
+
+ return;
+
+fail:
+ if (io) {
+ ocf_io_put(io);
+ }
+
+ if (err == -ENOMEM) {
+ spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_NOMEM);
+ } else {
+ spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_FAILED);
+ }
+}
+
+static void
+vbdev_ocf_get_buf_cb(struct spdk_io_channel *ch, struct spdk_bdev_io *bdev_io,
+ bool success)
+{
+ if (!success) {
+ spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_FAILED);
+ return;
+ }
+
+ io_handle(ch, bdev_io);
+}
+
+/* Called from bdev layer when an io to Cache vbdev is submitted */
+static void
+vbdev_ocf_submit_request(struct spdk_io_channel *ch, struct spdk_bdev_io *bdev_io)
+{
+ switch (bdev_io->type) {
+ case SPDK_BDEV_IO_TYPE_READ:
+ /* User does not have to allocate io vectors for the request,
+ * so in case they are not allocated, we allocate them here */
+ spdk_bdev_io_get_buf(bdev_io, vbdev_ocf_get_buf_cb,
+ bdev_io->u.bdev.num_blocks * bdev_io->bdev->blocklen);
+ break;
+ case SPDK_BDEV_IO_TYPE_WRITE:
+ case SPDK_BDEV_IO_TYPE_FLUSH:
+ case SPDK_BDEV_IO_TYPE_UNMAP:
+ io_handle(ch, bdev_io);
+ break;
+ case SPDK_BDEV_IO_TYPE_RESET:
+ case SPDK_BDEV_IO_TYPE_WRITE_ZEROES:
+ default:
+ SPDK_ERRLOG("Unknown I/O type %d\n", bdev_io->type);
+ spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_FAILED);
+ break;
+ }
+}
+
+/* Called from bdev layer */
+static bool
+vbdev_ocf_io_type_supported(void *opaque, enum spdk_bdev_io_type io_type)
+{
+ struct vbdev_ocf *vbdev = opaque;
+
+ switch (io_type) {
+ case SPDK_BDEV_IO_TYPE_READ:
+ case SPDK_BDEV_IO_TYPE_WRITE:
+ case SPDK_BDEV_IO_TYPE_FLUSH:
+ case SPDK_BDEV_IO_TYPE_UNMAP:
+ return spdk_bdev_io_type_supported(vbdev->core.bdev, io_type);
+ case SPDK_BDEV_IO_TYPE_RESET:
+ case SPDK_BDEV_IO_TYPE_WRITE_ZEROES:
+ default:
+ return false;
+ }
+}
+
+/* Called from bdev layer */
+static struct spdk_io_channel *
+vbdev_ocf_get_io_channel(void *opaque)
+{
+ struct vbdev_ocf *bdev = opaque;
+
+ return spdk_get_io_channel(bdev);
+}
+
+static int
+vbdev_ocf_dump_info_json(void *opaque, struct spdk_json_write_ctx *w)
+{
+ struct vbdev_ocf *vbdev = opaque;
+
+ spdk_json_write_named_string(w, "cache_device", vbdev->cache.name);
+ spdk_json_write_named_string(w, "core_device", vbdev->core.name);
+
+ spdk_json_write_named_string(w, "mode",
+ ocf_get_cache_modename(ocf_cache_get_mode(vbdev->ocf_cache)));
+ spdk_json_write_named_uint32(w, "cache_line_size",
+ ocf_cache_get_line_size(vbdev->ocf_cache));
+ spdk_json_write_named_bool(w, "metadata_volatile",
+ vbdev->cfg.cache.metadata_volatile);
+
+ return 0;
+}
+
+static void
+vbdev_ocf_write_json_config(struct spdk_bdev *bdev, struct spdk_json_write_ctx *w)
+{
+ struct vbdev_ocf *vbdev = bdev->ctxt;
+
+ spdk_json_write_object_begin(w);
+
+ spdk_json_write_named_string(w, "method", "bdev_ocf_create");
+
+ spdk_json_write_named_object_begin(w, "params");
+ spdk_json_write_named_string(w, "name", vbdev->name);
+ spdk_json_write_named_string(w, "mode",
+ ocf_get_cache_modename(ocf_cache_get_mode(vbdev->ocf_cache)));
+ spdk_json_write_named_string(w, "cache_bdev_name", vbdev->cache.name);
+ spdk_json_write_named_string(w, "core_bdev_name", vbdev->core.name);
+ spdk_json_write_object_end(w);
+
+ spdk_json_write_object_end(w);
+}
+
+/* Cache vbdev function table
+ * Used by bdev layer */
+static struct spdk_bdev_fn_table cache_dev_fn_table = {
+ .destruct = vbdev_ocf_destruct,
+ .io_type_supported = vbdev_ocf_io_type_supported,
+ .submit_request = vbdev_ocf_submit_request,
+ .get_io_channel = vbdev_ocf_get_io_channel,
+ .write_config_json = vbdev_ocf_write_json_config,
+ .dump_info_json = vbdev_ocf_dump_info_json,
+};
+
+/* Poller function for the OCF queue
+ * We execute OCF requests here synchronously */
+static int
+queue_poll(void *opaque)
+{
+ struct vbdev_ocf_qctx *qctx = opaque;
+ uint32_t iono = ocf_queue_pending_io(qctx->queue);
+ int i, max = spdk_min(32, iono);
+
+ for (i = 0; i < max; i++) {
+ ocf_queue_run_single(qctx->queue);
+ }
+
+ if (iono > 0) {
+ return SPDK_POLLER_BUSY;
+ } else {
+ return SPDK_POLLER_IDLE;
+ }
+}
+
+/* Called during ocf_submit_io, ocf_purge*
+ * and any other requests that need to submit io */
+static void
+vbdev_ocf_ctx_queue_kick(ocf_queue_t q)
+{
+}
+
+/* OCF queue deinitialization
+ * Called at ocf_cache_stop */
+static void
+vbdev_ocf_ctx_queue_stop(ocf_queue_t q)
+{
+ struct vbdev_ocf_qctx *qctx = ocf_queue_get_priv(q);
+
+ if (qctx) {
+ spdk_put_io_channel(qctx->cache_ch);
+ spdk_put_io_channel(qctx->core_ch);
+ spdk_poller_unregister(&qctx->poller);
+ if (qctx->allocated) {
+ free(qctx);
+ }
+ }
+}
+
+/* Queue ops is an interface for running queue thread
+ * stop() operation in called just before queue gets destroyed */
+const struct ocf_queue_ops queue_ops = {
+ .kick_sync = vbdev_ocf_ctx_queue_kick,
+ .kick = vbdev_ocf_ctx_queue_kick,
+ .stop = vbdev_ocf_ctx_queue_stop,
+};
+
+/* Called on cache vbdev creation at every thread
+ * We allocate OCF queues here and SPDK poller for it */
+static int
+io_device_create_cb(void *io_device, void *ctx_buf)
+{
+ struct vbdev_ocf *vbdev = io_device;
+ struct vbdev_ocf_qctx *qctx = ctx_buf;
+ int rc;
+
+ rc = vbdev_ocf_queue_create(vbdev->ocf_cache, &qctx->queue, &queue_ops);
+ if (rc) {
+ return rc;
+ }
+
+ ocf_queue_set_priv(qctx->queue, qctx);
+
+ qctx->vbdev = vbdev;
+ qctx->cache_ch = spdk_bdev_get_io_channel(vbdev->cache.desc);
+ qctx->core_ch = spdk_bdev_get_io_channel(vbdev->core.desc);
+ qctx->poller = SPDK_POLLER_REGISTER(queue_poll, qctx, 0);
+
+ return rc;
+}
+
+/* Called per thread
+ * Put OCF queue and relaunch poller with new context to finish pending requests */
+static void
+io_device_destroy_cb(void *io_device, void *ctx_buf)
+{
+ /* Making a copy of context to use it after io channel will be destroyed */
+ struct vbdev_ocf_qctx *copy = malloc(sizeof(*copy));
+ struct vbdev_ocf_qctx *qctx = ctx_buf;
+
+ if (copy) {
+ ocf_queue_set_priv(qctx->queue, copy);
+ memcpy(copy, qctx, sizeof(*copy));
+ spdk_poller_unregister(&qctx->poller);
+ copy->poller = SPDK_POLLER_REGISTER(queue_poll, copy, 0);
+ copy->allocated = true;
+ } else {
+ SPDK_ERRLOG("Unable to stop OCF queue properly: %s\n",
+ spdk_strerror(ENOMEM));
+ }
+
+ vbdev_ocf_queue_put(qctx->queue);
+}
+
+/* OCF management queue deinitialization */
+static void
+vbdev_ocf_ctx_mngt_queue_stop(ocf_queue_t q)
+{
+ struct spdk_poller *poller = ocf_queue_get_priv(q);
+
+ if (poller) {
+ spdk_poller_unregister(&poller);
+ }
+}
+
+static int
+mngt_queue_poll(void *opaque)
+{
+ ocf_queue_t q = opaque;
+ uint32_t iono = ocf_queue_pending_io(q);
+ int i, max = spdk_min(32, iono);
+
+ for (i = 0; i < max; i++) {
+ ocf_queue_run_single(q);
+ }
+
+ if (iono > 0) {
+ return SPDK_POLLER_BUSY;
+ } else {
+ return SPDK_POLLER_IDLE;
+ }
+}
+
+static void
+vbdev_ocf_ctx_mngt_queue_kick(ocf_queue_t q)
+{
+}
+
+/* Queue ops is an interface for running queue thread
+ * stop() operation in called just before queue gets destroyed */
+const struct ocf_queue_ops mngt_queue_ops = {
+ .kick_sync = NULL,
+ .kick = vbdev_ocf_ctx_mngt_queue_kick,
+ .stop = vbdev_ocf_ctx_mngt_queue_stop,
+};
+
+static void
+vbdev_ocf_mngt_exit(struct vbdev_ocf *vbdev, vbdev_ocf_mngt_fn *rollback_path, int rc)
+{
+ vbdev->state.starting = false;
+ vbdev_ocf_mngt_stop(vbdev, rollback_path, rc);
+}
+
+/* Create exported spdk object */
+static void
+finish_register(struct vbdev_ocf *vbdev)
+{
+ int result;
+
+ /* Copy properties of the base bdev */
+ vbdev->exp_bdev.blocklen = vbdev->core.bdev->blocklen;
+ vbdev->exp_bdev.write_cache = vbdev->core.bdev->write_cache;
+ vbdev->exp_bdev.required_alignment = vbdev->core.bdev->required_alignment;
+
+ vbdev->exp_bdev.name = vbdev->name;
+ vbdev->exp_bdev.product_name = "SPDK OCF";
+
+ vbdev->exp_bdev.blockcnt = vbdev->core.bdev->blockcnt;
+ vbdev->exp_bdev.ctxt = vbdev;
+ vbdev->exp_bdev.fn_table = &cache_dev_fn_table;
+ vbdev->exp_bdev.module = &ocf_if;
+
+ /* Finally register vbdev in SPDK */
+ spdk_io_device_register(vbdev, io_device_create_cb, io_device_destroy_cb,
+ sizeof(struct vbdev_ocf_qctx), vbdev->name);
+ result = spdk_bdev_register(&vbdev->exp_bdev);
+ if (result) {
+ SPDK_ERRLOG("Could not register exposed bdev %s\n",
+ vbdev->name);
+ vbdev_ocf_mngt_exit(vbdev, unregister_path_dirty, result);
+ return;
+ } else {
+ vbdev->state.started = true;
+ }
+
+ vbdev_ocf_mngt_continue(vbdev, result);
+}
+
+static void
+add_core_cmpl(ocf_cache_t cache, ocf_core_t core, void *priv, int error)
+{
+ struct vbdev_ocf *vbdev = priv;
+
+ ocf_mngt_cache_unlock(cache);
+
+ if (error) {
+ SPDK_ERRLOG("Error %d, failed to add core device to cache instance %s,"
+ "starting rollback\n", error, vbdev->name);
+ vbdev_ocf_mngt_exit(vbdev, unregister_path_dirty, error);
+ return;
+ } else {
+ vbdev->ocf_core = core;
+ }
+
+ vbdev_ocf_mngt_continue(vbdev, error);
+}
+
+/* Try to lock cache, then add core */
+static void
+add_core_cache_lock_cmpl(ocf_cache_t cache, void *priv, int error)
+{
+ struct vbdev_ocf *vbdev = (struct vbdev_ocf *)priv;
+
+ if (error) {
+ SPDK_ERRLOG("Error %d, can not lock cache instance %s,"
+ "starting rollback\n", error, vbdev->name);
+ vbdev_ocf_mngt_exit(vbdev, unregister_path_dirty, error);
+ }
+ ocf_mngt_cache_add_core(vbdev->ocf_cache, &vbdev->cfg.core, add_core_cmpl, vbdev);
+}
+
+/* Add core for existing OCF cache instance */
+static void
+add_core(struct vbdev_ocf *vbdev)
+{
+ ocf_mngt_cache_lock(vbdev->ocf_cache, add_core_cache_lock_cmpl, vbdev);
+}
+
+static void
+start_cache_cmpl(ocf_cache_t cache, void *priv, int error)
+{
+ struct vbdev_ocf *vbdev = priv;
+
+ ocf_mngt_cache_unlock(cache);
+
+ if (error) {
+ SPDK_ERRLOG("Error %d during start cache %s, starting rollback\n",
+ error, vbdev->name);
+ vbdev_ocf_mngt_exit(vbdev, unregister_path_dirty, error);
+ return;
+ }
+
+ vbdev_ocf_mngt_continue(vbdev, error);
+}
+
+static int
+create_management_queue(struct vbdev_ocf *vbdev)
+{
+ struct spdk_poller *mngt_poller;
+ int rc;
+
+ rc = vbdev_ocf_queue_create(vbdev->ocf_cache, &vbdev->cache_ctx->mngt_queue, &mngt_queue_ops);
+ if (rc) {
+ SPDK_ERRLOG("Unable to create mngt_queue: %d\n", rc);
+ return rc;
+ }
+
+ mngt_poller = SPDK_POLLER_REGISTER(mngt_queue_poll, vbdev->cache_ctx->mngt_queue, 100);
+ if (mngt_poller == NULL) {
+ SPDK_ERRLOG("Unable to initiate mngt request: %s", spdk_strerror(ENOMEM));
+ return -ENOMEM;
+ }
+
+ ocf_queue_set_priv(vbdev->cache_ctx->mngt_queue, mngt_poller);
+ ocf_mngt_cache_set_mngt_queue(vbdev->ocf_cache, vbdev->cache_ctx->mngt_queue);
+
+ return 0;
+}
+
+/* Start OCF cache, attach caching device */
+static void
+start_cache(struct vbdev_ocf *vbdev)
+{
+ ocf_cache_t existing;
+ int rc;
+
+ if (is_ocf_cache_running(vbdev)) {
+ vbdev_ocf_mngt_stop(vbdev, NULL, -EALREADY);
+ return;
+ }
+
+ existing = get_other_cache_instance(vbdev);
+ if (existing) {
+ SPDK_NOTICELOG("OCF bdev %s connects to existing cache device %s\n",
+ vbdev->name, vbdev->cache.name);
+ vbdev->ocf_cache = existing;
+ ocf_mngt_cache_get(vbdev->ocf_cache);
+ vbdev->cache_ctx = ocf_cache_get_priv(existing);
+ vbdev_ocf_cache_ctx_get(vbdev->cache_ctx);
+ vbdev_ocf_mngt_continue(vbdev, 0);
+ return;
+ }
+
+ vbdev->cache_ctx = calloc(1, sizeof(struct vbdev_ocf_cache_ctx));
+ if (vbdev->cache_ctx == NULL) {
+ vbdev_ocf_mngt_exit(vbdev, unregister_path_dirty, -ENOMEM);
+ return;
+ }
+
+ vbdev_ocf_cache_ctx_get(vbdev->cache_ctx);
+ pthread_mutex_init(&vbdev->cache_ctx->lock, NULL);
+
+ rc = ocf_mngt_cache_start(vbdev_ocf_ctx, &vbdev->ocf_cache, &vbdev->cfg.cache);
+ if (rc) {
+ vbdev_ocf_mngt_exit(vbdev, unregister_path_dirty, rc);
+ return;
+ }
+ ocf_mngt_cache_get(vbdev->ocf_cache);
+
+ ocf_cache_set_priv(vbdev->ocf_cache, vbdev->cache_ctx);
+
+ rc = create_management_queue(vbdev);
+ if (rc) {
+ SPDK_ERRLOG("Unable to create mngt_queue: %d\n", rc);
+ vbdev_ocf_mngt_exit(vbdev, unregister_path_dirty, rc);
+ return;
+ }
+
+ if (vbdev->cfg.loadq) {
+ ocf_mngt_cache_load(vbdev->ocf_cache, &vbdev->cfg.device, start_cache_cmpl, vbdev);
+ } else {
+ ocf_mngt_cache_attach(vbdev->ocf_cache, &vbdev->cfg.device, start_cache_cmpl, vbdev);
+ }
+}
+
+/* Procedures called during register operation */
+vbdev_ocf_mngt_fn register_path[] = {
+ start_cache,
+ add_core,
+ finish_register,
+ NULL
+};
+
+/* Start cache instance and register OCF bdev */
+static void
+register_vbdev(struct vbdev_ocf *vbdev, vbdev_ocf_mngt_callback cb, void *cb_arg)
+{
+ int rc;
+
+ if (!(vbdev->core.attached && vbdev->cache.attached) || vbdev->state.started) {
+ cb(-EPERM, vbdev, cb_arg);
+ return;
+ }
+
+ vbdev->state.starting = true;
+ rc = vbdev_ocf_mngt_start(vbdev, register_path, cb, cb_arg);
+ if (rc) {
+ cb(rc, vbdev, cb_arg);
+ }
+}
+
+/* Init OCF configuration options
+ * for core and cache devices */
+static void
+init_vbdev_config(struct vbdev_ocf *vbdev)
+{
+ struct vbdev_ocf_config *cfg = &vbdev->cfg;
+
+ snprintf(cfg->cache.name, sizeof(cfg->cache.name), "%s", vbdev->name);
+ snprintf(cfg->core.name, sizeof(cfg->core.name), "%s", vbdev->core.name);
+
+ /* TODO [metadata]: make configurable with persistent
+ * metadata support */
+ cfg->cache.metadata_volatile = false;
+
+ /* TODO [cache line size]: make cache line size configurable
+ * Using standard 4KiB for now */
+ cfg->cache.cache_line_size = ocf_cache_line_size_4;
+
+ /* This are suggested values that
+ * should be sufficient for most use cases */
+ cfg->cache.backfill.max_queue_size = 65536;
+ cfg->cache.backfill.queue_unblock_size = 60000;
+
+ /* TODO [cache line size] */
+ cfg->device.cache_line_size = ocf_cache_line_size_4;
+ cfg->device.force = true;
+ cfg->device.perform_test = false;
+ cfg->device.discard_on_start = false;
+
+ vbdev->cfg.cache.locked = true;
+
+ cfg->core.volume_type = SPDK_OBJECT;
+ cfg->device.volume_type = SPDK_OBJECT;
+
+ if (vbdev->cfg.loadq) {
+ /* When doing cache_load(), we need to set try_add to true,
+ * otherwise OCF will interpret this core as new
+ * instead of the inactive one */
+ vbdev->cfg.core.try_add = true;
+ }
+
+ /* Serialize bdev names in OCF UUID to interpret on future loads
+ * Core UUID is a triple of (core name, vbdev name, cache name)
+ * Cache UUID is cache bdev name */
+ cfg->device.uuid.size = strlen(vbdev->cache.name) + 1;
+ cfg->device.uuid.data = vbdev->cache.name;
+
+ snprintf(vbdev->uuid, VBDEV_OCF_MD_MAX_LEN, "%s %s %s",
+ vbdev->core.name, vbdev->name, vbdev->cache.name);
+ cfg->core.uuid.size = strlen(vbdev->uuid) + 1;
+ cfg->core.uuid.data = vbdev->uuid;
+ vbdev->uuid[strlen(vbdev->core.name)] = 0;
+ vbdev->uuid[strlen(vbdev->core.name) + 1 + strlen(vbdev->name)] = 0;
+}
+
+/* Allocate vbdev structure object and add it to the global list */
+static int
+init_vbdev(const char *vbdev_name,
+ const char *cache_mode_name,
+ const char *cache_name,
+ const char *core_name,
+ bool loadq)
+{
+ struct vbdev_ocf *vbdev;
+ int rc = 0;
+
+ if (spdk_bdev_get_by_name(vbdev_name) || vbdev_ocf_get_by_name(vbdev_name)) {
+ SPDK_ERRLOG("Device with name '%s' already exists\n", vbdev_name);
+ return -EPERM;
+ }
+
+ vbdev = calloc(1, sizeof(*vbdev));
+ if (!vbdev) {
+ goto error_mem;
+ }
+
+ vbdev->cache.parent = vbdev;
+ vbdev->core.parent = vbdev;
+ vbdev->cache.is_cache = true;
+ vbdev->core.is_cache = false;
+
+ if (cache_mode_name) {
+ vbdev->cfg.cache.cache_mode
+ = ocf_get_cache_mode(cache_mode_name);
+ } else if (!loadq) { /* In load path it is OK to pass NULL as cache mode */
+ SPDK_ERRLOG("No cache mode specified\n");
+ rc = -EINVAL;
+ goto error_free;
+ }
+ if (vbdev->cfg.cache.cache_mode < 0) {
+ SPDK_ERRLOG("Incorrect cache mode '%s'\n", cache_mode_name);
+ rc = -EINVAL;
+ goto error_free;
+ }
+
+ vbdev->name = strdup(vbdev_name);
+ if (!vbdev->name) {
+ goto error_mem;
+ }
+
+ vbdev->cache.name = strdup(cache_name);
+ if (!vbdev->cache.name) {
+ goto error_mem;
+ }
+
+ vbdev->core.name = strdup(core_name);
+ if (!vbdev->core.name) {
+ goto error_mem;
+ }
+
+ vbdev->cfg.loadq = loadq;
+ init_vbdev_config(vbdev);
+ TAILQ_INSERT_TAIL(&g_ocf_vbdev_head, vbdev, tailq);
+ return rc;
+
+error_mem:
+ rc = -ENOMEM;
+error_free:
+ free_vbdev(vbdev);
+ return rc;
+}
+
+/* Read configuration file at the start of SPDK application
+ * This adds vbdevs to global list if some mentioned in config */
+static int
+vbdev_ocf_init(void)
+{
+ const char *vbdev_name, *modename, *cache_name, *core_name;
+ struct spdk_conf_section *sp;
+ int status;
+
+ status = vbdev_ocf_ctx_init();
+ if (status) {
+ SPDK_ERRLOG("OCF ctx initialization failed with=%d\n", status);
+ return status;
+ }
+
+ status = vbdev_ocf_volume_init();
+ if (status) {
+ vbdev_ocf_ctx_cleanup();
+ SPDK_ERRLOG("OCF volume initialization failed with=%d\n", status);
+ return status;
+ }
+
+ sp = spdk_conf_find_section(NULL, "OCF");
+ if (sp == NULL) {
+ return 0;
+ }
+
+ for (int i = 0; ; i++) {
+ if (!spdk_conf_section_get_nval(sp, "OCF", i)) {
+ break;
+ }
+
+ vbdev_name = spdk_conf_section_get_nmval(sp, "OCF", i, 0);
+ if (!vbdev_name) {
+ SPDK_ERRLOG("No vbdev name specified\n");
+ continue;
+ }
+
+ modename = spdk_conf_section_get_nmval(sp, "OCF", i, 1);
+ if (!modename) {
+ SPDK_ERRLOG("No modename specified for OCF vbdev '%s'\n", vbdev_name);
+ continue;
+ }
+
+ cache_name = spdk_conf_section_get_nmval(sp, "OCF", i, 2);
+ if (!cache_name) {
+ SPDK_ERRLOG("No cache device specified for OCF vbdev '%s'\n", vbdev_name);
+ continue;
+ }
+
+ core_name = spdk_conf_section_get_nmval(sp, "OCF", i, 3);
+ if (!core_name) {
+ SPDK_ERRLOG("No core devices specified for OCF vbdev '%s'\n", vbdev_name);
+ continue;
+ }
+
+ status = init_vbdev(vbdev_name, modename, cache_name, core_name, false);
+ if (status) {
+ SPDK_ERRLOG("Config initialization failed with code: %d\n", status);
+ }
+ }
+
+ return status;
+}
+
+/* Called after application shutdown started
+ * Release memory of allocated structures here */
+static void
+vbdev_ocf_module_fini(void)
+{
+ struct vbdev_ocf *vbdev;
+
+ while ((vbdev = TAILQ_FIRST(&g_ocf_vbdev_head))) {
+ TAILQ_REMOVE(&g_ocf_vbdev_head, vbdev, tailq);
+ free_vbdev(vbdev);
+ }
+
+ vbdev_ocf_volume_cleanup();
+ vbdev_ocf_ctx_cleanup();
+}
+
+/* When base device gets unpluged this is called
+ * We will unregister cache vbdev here
+ * When cache device is removed, we delete every OCF bdev that used it */
+static void
+hotremove_cb(void *ctx)
+{
+ struct vbdev_ocf_base *base = ctx;
+ struct vbdev_ocf *vbdev;
+
+ if (!base->is_cache) {
+ if (base->parent->state.doing_finish) {
+ return;
+ }
+
+ SPDK_NOTICELOG("Deinitializing '%s' because its core device '%s' was removed\n",
+ base->parent->name, base->name);
+ vbdev_ocf_delete(base->parent, NULL, NULL);
+ return;
+ }
+
+ TAILQ_FOREACH(vbdev, &g_ocf_vbdev_head, tailq) {
+ if (vbdev->state.doing_finish) {
+ continue;
+ }
+ if (strcmp(base->name, vbdev->cache.name) == 0) {
+ SPDK_NOTICELOG("Deinitializing '%s' because"
+ " its cache device '%s' was removed\n",
+ vbdev->name, base->name);
+ vbdev_ocf_delete(vbdev, NULL, NULL);
+ }
+ }
+}
+
+/* Open base SPDK bdev and claim it */
+static int
+attach_base(struct vbdev_ocf_base *base)
+{
+ int status;
+
+ if (base->attached) {
+ return -EALREADY;
+ }
+
+ /* If base cache bdev was already opened by other vbdev,
+ * we just copy its descriptor here */
+ if (base->is_cache) {
+ struct vbdev_ocf_base *existing = get_other_cache_base(base);
+ if (existing) {
+ base->desc = existing->desc;
+ base->management_channel = existing->management_channel;
+ base->attached = true;
+ return 0;
+ }
+ }
+
+ status = spdk_bdev_open(base->bdev, true, hotremove_cb, base, &base->desc);
+ if (status) {
+ SPDK_ERRLOG("Unable to open device '%s' for writing\n", base->name);
+ return status;
+ }
+
+ status = spdk_bdev_module_claim_bdev(base->bdev, base->desc,
+ &ocf_if);
+ if (status) {
+ SPDK_ERRLOG("Unable to claim device '%s'\n", base->name);
+ spdk_bdev_close(base->desc);
+ return status;
+ }
+
+ base->management_channel = spdk_bdev_get_io_channel(base->desc);
+ if (!base->management_channel) {
+ SPDK_ERRLOG("Unable to get io channel '%s'\n", base->name);
+ spdk_bdev_module_release_bdev(base->bdev);
+ spdk_bdev_close(base->desc);
+ return -ENOMEM;
+ }
+
+ /* Save the thread where the base device is opened */
+ base->thread = spdk_get_thread();
+
+ base->attached = true;
+ return status;
+}
+
+/* Attach base bdevs */
+static int
+attach_base_bdevs(struct vbdev_ocf *vbdev,
+ struct spdk_bdev *cache_bdev,
+ struct spdk_bdev *core_bdev)
+{
+ int rc = 0;
+
+ if (cache_bdev) {
+ vbdev->cache.bdev = cache_bdev;
+ rc |= attach_base(&vbdev->cache);
+ }
+
+ if (core_bdev) {
+ vbdev->core.bdev = core_bdev;
+ rc |= attach_base(&vbdev->core);
+ }
+
+ return rc;
+}
+
+/* Init and then start vbdev if all base devices are present */
+void
+vbdev_ocf_construct(const char *vbdev_name,
+ const char *cache_mode_name,
+ const char *cache_name,
+ const char *core_name,
+ bool loadq,
+ void (*cb)(int, struct vbdev_ocf *, void *),
+ void *cb_arg)
+{
+ int rc;
+ struct spdk_bdev *cache_bdev = spdk_bdev_get_by_name(cache_name);
+ struct spdk_bdev *core_bdev = spdk_bdev_get_by_name(core_name);
+ struct vbdev_ocf *vbdev;
+
+ rc = init_vbdev(vbdev_name, cache_mode_name, cache_name, core_name, loadq);
+ if (rc) {
+ cb(rc, NULL, cb_arg);
+ return;
+ }
+
+ vbdev = vbdev_ocf_get_by_name(vbdev_name);
+ if (vbdev == NULL) {
+ cb(-ENODEV, NULL, cb_arg);
+ return;
+ }
+
+ if (cache_bdev == NULL) {
+ SPDK_NOTICELOG("OCF bdev '%s' is waiting for cache device '%s' to connect\n",
+ vbdev->name, cache_name);
+ }
+ if (core_bdev == NULL) {
+ SPDK_NOTICELOG("OCF bdev '%s' is waiting for core device '%s' to connect\n",
+ vbdev->name, core_name);
+ }
+
+ rc = attach_base_bdevs(vbdev, cache_bdev, core_bdev);
+ if (rc) {
+ cb(rc, vbdev, cb_arg);
+ return;
+ }
+
+ if (core_bdev && cache_bdev) {
+ register_vbdev(vbdev, cb, cb_arg);
+ } else {
+ cb(0, vbdev, cb_arg);
+ }
+}
+
+/* This called if new device is created in SPDK application
+ * If that device named as one of base bdevs of OCF vbdev,
+ * claim and open them */
+static void
+vbdev_ocf_examine(struct spdk_bdev *bdev)
+{
+ const char *bdev_name = spdk_bdev_get_name(bdev);
+ struct vbdev_ocf *vbdev;
+
+ TAILQ_FOREACH(vbdev, &g_ocf_vbdev_head, tailq) {
+ if (vbdev->state.doing_finish) {
+ continue;
+ }
+
+ if (!strcmp(bdev_name, vbdev->cache.name)) {
+ attach_base_bdevs(vbdev, bdev, NULL);
+ continue;
+ }
+ if (!strcmp(bdev_name, vbdev->core.name)) {
+ attach_base_bdevs(vbdev, NULL, bdev);
+ break;
+ }
+ }
+ spdk_bdev_module_examine_done(&ocf_if);
+}
+
+struct metadata_probe_ctx {
+ struct vbdev_ocf_base base;
+ ocf_volume_t volume;
+
+ struct ocf_volume_uuid *core_uuids;
+ unsigned int uuid_count;
+
+ int result;
+ int refcnt;
+};
+
+static void
+_examine_ctx_put(void *ctx)
+{
+ struct spdk_bdev_desc *desc = ctx;
+
+ spdk_bdev_close(desc);
+}
+
+static void
+examine_ctx_put(struct metadata_probe_ctx *ctx)
+{
+ unsigned int i;
+
+ ctx->refcnt--;
+ if (ctx->refcnt > 0) {
+ return;
+ }
+
+ if (ctx->result) {
+ SPDK_ERRLOG("OCF metadata probe for bdev '%s' failed with %d\n",
+ spdk_bdev_get_name(ctx->base.bdev), ctx->result);
+ }
+
+ if (ctx->base.desc) {
+ /* Close the underlying bdev on its same opened thread. */
+ if (ctx->base.thread && ctx->base.thread != spdk_get_thread()) {
+ spdk_thread_send_msg(ctx->base.thread, _examine_ctx_put, ctx->base.desc);
+ } else {
+ spdk_bdev_close(ctx->base.desc);
+ }
+ }
+
+ if (ctx->volume) {
+ ocf_volume_destroy(ctx->volume);
+ }
+
+ if (ctx->core_uuids) {
+ for (i = 0; i < ctx->uuid_count; i++) {
+ free(ctx->core_uuids[i].data);
+ }
+ }
+ free(ctx->core_uuids);
+
+ examine_done(ctx->result, NULL, ctx->base.bdev);
+ free(ctx);
+}
+
+static void
+metadata_probe_construct_cb(int rc, struct vbdev_ocf *vbdev, void *vctx)
+{
+ struct metadata_probe_ctx *ctx = vctx;
+
+ examine_ctx_put(ctx);
+}
+
+/* This is second callback for ocf_metadata_probe_cores()
+ * Here we create vbdev configurations based on UUIDs */
+static void
+metadata_probe_cores_construct(void *priv, int error, unsigned int num_cores)
+{
+ struct metadata_probe_ctx *ctx = priv;
+ const char *vbdev_name;
+ const char *core_name;
+ const char *cache_name;
+ unsigned int i;
+
+ if (error) {
+ ctx->result = error;
+ examine_ctx_put(ctx);
+ return;
+ }
+
+ for (i = 0; i < num_cores; i++) {
+ core_name = ocf_uuid_to_str(&ctx->core_uuids[i]);
+ vbdev_name = core_name + strlen(core_name) + 1;
+ cache_name = vbdev_name + strlen(vbdev_name) + 1;
+
+ if (strcmp(ctx->base.bdev->name, cache_name)) {
+ SPDK_NOTICELOG("OCF metadata found on %s belongs to bdev named '%s'\n",
+ ctx->base.bdev->name, cache_name);
+ }
+
+ ctx->refcnt++;
+ vbdev_ocf_construct(vbdev_name, NULL, cache_name, core_name, true,
+ metadata_probe_construct_cb, ctx);
+ }
+
+ examine_ctx_put(ctx);
+}
+
+/* This callback is called after OCF reads cores UUIDs from cache metadata
+ * Here we allocate memory for those UUIDs and call ocf_metadata_probe_cores() again */
+static void
+metadata_probe_cores_get_num(void *priv, int error, unsigned int num_cores)
+{
+ struct metadata_probe_ctx *ctx = priv;
+ unsigned int i;
+
+ if (error) {
+ ctx->result = error;
+ examine_ctx_put(ctx);
+ return;
+ }
+
+ ctx->uuid_count = num_cores;
+ ctx->core_uuids = calloc(num_cores, sizeof(struct ocf_volume_uuid));
+ if (!ctx->core_uuids) {
+ ctx->result = -ENOMEM;
+ examine_ctx_put(ctx);
+ return;
+ }
+
+ for (i = 0; i < ctx->uuid_count; i++) {
+ ctx->core_uuids[i].size = OCF_VOLUME_UUID_MAX_SIZE;
+ ctx->core_uuids[i].data = malloc(OCF_VOLUME_UUID_MAX_SIZE);
+ if (!ctx->core_uuids[i].data) {
+ ctx->result = -ENOMEM;
+ examine_ctx_put(ctx);
+ return;
+ }
+ }
+
+ ocf_metadata_probe_cores(vbdev_ocf_ctx, ctx->volume, ctx->core_uuids, ctx->uuid_count,
+ metadata_probe_cores_construct, ctx);
+}
+
+static void
+metadata_probe_cb(void *priv, int rc,
+ struct ocf_metadata_probe_status *status)
+{
+ struct metadata_probe_ctx *ctx = priv;
+
+ if (rc) {
+ /* -ENODATA means device does not have cache metadata on it */
+ if (rc != -OCF_ERR_NO_METADATA) {
+ ctx->result = rc;
+ }
+ examine_ctx_put(ctx);
+ return;
+ }
+
+ ocf_metadata_probe_cores(vbdev_ocf_ctx, ctx->volume, NULL, 0,
+ metadata_probe_cores_get_num, ctx);
+}
+
+/* This is called after vbdev_ocf_examine
+ * It allows to delay application initialization
+ * until all OCF bdevs get registered
+ * If vbdev has all of its base devices it starts asynchronously here
+ * We first check if bdev appears in configuration,
+ * if not we do metadata_probe() to create its configuration from bdev metadata */
+static void
+vbdev_ocf_examine_disk(struct spdk_bdev *bdev)
+{
+ const char *bdev_name = spdk_bdev_get_name(bdev);
+ struct vbdev_ocf *vbdev;
+ struct metadata_probe_ctx *ctx;
+ bool created_from_config = false;
+ int rc;
+
+ examine_start(bdev);
+
+ TAILQ_FOREACH(vbdev, &g_ocf_vbdev_head, tailq) {
+ if (vbdev->state.doing_finish || vbdev->state.started) {
+ continue;
+ }
+
+ if (!strcmp(bdev_name, vbdev->cache.name)) {
+ examine_start(bdev);
+ register_vbdev(vbdev, examine_done, bdev);
+ created_from_config = true;
+ continue;
+ }
+ if (!strcmp(bdev_name, vbdev->core.name)) {
+ examine_start(bdev);
+ register_vbdev(vbdev, examine_done, bdev);
+ examine_done(0, NULL, bdev);
+ return;
+ }
+ }
+
+ /* If devices is discovered during config we do not check for metadata */
+ if (created_from_config) {
+ examine_done(0, NULL, bdev);
+ return;
+ }
+
+ /* Metadata probe path
+ * We create temporary OCF volume and a temporary base structure
+ * to use them for ocf_metadata_probe() and for bottom adapter IOs
+ * Then we get UUIDs of core devices an create configurations based on them */
+ ctx = calloc(1, sizeof(*ctx));
+ if (!ctx) {
+ examine_done(-ENOMEM, NULL, bdev);
+ return;
+ }
+
+ ctx->base.bdev = bdev;
+ ctx->refcnt = 1;
+
+ rc = spdk_bdev_open(ctx->base.bdev, true, NULL, NULL, &ctx->base.desc);
+ if (rc) {
+ ctx->result = rc;
+ examine_ctx_put(ctx);
+ return;
+ }
+
+ rc = ocf_ctx_volume_create(vbdev_ocf_ctx, &ctx->volume, NULL, SPDK_OBJECT);
+ if (rc) {
+ ctx->result = rc;
+ examine_ctx_put(ctx);
+ return;
+ }
+
+ rc = ocf_volume_open(ctx->volume, &ctx->base);
+ if (rc) {
+ ctx->result = rc;
+ examine_ctx_put(ctx);
+ return;
+ }
+
+ /* Save the thread where the base device is opened */
+ ctx->base.thread = spdk_get_thread();
+
+ ocf_metadata_probe(vbdev_ocf_ctx, ctx->volume, metadata_probe_cb, ctx);
+}
+
+static int
+vbdev_ocf_get_ctx_size(void)
+{
+ return sizeof(struct bdev_ocf_data);
+}
+
+static void
+fini_start(void)
+{
+ g_fini_started = true;
+}
+
+/* Module-global function table
+ * Does not relate to vbdev instances */
+static struct spdk_bdev_module ocf_if = {
+ .name = "ocf",
+ .module_init = vbdev_ocf_init,
+ .fini_start = fini_start,
+ .module_fini = vbdev_ocf_module_fini,
+ .config_text = NULL,
+ .get_ctx_size = vbdev_ocf_get_ctx_size,
+ .examine_config = vbdev_ocf_examine,
+ .examine_disk = vbdev_ocf_examine_disk,
+};
+SPDK_BDEV_MODULE_REGISTER(ocf, &ocf_if);
+
+SPDK_LOG_REGISTER_COMPONENT("vbdev_ocf", SPDK_TRACE_VBDEV_OCF)