test/autobuild: verify external code can link to libs

This test helps ensure that nothing about the way we build our shared
objects changes in a way that breaks external applications or bdev
modules trying to link to SPDK.

Change-Id: I45ac36a2afc873b04eb237f4e633a9d84066737c
Signed-off-by: Seth Howell <seth.howell@intel.com>
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/435
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Reviewed-by: Tomasz Zawadzki <tomasz.zawadzki@intel.com>
Reviewed-by: Ben Walker <benjamin.walker@intel.com>
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Community-CI: Broadcom CI
This commit is contained in:
Seth Howell 2020-01-21 10:18:46 -07:00 committed by Jim Harris
parent ae1dc68f39
commit 3509378604
14 changed files with 1572 additions and 0 deletions

View File

@ -151,6 +151,7 @@ function build_doc {
function autobuild_test_suite {
run_test "autobuild_check_format" ./scripts/check_format.sh
run_test "autobuild_external_code" sudo -E $rootdir/test/external_code/test_make.sh $rootdir
if [ "$SPDK_TEST_OCF" -eq 1 ]; then
run_test "autobuild_ocf_precompile" ocf_precompile
fi

View File

@ -48,3 +48,8 @@ test/unit/lib/ftl/common/utils
test/unit/lib/iscsi/common
test/unit/lib/json_mock
test/unit/lib/sock/uring.c/uring_ut
# These files are in the external_code directory which doesn't get compiled with SPDK.
test/external_code/hello_world/hello_bdev
test/external_code/passthru/vbdev_passthru
test/external_code/passthru/vbdev_passthru_rpc

View File

@ -0,0 +1,80 @@
#
# 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.
#
ALL_DEPDIRS+= hello_world passthru
ifneq ($(SPDK_HEADER_DIR),)
COMMON_CFLAGS+=-I$(SPDK_HEADER_DIR)
endif
ifneq ($(SPDK_LIB_DIR),)
COMMON_CFLAGS+=-L$(SPDK_LIB_DIR)
endif
ifneq ($(DPDK_LIB_DIR),)
COMMON_CFLAGS+=-L$(DPDK_LIB_DIR)
endif
export
.PHONY: all
all: hello_world_bdev_shared_combo
static: hello_world_bdev_static
hello_world_bdev_shared_combo: passthru_shared
$(MAKE) --directory=hello_world bdev_shared_combo
hello_world_bdev_shared_iso: passthru_shared
$(MAKE) --directory=hello_world bdev_shared_iso
hello_world_no_bdev_shared_combo:
$(MAKE) --directory=hello_world alone_shared_combo
hello_world_no_bdev_shared_iso:
$(MAKE) --directory=hello_world alone_shared_iso
hello_world_bdev_static: passthru_static
$(MAKE) --directory=hello_world bdev_static
hello_world_no_bdev_static:
$(MAKE) --directory=hello_world alone_static
passthru_shared:
$(MAKE) --directory=passthru shared
passthru_static:
$(MAKE) --directory=passthru static
clean:
rm -f ./hello_world/hello_bdev
rm -f ./passthru/libpassthru_external.*

View File

@ -0,0 +1,17 @@
This directory is meant to demonstrate how to link an external application and bdev
module to the SPDK libraries. The makefiles contain six examples of linking against spdk
libraries. They cover linking an application both with and without a custom bdev. For each of
these categories, they also demonstrate linking against the spdk combined shared library,
individual shared libraries, and static libraries.
This directory also contains a convenient test script, test_make.sh, which automates making SPDK
and testing all six of these linker options. It takes a single argument, the path to an SPDK
repository and should be run as follows:
~~~
sudo ./test_make.sh /path/to/spdk
~~~
The application `hello_world` and bdev module `passthru_external` have been copied from their namesakes
in the top level [SPDK github repository](https://github.com/spdk/spdk) and don't have any special
functionality.

View File

@ -0,0 +1 @@
hello_bdev

View File

@ -0,0 +1,66 @@
#
# 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.
#
# Shows how to compile both an external bdev and an external application against the SPDK combined shared object and dpdk shared object.
bdev_shared_combo:
$(CC) $(COMMON_CFLAGS) -L../passthru -o hello_bdev ./hello_bdev.c -lpassthru_external -lspdk -lspdk_env_dpdk -ldpdk
# Shows how to compile both an external bdev and an external application against the SPDK individual shared objects and dpdk shared object.
bdev_shared_iso:
$(CC) $(COMMON_CFLAGS) -L../passthru -Wl,-rpath=$(SPDK_LIB_DIR),--no-as-needed -o hello_bdev ./hello_bdev.c \
-lpassthru_external -lspdk_event_bdev -lspdk_bdev -lspdk_bdev_malloc -lspdk_log -lspdk_thread -lspdk_util -lspdk_event -lspdk_env_dpdk -ldpdk
# Shows how to compile an external application against the SPDK combined shared object and dpdk shared object.
alone_shared_combo:
$(CC) $(COMMON_CFLAGS) -o hello_bdev ./hello_bdev.c -lspdk -lspdk_env_dpdk -ldpdk
# Shows how to compile an external application against the SPDK individual shared objects and dpdk shared object.
alone_shared_iso:
$(CC) $(COMMON_CFLAGS) -Wl,-rpath=$(SPDK_LIB_DIR),--no-as-needed -o hello_bdev ./hello_bdev.c -lspdk_event_bdev \
-lspdk_bdev -lspdk_bdev_malloc -lspdk_log -lspdk_thread -lspdk_util -lspdk_event -lspdk_env_dpdk -ldpdk
# Shows how to compile an external application against the SPDK archives.
alone_static:
$(CC) $(COMMON_CFLAGS) -o hello_bdev ./hello_bdev.c -Wl,--whole-archive -lspdk_bdev_malloc -lspdk_event_bdev -lspdk_event_accel -lspdk_event_vmd \
-lspdk_bdev -lspdk_accel -lspdk_event -lspdk_thread -lspdk_util -lspdk_conf -lspdk_trace -lspdk_log -lspdk_json \
-lspdk_jsonrpc -lspdk_rpc -lspdk_sock -lspdk_notify -lspdk_vmd -lspdk_env_dpdk -lrte_eal -lrte_mempool -lrte_ring \
-lrte_mbuf -lrte_mempool_ring -lrte_pci -lrte_bus_pci -lrte_kvargs -lrte_vhost -lrte_net -lrte_hash \
-lrte_cryptodev -Wl,--no-whole-archive -lnuma -luuid -lpthread -ldl -lrt
# Shows how to compile and external bdev and application sgainst the SPDK archives.
bdev_static:
$(CC) $(COMMON_CFLAGS) -L../passthru -o hello_bdev ./hello_bdev.c -Wl,--whole-archive -lpassthru_external -lspdk_bdev_malloc -lspdk_event_bdev \
-lspdk_event_accel -lspdk_event_vmd -lspdk_bdev -lspdk_accel -lspdk_event -lspdk_thread -lspdk_util -lspdk_conf -lspdk_trace \
-lspdk_log -lspdk_json -lspdk_jsonrpc -lspdk_rpc -lspdk_sock -lspdk_notify -lspdk_vmd -lspdk_env_dpdk -lrte_eal -lrte_mempool \
-lrte_ring -lrte_mbuf -lrte_mempool_ring -lrte_pci -lrte_bus_pci -lrte_kvargs -lrte_vhost -lrte_net -lrte_hash -lrte_cryptodev \
-Wl,--no-whole-archive -lnuma -luuid -lpthread -ldl -lrt

View File

@ -0,0 +1,17 @@
{
"subsystems": [
{
"subsystem": "bdev",
"config": [
{
"params": {
"name": "Malloc0",
"block_size": 4096,
"num_blocks": 32
},
"method": "construct_malloc_bdev"
}
]
}
]
}

View File

@ -0,0 +1,24 @@
{
"subsystems": [
{
"subsystem": "bdev",
"config": [
{
"params": {
"name": "Malloc0",
"block_size": 4096,
"num_blocks": 32
},
"method": "construct_malloc_bdev"
},
{
"params": {
"base_bdev_name": "Malloc0",
"name": "TestPT"
},
"method": "construct_ext_passthru_bdev"
}
]
}
]
}

View File

@ -0,0 +1,300 @@
/*-
* 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/thread.h"
#include "spdk/bdev.h"
#include "spdk/env.h"
#include "spdk/event.h"
#include "spdk/log.h"
#include "spdk/string.h"
#include "spdk/bdev_module.h"
static char *g_bdev_name = "Malloc0";
/*
* We'll use this struct to gather housekeeping hello_context to pass between
* our events and callbacks.
*/
struct hello_context_t {
struct spdk_bdev *bdev;
struct spdk_bdev_desc *bdev_desc;
struct spdk_io_channel *bdev_io_channel;
char *buff;
char *bdev_name;
struct spdk_bdev_io_wait_entry bdev_io_wait;
};
/*
* Usage function for printing parameters that are specific to this application
*/
static void
hello_bdev_usage(void)
{
printf(" -b <bdev> name of the bdev to use\n");
}
/*
* This function is called to parse the parameters that are specific to this application
*/
static int hello_bdev_parse_arg(int ch, char *arg)
{
switch (ch) {
case 'b':
g_bdev_name = arg;
break;
default:
return -EINVAL;
}
return 0;
}
/*
* Callback function for read io completion.
*/
static void
read_complete(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
{
struct hello_context_t *hello_context = cb_arg;
if (success) {
SPDK_NOTICELOG("Read string from bdev : %s\n", hello_context->buff);
} else {
SPDK_ERRLOG("bdev io read error\n");
}
/* Complete the bdev io and close the channel */
spdk_bdev_free_io(bdev_io);
spdk_put_io_channel(hello_context->bdev_io_channel);
spdk_bdev_close(hello_context->bdev_desc);
SPDK_NOTICELOG("Stopping app\n");
spdk_app_stop(success ? 0 : -1);
}
static void
hello_read(void *arg)
{
struct hello_context_t *hello_context = arg;
int rc = 0;
uint32_t length = spdk_bdev_get_block_size(hello_context->bdev);
SPDK_NOTICELOG("Reading io\n");
rc = spdk_bdev_read(hello_context->bdev_desc, hello_context->bdev_io_channel,
hello_context->buff, 0, length, read_complete, hello_context);
if (rc == -ENOMEM) {
SPDK_NOTICELOG("Queueing io\n");
/* In case we cannot perform I/O now, queue I/O */
hello_context->bdev_io_wait.bdev = hello_context->bdev;
hello_context->bdev_io_wait.cb_fn = hello_read;
hello_context->bdev_io_wait.cb_arg = hello_context;
spdk_bdev_queue_io_wait(hello_context->bdev, hello_context->bdev_io_channel,
&hello_context->bdev_io_wait);
} else if (rc) {
SPDK_ERRLOG("%s error while reading from bdev: %d\n", spdk_strerror(-rc), rc);
spdk_put_io_channel(hello_context->bdev_io_channel);
spdk_bdev_close(hello_context->bdev_desc);
spdk_app_stop(-1);
}
}
/*
* Callback function for write io completion.
*/
static void
write_complete(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
{
struct hello_context_t *hello_context = cb_arg;
uint32_t length;
/* Complete the I/O */
spdk_bdev_free_io(bdev_io);
if (success) {
SPDK_NOTICELOG("bdev io write completed successfully\n");
} else {
SPDK_ERRLOG("bdev io write error: %d\n", EIO);
spdk_put_io_channel(hello_context->bdev_io_channel);
spdk_bdev_close(hello_context->bdev_desc);
spdk_app_stop(-1);
return;
}
/* Zero the buffer so that we can use it for reading */
length = spdk_bdev_get_block_size(hello_context->bdev);
memset(hello_context->buff, 0, length);
hello_read(hello_context);
}
static void
hello_write(void *arg)
{
struct hello_context_t *hello_context = arg;
int rc = 0;
uint32_t length = spdk_bdev_get_block_size(hello_context->bdev);
SPDK_NOTICELOG("Writing to the bdev\n");
rc = spdk_bdev_write(hello_context->bdev_desc, hello_context->bdev_io_channel,
hello_context->buff, 0, length, write_complete, hello_context);
if (rc == -ENOMEM) {
SPDK_NOTICELOG("Queueing io\n");
/* In case we cannot perform I/O now, queue I/O */
hello_context->bdev_io_wait.bdev = hello_context->bdev;
hello_context->bdev_io_wait.cb_fn = hello_write;
hello_context->bdev_io_wait.cb_arg = hello_context;
spdk_bdev_queue_io_wait(hello_context->bdev, hello_context->bdev_io_channel,
&hello_context->bdev_io_wait);
} else if (rc) {
SPDK_ERRLOG("%s error while writing to bdev: %d\n", spdk_strerror(-rc), rc);
spdk_put_io_channel(hello_context->bdev_io_channel);
spdk_bdev_close(hello_context->bdev_desc);
spdk_app_stop(-1);
}
}
/*
* Our initial event that kicks off everything from main().
*/
static void
hello_start(void *arg1)
{
struct hello_context_t *hello_context = arg1;
uint32_t blk_size, buf_align;
int rc = 0;
hello_context->bdev = NULL;
hello_context->bdev_desc = NULL;
SPDK_NOTICELOG("Successfully started the application\n");
/*
* Get the bdev. There can be many bdevs configured in
* in the configuration file but this application will only
* use the one input by the user at runtime so we get it via its name.
*/
hello_context->bdev = spdk_bdev_get_by_name(hello_context->bdev_name);
if (hello_context->bdev == NULL) {
SPDK_ERRLOG("Could not find the bdev: %s\n", hello_context->bdev_name);
spdk_app_stop(-1);
return;
}
/*
* Open the bdev by calling spdk_bdev_open()
* The function will return a descriptor
*/
SPDK_NOTICELOG("Opening the bdev %s\n", hello_context->bdev_name);
rc = spdk_bdev_open(hello_context->bdev, true, NULL, NULL, &hello_context->bdev_desc);
if (rc) {
SPDK_ERRLOG("Could not open bdev: %s\n", hello_context->bdev_name);
spdk_app_stop(-1);
return;
}
SPDK_NOTICELOG("Opening io channel\n");
/* Open I/O channel */
hello_context->bdev_io_channel = spdk_bdev_get_io_channel(hello_context->bdev_desc);
if (hello_context->bdev_io_channel == NULL) {
SPDK_ERRLOG("Could not create bdev I/O channel!!\n");
spdk_bdev_close(hello_context->bdev_desc);
spdk_app_stop(-1);
return;
}
/* Allocate memory for the write buffer.
* Initialize the write buffer with the string "Hello World!"
*/
blk_size = spdk_bdev_get_block_size(hello_context->bdev);
buf_align = spdk_bdev_get_buf_align(hello_context->bdev);
hello_context->buff = spdk_dma_zmalloc(blk_size, buf_align, NULL);
if (!hello_context->buff) {
SPDK_ERRLOG("Failed to allocate buffer\n");
spdk_put_io_channel(hello_context->bdev_io_channel);
spdk_bdev_close(hello_context->bdev_desc);
spdk_app_stop(-1);
return;
}
snprintf(hello_context->buff, blk_size, "%s", "Hello World!\n");
hello_write(hello_context);
}
int
main(int argc, char **argv)
{
struct spdk_app_opts opts = {};
int rc = 0;
struct hello_context_t hello_context = {};
/* Set default values in opts structure. */
spdk_app_opts_init(&opts);
opts.name = "hello_bdev";
/*
* The user can provide the config file and bdev name at run time.
* For example, to use Malloc0 in file bdev.conf run with params
* ./hello_bdev -c bdev.conf -b Malloc0
* To use passthru bdev PT0 run with params
* ./hello_bdev -c bdev.conf -b PT0
* If the bdev name is not specified,
* then Malloc0 is used by default
*/
if ((rc = spdk_app_parse_args(argc, argv, &opts, "b:", NULL, hello_bdev_parse_arg,
hello_bdev_usage)) != SPDK_APP_PARSE_ARGS_SUCCESS) {
exit(rc);
}
if (opts.json_config_file == NULL) {
SPDK_ERRLOG("configfile must be specified using --json <conffile> e.g. -c bdev.conf\n");
exit(1);
}
hello_context.bdev_name = g_bdev_name;
/*
* spdk_app_start() will block running hello_start() until
* spdk_app_stop() is called by someone (not simply when
* hello_start() returns), or if an error occurs during
* spdk_app_start() before hello_start() runs.
*/
rc = spdk_app_start(&opts, hello_start, &hello_context);
if (rc) {
SPDK_ERRLOG("ERROR starting application\n");
}
/* When the app stops, free up memory that we allocated. */
spdk_dma_free(hello_context.buff);
/* Gracefully close out all of the SPDK subsystems. */
spdk_app_fini();
return rc;
}

View File

@ -0,0 +1,43 @@
#
# 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.
#
src=vbdev_passthru_rpc.c vbdev_passthru.c
shared:
$(CC) $(COMMON_CFLAGS) -c -fPIC ./vbdev_passthru_rpc.c -o ./vbdev_passthru_rpc.o
$(CC) $(COMMON_CFLAGS) -c -fPIC ./vbdev_passthru.c -o ./vbdev_passthru.o
$(CC) $(COMMON_CFLAGS) -shared ./vbdev_passthru_rpc.o ./vbdev_passthru.o -o ./libpassthru_external.so
static:
$(CC) $(COMMON_CFLAGS) -c ./vbdev_passthru_rpc.c -o ./vbdev_passthru_rpc.o
$(CC) $(COMMON_CFLAGS) -c ./vbdev_passthru.c -o ./vbdev_passthru.o
$(AR) rcs ./libpassthru_external.a ./vbdev_passthru_rpc.o ./vbdev_passthru.o

View File

@ -0,0 +1,748 @@
/*-
* 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.
*/
/*
* This is a simple example of a virtual block device module that passes IO
* down to a bdev (or bdevs) that its configured to attach to.
*/
#include "vbdev_passthru.h"
#include "spdk/env.h"
#include "spdk/conf.h"
#include "spdk/endian.h"
#include "spdk/thread.h"
static int vbdev_ext_passthru_init(void);
static void vbdev_ext_passthru_get_spdk_running_config(FILE *fp);
static int vbdev_ext_passthru_get_ctx_size(void);
static void vbdev_ext_passthru_examine(struct spdk_bdev *bdev);
static void vbdev_ext_passthru_finish(void);
static int vbdev_ext_passthru_config_json(struct spdk_json_write_ctx *w);
static struct spdk_bdev_module passthru_if_external = {
.name = "passthru_external",
.module_init = vbdev_ext_passthru_init,
.config_text = vbdev_ext_passthru_get_spdk_running_config,
.get_ctx_size = vbdev_ext_passthru_get_ctx_size,
.examine_config = vbdev_ext_passthru_examine,
.module_fini = vbdev_ext_passthru_finish,
.config_json = vbdev_ext_passthru_config_json
};
SPDK_BDEV_MODULE_REGISTER(passthru, &passthru_if_external)
/* List of pt_bdev names and their base bdevs via configuration file.
* Used so we can parse the conf once at init and use this list in examine().
*/
struct bdev_names {
char *vbdev_name;
char *bdev_name;
TAILQ_ENTRY(bdev_names) link;
};
static TAILQ_HEAD(, bdev_names) g_bdev_names = TAILQ_HEAD_INITIALIZER(g_bdev_names);
/* List of virtual bdevs and associated info for each. */
struct vbdev_passthru {
struct spdk_bdev *base_bdev; /* the thing we're attaching to */
struct spdk_bdev_desc *base_desc; /* its descriptor we get from open */
struct spdk_bdev pt_bdev; /* the PT virtual bdev */
TAILQ_ENTRY(vbdev_passthru) link;
};
static TAILQ_HEAD(, vbdev_passthru) g_pt_nodes = TAILQ_HEAD_INITIALIZER(g_pt_nodes);
/* The pt vbdev channel struct. It is allocated and freed on my behalf by the io channel code.
* If this vbdev needed to implement a poller or a queue for IO, this is where those things
* would be defined. This passthru bdev doesn't actually need to allocate a channel, it could
* simply pass back the channel of the bdev underneath it but for example purposes we will
* present its own to the upper layers.
*/
struct pt_io_channel {
struct spdk_io_channel *base_ch; /* IO channel of base device */
};
/* Just for fun, this pt_bdev module doesn't need it but this is essentially a per IO
* context that we get handed by the bdev layer.
*/
struct passthru_bdev_io {
uint8_t test;
/* bdev related */
struct spdk_io_channel *ch;
/* for bdev_io_wait */
struct spdk_bdev_io_wait_entry bdev_io_wait;
};
static void
vbdev_passthru_submit_request(struct spdk_io_channel *ch, struct spdk_bdev_io *bdev_io);
/* Callback for unregistering the IO device. */
static void
_device_unregister_cb(void *io_device)
{
struct vbdev_passthru *pt_node = io_device;
/* Done with this pt_node. */
free(pt_node->pt_bdev.name);
free(pt_node);
}
/* Called after we've unregistered following a hot remove callback.
* Our finish entry point will be called next.
*/
static int
vbdev_passthru_destruct(void *ctx)
{
struct vbdev_passthru *pt_node = (struct vbdev_passthru *)ctx;
/* It is important to follow this exact sequence of steps for destroying
* a vbdev...
*/
TAILQ_REMOVE(&g_pt_nodes, pt_node, link);
/* Unclaim the underlying bdev. */
spdk_bdev_module_release_bdev(pt_node->base_bdev);
/* Close the underlying bdev. */
spdk_bdev_close(pt_node->base_desc);
/* Unregister the io_device. */
spdk_io_device_unregister(pt_node, _device_unregister_cb);
return 0;
}
/* Completion callback for IO that were issued from this bdev. The original bdev_io
* is passed in as an arg so we'll complete that one with the appropriate status
* and then free the one that this module issued.
*/
static void
_pt_complete_io(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
{
struct spdk_bdev_io *orig_io = cb_arg;
int status = success ? SPDK_BDEV_IO_STATUS_SUCCESS : SPDK_BDEV_IO_STATUS_FAILED;
struct passthru_bdev_io *io_ctx = (struct passthru_bdev_io *)orig_io->driver_ctx;
/* We setup this value in the submission routine, just showing here that it is
* passed back to us.
*/
if (io_ctx->test != 0x5a) {
SPDK_ERRLOG("Error, original IO device_ctx is wrong! 0x%x\n",
io_ctx->test);
}
/* Complete the original IO and then free the one that we created here
* as a result of issuing an IO via submit_reqeust.
*/
spdk_bdev_io_complete(orig_io, status);
spdk_bdev_free_io(bdev_io);
}
static void
vbdev_passthru_resubmit_io(void *arg)
{
struct spdk_bdev_io *bdev_io = (struct spdk_bdev_io *)arg;
struct passthru_bdev_io *io_ctx = (struct passthru_bdev_io *)bdev_io->driver_ctx;
vbdev_passthru_submit_request(io_ctx->ch, bdev_io);
}
static void
vbdev_passthru_queue_io(struct spdk_bdev_io *bdev_io)
{
struct passthru_bdev_io *io_ctx = (struct passthru_bdev_io *)bdev_io->driver_ctx;
int rc;
io_ctx->bdev_io_wait.bdev = bdev_io->bdev;
io_ctx->bdev_io_wait.cb_fn = vbdev_passthru_resubmit_io;
io_ctx->bdev_io_wait.cb_arg = bdev_io;
rc = spdk_bdev_queue_io_wait(bdev_io->bdev, io_ctx->ch, &io_ctx->bdev_io_wait);
if (rc != 0) {
SPDK_ERRLOG("Queue io failed in vbdev_passthru_queue_io, rc=%d.\n", rc);
spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_FAILED);
}
}
/* Callback for getting a buf from the bdev pool in the event that the caller passed
* in NULL, we need to own the buffer so it doesn't get freed by another vbdev module
* beneath us before we're done with it. That won't happen in this example but it could
* if this example were used as a template for something more complex.
*/
static void
pt_read_get_buf_cb(struct spdk_io_channel *ch, struct spdk_bdev_io *bdev_io, bool success)
{
struct vbdev_passthru *pt_node = SPDK_CONTAINEROF(bdev_io->bdev, struct vbdev_passthru,
pt_bdev);
struct pt_io_channel *pt_ch = spdk_io_channel_get_ctx(ch);
struct passthru_bdev_io *io_ctx = (struct passthru_bdev_io *)bdev_io->driver_ctx;
int rc;
if (!success) {
spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_FAILED);
return;
}
if (bdev_io->u.bdev.md_buf == NULL) {
rc = spdk_bdev_readv_blocks(pt_node->base_desc, pt_ch->base_ch, bdev_io->u.bdev.iovs,
bdev_io->u.bdev.iovcnt, bdev_io->u.bdev.offset_blocks,
bdev_io->u.bdev.num_blocks, _pt_complete_io,
bdev_io);
} else {
rc = spdk_bdev_readv_blocks_with_md(pt_node->base_desc, pt_ch->base_ch,
bdev_io->u.bdev.iovs, bdev_io->u.bdev.iovcnt,
bdev_io->u.bdev.md_buf,
bdev_io->u.bdev.offset_blocks,
bdev_io->u.bdev.num_blocks,
_pt_complete_io, bdev_io);
}
if (rc != 0) {
if (rc == -ENOMEM) {
SPDK_ERRLOG("No memory, start to queue io for passthru.\n");
io_ctx->ch = ch;
vbdev_passthru_queue_io(bdev_io);
} else {
SPDK_ERRLOG("ERROR on bdev_io submission!\n");
spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_FAILED);
}
}
}
/* Called when someone above submits IO to this pt vbdev. We're simply passing it on here
* via SPDK IO calls which in turn allocate another bdev IO and call our cpl callback provided
* below along with the original bdiv_io so that we can complete it once this IO completes.
*/
static void
vbdev_passthru_submit_request(struct spdk_io_channel *ch, struct spdk_bdev_io *bdev_io)
{
struct vbdev_passthru *pt_node = SPDK_CONTAINEROF(bdev_io->bdev, struct vbdev_passthru, pt_bdev);
struct pt_io_channel *pt_ch = spdk_io_channel_get_ctx(ch);
struct passthru_bdev_io *io_ctx = (struct passthru_bdev_io *)bdev_io->driver_ctx;
int rc = 0;
/* Setup a per IO context value; we don't do anything with it in the vbdev other
* than confirm we get the same thing back in the completion callback just to
* demonstrate.
*/
io_ctx->test = 0x5a;
switch (bdev_io->type) {
case SPDK_BDEV_IO_TYPE_READ:
spdk_bdev_io_get_buf(bdev_io, pt_read_get_buf_cb,
bdev_io->u.bdev.num_blocks * bdev_io->bdev->blocklen);
break;
case SPDK_BDEV_IO_TYPE_WRITE:
if (bdev_io->u.bdev.md_buf == NULL) {
rc = spdk_bdev_writev_blocks(pt_node->base_desc, pt_ch->base_ch, bdev_io->u.bdev.iovs,
bdev_io->u.bdev.iovcnt, bdev_io->u.bdev.offset_blocks,
bdev_io->u.bdev.num_blocks, _pt_complete_io,
bdev_io);
} else {
rc = spdk_bdev_writev_blocks_with_md(pt_node->base_desc, pt_ch->base_ch,
bdev_io->u.bdev.iovs, bdev_io->u.bdev.iovcnt,
bdev_io->u.bdev.md_buf,
bdev_io->u.bdev.offset_blocks,
bdev_io->u.bdev.num_blocks,
_pt_complete_io, bdev_io);
}
break;
case SPDK_BDEV_IO_TYPE_WRITE_ZEROES:
rc = spdk_bdev_write_zeroes_blocks(pt_node->base_desc, pt_ch->base_ch,
bdev_io->u.bdev.offset_blocks,
bdev_io->u.bdev.num_blocks,
_pt_complete_io, bdev_io);
break;
case SPDK_BDEV_IO_TYPE_UNMAP:
rc = spdk_bdev_unmap_blocks(pt_node->base_desc, pt_ch->base_ch,
bdev_io->u.bdev.offset_blocks,
bdev_io->u.bdev.num_blocks,
_pt_complete_io, bdev_io);
break;
case SPDK_BDEV_IO_TYPE_FLUSH:
rc = spdk_bdev_flush_blocks(pt_node->base_desc, pt_ch->base_ch,
bdev_io->u.bdev.offset_blocks,
bdev_io->u.bdev.num_blocks,
_pt_complete_io, bdev_io);
break;
case SPDK_BDEV_IO_TYPE_RESET:
rc = spdk_bdev_reset(pt_node->base_desc, pt_ch->base_ch,
_pt_complete_io, bdev_io);
break;
default:
SPDK_ERRLOG("passthru: unknown I/O type %d\n", bdev_io->type);
spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_FAILED);
return;
}
if (rc != 0) {
if (rc == -ENOMEM) {
SPDK_ERRLOG("No memory, start to queue io for passthru.\n");
io_ctx->ch = ch;
vbdev_passthru_queue_io(bdev_io);
} else {
SPDK_ERRLOG("ERROR on bdev_io submission!\n");
spdk_bdev_io_complete(bdev_io, SPDK_BDEV_IO_STATUS_FAILED);
}
}
}
/* We'll just call the base bdev and let it answer however if we were more
* restrictive for some reason (or less) we could get the response back
* and modify according to our purposes.
*/
static bool
vbdev_passthru_io_type_supported(void *ctx, enum spdk_bdev_io_type io_type)
{
struct vbdev_passthru *pt_node = (struct vbdev_passthru *)ctx;
return spdk_bdev_io_type_supported(pt_node->base_bdev, io_type);
}
/* We supplied this as an entry point for upper layers who want to communicate to this
* bdev. This is how they get a channel. We are passed the same context we provided when
* we created our PT vbdev in examine() which, for this bdev, is the address of one of
* our context nodes. From here we'll ask the SPDK channel code to fill out our channel
* struct and we'll keep it in our PT node.
*/
static struct spdk_io_channel *
vbdev_passthru_get_io_channel(void *ctx)
{
struct vbdev_passthru *pt_node = (struct vbdev_passthru *)ctx;
struct spdk_io_channel *pt_ch = NULL;
/* The IO channel code will allocate a channel for us which consists of
* the SPDK channel structure plus the size of our pt_io_channel struct
* that we passed in when we registered our IO device. It will then call
* our channel create callback to populate any elements that we need to
* update.
*/
pt_ch = spdk_get_io_channel(pt_node);
return pt_ch;
}
/* This is the output for get_bdevs() for this vbdev */
static int
vbdev_passthru_dump_info_json(void *ctx, struct spdk_json_write_ctx *w)
{
struct vbdev_passthru *pt_node = (struct vbdev_passthru *)ctx;
spdk_json_write_name(w, "passthru");
spdk_json_write_object_begin(w);
spdk_json_write_named_string(w, "name", spdk_bdev_get_name(&pt_node->pt_bdev));
spdk_json_write_named_string(w, "base_bdev_name", spdk_bdev_get_name(pt_node->base_bdev));
spdk_json_write_object_end(w);
return 0;
}
/* This is used to generate JSON that can configure this module to its current state. */
static int
vbdev_ext_passthru_config_json(struct spdk_json_write_ctx *w)
{
struct vbdev_passthru *pt_node;
TAILQ_FOREACH(pt_node, &g_pt_nodes, link) {
spdk_json_write_object_begin(w);
spdk_json_write_named_string(w, "method", "construct_passthru_bdev");
spdk_json_write_named_object_begin(w, "params");
spdk_json_write_named_string(w, "base_bdev_name", spdk_bdev_get_name(pt_node->base_bdev));
spdk_json_write_named_string(w, "name", spdk_bdev_get_name(&pt_node->pt_bdev));
spdk_json_write_object_end(w);
spdk_json_write_object_end(w);
}
return 0;
}
/* We provide this callback for the SPDK channel code to create a channel using
* the channel struct we provided in our module get_io_channel() entry point. Here
* we get and save off an underlying base channel of the device below us so that
* we can communicate with the base bdev on a per channel basis. If we needed
* our own poller for this vbdev, we'd register it here.
*/
static int
pt_bdev_ch_create_cb(void *io_device, void *ctx_buf)
{
struct pt_io_channel *pt_ch = ctx_buf;
struct vbdev_passthru *pt_node = io_device;
pt_ch->base_ch = spdk_bdev_get_io_channel(pt_node->base_desc);
return 0;
}
/* We provide this callback for the SPDK channel code to destroy a channel
* created with our create callback. We just need to undo anything we did
* when we created. If this bdev used its own poller, we'd unregsiter it here.
*/
static void
pt_bdev_ch_destroy_cb(void *io_device, void *ctx_buf)
{
struct pt_io_channel *pt_ch = ctx_buf;
spdk_put_io_channel(pt_ch->base_ch);
}
/* Create the passthru association from the bdev and vbdev name and insert
* on the global list. */
static int
vbdev_passthru_insert_name(const char *bdev_name, const char *vbdev_name)
{
struct bdev_names *name;
TAILQ_FOREACH(name, &g_bdev_names, link) {
if (strcmp(vbdev_name, name->vbdev_name) == 0) {
SPDK_ERRLOG("passthru bdev %s already exists\n", vbdev_name);
return -EEXIST;
}
}
name = calloc(1, sizeof(struct bdev_names));
if (!name) {
SPDK_ERRLOG("could not allocate bdev_names\n");
return -ENOMEM;
}
name->bdev_name = strdup(bdev_name);
if (!name->bdev_name) {
SPDK_ERRLOG("could not allocate name->bdev_name\n");
free(name);
return -ENOMEM;
}
name->vbdev_name = strdup(vbdev_name);
if (!name->vbdev_name) {
SPDK_ERRLOG("could not allocate name->vbdev_name\n");
free(name->bdev_name);
free(name);
return -ENOMEM;
}
TAILQ_INSERT_TAIL(&g_bdev_names, name, link);
return 0;
}
/* On init, just parse config file and build list of pt vbdevs and bdev name pairs. */
static int
vbdev_ext_passthru_init(void)
{
struct spdk_conf_section *sp = NULL;
const char *conf_bdev_name = NULL;
const char *conf_vbdev_name = NULL;
struct bdev_names *name;
int i, rc;
sp = spdk_conf_find_section(NULL, "Ext_Pt");
if (sp == NULL) {
return 0;
}
for (i = 0; ; i++) {
if (!spdk_conf_section_get_nval(sp, "PTE", i)) {
break;
}
conf_bdev_name = spdk_conf_section_get_nmval(sp, "PTE", i, 0);
if (!conf_bdev_name) {
SPDK_ERRLOG("Passthru configuration missing bdev name\n");
break;
}
conf_vbdev_name = spdk_conf_section_get_nmval(sp, "PTE", i, 1);
if (!conf_vbdev_name) {
SPDK_ERRLOG("Passthru configuration missing pt_bdev name\n");
break;
}
rc = vbdev_passthru_insert_name(conf_bdev_name, conf_vbdev_name);
if (rc != 0) {
return rc;
}
}
TAILQ_FOREACH(name, &g_bdev_names, link) {
SPDK_NOTICELOG("conf parse matched: %s\n", name->bdev_name);
}
return 0;
}
/* Called when the entire module is being torn down. */
static void
vbdev_ext_passthru_finish(void)
{
struct bdev_names *name;
while ((name = TAILQ_FIRST(&g_bdev_names))) {
TAILQ_REMOVE(&g_bdev_names, name, link);
free(name->bdev_name);
free(name->vbdev_name);
free(name);
}
}
/* During init we'll be asked how much memory we'd like passed to us
* in bev_io structures as context. Here's where we specify how
* much context we want per IO.
*/
static int
vbdev_ext_passthru_get_ctx_size(void)
{
return sizeof(struct passthru_bdev_io);
}
/* Called when SPDK wants to save the current config of this vbdev module to
* a file.
*/
static void
vbdev_ext_passthru_get_spdk_running_config(FILE *fp)
{
struct bdev_names *names = NULL;
fprintf(fp, "\n[Ext_Pt]\n");
TAILQ_FOREACH(names, &g_bdev_names, link) {
fprintf(fp, " PTE %s %s\n", names->bdev_name, names->vbdev_name);
}
fprintf(fp, "\n");
}
/* Where vbdev_ext_passthru_config_json() is used to generate per module JSON config data, this
* function is called to output any per bdev specific methods. For the PT module, there are
* none.
*/
static void
vbdev_passthru_write_config_json(struct spdk_bdev *bdev, struct spdk_json_write_ctx *w)
{
/* No config per bdev needed */
}
/* When we register our bdev this is how we specify our entry points. */
static const struct spdk_bdev_fn_table vbdev_passthru_fn_table = {
.destruct = vbdev_passthru_destruct,
.submit_request = vbdev_passthru_submit_request,
.io_type_supported = vbdev_passthru_io_type_supported,
.get_io_channel = vbdev_passthru_get_io_channel,
.dump_info_json = vbdev_passthru_dump_info_json,
.write_config_json = vbdev_passthru_write_config_json,
};
/* Called when the underlying base bdev goes away. */
static void
vbdev_passthru_base_bdev_hotremove_cb(void *ctx)
{
struct vbdev_passthru *pt_node, *tmp;
struct spdk_bdev *bdev_find = ctx;
TAILQ_FOREACH_SAFE(pt_node, &g_pt_nodes, link, tmp) {
if (bdev_find == pt_node->base_bdev) {
spdk_bdev_unregister(&pt_node->pt_bdev, NULL, NULL);
}
}
}
/* Create and register the passthru vbdev if we find it in our list of bdev names.
* This can be called either by the examine path or RPC method.
*/
static int
vbdev_passthru_register(struct spdk_bdev *bdev)
{
struct bdev_names *name;
struct vbdev_passthru *pt_node;
int rc = 0;
/* Check our list of names from config versus this bdev and if
* there's a match, create the pt_node & bdev accordingly.
*/
TAILQ_FOREACH(name, &g_bdev_names, link) {
if (strcmp(name->bdev_name, bdev->name) != 0) {
continue;
}
SPDK_NOTICELOG("Match on %s\n", bdev->name);
pt_node = calloc(1, sizeof(struct vbdev_passthru));
if (!pt_node) {
rc = -ENOMEM;
SPDK_ERRLOG("could not allocate pt_node\n");
break;
}
/* The base bdev that we're attaching to. */
pt_node->base_bdev = bdev;
pt_node->pt_bdev.name = strdup(name->vbdev_name);
if (!pt_node->pt_bdev.name) {
rc = -ENOMEM;
SPDK_ERRLOG("could not allocate pt_bdev name\n");
free(pt_node);
break;
}
pt_node->pt_bdev.product_name = "passthru";
/* Copy some properties from the underlying base bdev. */
pt_node->pt_bdev.write_cache = bdev->write_cache;
pt_node->pt_bdev.required_alignment = bdev->required_alignment;
pt_node->pt_bdev.optimal_io_boundary = bdev->optimal_io_boundary;
pt_node->pt_bdev.blocklen = bdev->blocklen;
pt_node->pt_bdev.blockcnt = bdev->blockcnt;
pt_node->pt_bdev.md_interleave = bdev->md_interleave;
pt_node->pt_bdev.md_len = bdev->md_len;
pt_node->pt_bdev.dif_type = bdev->dif_type;
pt_node->pt_bdev.dif_is_head_of_md = bdev->dif_is_head_of_md;
pt_node->pt_bdev.dif_check_flags = bdev->dif_check_flags;
/* This is the context that is passed to us when the bdev
* layer calls in so we'll save our pt_bdev node here.
*/
pt_node->pt_bdev.ctxt = pt_node;
pt_node->pt_bdev.fn_table = &vbdev_passthru_fn_table;
pt_node->pt_bdev.module = &passthru_if_external;
TAILQ_INSERT_TAIL(&g_pt_nodes, pt_node, link);
spdk_io_device_register(pt_node, pt_bdev_ch_create_cb, pt_bdev_ch_destroy_cb,
sizeof(struct pt_io_channel),
name->vbdev_name);
SPDK_NOTICELOG("io_device created at: 0x%p\n", pt_node);
rc = spdk_bdev_open(bdev, true, vbdev_passthru_base_bdev_hotremove_cb,
bdev, &pt_node->base_desc);
if (rc) {
SPDK_ERRLOG("could not open bdev %s\n", spdk_bdev_get_name(bdev));
TAILQ_REMOVE(&g_pt_nodes, pt_node, link);
spdk_io_device_unregister(pt_node, NULL);
free(pt_node->pt_bdev.name);
free(pt_node);
break;
}
SPDK_NOTICELOG("bdev opened\n");
rc = spdk_bdev_module_claim_bdev(bdev, pt_node->base_desc, pt_node->pt_bdev.module);
if (rc) {
SPDK_ERRLOG("could not claim bdev %s\n", spdk_bdev_get_name(bdev));
spdk_bdev_close(pt_node->base_desc);
TAILQ_REMOVE(&g_pt_nodes, pt_node, link);
spdk_io_device_unregister(pt_node, NULL);
free(pt_node->pt_bdev.name);
free(pt_node);
break;
}
SPDK_NOTICELOG("bdev claimed\n");
rc = spdk_bdev_register(&pt_node->pt_bdev);
if (rc) {
SPDK_ERRLOG("could not register pt_bdev\n");
spdk_bdev_module_release_bdev(&pt_node->pt_bdev);
spdk_bdev_close(pt_node->base_desc);
TAILQ_REMOVE(&g_pt_nodes, pt_node, link);
spdk_io_device_unregister(pt_node, NULL);
free(pt_node->pt_bdev.name);
free(pt_node);
break;
}
SPDK_NOTICELOG("ext_pt_bdev registered\n");
SPDK_NOTICELOG("created ext_pt_bdev for: %s\n", name->vbdev_name);
}
return rc;
}
/* Create the passthru disk from the given bdev and vbdev name. */
int
create_passthru_disk(const char *bdev_name, const char *vbdev_name)
{
struct spdk_bdev *bdev = NULL;
int rc = 0;
/* Insert the bdev into our global name list even if it doesn't exist yet,
* it may show up soon...
*/
rc = vbdev_passthru_insert_name(bdev_name, vbdev_name);
if (rc) {
return rc;
}
bdev = spdk_bdev_get_by_name(bdev_name);
if (!bdev) {
/* This is not an error, we tracked the name above and it still
* may show up later.
*/
SPDK_NOTICELOG("vbdev creation deferred pending base bdev arrival\n");
return 0;
}
return vbdev_passthru_register(bdev);
}
void
delete_passthru_disk(struct spdk_bdev *bdev, spdk_bdev_unregister_cb cb_fn, void *cb_arg)
{
struct bdev_names *name;
if (!bdev || bdev->module != &passthru_if_external) {
cb_fn(cb_arg, -ENODEV);
return;
}
/* Remove the association (vbdev, bdev) from g_bdev_names. This is required so that the
* vbdev does not get re-created if the same bdev is constructed at some other time,
* unless the underlying bdev was hot-removed.
*/
TAILQ_FOREACH(name, &g_bdev_names, link) {
if (strcmp(name->vbdev_name, bdev->name) == 0) {
TAILQ_REMOVE(&g_bdev_names, name, link);
free(name->bdev_name);
free(name->vbdev_name);
free(name);
break;
}
}
/* Additional cleanup happens in the destruct callback. */
spdk_bdev_unregister(bdev, cb_fn, cb_arg);
}
/* Because we specified this function in our pt bdev function table when we
* registered our pt bdev, we'll get this call anytime a new bdev shows up.
* Here we need to decide if we care about it and if so what to do. We
* parsed the config file at init so we check the new bdev against the list
* we built up at that time and if the user configured us to attach to this
* bdev, here's where we do it.
*/
static void
vbdev_ext_passthru_examine(struct spdk_bdev *bdev)
{
vbdev_passthru_register(bdev);
spdk_bdev_module_examine_done(&passthru_if_external);
}

View File

@ -0,0 +1,65 @@
/*-
* 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.
*/
#ifndef SPDK_VBDEV_PASSTHRU_H
#define SPDK_VBDEV_PASSTHRU_H
#include "spdk/stdinc.h"
#include "spdk/bdev.h"
#include "spdk/bdev_module.h"
#include "spdk/log.h"
#include "spdk/rpc.h"
#include "spdk/string.h"
#include "spdk/util.h"
/**
* Create new pass through bdev.
*
* \param bdev_name Bdev on which pass through vbdev will be created.
* \param vbdev_name Name of the pass through bdev.
* \return 0 on success, other on failure.
*/
int create_passthru_disk(const char *bdev_name, const char *vbdev_name);
/**
* Delete passthru bdev.
*
* \param bdev Pointer to pass through bdev.
* \param cb_fn Function to call after deletion.
* \param cb_arg Argument to pass to cb_fn.
*/
void delete_passthru_disk(struct spdk_bdev *bdev, spdk_bdev_unregister_cb cb_fn,
void *cb_arg);
#endif /* SPDK_VBDEV_PASSTHRU_H */

View File

@ -0,0 +1,142 @@
/*-
* 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 "vbdev_passthru.h"
/* Structure to hold the parameters for this RPC method. */
struct rpc_construct_passthru {
char *base_bdev_name;
char *name;
};
/* Free the allocated memory resource after the RPC handling. */
static void
free_rpc_construct_passthru(struct rpc_construct_passthru *r)
{
free(r->base_bdev_name);
free(r->name);
}
/* Structure to decode the input parameters for this RPC method. */
static const struct spdk_json_object_decoder rpc_construct_passthru_decoders[] = {
{"base_bdev_name", offsetof(struct rpc_construct_passthru, base_bdev_name), spdk_json_decode_string},
{"name", offsetof(struct rpc_construct_passthru, name), spdk_json_decode_string},
};
/* Decode the parameters for this RPC method and properly construct the passthru
* device. Error status returned in the failed cases.
*/
static void
spdk_rpc_construct_passthru_bdev(struct spdk_jsonrpc_request *request,
const struct spdk_json_val *params)
{
struct rpc_construct_passthru req = {NULL};
struct spdk_json_write_ctx *w;
int rc;
if (spdk_json_decode_object(params, rpc_construct_passthru_decoders,
SPDK_COUNTOF(rpc_construct_passthru_decoders),
&req)) {
SPDK_ERRLOG("spdk_json_decode_object failed\n");
spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR,
"spdk_json_decode_object failed");
goto cleanup;
}
rc = create_passthru_disk(req.base_bdev_name, req.name);
if (rc != 0) {
spdk_jsonrpc_send_error_response(request, rc, spdk_strerror(-rc));
goto cleanup;
}
w = spdk_jsonrpc_begin_result(request);
spdk_json_write_string(w, req.name);
spdk_jsonrpc_end_result(request, w);
cleanup:
free_rpc_construct_passthru(&req);
}
SPDK_RPC_REGISTER("construct_ext_passthru_bdev", spdk_rpc_construct_passthru_bdev, SPDK_RPC_RUNTIME)
struct rpc_delete_passthru {
char *name;
};
static void
free_rpc_delete_passthru(struct rpc_delete_passthru *req)
{
free(req->name);
}
static const struct spdk_json_object_decoder rpc_delete_passthru_decoders[] = {
{"name", offsetof(struct rpc_delete_passthru, name), spdk_json_decode_string},
};
static void
_spdk_rpc_delete_passthru_bdev_cb(void *cb_arg, int bdeverrno)
{
struct spdk_jsonrpc_request *request = cb_arg;
struct spdk_json_write_ctx *w;
w = spdk_jsonrpc_begin_result(request);
spdk_json_write_bool(w, bdeverrno == 0);
spdk_jsonrpc_end_result(request, w);
}
static void
spdk_rpc_delete_passthru_bdev(struct spdk_jsonrpc_request *request,
const struct spdk_json_val *params)
{
struct rpc_delete_passthru req = {NULL};
struct spdk_bdev *bdev;
if (spdk_json_decode_object(params, rpc_delete_passthru_decoders,
SPDK_COUNTOF(rpc_delete_passthru_decoders),
&req)) {
spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR,
"spdk_json_decode_object failed");
goto cleanup;
}
bdev = spdk_bdev_get_by_name(req.name);
if (bdev == NULL) {
spdk_jsonrpc_send_error_response(request, -ENODEV, spdk_strerror(ENODEV));
goto cleanup;
}
delete_passthru_disk(bdev, _spdk_rpc_delete_passthru_bdev_cb, request);
cleanup:
free_rpc_delete_passthru(&req);
}
SPDK_RPC_REGISTER("delete_ext_passthru_bdev", spdk_rpc_delete_passthru_bdev, SPDK_RPC_RUNTIME)

