mirror of
https://github.com/freebsd/freebsd-src.git
synced 2024-11-26 20:12:44 +00:00
ee912ee57a
The internal KATO is stored in milliseconds, so convert the command line argument (given in seconds) to milliseconds before passing to nvmf_connect(). Sponsored by: Chelsio Communications
287 lines
6.7 KiB
C
287 lines
6.7 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*
|
|
* Copyright (c) 2023-2024 Chelsio Communications, Inc.
|
|
* Written by: John Baldwin <jhb@FreeBSD.org>
|
|
*/
|
|
|
|
#include <sys/socket.h>
|
|
#include <err.h>
|
|
#include <libnvmf.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sysexits.h>
|
|
#include <unistd.h>
|
|
|
|
#include "comnd.h"
|
|
#include "fabrics.h"
|
|
|
|
/*
|
|
* Settings that are currently hardcoded but could be exposed to the
|
|
* user via additional command line options:
|
|
*
|
|
* - ADMIN queue entries
|
|
* - MaxR2T
|
|
*/
|
|
|
|
static struct options {
|
|
const char *transport;
|
|
const char *address;
|
|
const char *cntlid;
|
|
const char *subnqn;
|
|
const char *hostnqn;
|
|
uint32_t kato;
|
|
uint16_t num_io_queues;
|
|
uint16_t queue_size;
|
|
bool data_digests;
|
|
bool flow_control;
|
|
bool header_digests;
|
|
} opt = {
|
|
.transport = "tcp",
|
|
.address = NULL,
|
|
.cntlid = "dynamic",
|
|
.subnqn = NULL,
|
|
.hostnqn = NULL,
|
|
.kato = NVMF_KATO_DEFAULT / 1000,
|
|
.num_io_queues = 1,
|
|
.queue_size = 0,
|
|
.data_digests = false,
|
|
.flow_control = false,
|
|
.header_digests = false,
|
|
};
|
|
|
|
static void
|
|
tcp_association_params(struct nvmf_association_params *params)
|
|
{
|
|
params->tcp.pda = 0;
|
|
params->tcp.header_digests = opt.header_digests;
|
|
params->tcp.data_digests = opt.data_digests;
|
|
/* XXX */
|
|
params->tcp.maxr2t = 1;
|
|
}
|
|
|
|
static int
|
|
connect_nvm_controller(enum nvmf_trtype trtype, int adrfam, const char *address,
|
|
const char *port, uint16_t cntlid, const char *subnqn)
|
|
{
|
|
struct nvme_controller_data cdata;
|
|
struct nvmf_association_params aparams;
|
|
struct nvmf_qpair *admin, **io;
|
|
int error;
|
|
|
|
memset(&aparams, 0, sizeof(aparams));
|
|
aparams.sq_flow_control = opt.flow_control;
|
|
switch (trtype) {
|
|
case NVMF_TRTYPE_TCP:
|
|
tcp_association_params(&aparams);
|
|
break;
|
|
default:
|
|
warnx("Unsupported transport %s", nvmf_transport_type(trtype));
|
|
return (EX_UNAVAILABLE);
|
|
}
|
|
|
|
io = calloc(opt.num_io_queues, sizeof(*io));
|
|
error = connect_nvm_queues(&aparams, trtype, adrfam, address, port,
|
|
cntlid, subnqn, opt.hostnqn, opt.kato * 1000, &admin, io,
|
|
opt.num_io_queues, opt.queue_size, &cdata);
|
|
if (error != 0) {
|
|
free(io);
|
|
return (error);
|
|
}
|
|
|
|
error = nvmf_handoff_host(admin, opt.num_io_queues, io, &cdata);
|
|
if (error != 0) {
|
|
warnc(error, "Failed to handoff queues to kernel");
|
|
free(io);
|
|
return (EX_IOERR);
|
|
}
|
|
free(io);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
connect_discovery_entry(struct nvme_discovery_log_entry *entry)
|
|
{
|
|
int adrfam;
|
|
|
|
switch (entry->trtype) {
|
|
case NVMF_TRTYPE_TCP:
|
|
switch (entry->adrfam) {
|
|
case NVMF_ADRFAM_IPV4:
|
|
adrfam = AF_INET;
|
|
break;
|
|
case NVMF_ADRFAM_IPV6:
|
|
adrfam = AF_INET6;
|
|
break;
|
|
default:
|
|
warnx("Skipping unsupported address family for %s",
|
|
entry->subnqn);
|
|
return;
|
|
}
|
|
switch (entry->tsas.tcp.sectype) {
|
|
case NVME_TCP_SECURITY_NONE:
|
|
break;
|
|
default:
|
|
warnx("Skipping unsupported TCP security type for %s",
|
|
entry->subnqn);
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
warnx("Skipping unsupported transport %s for %s",
|
|
nvmf_transport_type(entry->trtype), entry->subnqn);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* XXX: Track portids and avoid duplicate connections for a
|
|
* given (subnqn,portid)?
|
|
*/
|
|
|
|
/* XXX: Should this make use of entry->aqsz in some way? */
|
|
connect_nvm_controller(entry->trtype, adrfam, entry->traddr,
|
|
entry->trsvcid, entry->cntlid, entry->subnqn);
|
|
}
|
|
|
|
static void
|
|
connect_discovery_log_page(struct nvmf_qpair *qp)
|
|
{
|
|
struct nvme_discovery_log *log;
|
|
int error;
|
|
|
|
error = nvmf_host_fetch_discovery_log_page(qp, &log);
|
|
if (error != 0)
|
|
errc(EX_IOERR, error, "Failed to fetch discovery log page");
|
|
|
|
for (u_int i = 0; i < log->numrec; i++)
|
|
connect_discovery_entry(&log->entries[i]);
|
|
free(log);
|
|
}
|
|
|
|
static void
|
|
discover_controllers(enum nvmf_trtype trtype, const char *address,
|
|
const char *port)
|
|
{
|
|
struct nvmf_qpair *qp;
|
|
|
|
qp = connect_discovery_adminq(trtype, address, port, opt.hostnqn);
|
|
|
|
connect_discovery_log_page(qp);
|
|
|
|
nvmf_free_qpair(qp);
|
|
}
|
|
|
|
static void
|
|
connect_fn(const struct cmd *f, int argc, char *argv[])
|
|
{
|
|
enum nvmf_trtype trtype;
|
|
const char *address, *port;
|
|
char *tofree;
|
|
u_long cntlid;
|
|
int error;
|
|
|
|
if (arg_parse(argc, argv, f))
|
|
return;
|
|
|
|
if (opt.num_io_queues <= 0)
|
|
errx(EX_USAGE, "Invalid number of I/O queues");
|
|
|
|
if (strcasecmp(opt.transport, "tcp") == 0) {
|
|
trtype = NVMF_TRTYPE_TCP;
|
|
} else
|
|
errx(EX_USAGE, "Unsupported or invalid transport");
|
|
|
|
nvmf_parse_address(opt.address, &address, &port, &tofree);
|
|
if (port == NULL)
|
|
errx(EX_USAGE, "Explicit port required");
|
|
|
|
cntlid = nvmf_parse_cntlid(opt.cntlid);
|
|
|
|
error = connect_nvm_controller(trtype, AF_UNSPEC, address, port, cntlid,
|
|
opt.subnqn);
|
|
if (error != 0)
|
|
exit(error);
|
|
|
|
free(tofree);
|
|
}
|
|
|
|
static void
|
|
connect_all_fn(const struct cmd *f, int argc, char *argv[])
|
|
{
|
|
enum nvmf_trtype trtype;
|
|
const char *address, *port;
|
|
char *tofree;
|
|
|
|
if (arg_parse(argc, argv, f))
|
|
return;
|
|
|
|
if (opt.num_io_queues <= 0)
|
|
errx(EX_USAGE, "Invalid number of I/O queues");
|
|
|
|
if (strcasecmp(opt.transport, "tcp") == 0) {
|
|
trtype = NVMF_TRTYPE_TCP;
|
|
} else
|
|
errx(EX_USAGE, "Unsupported or invalid transport");
|
|
|
|
nvmf_parse_address(opt.address, &address, &port, &tofree);
|
|
discover_controllers(trtype, address, port);
|
|
|
|
free(tofree);
|
|
}
|
|
|
|
static const struct opts connect_opts[] = {
|
|
#define OPT(l, s, t, opt, addr, desc) { l, s, t, &opt.addr, desc }
|
|
OPT("transport", 't', arg_string, opt, transport,
|
|
"Transport type"),
|
|
OPT("cntlid", 'c', arg_string, opt, cntlid,
|
|
"Controller ID"),
|
|
OPT("nr-io-queues", 'i', arg_uint16, opt, num_io_queues,
|
|
"Number of I/O queues"),
|
|
OPT("queue-size", 'Q', arg_uint16, opt, queue_size,
|
|
"Number of entries in each I/O queue"),
|
|
OPT("keep-alive-tmo", 'k', arg_uint32, opt, kato,
|
|
"Keep Alive timeout (in seconds)"),
|
|
OPT("hostnqn", 'q', arg_string, opt, hostnqn,
|
|
"Host NQN"),
|
|
OPT("flow_control", 'F', arg_none, opt, flow_control,
|
|
"Request SQ flow control"),
|
|
OPT("hdr_digests", 'g', arg_none, opt, header_digests,
|
|
"Enable TCP PDU header digests"),
|
|
OPT("data_digests", 'G', arg_none, opt, data_digests,
|
|
"Enable TCP PDU data digests"),
|
|
{ NULL, 0, arg_none, NULL, NULL }
|
|
};
|
|
#undef OPT
|
|
|
|
static const struct args connect_args[] = {
|
|
{ arg_string, &opt.address, "address" },
|
|
{ arg_string, &opt.subnqn, "SubNQN" },
|
|
{ arg_none, NULL, NULL },
|
|
};
|
|
|
|
static const struct args connect_all_args[] = {
|
|
{ arg_string, &opt.address, "address" },
|
|
{ arg_none, NULL, NULL },
|
|
};
|
|
|
|
static struct cmd connect_cmd = {
|
|
.name = "connect",
|
|
.fn = connect_fn,
|
|
.descr = "Connect to a fabrics controller",
|
|
.ctx_size = sizeof(opt),
|
|
.opts = connect_opts,
|
|
.args = connect_args,
|
|
};
|
|
|
|
static struct cmd connect_all_cmd = {
|
|
.name = "connect-all",
|
|
.fn = connect_all_fn,
|
|
.descr = "Discover and connect to fabrics controllers",
|
|
.ctx_size = sizeof(opt),
|
|
.opts = connect_opts,
|
|
.args = connect_all_args,
|
|
};
|
|
|
|
CMD_COMMAND(connect_cmd);
|
|
CMD_COMMAND(connect_all_cmd);
|