diff options
Diffstat (limited to 'sbin/nvmecontrol/fabrics.c')
| -rw-r--r-- | sbin/nvmecontrol/fabrics.c | 559 |
1 files changed, 559 insertions, 0 deletions
diff --git a/sbin/nvmecontrol/fabrics.c b/sbin/nvmecontrol/fabrics.c new file mode 100644 index 000000000000..5fdbf68127d4 --- /dev/null +++ b/sbin/nvmecontrol/fabrics.c @@ -0,0 +1,559 @@ +/*- + * 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 <netinet/in.h> +#include <arpa/inet.h> +#include <err.h> +#include <libnvmf.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +#include "fabrics.h" + +/* + * Subroutines shared by several Fabrics commands. + */ +static char nqn[NVMF_NQN_MAX_LEN]; +static uint8_t hostid[16]; +static bool hostid_initted = false; + +static bool +init_hostid(void) +{ + int error; + + if (hostid_initted) + return (true); + + error = nvmf_hostid_from_hostuuid(hostid); + if (error != 0) { + warnc(error, "Failed to generate hostid"); + return (false); + } + error = nvmf_nqn_from_hostuuid(nqn); + if (error != 0) { + warnc(error, "Failed to generate host NQN"); + return (false); + } + + hostid_initted = true; + return (true); +} + +const char * +nvmf_default_hostnqn(void) +{ + if (!init_hostid()) + exit(EX_IOERR); + return (nqn); +} + +void +nvmf_parse_address(const char *in_address, const char **address, + const char **port, char **tofree) +{ + char *cp; + + /* + * Accepts the following address formats: + * + * [IPv6 address]:port + * IPv4 address:port + * hostname:port + * [IPv6 address] + * IPv6 address + * IPv4 address + * hostname + */ + if (in_address[0] == '[') { + /* IPv6 address in square brackets. */ + cp = strchr(in_address + 1, ']'); + if (cp == NULL || cp == in_address + 1) + errx(EX_USAGE, "Invalid address %s", in_address); + *tofree = strndup(in_address + 1, cp - (in_address + 1)); + *address = *tofree; + + /* Skip over ']' */ + cp++; + switch (*cp) { + case '\0': + *port = NULL; + return; + case ':': + if (cp[1] != '\0') { + *port = cp + 1; + return; + } + /* FALLTHROUGH */ + default: + errx(EX_USAGE, "Invalid address %s", in_address); + } + } + + /* Look for the first colon. */ + cp = strchr(in_address, ':'); + if (cp == NULL) { + *address = in_address; + *port = NULL; + *tofree = NULL; + return; + } + + /* If there is another colon, assume this is an IPv6 address. */ + if (strchr(cp + 1, ':') != NULL) { + *address = in_address; + *port = NULL; + *tofree = NULL; + return; + } + + /* Both strings on either side of the colon must be non-empty. */ + if (cp == in_address || cp[1] == '\0') + errx(EX_USAGE, "Invalid address %s", in_address); + + *tofree = strndup(in_address, cp - in_address); + *address = *tofree; + + /* Skip over ':' */ + *port = cp + 1; +} + +uint16_t +nvmf_parse_cntlid(const char *cntlid) +{ + u_long value; + + if (strcasecmp(cntlid, "dynamic") == 0) + return (NVMF_CNTLID_DYNAMIC); + else if (strcasecmp(cntlid, "static") == 0) + return (NVMF_CNTLID_STATIC_ANY); + else { + value = strtoul(cntlid, NULL, 0); + + if (value > NVMF_CNTLID_STATIC_MAX) + errx(EX_USAGE, "Invalid controller ID"); + + return (value); + } +} + +static bool +tcp_qpair_params(struct nvmf_qpair_params *params, int adrfam, + const char *address, const char *port, struct addrinfo **aip, + struct addrinfo **listp) +{ + struct addrinfo hints, *ai, *list; + int error, s; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = adrfam; + hints.ai_protocol = IPPROTO_TCP; + error = getaddrinfo(address, port, &hints, &list); + if (error != 0) { + warnx("%s", gai_strerror(error)); + return (false); + } + + for (ai = list; ai != NULL; ai = ai->ai_next) { + s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (s == -1) + continue; + + if (connect(s, ai->ai_addr, ai->ai_addrlen) != 0) { + close(s); + continue; + } + + params->tcp.fd = s; + if (listp != NULL) { + *aip = ai; + *listp = list; + } else + freeaddrinfo(list); + return (true); + } + warn("Failed to connect to controller at %s:%s", address, port); + freeaddrinfo(list); + return (false); +} + +static bool +tcp_qpair_params_ai(struct nvmf_qpair_params *params, struct addrinfo *ai) +{ + int s; + + s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (s == -1) + return (false); + + if (connect(s, ai->ai_addr, ai->ai_addrlen) != 0) { + close(s); + return (false); + } + + params->tcp.fd = s; + return (true); +} + +static void +tcp_discovery_association_params(struct nvmf_association_params *params) +{ + params->tcp.pda = 0; + params->tcp.header_digests = false; + params->tcp.data_digests = false; + params->tcp.maxr2t = 1; +} + +struct nvmf_qpair * +connect_discovery_adminq(enum nvmf_trtype trtype, const char *address, + const char *port, const char *hostnqn) +{ + struct nvmf_association_params aparams; + struct nvmf_qpair_params qparams; + struct nvmf_association *na; + struct nvmf_qpair *qp; + uint64_t cap, cc, csts; + int error, timo; + + memset(&aparams, 0, sizeof(aparams)); + aparams.sq_flow_control = false; + switch (trtype) { + case NVMF_TRTYPE_TCP: + /* 7.4.9.3 Default port for discovery */ + if (port == NULL) + port = "8009"; + tcp_discovery_association_params(&aparams); + break; + default: + errx(EX_UNAVAILABLE, "Unsupported transport %s", + nvmf_transport_type(trtype)); + } + + if (!init_hostid()) + exit(EX_IOERR); + if (hostnqn != NULL) { + if (!nvmf_nqn_valid(hostnqn)) + errx(EX_USAGE, "Invalid HostNQN %s", hostnqn); + } else + hostnqn = nqn; + + na = nvmf_allocate_association(trtype, false, &aparams); + if (na == NULL) + err(EX_IOERR, "Failed to create discovery association"); + memset(&qparams, 0, sizeof(qparams)); + qparams.admin = true; + if (!tcp_qpair_params(&qparams, AF_UNSPEC, address, port, NULL, NULL)) + exit(EX_NOHOST); + qp = nvmf_connect(na, &qparams, 0, NVME_MIN_ADMIN_ENTRIES, hostid, + NVMF_CNTLID_DYNAMIC, NVMF_DISCOVERY_NQN, hostnqn, 0); + if (qp == NULL) + errx(EX_IOERR, "Failed to connect to discovery controller: %s", + nvmf_association_error(na)); + nvmf_free_association(na); + + /* Fetch Controller Capabilities Property */ + error = nvmf_read_property(qp, NVMF_PROP_CAP, 8, &cap); + if (error != 0) + errc(EX_IOERR, error, "Failed to fetch CAP"); + + /* Set Controller Configuration Property (CC.EN=1) */ + error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc); + if (error != 0) + errc(EX_IOERR, error, "Failed to fetch CC"); + + /* Clear known fields preserving any reserved fields. */ + cc &= ~(NVMEM(NVME_CC_REG_SHN) | NVMEM(NVME_CC_REG_AMS) | + NVMEM(NVME_CC_REG_MPS) | NVMEM(NVME_CC_REG_CSS)); + + /* Leave AMS, MPS, and CSS as 0. */ + + cc |= NVMEF(NVME_CC_REG_EN, 1); + + error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc); + if (error != 0) + errc(EX_IOERR, error, "Failed to set CC"); + + /* Wait for CSTS.RDY in Controller Status */ + timo = NVME_CAP_LO_TO(cap); + for (;;) { + error = nvmf_read_property(qp, NVMF_PROP_CSTS, 4, &csts); + if (error != 0) + errc(EX_IOERR, error, "Failed to fetch CSTS"); + + if (NVMEV(NVME_CSTS_REG_RDY, csts) != 0) + break; + + if (timo == 0) + errx(EX_IOERR, "Controller failed to become ready"); + timo--; + usleep(500 * 1000); + } + + return (qp); +} + +/* + * XXX: Should this accept the admin queue size as a parameter rather + * than always using NVMF_MIN_ADMIN_MAX_SQ_SIZE? + */ +static int +connect_nvm_adminq(struct nvmf_association *na, + const struct nvmf_qpair_params *params, struct nvmf_qpair **qpp, + uint16_t cntlid, const char *subnqn, const char *hostnqn, uint32_t kato, + uint16_t *mqes) +{ + struct nvmf_qpair *qp; + uint64_t cap, cc, csts; + u_int mps, mpsmin, mpsmax; + int error, timo; + + qp = nvmf_connect(na, params, 0, NVMF_MIN_ADMIN_MAX_SQ_SIZE, hostid, + cntlid, subnqn, hostnqn, kato); + if (qp == NULL) { + warnx("Failed to connect to NVM controller %s: %s", subnqn, + nvmf_association_error(na)); + return (EX_IOERR); + } + + /* Fetch Controller Capabilities Property */ + error = nvmf_read_property(qp, NVMF_PROP_CAP, 8, &cap); + if (error != 0) { + warnc(error, "Failed to fetch CAP"); + nvmf_free_qpair(qp); + return (EX_IOERR); + } + + /* Require the NVM command set. */ + if (NVME_CAP_HI_CSS_NVM(cap >> 32) == 0) { + warnx("Controller %s does not support the NVM command set", + subnqn); + nvmf_free_qpair(qp); + return (EX_UNAVAILABLE); + } + + *mqes = NVME_CAP_LO_MQES(cap); + + /* Prefer native host page size if it fits. */ + mpsmin = NVMEV(NVME_CAP_HI_REG_MPSMIN, cap >> 32); + mpsmax = NVMEV(NVME_CAP_HI_REG_MPSMAX, cap >> 32); + mps = ffs(getpagesize()) - 1; + if (mps < mpsmin + NVME_MPS_SHIFT) + mps = mpsmin; + else if (mps > mpsmax + NVME_MPS_SHIFT) + mps = mpsmax; + else + mps -= NVME_MPS_SHIFT; + + /* Configure controller. */ + error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc); + if (error != 0) { + warnc(error, "Failed to fetch CC"); + nvmf_free_qpair(qp); + return (EX_IOERR); + } + + /* Clear known fields preserving any reserved fields. */ + cc &= ~(NVMEM(NVME_CC_REG_IOCQES) | NVMEM(NVME_CC_REG_IOSQES) | + NVMEM(NVME_CC_REG_SHN) | NVMEM(NVME_CC_REG_AMS) | + NVMEM(NVME_CC_REG_MPS) | NVMEM(NVME_CC_REG_CSS)); + + cc |= NVMEF(NVME_CC_REG_IOCQES, 4); /* CQE entry size == 16 */ + cc |= NVMEF(NVME_CC_REG_IOSQES, 6); /* SEQ entry size == 64 */ + cc |= NVMEF(NVME_CC_REG_AMS, 0); /* AMS 0 (Round-robin) */ + cc |= NVMEF(NVME_CC_REG_MPS, mps); + cc |= NVMEF(NVME_CC_REG_CSS, 0); /* NVM command set */ + cc |= NVMEF(NVME_CC_REG_EN, 1); /* EN = 1 */ + + error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc); + if (error != 0) { + warnc(error, "Failed to set CC"); + nvmf_free_qpair(qp); + return (EX_IOERR); + } + + /* Wait for CSTS.RDY in Controller Status */ + timo = NVME_CAP_LO_TO(cap); + for (;;) { + error = nvmf_read_property(qp, NVMF_PROP_CSTS, 4, &csts); + if (error != 0) { + warnc(error, "Failed to fetch CSTS"); + nvmf_free_qpair(qp); + return (EX_IOERR); + } + + if (NVMEV(NVME_CSTS_REG_RDY, csts) != 0) + break; + + if (timo == 0) { + warnx("Controller failed to become ready"); + nvmf_free_qpair(qp); + return (EX_IOERR); + } + timo--; + usleep(500 * 1000); + } + + *qpp = qp; + return (0); +} + +static void +shutdown_controller(struct nvmf_qpair *qp) +{ + uint64_t cc; + int error; + + error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc); + if (error != 0) { + warnc(error, "Failed to fetch CC"); + goto out; + } + + cc |= NVMEF(NVME_CC_REG_SHN, NVME_SHN_NORMAL); + + error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc); + if (error != 0) { + warnc(error, "Failed to set CC to trigger shutdown"); + goto out; + } + +out: + nvmf_free_qpair(qp); +} + +/* Returns a value from <sysexits.h> */ +int +connect_nvm_queues(const struct nvmf_association_params *aparams, + enum nvmf_trtype trtype, int adrfam, const char *address, + const char *port, uint16_t cntlid, const char *subnqn, const char *hostnqn, + uint32_t kato, struct nvmf_qpair **admin, struct nvmf_qpair **io, + u_int num_io_queues, u_int queue_size, struct nvme_controller_data *cdata) +{ + struct nvmf_qpair_params qparams; + struct nvmf_association *na; + struct addrinfo *ai, *list; + u_int queues; + int error; + uint16_t mqes; + + switch (trtype) { + case NVMF_TRTYPE_TCP: + break; + default: + warnx("Unsupported transport %s", nvmf_transport_type(trtype)); + return (EX_UNAVAILABLE); + } + + if (!init_hostid()) + return (EX_IOERR); + if (hostnqn == NULL || !nvmf_nqn_valid(hostnqn)) { + warnx("Invalid HostNQN %s", hostnqn); + return (EX_USAGE); + } + + /* Association. */ + na = nvmf_allocate_association(trtype, false, aparams); + if (na == NULL) { + warn("Failed to create association for %s", subnqn); + return (EX_IOERR); + } + + /* Admin queue. */ + memset(&qparams, 0, sizeof(qparams)); + qparams.admin = true; + if (!tcp_qpair_params(&qparams, adrfam, address, port, &ai, &list)) { + nvmf_free_association(na); + return (EX_NOHOST); + } + error = connect_nvm_adminq(na, &qparams, admin, cntlid, subnqn, hostnqn, + kato, &mqes); + if (error != 0) { + nvmf_free_association(na); + freeaddrinfo(list); + return (error); + } + + /* Validate I/O queue size. */ + memset(io, 0, sizeof(*io) * num_io_queues); + if (queue_size == 0) + queue_size = (u_int)mqes + 1; + else if (queue_size > (u_int)mqes + 1) { + warnx("I/O queue size exceeds controller maximum (%u)", + mqes + 1); + error = EX_USAGE; + goto out; + } + + /* Fetch controller data. */ + error = nvmf_host_identify_controller(*admin, cdata); + if (error != 0) { + warnc(error, "Failed to fetch controller data for %s", subnqn); + error = EX_IOERR; + goto out; + } + + nvmf_update_assocation(na, cdata); + + error = nvmf_host_request_queues(*admin, num_io_queues, &queues); + if (error != 0) { + warnc(error, "Failed to request I/O queues"); + error = EX_IOERR; + goto out; + } + if (queues < num_io_queues) { + warnx("Controller enabled fewer I/O queues (%u) than requested (%u)", + queues, num_io_queues); + error = EX_PROTOCOL; + goto out; + } + + /* I/O queues. */ + for (u_int i = 0; i < num_io_queues; i++) { + memset(&qparams, 0, sizeof(qparams)); + qparams.admin = false; + if (!tcp_qpair_params_ai(&qparams, ai)) { + warn("Failed to connect to controller at %s:%s", + address, port); + error = EX_NOHOST; + goto out; + } + io[i] = nvmf_connect(na, &qparams, i + 1, queue_size, hostid, + nvmf_cntlid(*admin), subnqn, hostnqn, 0); + if (io[i] == NULL) { + warnx("Failed to create I/O queue: %s", + nvmf_association_error(na)); + error = EX_IOERR; + goto out; + } + } + nvmf_free_association(na); + freeaddrinfo(list); + return (0); + +out: + disconnect_nvm_queues(*admin, io, num_io_queues); + nvmf_free_association(na); + freeaddrinfo(list); + return (error); +} + +void +disconnect_nvm_queues(struct nvmf_qpair *admin, struct nvmf_qpair **io, + u_int num_io_queues) +{ + for (u_int i = 0; i < num_io_queues; i++) { + if (io[i] == NULL) + break; + nvmf_free_qpair(io[i]); + } + shutdown_controller(admin); +} |
