diff --git a/example/simple/Makefile b/example/simple/Makefile new file mode 100644 index 0000000..4c3a5b3 --- /dev/null +++ b/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) $(LDFLAGS) -o $@ $^ + +sync: + @$(MAKE) -C ${OCFDIR} inc O=$(PWD) + @$(MAKE) -C ${OCFDIR} src O=$(PWD) + @$(MAKE) -C ${OCFDIR} env O=$(PWD) 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/example/simple/src/ctx.c b/example/simple/src/ctx.c new file mode 100644 index 0000000..fd6798a --- /dev/null +++ b/example/simple/src/ctx.c @@ -0,0 +1,331 @@ +/* + * Copyright(c) 2019 Intel Corporation + * SPDX-License-Identifier: BSD-3-Clause-Clear + */ + +#include +#include +#include "ocf_env.h" +#include "data.h" +#include "dobj.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 dobj_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 dobj_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_rd(void *dst, ctx_data_t *src, uint32_t size) +{ + struct dobj_data *data = src; + + memcpy(dst, data->ptr + data->offset, size); + + return size; +} + +/* + * Write data from flat memory buffer. + */ +static uint32_t ctx_data_wr(ctx_data_t *dst, const void *src, uint32_t size) +{ + struct dobj_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 dobj_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 dobj_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_cpy(ctx_data_t *dst, ctx_data_t *src, + uint64_t to, uint64_t from, uint64_t bytes) +{ + struct dobj_data *data_dst = dst; + struct dobj_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 queue thread. To keep this example simple we handle queues + * synchronously, thus it's left non-implemented. + */ +static int ctx_queue_init(ocf_queue_t q) +{ + return 0; +} + +/* + * Trigger queue asynchronously. Made synchronous for simplicity. + */ +static inline void ctx_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 ctx_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 ctx_queue_stop(ocf_queue_t q) +{ +} + +/* + * 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; +} + +/* + * 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) +{ +} + +/* + * 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) +{ +} + +/* + * This structure describes context ops. They are splitted into few categories: + * - data ops, providing context specific data handing interface, + * - queue ops, providing interface for starting, stoping and kicking + * queue thread in both synchronous and asynchronous way, + * - cleaner ops, providing interface to start and stop clener thread, + * - metadata updater ops, providing interface for starting, stoping + * and kicking metadata updater thread. + */ +static const struct ocf_ctx_ops ctx_ops = { + .name = "OCF Example", + + .data_alloc = ctx_data_alloc, + .data_free = ctx_data_free, + .data_mlock = ctx_data_mlock, + .data_munlock = ctx_data_munlock, + .data_rd = ctx_data_rd, + .data_wr = ctx_data_wr, + .data_zero = ctx_data_zero, + .data_seek = ctx_data_seek, + .data_cpy = ctx_data_cpy, + .data_secure_erase = ctx_data_secure_erase, + + .queue_init = ctx_queue_init, + .queue_kick_sync = ctx_queue_kick_sync, + .queue_kick = ctx_queue_kick_async, + .queue_stop = ctx_queue_stop, + + .cleaner_init = ctx_cleaner_init, + .cleaner_stop = ctx_cleaner_stop, + + .metadata_updater_init = ctx_metadata_updater_init, + .metadata_updater_kick = ctx_metadata_updater_kick, + .metadata_updater_stop = ctx_metadata_updater_stop, +}; + +/* + * Function prividing interface for printing to log used by OCF internals. + * It can handle differently messages at varous log levels. + */ +static int ctx_log_printf(const struct ocf_logger *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_log_dump_stack(const struct ocf_logger *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; +} + +/* + * Structure containng logger ops. + */ +static const struct ocf_logger logger = { + .printf = ctx_log_printf, + .dump_stack = ctx_log_dump_stack, +}; + +/* + * Function initializing context. Prepares context, sets logger and + * registers data object type. + */ +int ctx_init(ocf_ctx_t *ctx) +{ + int ret; + + ret = ocf_ctx_init(ctx, &ctx_ops); + if (ret) + return ret; + + ocf_ctx_set_logger(*ctx, &logger); + + ret = dobj_init(*ctx); + if (ret) { + dobj_cleanup(*ctx); + return ret; + } + + return 0; +} + +/* + * Function cleaning up context. Unregisters data object type and + * deinitializes context. + */ +void ctx_cleanup(ocf_ctx_t ctx) +{ + dobj_cleanup(ctx); + ocf_ctx_exit(ctx); +} diff --git a/example/simple/src/ctx.h b/example/simple/src/ctx.h new file mode 100644 index 0000000..ec2e0d0 --- /dev/null +++ b/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 + +#define OBJ_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/example/simple/src/data.h b/example/simple/src/data.h new file mode 100644 index 0000000..39772d6 --- /dev/null +++ b/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 dobj_data { + void *ptr; + int offset; +}; + +#endif diff --git a/example/simple/src/dobj.c b/example/simple/src/dobj.c new file mode 100644 index 0000000..6dc8d21 --- /dev/null +++ b/example/simple/src/dobj.c @@ -0,0 +1,168 @@ +/* + * Copyright(c) 2019 Intel Corporation + * SPDX-License-Identifier: BSD-3-Clause-Clear + */ + +#include +#include "dobj.h" +#include "data.h" +#include "ctx.h" + +#define DOBJ_SIZE 200*1024*1024 + +/* + * In open() function we store uuid data as object name (for debug messages) + * and allocate 200 MiB of memory to simulate backend storage device. + */ +static int dobj_open(ocf_data_obj_t obj) +{ + const struct ocf_data_obj_uuid *uuid = ocf_dobj_get_uuid(obj); + struct dobj *dobj = ocf_dobj_get_priv(obj); + + dobj->name = ocf_uuid_to_str(uuid); + dobj->mem = malloc(DOBJ_SIZE); + + printf("DOBJ OPEN: (name: %s)\n", dobj->name); + + return 0; +} + +/* + * In close() function we just free memory allocated in open(). + */ +static void dobj_close(ocf_data_obj_t obj) +{ + struct dobj *dobj = ocf_dobj_get_priv(obj); + + printf("DOBJ CLOSE: (name: %s)\n", dobj->name); + free(dobj->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 dobj_submit_io(struct ocf_io *io) +{ + struct dobj_data *data; + struct dobj *dobj; + + data = ocf_io_get_data(io); + dobj = ocf_dobj_get_priv(io->obj); + + if (io->dir == OCF_WRITE) { + memcpy(dobj->mem + io->addr, + data->ptr + data->offset, io->bytes); + } else { + memcpy(data->ptr + data->offset, + dobj->mem + io->addr, io->bytes); + } + + printf("DOBJ: (name: %s), IO: (dir: %s, addr: %ld, bytes: %d)\n", + dobj->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 dobj_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 dobj_submit_discard(struct ocf_io *io) +{ + io->end(io, 0); +} + +/* + * Let's set maximum io size to 128 KiB. + */ +static unsigned int dobj_get_max_io_size(ocf_data_obj_t obj) +{ + return 128 * 1024; +} + +/* + * Return data object size. + */ +static uint64_t dobj_get_length(ocf_data_obj_t obj) +{ + return DOBJ_SIZE; +} + +/* + * In set_data() we just assing data and offset to io. + */ +static int dobj_io_set_data(struct ocf_io *io, ctx_data_t *data, + uint32_t offset) +{ + struct dobj_io *dobj_io = ocf_io_get_priv(io); + + dobj_io->data = data; + dobj_io->offset = offset; + + return 0; +} + +/* + * In get_data() return data stored in io. + */ +static ctx_data_t *dobj_io_get_data(struct ocf_io *io) +{ + struct dobj_io *dobj_io = ocf_io_get_priv(io); + + return dobj_io->data; +} + +/* + * This structure contains data object properties. It describes data object + * type, which can be later instantiated as backend storage object for cache + * or core. + */ +const struct ocf_data_obj_properties dobj_properties = { + .name = "Example dobj", + .io_priv_size = sizeof(struct dobj_io), + .dobj_priv_size = sizeof(struct dobj), + .caps = { + .atomic_writes = 0, + }, + .ops = { + .open = dobj_open, + .close = dobj_close, + .submit_io = dobj_submit_io, + .submit_flush = dobj_submit_flush, + .submit_discard = dobj_submit_discard, + .get_max_io_size = dobj_get_max_io_size, + .get_length = dobj_get_length, + }, + .io_ops = { + .set_data = dobj_io_set_data, + .get_data = dobj_io_get_data, + }, +}; + +/* + * This function registers data object type in OCF context. + * It should be called just after context initialization. + */ +int dobj_init(ocf_ctx_t ocf_ctx) +{ + return ocf_ctx_register_data_obj_type(ocf_ctx, OBJ_TYPE, + &dobj_properties); +} + +/* + * This function unregisters data object type in OCF context. + * It should be called just before context cleanup. + */ +void dobj_cleanup(ocf_ctx_t ocf_ctx) +{ + ocf_ctx_unregister_data_obj_type(ocf_ctx, OBJ_TYPE); +} diff --git a/example/simple/src/dobj.h b/example/simple/src/dobj.h new file mode 100644 index 0000000..62dff29 --- /dev/null +++ b/example/simple/src/dobj.h @@ -0,0 +1,27 @@ +/* + * Copyright(c) 2019 Intel Corporation + * SPDX-License-Identifier: BSD-3-Clause-Clear + */ + +#ifndef __DOBJ_H__ +#define __DOBJ_H__ + +#include +#include "ocf_env.h" +#include "ctx.h" +#include "data.h" + +struct dobj_io { + struct dobj_data *data; + uint32_t offset; +}; + +struct dobj { + uint8_t *mem; + const char *name; +}; + +int dobj_init(ocf_ctx_t ocf_ctx); +void dobj_cleanup(ocf_ctx_t ocf_ctx); + +#endif diff --git a/example/simple/src/main.c b/example/simple/src/main.c new file mode 100644 index 0000000..db03b07 --- /dev/null +++ b/example/simple/src/main.c @@ -0,0 +1,217 @@ +/* + * Copyright(c) 2019 Intel Corporation + * SPDX-License-Identifier: BSD-3-Clause-Clear + */ + +#include +#include +#include +#include "data.h" +#include "ctx.h" + +/* + * Helper function for error handling. + */ +void error(char *msg) +{ + printf("ERROR: %s", msg); + exit(1); +} + +/* + * 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 = { }; + struct ocf_mngt_cache_device_config device_cfg = { }; + int ret; + + /* Cache configuration */ + cache_cfg.backfill.max_queue_size = 65536; + cache_cfg.backfill.queue_unblock_size = 60000; + cache_cfg.cache_line_size = ocf_cache_line_size_4; + cache_cfg.cache_mode = ocf_cache_mode_wt; + cache_cfg.metadata_volatile = true; + cache_cfg.io_queues = 1; + cache_cfg.name = "cache1"; + + /* Cache deivce (data object) configuration */ + device_cfg.data_obj_type = OBJ_TYPE; + device_cfg.cache_line_size = ocf_cache_line_size_4; + ret = ocf_uuid_set_str(&device_cfg.uuid, "cache"); + if (ret) + return ret; + + /* Start cache */ + ret = ocf_mngt_cache_start(ctx, cache, &cache_cfg); + if (ret) + return ret; + + /* Attach data object to cache */ + ret = ocf_mngt_cache_attach(*cache, &device_cfg); + if (ret) { + ocf_mngt_cache_stop(*cache); + return ret; + } + + return 0; +} + +/* + * Function adding cache to core. + */ +int initialize_core(ocf_cache_t cache, ocf_core_t *core) +{ + struct ocf_mngt_core_config core_cfg = { }; + int ret; + + /* Core configuration */ + core_cfg.data_obj_type = OBJ_TYPE; + core_cfg.name = "core1"; + ret = ocf_uuid_set_str(&core_cfg.uuid, "core"); + if (ret) + return ret; + + /* Add core to cache */ + return ocf_mngt_cache_add_core(cache, core, &core_cfg); +} + +/* + * Callback function called when write completes. + */ +void complete_write(struct ocf_io *io, int error) +{ + struct dobj_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 dobj_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 dobj_data *data, + uint64_t addr, uint64_t len, int dir, ocf_end_io_t cmpl) +{ + struct ocf_io *io; + + /* Allocate new io */ + io = ocf_core_new_io(core); + if (!io) + return -ENOMEM; + + /* Setup io address, lenght, direction, flags and ioclass */ + ocf_io_configure(io, addr, len, dir, 0, 0); + /* 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 dobj_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. + */ +} + +int main(int argc, char *argv[]) +{ + ocf_ctx_t ctx; + ocf_cache_t cache1; + ocf_core_t core1; + + /* 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 */ + if (ocf_mngt_cache_remove_core(cache1, ocf_core_get_id(core1), false)) + error("Unable to remove core\n"); + + /* Stop cache */ + if (ocf_mngt_cache_stop(cache1)) + error("Unable to stop cache\n"); + + /* Deinitialize context */ + ctx_cleanup(ctx); + + return 0; +}