Spdk/lib/nvme/nvme_ns_cmd.c
Changpeng Liu d3d6d19bef spdk: add scattered payloads support to NVMe driver
For the purpose to support different types of input scattered payloads,
such as iovs or scattered list, we define common method in the NVMe
driver, users should implement their own functions to iterate each
segment memory.

Change-Id: Id2765747296a66997518281af0db04888ffc4b53
Signed-off-by: Changpeng Liu <changpeng.liu@intel.com>
2016-01-22 14:51:53 -07:00

297 lines
8.5 KiB
C

/*-
* BSD LICENSE
*
* Copyright(c) 2010-2015 Intel Corporation. All rights reserved.
* 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 "nvme_internal.h"
/**
* \file
*
*/
static struct nvme_request *
_nvme_ns_cmd_rw(struct nvme_namespace *ns, void *payload, uint64_t lba,
uint32_t lba_count, nvme_cb_fn_t cb_fn, void *cb_arg,
uint32_t opc, nvme_req_reset_sgl_fn_t reset_sgl_fn,
nvme_req_next_sge_fn_t next_sge_fn);
static void
nvme_cb_complete_child(void *child_arg, const struct nvme_completion *cpl)
{
struct nvme_request *child = child_arg;
struct nvme_request *parent = child->parent;
parent->num_children--;
TAILQ_REMOVE(&parent->children, child, child_tailq);
if (nvme_completion_is_error(cpl)) {
memcpy(&parent->parent_status, cpl, sizeof(*cpl));
}
if (parent->num_children == 0) {
if (parent->cb_fn) {
parent->cb_fn(parent->cb_arg, &parent->parent_status);
}
nvme_free_request(parent);
}
}
static void
nvme_request_add_child(struct nvme_request *parent, struct nvme_request *child)
{
if (parent->num_children == 0) {
/*
* Defer initialization of the children TAILQ since it falls
* on a separate cacheline. This ensures we do not touch this
* cacheline except on request splitting cases, which are
* relatively rare.
*/
TAILQ_INIT(&parent->children);
parent->parent = NULL;
memset(&parent->parent_status, 0, sizeof(struct nvme_completion));
}
parent->num_children++;
TAILQ_INSERT_TAIL(&parent->children, child, child_tailq);
child->parent = parent;
child->cb_fn = nvme_cb_complete_child;
child->cb_arg = child;
}
static struct nvme_request *
_nvme_ns_cmd_split_request(struct nvme_namespace *ns, void *payload,
uint64_t lba, uint32_t lba_count,
nvme_cb_fn_t cb_fn, void *cb_arg, uint32_t opc,
struct nvme_request *req,
uint32_t sectors_per_max_io, uint32_t sector_mask,
nvme_req_reset_sgl_fn_t reset_sgl_fn,
nvme_req_next_sge_fn_t next_sge_fn)
{
uint32_t sector_size = ns->sector_size;
uint32_t remaining_lba_count = lba_count;
uint32_t offset = 0;
struct nvme_request *child;
while (remaining_lba_count > 0) {
lba_count = sectors_per_max_io - (lba & sector_mask);
lba_count = nvme_min(remaining_lba_count, lba_count);
child = _nvme_ns_cmd_rw(ns, payload, lba, lba_count, cb_fn,
cb_arg, opc, reset_sgl_fn, next_sge_fn);
if (child == NULL) {
nvme_free_request(req);
return NULL;
}
nvme_request_add_child(req, child);
remaining_lba_count -= lba_count;
lba += lba_count;
if (req->u.payload == NULL) {
child->sgl_offset = offset;
offset += lba_count * ns->sector_size;
} else
payload = (void *)((uintptr_t)payload + (lba_count * sector_size));
}
return req;
}
static struct nvme_request *
_nvme_ns_cmd_rw(struct nvme_namespace *ns, void *payload, uint64_t lba,
uint32_t lba_count, nvme_cb_fn_t cb_fn, void *cb_arg,
uint32_t opc, nvme_req_reset_sgl_fn_t reset_sgl_fn,
nvme_req_next_sge_fn_t next_sge_fn)
{
struct nvme_request *req;
struct nvme_command *cmd;
uint64_t *tmp_lba;
uint32_t sector_size;
uint32_t sectors_per_max_io;
uint32_t sectors_per_stripe;
sector_size = ns->sector_size;
sectors_per_max_io = ns->sectors_per_max_io;
sectors_per_stripe = ns->sectors_per_stripe;
req = nvme_allocate_request(payload, lba_count * sector_size, cb_fn, cb_arg);
if (req == NULL) {
return NULL;
}
req->reset_sgl_fn = reset_sgl_fn;
req->next_sge_fn = next_sge_fn;
/*
* Intel DC P3*00 NVMe controllers benefit from driver-assisted striping.
* If this controller defines a stripe boundary and this I/O spans a stripe
* boundary, split the request into multiple requests and submit each
* separately to hardware.
*/
if (sectors_per_stripe > 0 &&
(((lba & (sectors_per_stripe - 1)) + lba_count) > sectors_per_stripe)) {
return _nvme_ns_cmd_split_request(ns, payload, lba, lba_count, cb_fn, cb_arg, opc,
req, sectors_per_stripe, sectors_per_stripe - 1,
reset_sgl_fn, next_sge_fn);
} else if (lba_count > sectors_per_max_io) {
return _nvme_ns_cmd_split_request(ns, payload, lba, lba_count, cb_fn, cb_arg, opc,
req, sectors_per_max_io, 0,
reset_sgl_fn, next_sge_fn);
} else {
cmd = &req->cmd;
cmd->opc = opc;
cmd->nsid = ns->id;
tmp_lba = (uint64_t *)&cmd->cdw10;
*tmp_lba = lba;
cmd->cdw12 = lba_count - 1;
}
return req;
}
int
nvme_ns_cmd_read(struct nvme_namespace *ns, void *payload, uint64_t lba,
uint32_t lba_count, nvme_cb_fn_t cb_fn, void *cb_arg)
{
struct nvme_request *req;
req = _nvme_ns_cmd_rw(ns, payload, lba, lba_count, cb_fn, cb_arg, NVME_OPC_READ, NULL, NULL);
if (req != NULL) {
nvme_ctrlr_submit_io_request(ns->ctrlr, req);
return 0;
} else {
return ENOMEM;
}
}
int
nvme_ns_cmd_readv(struct nvme_namespace *ns, uint64_t lba, uint32_t lba_count,
nvme_cb_fn_t cb_fn, void *cb_arg,
nvme_req_reset_sgl_fn_t reset_sgl_fn,
nvme_req_next_sge_fn_t next_sge_fn)
{
struct nvme_request *req;
req = _nvme_ns_cmd_rw(ns, NULL, lba, lba_count, cb_fn, cb_arg, NVME_OPC_READ, reset_sgl_fn,
next_sge_fn);
if (req != NULL) {
nvme_ctrlr_submit_io_request(ns->ctrlr, req);
return 0;
} else {
return ENOMEM;
}
}
int
nvme_ns_cmd_write(struct nvme_namespace *ns, void *payload, uint64_t lba,
uint32_t lba_count, nvme_cb_fn_t cb_fn, void *cb_arg)
{
struct nvme_request *req;
req = _nvme_ns_cmd_rw(ns, payload, lba, lba_count, cb_fn, cb_arg, NVME_OPC_WRITE, NULL, NULL);
if (req != NULL) {
nvme_ctrlr_submit_io_request(ns->ctrlr, req);
return 0;
} else {
return ENOMEM;
}
}
int
nvme_ns_cmd_writev(struct nvme_namespace *ns, uint64_t lba, uint32_t lba_count,
nvme_cb_fn_t cb_fn, void *cb_arg,
nvme_req_reset_sgl_fn_t reset_sgl_fn,
nvme_req_next_sge_fn_t next_sge_fn)
{
struct nvme_request *req;
req = _nvme_ns_cmd_rw(ns, NULL, lba, lba_count, cb_fn, cb_arg, NVME_OPC_WRITE, reset_sgl_fn,
next_sge_fn);
if (req != NULL) {
nvme_ctrlr_submit_io_request(ns->ctrlr, req);
return 0;
} else {
return ENOMEM;
}
}
int
nvme_ns_cmd_deallocate(struct nvme_namespace *ns, void *payload,
uint16_t num_ranges, nvme_cb_fn_t cb_fn, void *cb_arg)
{
struct nvme_request *req;
struct nvme_command *cmd;
if (num_ranges == 0 || num_ranges > NVME_DATASET_MANAGEMENT_MAX_RANGES) {
return EINVAL;
}
req = nvme_allocate_request(payload,
num_ranges * sizeof(struct nvme_dsm_range),
cb_fn, cb_arg);
if (req == NULL) {
return ENOMEM;
}
cmd = &req->cmd;
cmd->opc = NVME_OPC_DATASET_MANAGEMENT;
cmd->nsid = ns->id;
/* TODO: create a delete command data structure */
cmd->cdw10 = num_ranges - 1;
cmd->cdw11 = NVME_DSM_ATTR_DEALLOCATE;
nvme_ctrlr_submit_io_request(ns->ctrlr, req);
return 0;
}
int
nvme_ns_cmd_flush(struct nvme_namespace *ns, nvme_cb_fn_t cb_fn, void *cb_arg)
{
struct nvme_request *req;
struct nvme_command *cmd;
req = nvme_allocate_request(NULL, 0, cb_fn, cb_arg);
if (req == NULL) {
return ENOMEM;
}
cmd = &req->cmd;
cmd->opc = NVME_OPC_FLUSH;
cmd->nsid = ns->id;
nvme_ctrlr_submit_io_request(ns->ctrlr, req);
return 0;
}