summaryrefslogtreecommitdiffstats
path: root/src/spdk/ocf/example/simple
diff options
context:
space:
mode:
Diffstat (limited to 'src/spdk/ocf/example/simple')
-rw-r--r--src/spdk/ocf/example/simple/Makefile37
-rw-r--r--src/spdk/ocf/example/simple/src/ctx.c303
-rw-r--r--src/spdk/ocf/example/simple/src/ctx.h19
-rw-r--r--src/spdk/ocf/example/simple/src/data.h14
-rw-r--r--src/spdk/ocf/example/simple/src/main.c380
-rw-r--r--src/spdk/ocf/example/simple/src/volume.c168
-rw-r--r--src/spdk/ocf/example/simple/src/volume.h27
7 files changed, 948 insertions, 0 deletions
diff --git a/src/spdk/ocf/example/simple/Makefile b/src/spdk/ocf/example/simple/Makefile
new file mode 100644
index 000000000..f19cae0c5
--- /dev/null
+++ b/src/spdk/ocf/example/simple/Makefile
@@ -0,0 +1,37 @@
+#
+# Copyright(c) 2019 Intel Corporation
+# SPDX-License-Identifier: BSD-3-Clause-Clear
+#
+
+OCFDIR=../../
+SRCDIR=src/
+INCDIR=include/
+
+SRC=$(shell find ${SRCDIR} -name \*.c)
+OBJS = $(patsubst %.c, %.o, $(SRC))
+PROGRAM=simple
+
+CC = gcc
+CFLAGS = -g -Wall -I${INCDIR} -I${SRCDIR}/ocf/env/
+LDFLAGS = -lm -lz -pthread
+
+all: sync
+ $(MAKE) $(PROGRAM)
+
+$(PROGRAM): $(OBJS)
+ $(CC) -o $@ $^ $(LDFLAGS)
+
+sync:
+ @$(MAKE) -C ${OCFDIR} inc O=$(PWD)
+ @$(MAKE) -C ${OCFDIR} src O=$(PWD)
+ @$(MAKE) -C ${OCFDIR} env O=$(PWD) OCF_ENV=posix
+
+clean:
+ @rm -rf $(PROGRAM) $(OBJS)
+
+distclean:
+ @rm -rf $(PROGRAM) $(OBJS)
+ @rm -rf src/ocf
+ @rm -rf include/ocf
+
+.PHONY: all clean
diff --git a/src/spdk/ocf/example/simple/src/ctx.c b/src/spdk/ocf/example/simple/src/ctx.c
new file mode 100644
index 000000000..420ea63b0
--- /dev/null
+++ b/src/spdk/ocf/example/simple/src/ctx.c
@@ -0,0 +1,303 @@
+/*
+ * Copyright(c) 2019 Intel Corporation
+ * SPDX-License-Identifier: BSD-3-Clause-Clear
+ */
+
+#include <execinfo.h>
+#include <ocf/ocf.h>
+#include "ocf_env.h"
+#include "data.h"
+#include "volume.h"
+#include "ctx.h"
+
+#define PAGE_SIZE 4096
+
+/*
+ * Allocate structure representing data for io operations.
+ */
+ctx_data_t *ctx_data_alloc(uint32_t pages)
+{
+ struct volume_data *data;
+
+ data = malloc(sizeof(*data));
+ data->ptr = malloc(pages * PAGE_SIZE);
+ data->offset = 0;
+
+ return data;
+}
+
+/*
+ * Free data structure.
+ */
+void ctx_data_free(ctx_data_t *ctx_data)
+{
+ struct volume_data *data = ctx_data;
+
+ if (!data)
+ return;
+
+ free(data->ptr);
+ free(data);
+}
+
+/*
+ * This function is supposed to set protection of data pages against swapping.
+ * Can be non-implemented if not needed.
+ */
+static int ctx_data_mlock(ctx_data_t *ctx_data)
+{
+ return 0;
+}
+
+/*
+ * Stop protecting data pages against swapping.
+ */
+static void ctx_data_munlock(ctx_data_t *ctx_data)
+{
+}
+
+/*
+ * Read data into flat memory buffer.
+ */
+static uint32_t ctx_data_read(void *dst, ctx_data_t *src, uint32_t size)
+{
+ struct volume_data *data = src;
+
+ memcpy(dst, data->ptr + data->offset, size);
+
+ return size;
+}
+
+/*
+ * Write data from flat memory buffer.
+ */
+static uint32_t ctx_data_write(ctx_data_t *dst, const void *src, uint32_t size)
+{
+ struct volume_data *data = dst;
+
+ memcpy(data->ptr + data->offset, src, size);
+
+ return size;
+}
+
+/*
+ * Fill data with zeros.
+ */
+static uint32_t ctx_data_zero(ctx_data_t *dst, uint32_t size)
+{
+ struct volume_data *data = dst;
+
+ memset(data->ptr + data->offset, 0, size);
+
+ return size;
+}
+
+/*
+ * Perform seek operation on data.
+ */
+static uint32_t ctx_data_seek(ctx_data_t *dst, ctx_data_seek_t seek,
+ uint32_t offset)
+{
+ struct volume_data *data = dst;
+
+ switch (seek) {
+ case ctx_data_seek_begin:
+ data->offset = offset;
+ break;
+ case ctx_data_seek_current:
+ data->offset += offset;
+ break;
+ }
+
+ return offset;
+}
+
+/*
+ * Copy data from one structure to another.
+ */
+static uint64_t ctx_data_copy(ctx_data_t *dst, ctx_data_t *src,
+ uint64_t to, uint64_t from, uint64_t bytes)
+{
+ struct volume_data *data_dst = dst;
+ struct volume_data *data_src = src;
+
+ memcpy(data_dst->ptr + to, data_src->ptr + from, bytes);
+
+ return bytes;
+}
+
+/*
+ * Perform secure erase of data (e.g. fill pages with zeros).
+ * Can be left non-implemented if not needed.
+ */
+static void ctx_data_secure_erase(ctx_data_t *ctx_data)
+{
+}
+
+/*
+ * Initialize cleaner thread. Cleaner thread is left non-implemented,
+ * to keep this example as simple as possible.
+ */
+static int ctx_cleaner_init(ocf_cleaner_t c)
+{
+ return 0;
+}
+
+/*
+ * Kick cleaner thread. Cleaner thread is left non-implemented,
+ * to keep this example as simple as possible.
+ */
+static void ctx_cleaner_kick(ocf_cleaner_t c)
+{
+}
+
+/*
+ * Stop cleaner thread. Cleaner thread is left non-implemented, to keep
+ * this example as simple as possible.
+ */
+static void ctx_cleaner_stop(ocf_cleaner_t c)
+{
+}
+
+/*
+ * Initialize metadata updater thread. Metadata updater thread is left
+ * non-implemented to keep this example as simple as possible.
+ */
+static int ctx_metadata_updater_init(ocf_metadata_updater_t mu)
+{
+ return 0;
+}
+
+/*
+ * Kick metadata updater thread. Metadata updater thread is left
+ * non-implemented to keep this example as simple as possible.
+ */
+static void ctx_metadata_updater_kick(ocf_metadata_updater_t mu)
+{
+ ocf_metadata_updater_run(mu);
+}
+
+/*
+ * Stop metadata updater thread. Metadata updater thread is left
+ * non-implemented to keep this example as simple as possible.
+ */
+static void ctx_metadata_updater_stop(ocf_metadata_updater_t mu)
+{
+}
+
+/*
+ * Function prividing interface for printing to log used by OCF internals.
+ * It can handle differently messages at varous log levels.
+ */
+static int ctx_logger_print(ocf_logger_t logger, ocf_logger_lvl_t lvl,
+ const char *fmt, va_list args)
+{
+ FILE *lfile = stdout;
+
+ if (lvl > log_info)
+ return 0;
+
+ if (lvl <= log_warn)
+ lfile = stderr;
+
+ return vfprintf(lfile, fmt, args);
+}
+
+#define CTX_LOG_TRACE_DEPTH 16
+
+/*
+ * Function prividing interface for printing current stack. Used for debugging,
+ * and for providing additional information in log in case of errors.
+ */
+static int ctx_logger_dump_stack(ocf_logger_t logger)
+{
+ void *trace[CTX_LOG_TRACE_DEPTH];
+ char **messages = NULL;
+ int i, size;
+
+ size = backtrace(trace, CTX_LOG_TRACE_DEPTH);
+ messages = backtrace_symbols(trace, size);
+ printf("[stack trace]>>>\n");
+ for (i = 0; i < size; ++i)
+ printf("%s\n", messages[i]);
+ printf("<<<[stack trace]\n");
+ free(messages);
+
+ return 0;
+}
+
+/*
+ * This structure describes context config, containing simple context info
+ * and pointers to ops callbacks. Ops are splitted into few categories:
+ * - data ops, providing context specific data handing interface,
+ * - cleaner ops, providing interface to start and stop clener thread,
+ * - metadata updater ops, providing interface for starting, stoping
+ * and kicking metadata updater thread.
+ * - logger ops, providing interface for text message logging
+ */
+static const struct ocf_ctx_config ctx_cfg = {
+ .name = "OCF Example",
+ .ops = {
+ .data = {
+ .alloc = ctx_data_alloc,
+ .free = ctx_data_free,
+ .mlock = ctx_data_mlock,
+ .munlock = ctx_data_munlock,
+ .read = ctx_data_read,
+ .write = ctx_data_write,
+ .zero = ctx_data_zero,
+ .seek = ctx_data_seek,
+ .copy = ctx_data_copy,
+ .secure_erase = ctx_data_secure_erase,
+ },
+
+ .cleaner = {
+ .init = ctx_cleaner_init,
+ .kick = ctx_cleaner_kick,
+ .stop = ctx_cleaner_stop,
+ },
+
+ .metadata_updater = {
+ .init = ctx_metadata_updater_init,
+ .kick = ctx_metadata_updater_kick,
+ .stop = ctx_metadata_updater_stop,
+ },
+
+ .logger = {
+ .print = ctx_logger_print,
+ .dump_stack = ctx_logger_dump_stack,
+ },
+ },
+};
+
+
+/*
+ * Function initializing context. Prepares context, sets logger and
+ * registers volume type.
+ */
+int ctx_init(ocf_ctx_t *ctx)
+{
+ int ret;
+
+ ret = ocf_ctx_create(ctx, &ctx_cfg);
+ if (ret)
+ return ret;
+
+ ret = volume_init(*ctx);
+ if (ret) {
+ ocf_ctx_put(*ctx);
+ return ret;
+ }
+
+ return 0;
+}
+
+/*
+ * Function cleaning up context. Unregisters volume type and
+ * deinitializes context.
+ */
+void ctx_cleanup(ocf_ctx_t ctx)
+{
+ volume_cleanup(ctx);
+ ocf_ctx_put(ctx);
+}
diff --git a/src/spdk/ocf/example/simple/src/ctx.h b/src/spdk/ocf/example/simple/src/ctx.h
new file mode 100644
index 000000000..6f0360679
--- /dev/null
+++ b/src/spdk/ocf/example/simple/src/ctx.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright(c) 2019 Intel Corporation
+ * SPDX-License-Identifier: BSD-3-Clause-Clear
+ */
+
+#ifndef __CTX_H__
+#define __CTX_H__
+
+#include <ocf/ocf.h>
+
+#define VOL_TYPE 1
+
+ctx_data_t *ctx_data_alloc(uint32_t pages);
+void ctx_data_free(ctx_data_t *ctx_data);
+
+int ctx_init(ocf_ctx_t *ocf_ctx);
+void ctx_cleanup(ocf_ctx_t ctx);
+
+#endif
diff --git a/src/spdk/ocf/example/simple/src/data.h b/src/spdk/ocf/example/simple/src/data.h
new file mode 100644
index 000000000..bbef4ec66
--- /dev/null
+++ b/src/spdk/ocf/example/simple/src/data.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright(c) 2019 Intel Corporation
+ * SPDX-License-Identifier: BSD-3-Clause-Clear
+ */
+
+#ifndef __DATA_H__
+#define __DATA_H__
+
+struct volume_data {
+ void *ptr;
+ int offset;
+};
+
+#endif
diff --git a/src/spdk/ocf/example/simple/src/main.c b/src/spdk/ocf/example/simple/src/main.c
new file mode 100644
index 000000000..ef059fdd7
--- /dev/null
+++ b/src/spdk/ocf/example/simple/src/main.c
@@ -0,0 +1,380 @@
+/*
+ * Copyright(c) 2019 Intel Corporation
+ * SPDX-License-Identifier: BSD-3-Clause-Clear
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ocf/ocf.h>
+#include "data.h"
+#include "ctx.h"
+
+/*
+ * Cache private data. Used to share information between async contexts.
+ */
+struct cache_priv {
+ ocf_queue_t mngt_queue;
+ ocf_queue_t io_queue;
+};
+
+/*
+ * Helper function for error handling.
+ */
+void error(char *msg)
+{
+ printf("ERROR: %s", msg);
+ exit(1);
+}
+
+/*
+ * Trigger queue asynchronously. Made synchronous for simplicity.
+ * Notice that it makes all asynchronous calls synchronous, because
+ * asynchronism in OCF is achieved mostly by using queues.
+ */
+static inline void queue_kick_async(ocf_queue_t q)
+{
+ ocf_queue_run(q);
+}
+
+/*
+ * Trigger queue synchronously. May be implemented as asynchronous as well,
+ * but in some environments kicking queue synchronously may reduce latency,
+ * so to take advantage of such situations OCF call synchronous variant of
+ * queue kick callback where possible.
+ */
+static void queue_kick_sync(ocf_queue_t q)
+{
+ ocf_queue_run(q);
+}
+
+/*
+ * Stop queue thread. To keep this example simple we handle queues
+ * synchronously, thus it's left non-implemented.
+ */
+static void queue_stop(ocf_queue_t q)
+{
+}
+
+/*
+ * Queue ops providing interface for running queue thread in both synchronous
+ * and asynchronous way. The stop() operation in called just before queue is
+ * being destroyed.
+ */
+const struct ocf_queue_ops queue_ops = {
+ .kick_sync = queue_kick_sync,
+ .kick = queue_kick_async,
+ .stop = queue_stop,
+};
+
+/*
+ * Simple completion context. As lots of OCF API functions work asynchronously
+ * and call completion callback when job is done, we need some structure to
+ * share program state with completion callback. In this case we have single
+ * variable pointer to propagate error code.
+ */
+struct simple_context {
+ int *error;
+};
+
+/*
+ * Basic asynchronous completion callback. Just propagate error code.
+ */
+static void simple_complete(ocf_cache_t cache, void *priv, int error)
+{
+ struct simple_context *context= priv;
+
+ *context->error = error;
+}
+
+/*
+ * Function starting cache and attaching cache device.
+ */
+int initialize_cache(ocf_ctx_t ctx, ocf_cache_t *cache)
+{
+ struct ocf_mngt_cache_config cache_cfg = { .name = "cache1" };
+ struct ocf_mngt_cache_device_config device_cfg = { };
+ struct cache_priv *cache_priv;
+ struct simple_context context;
+ int ret;
+
+ /*
+ * Asynchronous callbacks will assign error code to ret. That
+ * way we have always the same variable holding last error code.
+ */
+ context.error = &ret;
+
+ /* Cache configuration */
+ ocf_mngt_cache_config_set_default(&cache_cfg);
+ cache_cfg.metadata_volatile = true;
+
+ /* Cache deivce (volume) configuration */
+ ocf_mngt_cache_device_config_set_default(&device_cfg);
+ device_cfg.volume_type = VOL_TYPE;
+ ret = ocf_uuid_set_str(&device_cfg.uuid, "cache");
+ if (ret)
+ return ret;
+
+ /*
+ * Allocate cache private structure. We can not initialize it
+ * on stack, as it may be used in various async contexts
+ * throughout the entire live span of cache object.
+ */
+ cache_priv = malloc(sizeof(*cache_priv));
+ if (!cache_priv)
+ return -ENOMEM;
+
+ /* Start cache */
+ ret = ocf_mngt_cache_start(ctx, cache, &cache_cfg);
+ if (ret)
+ goto err_priv;
+
+ /* Assing cache priv structure to cache. */
+ ocf_cache_set_priv(*cache, cache_priv);
+
+ /*
+ * Create management queue. It will be used for performing various
+ * asynchronous management operations, such as attaching cache volume
+ * or adding core object.
+ */
+ ret = ocf_queue_create(*cache, &cache_priv->mngt_queue, &queue_ops);
+ if (ret) {
+ ocf_mngt_cache_stop(*cache, simple_complete, &context);
+ goto err_priv;
+ }
+
+ /*
+ * Assign management queue to cache. This has to be done before any
+ * other management operation. Management queue is treated specially,
+ * and it may not be used for submitting IO requests. It also will not
+ * be put on the cache stop - we have to put it manually at the end.
+ */
+ ocf_mngt_cache_set_mngt_queue(*cache, cache_priv->mngt_queue);
+
+ /* Create queue which will be used for IO submission. */
+ ret = ocf_queue_create(*cache, &cache_priv->io_queue, &queue_ops);
+ if (ret)
+ goto err_cache;
+
+ /* Attach volume to cache */
+ ocf_mngt_cache_attach(*cache, &device_cfg, simple_complete, &context);
+ if (ret)
+ goto err_cache;
+
+ return 0;
+
+err_cache:
+ ocf_mngt_cache_stop(*cache, simple_complete, &context);
+ ocf_queue_put(cache_priv->mngt_queue);
+err_priv:
+ free(cache_priv);
+ return ret;
+}
+
+/*
+ * Add core completion callback context. We need this to propagate error code
+ * and handle to freshly initialized core object.
+ */
+struct add_core_context {
+ ocf_core_t *core;
+ int *error;
+};
+
+/* Add core complete callback. Just rewrite args to context structure. */
+static void add_core_complete(ocf_cache_t cache, ocf_core_t core,
+ void *priv, int error)
+{
+ struct add_core_context *context = priv;
+
+ *context->core = core;
+ *context->error = error;
+}
+
+/*
+ * Function adding cache to core.
+ */
+int initialize_core(ocf_cache_t cache, ocf_core_t *core)
+{
+ struct ocf_mngt_core_config core_cfg = { };
+ struct add_core_context context;
+ int ret;
+
+ /*
+ * Asynchronous callback will assign core handle to core,
+ * and to error code to ret.
+ */
+ context.core = core;
+ context.error = &ret;
+
+ /* Core configuration */
+ ocf_mngt_core_config_set_default(&core_cfg);
+ strcpy(core_cfg.name, "core1");
+ core_cfg.volume_type = VOL_TYPE;
+ ret = ocf_uuid_set_str(&core_cfg.uuid, "core");
+ if (ret)
+ return ret;
+
+ /* Add core to cache */
+ ocf_mngt_cache_add_core(cache, &core_cfg, add_core_complete, &context);
+
+ return ret;
+}
+
+/*
+ * Callback function called when write completes.
+ */
+void complete_write(struct ocf_io *io, int error)
+{
+ struct volume_data *data = ocf_io_get_data(io);
+
+ printf("WRITE COMPLETE: (error: %d)\n", error);
+
+ /* Free data buffer and io */
+ ctx_data_free(data);
+ ocf_io_put(io);
+}
+
+/*
+ * Callback function called when read completes.
+ */
+void complete_read(struct ocf_io *io, int error)
+{
+ struct volume_data *data = ocf_io_get_data(io);
+
+ printf("WRITE COMPLETE (error: %d)\n", error);
+ printf("DATA: \"%s\"\n", (char *)data->ptr);
+
+ /* Free data buffer and io */
+ ctx_data_free(data);
+ ocf_io_put(io);
+}
+
+/*
+ * Wrapper function for io submition.
+ */
+int submit_io(ocf_core_t core, struct volume_data *data,
+ uint64_t addr, uint64_t len, int dir, ocf_end_io_t cmpl)
+{
+ ocf_cache_t cache = ocf_core_get_cache(core);
+ struct cache_priv *cache_priv = ocf_cache_get_priv(cache);
+ struct ocf_io *io;
+
+ /* Allocate new io */
+ io = ocf_core_new_io(core, cache_priv->io_queue, addr, len, dir, 0, 0);
+ if (!io)
+ return -ENOMEM;
+
+ /* Assign data to io */
+ ocf_io_set_data(io, data, 0);
+ /* Setup completion function */
+ ocf_io_set_cmpl(io, NULL, NULL, cmpl);
+ /* Submit io */
+ ocf_core_submit_io(io);
+
+ return 0;
+}
+
+/*
+ * This function simulates actual business logic.
+ *
+ * It performs following steps:
+ * 1. Allocate data buffer for write and write it with example data.
+ * 2. Allocate new io, configure it for write, setup completion callback
+ * and perform write to the core.
+ * 3. Wait for write io completion (write is handled synchronosly, so no
+ * actual wait is needed, but in real life we would need to use some
+ * synchronization to be sure, that completion function has been already
+ * called). Alternatively we could issue read io from write completion
+ * callback.
+ * 4. Allocate data buffer for read.
+ * 5. Allocate new io, configure it for read, setup completion callback
+ * and perform read from the core, from the same address where data
+ * was previously written.
+ * 6. Print example data in read completion callback.
+ *
+ * Data buffers and ios are freed in completion callbacks, so there is no
+ * need to handle freeing in this function.
+ */
+void perform_workload(ocf_core_t core)
+{
+ struct volume_data *data1, *data2;
+
+ /* Allocate data buffer and fill it with example data */
+ data1 = ctx_data_alloc(1);
+ if (!data1)
+ error("Unable to allocate data1\n");
+ strcpy(data1->ptr, "This is some test data");
+ /* Prepare and submit write IO to the core */
+ submit_io(core, data1, 0, 512, OCF_WRITE, complete_write);
+ /* After write completes, complete_write() callback will be called. */
+
+ /*
+ * Here we would need to wait until write completes to be sure, that
+ * performing read we retrive written data.
+ */
+
+ /* Allocate data buffer for read */
+ data2 = ctx_data_alloc(1);
+ if (!data2)
+ error("Unable to allocate data2\n");
+ /* Prepare and submit read IO to the core */
+ submit_io(core, data2, 0, 512, OCF_READ, complete_read);
+ /* After read completes, complete_read() callback will be called,
+ * where we print our example data to stdout.
+ */
+}
+
+static void remove_core_complete(void *priv, int error)
+{
+ struct simple_context *context = priv;
+
+ *context->error = error;
+}
+
+int main(int argc, char *argv[])
+{
+ struct cache_priv *cache_priv;
+ struct simple_context context;
+ ocf_ctx_t ctx;
+ ocf_cache_t cache1;
+ ocf_core_t core1;
+ int ret;
+
+ context.error = &ret;
+
+ /* Initialize OCF context */
+ if (ctx_init(&ctx))
+ error("Unable to initialize context\n");
+
+ /* Start cache */
+ if (initialize_cache(ctx, &cache1))
+ error("Unable to start cache\n");
+
+ /* Add core */
+ if (initialize_core(cache1, &core1))
+ error("Unable to add core\n");
+
+ /* Do some actual io operations */
+ perform_workload(core1);
+
+ /* Remove core from cache */
+ ocf_mngt_cache_remove_core(core1, remove_core_complete, &context);
+ if (ret)
+ error("Unable to remove core\n");
+
+ /* Stop cache */
+ ocf_mngt_cache_stop(cache1, simple_complete, &context);
+ if (ret)
+ error("Unable to stop cache\n");
+
+ cache_priv = ocf_cache_get_priv(cache1);
+
+ /* Put the management queue */
+ ocf_queue_put(cache_priv->mngt_queue);
+
+ free(cache_priv);
+
+ /* Deinitialize context */
+ ctx_cleanup(ctx);
+
+ return 0;
+}
diff --git a/src/spdk/ocf/example/simple/src/volume.c b/src/spdk/ocf/example/simple/src/volume.c
new file mode 100644
index 000000000..1aae692bb
--- /dev/null
+++ b/src/spdk/ocf/example/simple/src/volume.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright(c) 2019 Intel Corporation
+ * SPDX-License-Identifier: BSD-3-Clause-Clear
+ */
+
+#include <ocf/ocf.h>
+#include "volume.h"
+#include "data.h"
+#include "ctx.h"
+
+#define VOL_SIZE 200*1024*1024
+
+/*
+ * In open() function we store uuid data as volume name (for debug messages)
+ * and allocate 200 MiB of memory to simulate backend storage device.
+ */
+static int volume_open(ocf_volume_t volume, void *volume_params)
+{
+ const struct ocf_volume_uuid *uuid = ocf_volume_get_uuid(volume);
+ struct myvolume *myvolume = ocf_volume_get_priv(volume);
+
+ myvolume->name = ocf_uuid_to_str(uuid);
+ myvolume->mem = malloc(VOL_SIZE);
+
+ printf("VOL OPEN: (name: %s)\n", myvolume->name);
+
+ return 0;
+}
+
+/*
+ * In close() function we just free memory allocated in open().
+ */
+static void volume_close(ocf_volume_t volume)
+{
+ struct myvolume *myvolume = ocf_volume_get_priv(volume);
+
+ printf("VOL CLOSE: (name: %s)\n", myvolume->name);
+ free(myvolume->mem);
+}
+
+/*
+ * In submit_io() function we simulate read or write to backend storage device
+ * by doing memcpy() to or from previously allocated memory buffer.
+ */
+static void volume_submit_io(struct ocf_io *io)
+{
+ struct volume_data *data;
+ struct myvolume *myvolume;
+
+ data = ocf_io_get_data(io);
+ myvolume = ocf_volume_get_priv(ocf_io_get_volume(io));
+
+ if (io->dir == OCF_WRITE) {
+ memcpy(myvolume->mem + io->addr,
+ data->ptr + data->offset, io->bytes);
+ } else {
+ memcpy(data->ptr + data->offset,
+ myvolume->mem + io->addr, io->bytes);
+ }
+
+ printf("VOL: (name: %s), IO: (dir: %s, addr: %ld, bytes: %d)\n",
+ myvolume->name, io->dir == OCF_READ ? "read" : "write",
+ io->addr, io->bytes);
+
+ io->end(io, 0);
+}
+
+/*
+ * We don't need to implement submit_flush(). Just complete io with success.
+ */
+static void volume_submit_flush(struct ocf_io *io)
+{
+ io->end(io, 0);
+}
+
+/*
+ * We don't need to implement submit_discard(). Just complete io with success.
+ */
+static void volume_submit_discard(struct ocf_io *io)
+{
+ io->end(io, 0);
+}
+
+/*
+ * Let's set maximum io size to 128 KiB.
+ */
+static unsigned int volume_get_max_io_size(ocf_volume_t volume)
+{
+ return 128 * 1024;
+}
+
+/*
+ * Return volume size.
+ */
+static uint64_t volume_get_length(ocf_volume_t volume)
+{
+ return VOL_SIZE;
+}
+
+/*
+ * In set_data() we just assing data and offset to io.
+ */
+static int myvolume_io_set_data(struct ocf_io *io, ctx_data_t *data,
+ uint32_t offset)
+{
+ struct myvolume_io *myvolume_io = ocf_io_get_priv(io);
+
+ myvolume_io->data = data;
+ myvolume_io->offset = offset;
+
+ return 0;
+}
+
+/*
+ * In get_data() return data stored in io.
+ */
+static ctx_data_t *myvolume_io_get_data(struct ocf_io *io)
+{
+ struct myvolume_io *myvolume_io = ocf_io_get_priv(io);
+
+ return myvolume_io->data;
+}
+
+/*
+ * This structure contains volume properties. It describes volume
+ * type, which can be later instantiated as backend storage for cache
+ * or core.
+ */
+const struct ocf_volume_properties volume_properties = {
+ .name = "Example volume",
+ .io_priv_size = sizeof(struct myvolume_io),
+ .volume_priv_size = sizeof(struct myvolume),
+ .caps = {
+ .atomic_writes = 0,
+ },
+ .ops = {
+ .open = volume_open,
+ .close = volume_close,
+ .submit_io = volume_submit_io,
+ .submit_flush = volume_submit_flush,
+ .submit_discard = volume_submit_discard,
+ .get_max_io_size = volume_get_max_io_size,
+ .get_length = volume_get_length,
+ },
+ .io_ops = {
+ .set_data = myvolume_io_set_data,
+ .get_data = myvolume_io_get_data,
+ },
+};
+
+/*
+ * This function registers volume type in OCF context.
+ * It should be called just after context initialization.
+ */
+int volume_init(ocf_ctx_t ocf_ctx)
+{
+ return ocf_ctx_register_volume_type(ocf_ctx, VOL_TYPE,
+ &volume_properties);
+}
+
+/*
+ * This function unregisters volume type in OCF context.
+ * It should be called just before context cleanup.
+ */
+void volume_cleanup(ocf_ctx_t ocf_ctx)
+{
+ ocf_ctx_unregister_volume_type(ocf_ctx, VOL_TYPE);
+}
diff --git a/src/spdk/ocf/example/simple/src/volume.h b/src/spdk/ocf/example/simple/src/volume.h
new file mode 100644
index 000000000..83314b66b
--- /dev/null
+++ b/src/spdk/ocf/example/simple/src/volume.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright(c) 2019 Intel Corporation
+ * SPDX-License-Identifier: BSD-3-Clause-Clear
+ */
+
+#ifndef __VOLUME_H__
+#define __VOLUME_H__
+
+#include <ocf/ocf.h>
+#include "ocf_env.h"
+#include "ctx.h"
+#include "data.h"
+
+struct myvolume_io {
+ struct volume_data *data;
+ uint32_t offset;
+};
+
+struct myvolume {
+ uint8_t *mem;
+ const char *name;
+};
+
+int volume_init(ocf_ctx_t ocf_ctx);
+void volume_cleanup(ocf_ctx_t ocf_ctx);
+
+#endif