365b89e8ea
For requests that handoff queues from userspace to the kernel as well as the request to fetch reconnect parameters from the kernel, switch from using flat structures to nvlists. In particular, this will permit adding support for additional transports in the future without breaking the ABI of the structures. Note that this is an ABI break for the ioctls used by nvmf(4) and nvmft(4). Since this is only present in main I did not bother implementing compatability shims. Inspired by: imp (suggestion on a different review) Reviewed by: imp Sponsored by: Chelsio Communications Differential Revision: https://reviews.freebsd.org/D48230
299 lines
6.1 KiB
C
299 lines
6.1 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*
|
|
* Copyright (c) 2022-2024 Chelsio Communications, Inc.
|
|
* Written by: John Baldwin <jhb@FreeBSD.org>
|
|
*/
|
|
|
|
#include <sys/refcount.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "libnvmf.h"
|
|
#include "internal.h"
|
|
|
|
struct nvmf_association *
|
|
nvmf_allocate_association(enum nvmf_trtype trtype, bool controller,
|
|
const struct nvmf_association_params *params)
|
|
{
|
|
struct nvmf_transport_ops *ops;
|
|
struct nvmf_association *na;
|
|
|
|
switch (trtype) {
|
|
case NVMF_TRTYPE_TCP:
|
|
ops = &tcp_ops;
|
|
break;
|
|
default:
|
|
errno = EINVAL;
|
|
return (NULL);
|
|
}
|
|
|
|
na = ops->allocate_association(controller, params);
|
|
if (na == NULL)
|
|
return (NULL);
|
|
|
|
na->na_ops = ops;
|
|
na->na_trtype = trtype;
|
|
na->na_controller = controller;
|
|
na->na_params = *params;
|
|
na->na_last_error = NULL;
|
|
refcount_init(&na->na_refs, 1);
|
|
return (na);
|
|
}
|
|
|
|
void
|
|
nvmf_update_assocation(struct nvmf_association *na,
|
|
const struct nvme_controller_data *cdata)
|
|
{
|
|
na->na_ops->update_association(na, cdata);
|
|
}
|
|
|
|
void
|
|
nvmf_free_association(struct nvmf_association *na)
|
|
{
|
|
if (refcount_release(&na->na_refs)) {
|
|
free(na->na_last_error);
|
|
na->na_ops->free_association(na);
|
|
}
|
|
}
|
|
|
|
const char *
|
|
nvmf_association_error(const struct nvmf_association *na)
|
|
{
|
|
return (na->na_last_error);
|
|
}
|
|
|
|
void
|
|
na_clear_error(struct nvmf_association *na)
|
|
{
|
|
free(na->na_last_error);
|
|
na->na_last_error = NULL;
|
|
}
|
|
|
|
void
|
|
na_error(struct nvmf_association *na, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
char *str;
|
|
|
|
if (na->na_last_error != NULL)
|
|
return;
|
|
va_start(ap, fmt);
|
|
vasprintf(&str, fmt, ap);
|
|
va_end(ap);
|
|
na->na_last_error = str;
|
|
}
|
|
|
|
struct nvmf_qpair *
|
|
nvmf_allocate_qpair(struct nvmf_association *na,
|
|
const struct nvmf_qpair_params *params)
|
|
{
|
|
struct nvmf_qpair *qp;
|
|
|
|
na_clear_error(na);
|
|
qp = na->na_ops->allocate_qpair(na, params);
|
|
if (qp == NULL)
|
|
return (NULL);
|
|
|
|
refcount_acquire(&na->na_refs);
|
|
qp->nq_association = na;
|
|
qp->nq_admin = params->admin;
|
|
TAILQ_INIT(&qp->nq_rx_capsules);
|
|
return (qp);
|
|
}
|
|
|
|
void
|
|
nvmf_free_qpair(struct nvmf_qpair *qp)
|
|
{
|
|
struct nvmf_association *na;
|
|
struct nvmf_capsule *nc, *tc;
|
|
|
|
TAILQ_FOREACH_SAFE(nc, &qp->nq_rx_capsules, nc_link, tc) {
|
|
TAILQ_REMOVE(&qp->nq_rx_capsules, nc, nc_link);
|
|
nvmf_free_capsule(nc);
|
|
}
|
|
na = qp->nq_association;
|
|
na->na_ops->free_qpair(qp);
|
|
nvmf_free_association(na);
|
|
}
|
|
|
|
struct nvmf_capsule *
|
|
nvmf_allocate_command(struct nvmf_qpair *qp, const void *sqe)
|
|
{
|
|
struct nvmf_capsule *nc;
|
|
|
|
nc = qp->nq_association->na_ops->allocate_capsule(qp);
|
|
if (nc == NULL)
|
|
return (NULL);
|
|
|
|
nc->nc_qpair = qp;
|
|
nc->nc_qe_len = sizeof(struct nvme_command);
|
|
memcpy(&nc->nc_sqe, sqe, nc->nc_qe_len);
|
|
|
|
/* 4.2 of NVMe base spec: Fabrics always uses SGL. */
|
|
nc->nc_sqe.fuse &= ~NVMEM(NVME_CMD_PSDT);
|
|
nc->nc_sqe.fuse |= NVMEF(NVME_CMD_PSDT, NVME_PSDT_SGL);
|
|
return (nc);
|
|
}
|
|
|
|
struct nvmf_capsule *
|
|
nvmf_allocate_response(struct nvmf_qpair *qp, const void *cqe)
|
|
{
|
|
struct nvmf_capsule *nc;
|
|
|
|
nc = qp->nq_association->na_ops->allocate_capsule(qp);
|
|
if (nc == NULL)
|
|
return (NULL);
|
|
|
|
nc->nc_qpair = qp;
|
|
nc->nc_qe_len = sizeof(struct nvme_completion);
|
|
memcpy(&nc->nc_cqe, cqe, nc->nc_qe_len);
|
|
return (nc);
|
|
}
|
|
|
|
int
|
|
nvmf_capsule_append_data(struct nvmf_capsule *nc, void *buf, size_t len,
|
|
bool send)
|
|
{
|
|
if (nc->nc_qe_len == sizeof(struct nvme_completion))
|
|
return (EINVAL);
|
|
if (nc->nc_data_len != 0)
|
|
return (EBUSY);
|
|
|
|
nc->nc_data = buf;
|
|
nc->nc_data_len = len;
|
|
nc->nc_send_data = send;
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
nvmf_free_capsule(struct nvmf_capsule *nc)
|
|
{
|
|
nc->nc_qpair->nq_association->na_ops->free_capsule(nc);
|
|
}
|
|
|
|
int
|
|
nvmf_transmit_capsule(struct nvmf_capsule *nc)
|
|
{
|
|
return (nc->nc_qpair->nq_association->na_ops->transmit_capsule(nc));
|
|
}
|
|
|
|
int
|
|
nvmf_receive_capsule(struct nvmf_qpair *qp, struct nvmf_capsule **ncp)
|
|
{
|
|
return (qp->nq_association->na_ops->receive_capsule(qp, ncp));
|
|
}
|
|
|
|
const void *
|
|
nvmf_capsule_sqe(const struct nvmf_capsule *nc)
|
|
{
|
|
assert(nc->nc_qe_len == sizeof(struct nvme_command));
|
|
return (&nc->nc_sqe);
|
|
}
|
|
|
|
const void *
|
|
nvmf_capsule_cqe(const struct nvmf_capsule *nc)
|
|
{
|
|
assert(nc->nc_qe_len == sizeof(struct nvme_completion));
|
|
return (&nc->nc_cqe);
|
|
}
|
|
|
|
uint8_t
|
|
nvmf_validate_command_capsule(const struct nvmf_capsule *nc)
|
|
{
|
|
assert(nc->nc_qe_len == sizeof(struct nvme_command));
|
|
|
|
if (NVMEV(NVME_CMD_PSDT, nc->nc_sqe.fuse) != NVME_PSDT_SGL)
|
|
return (NVME_SC_INVALID_FIELD);
|
|
|
|
return (nc->nc_qpair->nq_association->na_ops->validate_command_capsule(nc));
|
|
}
|
|
|
|
size_t
|
|
nvmf_capsule_data_len(const struct nvmf_capsule *nc)
|
|
{
|
|
return (nc->nc_qpair->nq_association->na_ops->capsule_data_len(nc));
|
|
}
|
|
|
|
int
|
|
nvmf_receive_controller_data(const struct nvmf_capsule *nc,
|
|
uint32_t data_offset, void *buf, size_t len)
|
|
{
|
|
return (nc->nc_qpair->nq_association->na_ops->receive_controller_data(nc,
|
|
data_offset, buf, len));
|
|
}
|
|
|
|
int
|
|
nvmf_send_controller_data(const struct nvmf_capsule *nc, const void *buf,
|
|
size_t len)
|
|
{
|
|
return (nc->nc_qpair->nq_association->na_ops->send_controller_data(nc,
|
|
buf, len));
|
|
}
|
|
|
|
int
|
|
nvmf_kernel_handoff_params(struct nvmf_qpair *qp, nvlist_t **nvlp)
|
|
{
|
|
nvlist_t *nvl;
|
|
int error;
|
|
|
|
nvl = nvlist_create(0);
|
|
nvlist_add_bool(nvl, "admin", qp->nq_admin);
|
|
nvlist_add_bool(nvl, "sq_flow_control", qp->nq_flow_control);
|
|
nvlist_add_number(nvl, "qsize", qp->nq_qsize);
|
|
nvlist_add_number(nvl, "sqhd", qp->nq_sqhd);
|
|
if (!qp->nq_association->na_controller)
|
|
nvlist_add_number(nvl, "sqtail", qp->nq_sqtail);
|
|
qp->nq_association->na_ops->kernel_handoff_params(qp, nvl);
|
|
error = nvlist_error(nvl);
|
|
if (error != 0) {
|
|
nvlist_destroy(nvl);
|
|
return (error);
|
|
}
|
|
|
|
*nvlp = nvl;
|
|
return (0);
|
|
}
|
|
|
|
const char *
|
|
nvmf_transport_type(uint8_t trtype)
|
|
{
|
|
static _Thread_local char buf[8];
|
|
|
|
switch (trtype) {
|
|
case NVMF_TRTYPE_RDMA:
|
|
return ("RDMA");
|
|
case NVMF_TRTYPE_FC:
|
|
return ("Fibre Channel");
|
|
case NVMF_TRTYPE_TCP:
|
|
return ("TCP");
|
|
case NVMF_TRTYPE_INTRA_HOST:
|
|
return ("Intra-host");
|
|
default:
|
|
snprintf(buf, sizeof(buf), "0x%02x\n", trtype);
|
|
return (buf);
|
|
}
|
|
}
|
|
|
|
int
|
|
nvmf_pack_ioc_nvlist(struct nvmf_ioc_nv *nv, nvlist_t *nvl)
|
|
{
|
|
int error;
|
|
|
|
memset(nv, 0, sizeof(*nv));
|
|
|
|
error = nvlist_error(nvl);
|
|
if (error)
|
|
return (error);
|
|
|
|
nv->data = nvlist_pack(nvl, &nv->size);
|
|
if (nv->data == NULL)
|
|
return (ENOMEM);
|
|
|
|
return (0);
|
|
}
|