Add simple ocf usage example
Signed-off-by: Robert Baldyga <robert.baldyga@intel.com>
This commit is contained in:
parent
8581520658
commit
95d35ef337
37
example/simple/Makefile
Normal file
37
example/simple/Makefile
Normal file
@ -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
|
331
example/simple/src/ctx.c
Normal file
331
example/simple/src/ctx.c
Normal file
@ -0,0 +1,331 @@
|
||||
/*
|
||||
* 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 "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);
|
||||
}
|
19
example/simple/src/ctx.h
Normal file
19
example/simple/src/ctx.h
Normal file
@ -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 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
|
14
example/simple/src/data.h
Normal file
14
example/simple/src/data.h
Normal file
@ -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
|
168
example/simple/src/dobj.c
Normal file
168
example/simple/src/dobj.c
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright(c) 2019 Intel Corporation
|
||||
* SPDX-License-Identifier: BSD-3-Clause-Clear
|
||||
*/
|
||||
|
||||
#include <ocf/ocf.h>
|
||||
#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);
|
||||
}
|
27
example/simple/src/dobj.h
Normal file
27
example/simple/src/dobj.h
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright(c) 2019 Intel Corporation
|
||||
* SPDX-License-Identifier: BSD-3-Clause-Clear
|
||||
*/
|
||||
|
||||
#ifndef __DOBJ_H__
|
||||
#define __DOBJ_H__
|
||||
|
||||
#include <ocf/ocf.h>
|
||||
#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
|
217
example/simple/src/main.c
Normal file
217
example/simple/src/main.c
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user