API Code Examples#
This section includes three examples of using the xNVMe library. These are gradually increasing in complexity, and are meant to complement the description of the Core API.
All of these examples can be run even without having an NVMe device. If you give
the argument 1GB
on the command-line, xNVMe will recognize that you want
to use the ramdisk
backend.
In the source repository, you will find more usage examples in the examples
folder.
Hello World#
In this example we open a device with the uri provided on the command line. This
is done by using the default xnvme_opts
, but this struct is where you would
specify the desired backend, sync/async/admin command interface, etc.
// SPDX-FileCopyrightText: Samsung Electronics Co., Ltd
//
// SPDX-License-Identifier: BSD-3-Clause
#include <errno.h>
#include <libxnvme.h>
int
main(int argc, char **argv)
{
struct xnvme_opts opts = xnvme_opts_default();
struct xnvme_dev *dev;
if (argc < 2) {
xnvme_cli_perr("Usage: %s <identifer>", EINVAL);
return 1;
}
dev = xnvme_dev_open(argv[1], &opts);
if (!dev) {
xnvme_cli_perr("xnvme_dev_open()", errno);
return 1;
}
if (xnvme_dev_derive_geo(dev)) {
printf("Failed deriving geometry, check your permissions.");
}
xnvme_dev_pr(dev, XNVME_PR_DEF);
xnvme_dev_close(dev);
return 0;
}
Synchronous I/O#
In this example we open a device and send a single synchronous read command.
Note, when doing synchronous IO, there is no need for a command-queue.
Instead, you get the command-context directly from the device with xnvme_cmd_ctx_from_dev()
.
The example is intended to be easily extended by adding a for-loop and submitting multiple commands.
// SPDX-FileCopyrightText: Samsung Electronics Co., Ltd
//
// SPDX-License-Identifier: BSD-3-Clause
#include <errno.h>
#include <libxnvme.h>
/**
* This example shows how send a single synchronous command
*
* - Allocate a buffer for the command payload
* - Submit the asynchronous command
* - Reporting on IO-errors
* - Teardown
*/
int
main(int argc, char **argv)
{
struct xnvme_opts opts = xnvme_opts_default();
struct xnvme_dev *dev = NULL;
uint32_t nsid;
char *buf = NULL;
size_t buf_nbytes;
int err = 0;
if (argc < 2) {
xnvme_cli_perr("Usage: %s <ident>", EINVAL);
return 1;
}
dev = xnvme_dev_open(argv[1], &opts);
if (!dev) {
xnvme_cli_perr("xnvme_dev_open()", errno);
return 1;
}
nsid = xnvme_dev_get_nsid(dev);
buf_nbytes = xnvme_dev_get_geo(dev)->nbytes;
xnvme_cli_pinf("Allocate a payload-buffer of nbytes: %zu", buf_nbytes);
buf = xnvme_buf_alloc(dev, buf_nbytes);
if (!buf) {
xnvme_cli_perr("xnvme_buf_alloc()", errno);
goto exit;
}
memset(buf, 0, buf_nbytes);
// This block would typically be a loop as more than a single command would be submitted
{
struct xnvme_cmd_ctx ctx = xnvme_cmd_ctx_from_dev(dev);
// Submit and wait for the completion of a read command
err = xnvme_nvm_read(&ctx, nsid, 0x0, 0, buf, NULL);
if (err || xnvme_cmd_ctx_cpl_status(&ctx)) {
xnvme_cli_perr("xnvme_nvm_read()", err);
xnvme_cmd_ctx_pr(&ctx, XNVME_PR_DEF);
err = err ? err : -EIO;
goto exit;
}
xnvme_cli_pinf("Submitted and completed command succesfully");
}
exit:
xnvme_buf_free(dev, buf);
xnvme_dev_close(dev);
return err;
}
Asynchronous I/O#
The typical flow for doing asynchronous IO can be reduced to the following:
Open the device with
xnvme_dev_open()
Initialize the command queue with
xnvme_queue_init()
Set the queue callback with
xnvme_queue_set_cb()
Get a command context from the queue with
xnvme_queue_get_cmd_ctx()
Submit an I/O command, such as with
xnvme_nvm_read()
(see libxnvme_nvm.h for more options)Get completion with
xnvme_queue_poke()
Terminate the command queue with
xnvme_queue_term()
In this example, we open a device and send a single asynchronous read command. The process follows these steps:
Initialize the Command Queue: The
xnvme_queue
, referred to as the command queue, is initialized with a number of command contexts equal to its capacity.Submit a Command: To submit a command, you obtain a command context from the command queue.
Process Completed Commands: When processing completed commands, you poke the command queue, and a callback function is called for each command context. At the end of the callback function, you should return the command context to the command queue.
The example is intended to be easily extended by adding a for-loop and submitting multiple commands.
// SPDX-FileCopyrightText: Samsung Electronics Co., Ltd
//
// SPDX-License-Identifier: BSD-3-Clause
#include <errno.h>
#include <libxnvme.h>
static void
cb_fn(struct xnvme_cmd_ctx *ctx, void *XNVME_UNUSED(cb_arg))
{
if (xnvme_cmd_ctx_cpl_status(ctx)) {
xnvme_cli_pinf("Command did not complete successfully");
xnvme_cmd_ctx_pr(ctx, XNVME_PR_DEF);
} else {
xnvme_cli_pinf("Command completed succesfully");
}
// Completed: Put the command-context back in the queue
xnvme_queue_put_cmd_ctx(ctx->async.queue, ctx);
}
/**
* This example shows how send a single asynchronous command
*
* - Allocate a buffer for the command payload
* - Setup a Command Queue
* | Callback functions and callback arguments
* | Using command-contexts
* - Submit the asynchronous command
* | Re-submission when busy, reap completion, waiting till empty
* - Teardown
*/
int
main(int argc, char **argv)
{
struct xnvme_opts opts = xnvme_opts_default();
struct xnvme_dev *dev = NULL;
uint32_t nsid;
char *buf = NULL;
size_t buf_nbytes;
struct xnvme_queue *queue = NULL;
const int qdepth = 16;
int ret = 0, err = 0;
if (argc < 2) {
xnvme_cli_perr("Usage: %s <ident>", EINVAL);
return 1;
}
dev = xnvme_dev_open(argv[1], &opts);
if (!dev) {
xnvme_cli_perr("xnvme_dev_open()", errno);
return 1;
}
nsid = xnvme_dev_get_nsid(dev);
// Initialize a command-queue
ret = xnvme_queue_init(dev, qdepth, 0, &queue);
if (ret) {
xnvme_cli_perr("xnvme_queue_init()", ret);
xnvme_dev_close(dev);
return 1;
}
// Set the callback function
// Note: for this example the cb_arg is NULL
// However, it would typically be a pointer to a struct
xnvme_queue_set_cb(queue, cb_fn, NULL);
buf_nbytes = xnvme_dev_get_geo(dev)->nbytes;
xnvme_cli_pinf("Allocate a payload-buffer of nbytes: %zu", buf_nbytes);
buf = xnvme_buf_alloc(dev, buf_nbytes);
if (!buf) {
xnvme_cli_perr("xnvme_buf_alloc()", errno);
goto exit;
}
memset(buf, 0, buf_nbytes);
// This block would typically be a loop as more than a single command would be submitted
{
struct xnvme_cmd_ctx *ctx = xnvme_queue_get_cmd_ctx(queue);
submit:
// Submit a read command
err = xnvme_nvm_read(ctx, nsid, 0x0, 0, buf, NULL);
switch (err) {
case 0:
xnvme_cli_pinf("Submission succeeded");
break;
// Submission failed: queue is full => process completions and try again
case -EBUSY:
case -EAGAIN:
xnvme_queue_poke(queue, 0);
goto submit;
// Submission failed: unexpected error, put the command-context back in the queue
default:
xnvme_cli_perr("xnvme_nvm_read()", err);
xnvme_queue_put_cmd_ctx(queue, ctx);
goto exit;
}
}
// Done submitting, wait for all outstanding I/O to complete as we are about to exit
ret = xnvme_queue_drain(queue);
if (ret < 0) {
xnvme_cli_perr("xnvme_queue_drain()", ret);
goto exit;
}
exit:
xnvme_buf_free(dev, buf);
xnvme_queue_term(queue);
xnvme_dev_close(dev);
return ret || err;
}