Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F175173649
D60478.id189069.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
10 KB
Referenced Files
None
Subscribers
None
D60478.id189069.diff
View Options
diff --git a/tests/sys/cam/ctl/nvmft/Makefile b/tests/sys/cam/ctl/nvmft/Makefile
--- a/tests/sys/cam/ctl/nvmft/Makefile
+++ b/tests/sys/cam/ctl/nvmft/Makefile
@@ -6,6 +6,7 @@
${PACKAGE}FILES+= ctl.conf
${PACKAGE}FILESMODE_ctl.conf= 440
+ATF_TESTS_C+= nvmft
ATF_TESTS_SH+= shutdown
PROGS+= nvmf_shutdown
diff --git a/tests/sys/cam/ctl/nvmft/nvmft.c b/tests/sys/cam/ctl/nvmft/nvmft.c
new file mode 100644
--- /dev/null
+++ b/tests/sys/cam/ctl/nvmft/nvmft.c
@@ -0,0 +1,387 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2023,2026 Chelsio Communications, Inc.
+ * Written by: John Baldwin <jhb@FreeBSD.org>
+ */
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <libnvmf.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <atf-c.h>
+
+static void
+require(atf_tc_t *tc)
+{
+ atf_tc_set_md_var(tc, "require.config", "address port nqn");
+}
+
+static void
+nvmf_init_sqe(void *sqe, uint8_t opcode)
+{
+ struct nvme_command *cmd = sqe;
+
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->opc = opcode;
+}
+
+static void
+nvmf_init_fabrics_sqe(void *sqe, uint8_t fctype)
+{
+ struct nvmf_capsule_cmd *cmd = sqe;
+
+ nvmf_init_sqe(sqe, NVME_OPC_FABRICS_COMMANDS);
+ cmd->fctype = fctype;
+}
+
+static void
+init_hostid(uint8_t hostid[16], char hostnqn[NVMF_NQN_MAX_LEN])
+{
+ int error;
+
+ error = nvmf_hostid_from_hostuuid(hostid);
+ if (error != 0)
+ atf_tc_fail("Failed to generate hostid: %s", strerror(error));
+ error = nvmf_nqn_from_hostuuid(hostnqn);
+ if (error != 0)
+ atf_tc_fail("Failed to generate host NQN: %s", strerror(error));
+}
+
+static void
+tcp_association_params(const atf_tc_t *tc,
+ struct nvmf_association_params *params)
+{
+ params->tcp.pda = 0;
+ params->tcp.header_digests =
+ atf_tc_get_config_var_as_bool_wd(tc, "header_digests", false);
+ params->tcp.data_digests =
+ atf_tc_get_config_var_as_bool_wd(tc, "data_digests", false);
+ params->tcp.maxr2t = 1;
+}
+
+static struct nvmf_association *
+create_association(const atf_tc_t *tc)
+{
+ struct nvmf_association_params aparams;
+ struct nvmf_association *na;
+ const char *transport;
+ enum nvmf_trtype trtype;
+
+ transport = atf_tc_get_config_var_wd(tc, "transport", "tcp");
+
+ memset(&aparams, 0, sizeof(aparams));
+ aparams.sq_flow_control =
+ atf_tc_get_config_var_as_bool_wd(tc, "flow_control", false);
+ if (strcasecmp(transport, "tcp") == 0) {
+ trtype = NVMF_TRTYPE_TCP;
+ tcp_association_params(tc, &aparams);
+ } else
+ atf_tc_fail("Invalid transport %s", transport);
+
+ na = nvmf_allocate_association(trtype, false, &aparams);
+ if (na == NULL)
+ atf_tc_fail("Failed to create association: %s",
+ strerror(errno));
+ return (na);
+}
+
+static int
+open_socket(const atf_tc_t *tc, struct addrinfo **aip, struct addrinfo **listp)
+{
+ struct addrinfo hints, *ai, *list;
+ int error, s;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_protocol = IPPROTO_TCP;
+ error = getaddrinfo(atf_tc_get_config_var(tc, "address"),
+ atf_tc_get_config_var(tc, "port"), &hints, &list);
+ if (error != 0)
+ atf_tc_fail("%s", gai_strerror(error));
+
+ 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;
+ }
+
+ if (listp != NULL) {
+ *aip = ai;
+ *listp = list;
+ } else
+ freeaddrinfo(list);
+ return (s);
+ }
+ atf_tc_fail("Failed to connect to controller: %s", strerror(errno));
+}
+
+static int
+open_socket_ai(struct addrinfo *ai)
+{
+ int s;
+
+ s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (s == -1)
+ atf_tc_fail("socket: %s", strerror(errno));
+
+ if (connect(s, ai->ai_addr, ai->ai_addrlen) != 0)
+ atf_tc_fail("connect: %s", strerror(errno));
+
+ return (s);
+}
+
+static uint16_t
+parse_cntlid(const atf_tc_t *tc)
+{
+ const char *cntlid;
+ char *cp;
+ u_long val;
+
+ cntlid = atf_tc_get_config_var_wd(tc, "cntlid", "dynamic");
+ if (strcasecmp(cntlid, "dynamic") == 0)
+ return (NVMF_CNTLID_DYNAMIC);
+ else if (strcasecmp(cntlid, "static") == 0)
+ return (NVMF_CNTLID_STATIC_ANY);
+
+ val = strtoul(cntlid, &cp, 0);
+ if (*cp != '\0' || cp == cntlid || val > NVMF_CNTLID_STATIC_MAX)
+ atf_tc_fail("Invalid controller ID");
+ return (val);
+}
+
+static struct nvmf_qpair *
+connect_admin_queue(const atf_tc_t *tc, struct nvmf_association *na,
+ const struct nvmf_qpair_params *params, const uint8_t hostid[16],
+ const char *hostnqn)
+{
+ struct nvmf_qpair *qp;
+ const char *subnqn;
+ uint64_t cap, cc, csts;
+ u_int mps, mpsmin, mpsmax;
+ int error, timo;
+ uint16_t cntlid;
+
+ cntlid = parse_cntlid(tc);
+ subnqn = atf_tc_get_config_var(tc, "nqn");
+ qp = nvmf_connect(na, params, 0, NVMF_MIN_ADMIN_MAX_SQ_SIZE, hostid,
+ cntlid, subnqn, hostnqn, 0);
+ if (qp == NULL)
+ return (NULL);
+
+ /* Fetch Controller Capabilities Property */
+ error = nvmf_read_property(qp, NVMF_PROP_CAP, 8, &cap);
+ if (error != 0)
+ atf_tc_fail("Failed to fetch CAP: %s", strerror(error));
+ timo = NVME_CAP_LO_TO(cap);
+
+ /* Require the NVM command set. */
+ if (NVME_CAP_HI_CSS_NVM(cap >> 32) == 0)
+ atf_tc_fail("Controller does not support the NVM command set");
+
+ /* 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 + 12)
+ mps = mpsmin;
+ else if (mps > mpsmax + 12)
+ mps = mpsmax;
+ else
+ mps -= 12;
+
+ /* Configure controller. */
+ error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
+ if (error != 0)
+ atf_tc_fail("Failed to fetch CC: %s", strerror(error));
+
+ /* 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); /* SQE 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)
+ atf_tc_fail("Failed to fetch CC: %s", strerror(error));
+
+ /* 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)
+ atf_tc_fail("Failed to fetch CSTS: %s",
+ strerror(error));
+
+ if (NVMEV(NVME_CSTS_REG_RDY, csts) != 0)
+ break;
+
+ if (timo == 0)
+ atf_tc_fail("Controller failed to become ready");
+ timo--;
+ usleep(500 * 1000);
+ }
+
+ return (qp);
+}
+
+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)
+ atf_tc_fail("Failed to fetch CC: %s", strerror(error));
+
+ cc |= NVMEF(NVME_CC_REG_SHN, NVME_SHN_NORMAL);
+
+ error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc);
+ if (error != 0)
+ atf_tc_fail("Failed to set CC to trigger shutdown: %s",
+ strerror(error));
+}
+
+/*
+ * Test that an I/O queue with a byte-swapped queue ID that is
+ * out-of-bounds is correctly rejected.
+ */
+ATF_TC(invalid_qid);
+ATF_TC_HEAD(invalid_qid, tc)
+{
+ require(tc);
+}
+ATF_TC_BODY(invalid_qid, tc)
+{
+ struct nvme_controller_data cdata;
+ struct nvmf_fabric_connect_cmd cmd;
+ struct nvmf_fabric_connect_data data;
+ struct nvmf_qpair_params qparams;
+ const struct nvmf_fabric_connect_rsp *rsp;
+ struct nvmf_association *na;
+ struct nvmf_qpair *admin, *io;
+ struct nvmf_capsule *cc, *rc;
+ char hostnqn[NVMF_NQN_MAX_LEN];
+ uint8_t hostid[16];
+ struct addrinfo *ai, *list;
+ int as, error, is, queues;
+ uint16_t status;
+
+ init_hostid(hostid, hostnqn);
+
+ na = create_association(tc);
+
+ as = open_socket(tc, &ai, &list);
+ memset(&qparams, 0, sizeof(qparams));
+ qparams.admin = true;
+ qparams.tcp.fd = as;
+
+ admin = connect_admin_queue(tc, na, &qparams, hostid, hostnqn);
+ if (admin == NULL)
+ atf_tc_fail("Failed to create admin queue: %s",
+ nvmf_association_error(na));
+ nvmf_free_association(na);
+
+ error = nvmf_host_identify_controller(admin, &cdata);
+ if (error != 0)
+ atf_tc_fail("Failed to fetch controller data: %s",
+ strerror(error));
+ nvmf_update_assocation(na, &cdata);
+
+ error = nvmf_host_request_queues(admin, 1, &queues);
+ if (error != 0)
+ atf_tc_fail("Failed to request I/O queues: %s",
+ strerror(error));
+
+ is = open_socket_ai(ai);
+ freeaddrinfo(list);
+ memset(&qparams, 0, sizeof(qparams));
+ qparams.admin = false;
+ qparams.tcp.fd = is;
+
+ /*
+ * Manually construct queue pair and send CONNECT command to
+ * verify error from controller.
+ */
+ io = nvmf_allocate_qpair(na, &qparams);
+ if (io == NULL)
+ atf_tc_fail("Failed to create I/O queue: %s",
+ nvmf_association_error(na));
+
+ memset(&cmd, 0, sizeof(cmd));
+ nvmf_init_fabrics_sqe(&cmd, NVMF_FABRIC_COMMAND_CONNECT);
+ cmd.recfmt = 0;
+ cmd.qid = htole16(0x100);
+
+ cmd.sqsize = htole16(NVME_MIN_IO_ENTRIES - 1);
+ if (!atf_tc_get_config_var_as_bool_wd(tc, "flow_control", false))
+ cmd.cattr |= NVMF_CONNECT_ATTR_DISABLE_SQ_FC;
+
+ cc = nvmf_allocate_command(io, &cmd);
+ if (cc == NULL)
+ atf_tc_fail("Failed to allocate command capsule: %s",
+ strerror(errno));
+
+ memset(&data, 0, sizeof(data));
+ memcpy(data.hostid, hostid, sizeof(data.hostid));
+ data.cntlid = htole16(nvmf_cntlid(admin));
+ strlcpy(data.subnqn, atf_tc_get_config_var(tc, "nqn"),
+ sizeof(data.subnqn));
+ strlcpy(data.hostnqn, hostnqn, sizeof(data.hostnqn));
+
+ error = nvmf_capsule_append_data(cc, &data, sizeof(data), true);
+ if (error != 0)
+ atf_tc_fail("Failed to append data to CONNECT capsule: %s",
+ strerror(error));
+
+ error = nvmf_transmit_capsule(cc);
+ if (error != 0)
+ atf_tc_fail("Failed to transmit CONNECT capsule: %s",
+ strerror(errno));
+
+ error = nvmf_receive_capsule(io, &rc);
+ if (error != 0)
+ atf_tc_fail("Failed to receive CONNECT response: %s",
+ strerror(error));
+
+ rsp = nvmf_capsule_cqe(rc);
+ status = le16toh(rsp->status);
+ ATF_REQUIRE(status != 0);
+ ATF_REQUIRE(NVME_STATUS_GET_SCT(status) == NVME_SCT_COMMAND_SPECIFIC);
+ ATF_REQUIRE(NVME_STATUS_GET_SC(status) == NVMF_FABRIC_SC_INVALID_PARAM);
+ ATF_REQUIRE(rsp->status_code_specific.invalid.iattr == 0);
+ ATF_REQUIRE(rsp->status_code_specific.invalid.ipo ==
+ offsetof(struct nvmf_fabric_connect_cmd, qid));
+
+ nvmf_free_qpair(io);
+ close(is);
+
+ shutdown_controller(admin);
+ nvmf_free_qpair(admin);
+ close(as);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, invalid_qid);
+
+ return (atf_no_error());
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Oct 9, 9:00 PM (1 h, 24 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40503593
Default Alt Text
D60478.id189069.diff (10 KB)
Attached To
Mode
D60478: nvmft: Add a test for an I/O queue with a byte-swapped queue ID
Attached
Detach File
Event Timeline
Log In to Comment