/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2023-2024 Chelsio Communications, Inc. * Written by: John Baldwin */ #include #include #include #include #include #include #include #include #include #include #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); } 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); } } bool tcp_qpair_params(struct nvmf_qpair_params *params, int adrfam, const char *address, const char *port) { 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; freeaddrinfo(list); return (true); } warn("Failed to connect to controller at %s:%s", address, port); return (false); } 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)) 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 */ 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; 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) { if (!nvmf_nqn_valid(hostnqn)) { warnx("Invalid HostNQN %s", hostnqn); return (EX_USAGE); } } else hostnqn = nqn; /* 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)) { 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); return (error); } /* Validate I/O queue size. */ if (queue_size == 0) queue_size = (u_int)mqes + 1; else if (queue_size > (u_int)mqes + 1) { shutdown_controller(*admin); nvmf_free_association(na); warn("I/O queue size exceeds controller maximum (%u)", mqes + 1); return (EX_USAGE); } /* Fetch controller data. */ error = nvmf_host_identify_controller(*admin, cdata); if (error != 0) { shutdown_controller(*admin); nvmf_free_association(na); warnc(error, "Failed to fetch controller data for %s", subnqn); return (EX_IOERR); } nvmf_update_assocation(na, cdata); error = nvmf_host_request_queues(*admin, num_io_queues, &queues); if (error != 0) { shutdown_controller(*admin); nvmf_free_association(na); warnc(error, "Failed to request I/O queues"); return (EX_IOERR); } if (queues < num_io_queues) { shutdown_controller(*admin); nvmf_free_association(na); warnx("Controller enabled fewer I/O queues (%u) than requested (%u)", queues, num_io_queues); return (EX_PROTOCOL); } /* I/O queues. */ memset(io, 0, sizeof(io) * num_io_queues); for (u_int i = 0; i < num_io_queues; i++) { memset(&qparams, 0, sizeof(qparams)); qparams.admin = false; if (!tcp_qpair_params(&qparams, adrfam, 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); return (0); out: for (u_int i = 0; i < num_io_queues; i++) { if (io[i] == NULL) break; nvmf_free_qpair(io[i]); } shutdown_controller(*admin); nvmf_free_association(na); return (error); }