63
test/external_code/test_make.sh Executable file
View File

@ -0,0 +1,63 @@
#!/usr/bin/env bash
test_root=$(readlink -f $(dirname $0))
rootdir="$test_root/../.."
source "$rootdir/test/common/autotest_common.sh"
set -e
SPDK_DIR=$1
$SPDK_DIR/configure --with-shared --without-isal --without-ocf --disable-asan
make -C $SPDK_DIR -j$(nproc)
export SPDK_HEADER_DIR="$SPDK_DIR/include"
export SPDK_LIB_DIR="$SPDK_DIR/build/lib"
export DPDK_LIB_DIR="$SPDK_DIR/dpdk/build/lib"
# The default target is to make both the app and bdev and link them against the combined SPDK shared library libspdk.so.
run_test "external_make_tc1" make -C $test_root hello_world_bdev_shared_combo
LD_LIBRARY_PATH=$SPDK_LIB_DIR:$DPDK_LIB_DIR:"$test_root/passthru" run_test "external_run_tc1" $test_root/hello_world/hello_bdev --json $test_root/hello_world/bdev_external.conf -b TestPT
make -C $test_root clean
# Make just the application linked against the combined SPDK shared library libspdk.so.
run_test "external_make_tc2" make -C $test_root hello_world_no_bdev_shared_combo
LD_LIBRARY_PATH=$SPDK_LIB_DIR:$DPDK_LIB_DIR run_test "external_run_tc2" $test_root/hello_world/hello_bdev --json $test_root/hello_world/bdev.conf -b Malloc0
make -C $test_root clean
# Make both the application and bdev against individual SPDK shared libraries.
run_test "external_make_tc3" make -C $test_root hello_world_bdev_shared_iso
LD_LIBRARY_PATH=$SPDK_LIB_DIR:$DPDK_LIB_DIR:"$test_root/passthru" run_test "external_run_tc3" $test_root/hello_world/hello_bdev --json $test_root/hello_world/bdev_external.conf -b TestPT
make -C $test_root clean
# Make just the application linked against individual SPDK shared libraries.
run_test "external_make_tc4" make -C $test_root hello_world_no_bdev_shared_iso
LD_LIBRARY_PATH=$SPDK_LIB_DIR:$DPDK_LIB_DIR run_test "external_run_tc4" $test_root/hello_world/hello_bdev --json $test_root/hello_world/bdev.conf -b Malloc0
make -C $test_root clean
make -C $SPDK_DIR clean
$SPDK_DIR/configure --without-shared --without-isal --without-ocf --disable-asan
make -C $SPDK_DIR -j$(nproc)
# Make both the application and bdev against individual SPDK archives.
run_test "external_make_tc5" make -C $test_root hello_world_bdev_static
LD_LIBRARY_PATH=$SPDK_LIB_DIR:$DPDK_LIB_DIR:"$test_root/passthru" run_test "external_run_tc5" $test_root/hello_world/hello_bdev --json $test_root/hello_world/bdev_external.conf -b TestPT
make -C $test_root clean
# Make just the application linked against individual SPDK archives.
run_test "external_make_tc6" make -C $test_root hello_world_no_bdev_static
LD_LIBRARY_PATH=$SPDK_LIB_DIR:$DPDK_LIB_DIR run_test "external_run_tc6" $test_root/hello_world/hello_bdev --json $test_root/hello_world/bdev.conf -b Malloc0
make -C $test_root clean
make -C $SPDK_DIR -j$(nproc) clean