/*- * 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 "spdk/stdinc.h" #include "spdk/bdev.h" #include "spdk/env.h" #include "spdk/event.h" #include "spdk/blob_bdev.h" #include "spdk/blob.h" #include "spdk/log.h" #include #define TOTAL_MB (65) #define IO_DEPTH_IN_BYTE (131072ULL) #define IO_DEPTH_IN_PAGE (32ULL) #define ONE (1ULL) #define THOUSAND (1000ULL * ONE) #define MILLION (THOUSAND * THOUSAND) inline double get_current_time() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (double)ts.tv_sec * MILLION + (double)ts.tv_nsec / THOUSAND; } static uint64_t count; static double time_start, time_stop; /* * We'll use this struct to gather housekeeping hello_context to pass between * our events and callbacks. */ struct hello_context_t { struct spdk_blob_store *bs; struct spdk_blob *blob; spdk_blob_id blobid; struct spdk_io_channel *channel; uint8_t *read_buff; uint8_t *write_buff; uint64_t buf_size; // uint64_t page_size; int rc; }; /* * Free up memory that we allocated. */ static void hello_cleanup(struct hello_context_t *hello_context) { spdk_dma_free(hello_context->read_buff); spdk_dma_free(hello_context->write_buff); free(hello_context); } /* * Callback routine for the blobstore unload. */ static void unload_complete(void *cb_arg, int bserrno) { struct hello_context_t *hello_context = cb_arg; SPDK_NOTICELOG("entry\n"); if (bserrno) { SPDK_ERRLOG("Error %d unloading the bobstore\n", bserrno); hello_context->rc = bserrno; } spdk_app_stop(hello_context->rc); } /* * Unload the blobstore, cleaning up as needed. */ static void unload_bs(struct hello_context_t *hello_context, char *msg, int bserrno) { if (bserrno) { SPDK_ERRLOG("%s (err %d)\n", msg, bserrno); hello_context->rc = bserrno; } if (hello_context->bs) { if (hello_context->channel) { spdk_bs_free_io_channel(hello_context->channel); } spdk_bs_unload(hello_context->bs, unload_complete, hello_context); } else { spdk_app_stop(bserrno); } } /* * Callback routine for the deletion of a blob. */ static void delete_complete(void *arg1, int bserrno) { struct hello_context_t *hello_context = arg1; SPDK_NOTICELOG("entry\n"); if (bserrno) { unload_bs(hello_context, "Error in delete completion", bserrno); return; } /* We're all done, we can unload the blobstore. */ unload_bs(hello_context, "", 0); } /* * Function for deleting a blob. */ static void delete_blob(void *arg1, int bserrno) { struct hello_context_t *hello_context = arg1; SPDK_NOTICELOG("entry\n"); if (bserrno) { unload_bs(hello_context, "Error in close completion", bserrno); return; } spdk_bs_delete_blob(hello_context->bs, hello_context->blobid, delete_complete, hello_context); } /* * Callback function for reading a blob. */ static void read_complete(void *arg1, int bserrno) { struct hello_context_t *hello_context = arg1; int match_res = -1; static uint64_t rd_cnt; // SPDK_NOTICELOG("entry\n"); if (bserrno) { unload_bs(hello_context, "Error in read completion", bserrno); return; } else { rd_cnt += 1; if (rd_cnt >= count) { time_stop = get_current_time(); printf("read %ld kb from SSD in %lf sec.\n", rd_cnt * 128, (time_stop - time_start)/MILLION); /* Now let's close it and delete the blob in the callback. */ spdk_blob_close(hello_context->blob, delete_blob, hello_context); } } #if 0 /* Now let's make sure things match. */ match_res = memcmp(hello_context->write_buff, hello_context->read_buff, hello_context->buf_size); if (match_res) { unload_bs(hello_context, "Error in data compare", -1); return; } else { SPDK_NOTICELOG("read SUCCESS and data matches!\n"); } #endif } /* * Function for reading a blob. */ static void read_blob(struct hello_context_t *hello_context) { // SPDK_NOTICELOG("entry\n"); uint64_t i; uint64_t offset = 0; for (i=0; iblob, hello_context->channel, hello_context->read_buff, offset, IO_DEPTH_IN_PAGE, read_complete, hello_context); offset += IO_DEPTH_IN_PAGE; } } /* * Callback function for writing a blob. */ static void write_complete(void *arg1, int bserrno) { struct hello_context_t *hello_context = arg1; static uint64_t wr_cnt; SPDK_NOTICELOG("entry\n"); if (bserrno) { unload_bs(hello_context, "Error in write completion", bserrno); return; } else { wr_cnt += 1; if (wr_cnt >= count) { printf("write %ld kb into SSD\n Begin reading ...\n", wr_cnt * 128); time_start = get_current_time(); read_blob(hello_context); } } } /* * Function for writing to a blob. */ static void blob_write(struct hello_context_t *hello_context) { SPDK_NOTICELOG("entry\n"); uint64_t i; /* * Buffers for data transfer need to be allocated via SPDK. We will * tranfer 1 page of 4K aligned data at offset 0 in the blob. */ hello_context->write_buff = spdk_dma_malloc(hello_context->buf_size, 0x1000, NULL); if (hello_context->write_buff == NULL) { unload_bs(hello_context, "Error in allocating memory", -ENOMEM); return; } memset(hello_context->write_buff, 0x5a, hello_context->buf_size); /* Now we have to allocate a channel. */ hello_context->channel = spdk_bs_alloc_io_channel(hello_context->bs); if (hello_context->channel == NULL) { unload_bs(hello_context, "Error in allocating channel", -ENOMEM); return; } uint64_t offset = 0; printf("count = %ld\n", count); for (i=0; iblob, hello_context->channel, hello_context->write_buff, offset, IO_DEPTH_IN_PAGE, write_complete, hello_context); offset += IO_DEPTH_IN_PAGE; } } /* * Callback function for sync'ing metadata. */ static void sync_complete(void *arg1, int bserrno) { struct hello_context_t *hello_context = arg1; SPDK_NOTICELOG("entry\n"); if (bserrno) { unload_bs(hello_context, "Error in sync callback", bserrno); return; } count = TOTAL_MB * 1024 / (IO_DEPTH_IN_BYTE / 1024); hello_context->read_buff = spdk_dma_malloc(hello_context->buf_size, 0x1000, NULL); if (hello_context->read_buff == NULL) { unload_bs(hello_context, "Error in memory allocation", -ENOMEM); return; } /* Blob has been created & sized & MD sync'd, let's write to it. */ blob_write(hello_context); } /* * Callback function for opening a blob. */ static void open_complete(void *cb_arg, struct spdk_blob *blob, int bserrno) { struct hello_context_t *hello_context = cb_arg; uint64_t free = 0; uint64_t total = 0; int rc = 0; SPDK_NOTICELOG("entry\n"); if (bserrno) { unload_bs(hello_context, "Error in open completion", bserrno); return; } hello_context->blob = blob; free = spdk_bs_free_cluster_count(hello_context->bs); SPDK_NOTICELOG("blobstore has FREE clusters of %" PRIu64 "\n", free); /* * Before we can use our new blob, we have to resize it * as the initial size is 0. For this example we'll use the * full size of the blobstore but it would be expected that * there'd usually be many blobs of various sizes. The resize * unit is a cluster. */ printf("cluster size = %ld\n", spdk_bs_get_cluster_size(hello_context->bs)); rc = spdk_blob_resize(hello_context->blob, TOTAL_MB); //rc = spdk_blob_resize(hello_context->blob, free); if (rc) { unload_bs(hello_context, "Error in blob resize", bserrno); return; } total = spdk_blob_get_num_clusters(hello_context->blob); SPDK_NOTICELOG("resized blob now has USED clusters of %" PRIu64 "\n", total); /* * Metadata is stored in volatile memory for performance * reasons and therefore needs to be synchronized with * non-volatile storage to make it persistent. This can be * done manually, as shown here, or if not it will be done * automatically when the blob is closed. It is always a * good idea to sync after making metadata changes unless * it has an unacceptable impact on application performance. */ spdk_blob_sync_md(hello_context->blob, sync_complete, hello_context); } /* * Callback function for creating a blob. */ static void blob_create_complete(void *arg1, spdk_blob_id blobid, int bserrno) { struct hello_context_t *hello_context = arg1; SPDK_NOTICELOG("entry\n"); if (bserrno) { unload_bs(hello_context, "Error in blob create callback", bserrno); return; } hello_context->blobid = blobid; SPDK_NOTICELOG("new blob id %" PRIu64 "\n", hello_context->blobid); /* We have to open the blob before we can do things like resize. */ spdk_bs_open_blob(hello_context->bs, hello_context->blobid, open_complete, hello_context); } /* * Function for creating a blob. */ static void create_blob(struct hello_context_t *hello_context) { SPDK_NOTICELOG("entry\n"); spdk_bs_create_blob(hello_context->bs, blob_create_complete, hello_context); } /* * Callback function for initializing the blobstore. */ static void bs_init_complete(void *cb_arg, struct spdk_blob_store *bs, int bserrno) { struct hello_context_t *hello_context = cb_arg; SPDK_NOTICELOG("entry\n"); if (bserrno) { unload_bs(hello_context, "Error init'ing the blobstore", bserrno); return; } hello_context->bs = bs; SPDK_NOTICELOG("blobstore: %p\n", hello_context->bs); /* * We will use the page size in allocating buffers, etc., later * so we'll just save it in out context buffer here. */ // hello_context->page_size = spdk_bs_get_page_size(hello_context->bs); hello_context->buf_size = IO_DEPTH_IN_BYTE; /* * The blostore has been initialized, let's create a blob. * Note that we could allcoate an SPDK event and use * spdk_event_call() to schedule it if we wanted to keep * our events as limited as possible wrt the amount of * work that they do. */ create_blob(hello_context); } /* * Our initial event that kicks off everything from main(). */ static void hello_start(void *arg1, void *arg2) { struct hello_context_t *hello_context = arg1; struct spdk_bdev *bdev = NULL; struct spdk_bs_dev *bs_dev = NULL; SPDK_NOTICELOG("entry\n"); /* * Get the bdev. For this example it is our malloc (RAM) * disk configured via hello_blob.conf that was passed * in when we started the SPDK app framework so we can * get it via its name. */ bdev = spdk_bdev_get_by_name("Malloc0"); //bdev = spdk_bdev_get_by_name("Nvme0n1"); if (bdev == NULL) { SPDK_ERRLOG("Could not find a bdev\n"); spdk_app_stop(-1); return; } /* * spdk_bs_init() requires us to fill out the structure * spdk_bs_dev with a set of callbacks. These callbacks * implement read, write, and other operations on the * underlying disks. As a convenience, a utility function * is provided that creates an spdk_bs_dev that implements * all of the callbacks by forwarding the I/O to the * SPDK bdev layer. Other helper functions are also * available in the blob lib in blob_bdev.c that simply * make it easier to layer blobstore on top of a bdev. * However blobstore can be more tightly integrated into * any lower layer, such as NVMe for example. */ bs_dev = spdk_bdev_create_bs_dev(bdev, NULL, NULL); if (bs_dev == NULL) { SPDK_ERRLOG("Could not create blob bdev!!\n"); spdk_app_stop(-1); return; } spdk_bs_init(bs_dev, NULL, bs_init_complete, hello_context); } int main(int argc, char **argv) { struct spdk_app_opts opts = {}; int rc = 0; struct hello_context_t *hello_context = NULL; SPDK_NOTICELOG("entry\n"); /* Set default values in opts structure. */ spdk_app_opts_init(&opts); /* * Setup a few specifics before we init, for most SPDK cmd line * apps, the config file will be passed in as an arg but to make * this example super simple we just hardcode it. We also need to * specify a name for the app. */ opts.name = "hello_blob"; opts.config_file = "hello_blob.conf"; /* * Now we'll allocate and intialize the blobstore itself. We * can pass in an spdk_bs_opts if we want something other than * the defaults (cluster size, etc), but here we'll just take the * defaults. We'll also pass in a struct that we'll use for * callbacks so we've got efficient bookeeping of what we're * creating. This is an async operation and bs_init_complete() * will be called when it is complete. */ hello_context = calloc(1, sizeof(struct hello_context_t)); if (hello_context != NULL) { /* * spdk_app_start() will block running hello_start() until * spdk_app_stop() is called by someone (not simply when * hello_start() returns) */ rc = spdk_app_start(&opts, hello_start, hello_context, NULL); if (rc) { SPDK_NOTICELOG("ERROR!\n"); } else { SPDK_NOTICELOG("SUCCCESS!\n"); } /* Free up memory that we allocated */ hello_cleanup(hello_context); } else { SPDK_ERRLOG("Could not alloc hello_context struct!!\n"); rc = -ENOMEM; } /* Gracefully close out all of the SPDK subsystems. */ spdk_app_fini(); return rc; }