Page MenuHomeFreeBSD

D60473.1794686109.diff
No OneTemporary

Authored By
Unknown
Size
15 KB
Referenced Files
None
Subscribers
None

D60473.1794686109.diff

diff --git a/etc/mtree/BSD.tests.dist b/etc/mtree/BSD.tests.dist
--- a/etc/mtree/BSD.tests.dist
+++ b/etc/mtree/BSD.tests.dist
@@ -567,6 +567,8 @@
..
cam
ctl
+ nvmft
+ ..
..
..
capsicum
diff --git a/tests/sys/cam/ctl/Makefile b/tests/sys/cam/ctl/Makefile
--- a/tests/sys/cam/ctl/Makefile
+++ b/tests/sys/cam/ctl/Makefile
@@ -3,6 +3,8 @@
TESTSDIR= ${TESTSBASE}/sys/cam/ctl
BINDIR=${TESTSDIR}
+TESTS_SUBDIRS+= nvmft
+
${PACKAGE}FILES+= all-supported-opcodes.txt
${PACKAGE}FILES+= ctl.subr
diff --git a/tests/sys/cam/ctl/nvmft/Makefile b/tests/sys/cam/ctl/nvmft/Makefile
new file mode 100644
--- /dev/null
+++ b/tests/sys/cam/ctl/nvmft/Makefile
@@ -0,0 +1,47 @@
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sys/cam/ctl/nvmft
+BINDIR=${TESTSDIR}
+
+${PACKAGE}FILES+= ctl.conf
+${PACKAGE}FILESMODE_ctl.conf= 440
+
+ATF_TESTS_SH+= shutdown
+
+PROGS+= nvmf_shutdown
+LIBADD+= nvmf
+CFLAGS+= -I${SRCTOP}/lib/libnvmf
+
+shutdown.sh: shutdown.head.sh shutdown.simple.sh shutdown.reenable.sh
+ cp ${.CURDIR}/shutdown.head.sh ${.TARGET}.tmp
+.for mode in normal abrupt close reset
+ sed -e "s,%%MODE%%,${mode}," ${.CURDIR}/shutdown.simple.sh >> \
+ ${.TARGET}.tmp
+.endfor
+ # close doesn't support reenable
+.for mode in normal abrupt reset
+.for rmode in normal abrupt close reset
+ sed -e "s,%%MODE%%,${mode}," -e "s,%%RMODE%%,${rmode}," \
+ ${.CURDIR}/shutdown.reenable.sh >> ${.TARGET}.tmp
+.endfor
+.endfor
+ echo "" >> ${.TARGET}.tmp
+ echo "atf_init_test_cases()" >> ${.TARGET}.tmp
+ echo "{" >> ${.TARGET}.tmp
+.for mode in normal abrupt close reset
+ echo " atf_add_test_case ${mode}" >> ${.TARGET}.tmp
+ echo " atf_add_test_case ${mode}_short_delay" >> ${.TARGET}.tmp
+ echo " atf_add_test_case ${mode}_long_delay" >> ${.TARGET}.tmp
+.endfor
+.for mode in normal abrupt reset
+.for rmode in normal abrupt close reset
+ echo " atf_add_test_case ${mode}_${rmode}" >> ${.TARGET}.tmp
+ echo " atf_add_test_case ${mode}_${rmode}_short_delay" >> \
+ ${.TARGET}.tmp
+ echo " atf_add_test_case ${mode}_${rmode}_long_delay" >> ${.TARGET}.tmp
+.endfor
+.endfor
+ echo "}" >> ${.TARGET}.tmp
+ mv ${.TARGET}.tmp ${.TARGET}
+
+.include <bsd.test.mk>
diff --git a/tests/sys/cam/ctl/nvmft/ctl.conf b/tests/sys/cam/ctl/nvmft/ctl.conf
new file mode 100644
--- /dev/null
+++ b/tests/sys/cam/ctl/nvmft/ctl.conf
@@ -0,0 +1,12 @@
+transport-group tg0 {
+ listen tcp 127.0.0.1:4420
+}
+
+controller nqn.1994-09.org.freebsd:test {
+ auth-group no-authentication
+ transport-group tg0
+ namespace 1 {
+ backend ramdisk
+ size 64M
+ }
+}
diff --git a/tests/sys/cam/ctl/nvmft/nvmf_shutdown.c b/tests/sys/cam/ctl/nvmft/nvmf_shutdown.c
new file mode 100644
--- /dev/null
+++ b/tests/sys/cam/ctl/nvmft/nvmf_shutdown.c
@@ -0,0 +1,435 @@
+/*-
+ * 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 <err.h>
+#include <inttypes.h>
+#include <libnvmf.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static bool header_digests, data_digests;
+static uint64_t cap;
+static u_int mps;
+
+enum shutdown_mode { NONE, NORMAL, ABRUPT, CLOSE, RESET };
+
+static void
+usage(void)
+{
+ fprintf(stderr,
+"nvmf_connect [-FGg] [-D reenable delay] [-R mode] [-c cntlid] [-d delay]\n"
+" [-t transport] <mode> <address:port> <nqn>\n"
+"\n"
+" Where mode is one of:\n"
+"\tclose - just close the socket\n"
+"\tnormal - normal shutdown\n"
+"\tabrupt - abrupt shutdown\n"
+"\treset - controller reset\n");
+ exit(1);
+}
+
+static enum shutdown_mode
+parse_mode(const char *s)
+{
+ if (strcasecmp(s, "normal") == 0)
+ return (NORMAL);
+ else if (strcasecmp(s, "abrupt") == 0)
+ return (ABRUPT);
+ else if (strcasecmp(s, "close") == 0)
+ return (CLOSE);
+ else if (strcasecmp(s, "reset") == 0)
+ return (RESET);
+ else
+ errx(1, "Invalid mode: %s", s);
+}
+
+static void
+tcp_association_params(struct nvmf_association_params *params)
+{
+ params->tcp.pda = 0;
+ params->tcp.header_digests = header_digests;
+ params->tcp.data_digests = data_digests;
+ params->tcp.maxr2t = 1;
+}
+
+static int
+open_socket(const char *address, const char *port)
+{
+ 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(address, port, &hints, &list);
+ if (error != 0)
+ errx(1, "%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;
+ }
+
+ freeaddrinfo(list);
+ return (s);
+ }
+ err(1, "Failed to connect to controller");
+}
+
+/* Wait for shutdown to complete by polling CSTS. */
+static void
+wait_for_shutdown(struct nvmf_qpair *qp)
+{
+ uint64_t csts;
+ int error, timo;
+
+ timo = NVME_CAP_LO_TO(cap);
+ for (;;) {
+ error = nvmf_read_property(qp, NVMF_PROP_CSTS, 4, &csts);
+ if (error != 0)
+ errc(1, error, "Failed to fetch CSTS");
+
+ switch (NVME_CSTS_GET_SHST(csts)) {
+ case NVME_SHST_COMPLETE:
+ return;
+ case NVME_SHST_OCCURRING:
+ break;
+ default:
+ errx(1, "Invalid shutdown status: %u",
+ (u_int)NVME_CSTS_GET_SHST(csts));
+ }
+
+ if (timo == 0)
+ errx(1, "Controller failed to complete shutdown");
+ timo--;
+ usleep(500 * 1000);
+ }
+}
+
+/* Wait for CSTS.RDY in Controller Status */
+static void
+wait_for_ready(struct nvmf_qpair *qp, bool ready)
+{
+ uint64_t csts, rdy;
+ int error, timo;
+
+ timo = NVME_CAP_LO_TO(cap);
+ for (;;) {
+ error = nvmf_read_property(qp, NVMF_PROP_CSTS, 4, &csts);
+ if (error != 0)
+ errc(1, error, "Failed to fetch CSTS");
+
+ rdy = NVMEV(NVME_CSTS_REG_RDY, csts);
+ if (ready) {
+ if (rdy != 0) {
+ if (NVME_CSTS_GET_SHST(csts) !=
+ NVME_SHST_NORMAL)
+ errx(1,
+ "Invalid shutdown status for ready controller: %u",
+ (u_int)NVME_CSTS_GET_SHST(csts));
+ break;
+ }
+ } else {
+ if (rdy == 0)
+ break;
+ }
+
+ if (timo == 0)
+ errx(1, "Controller failed to %s", ready ?
+ "become ready" : "reset");
+ timo--;
+ usleep(500 * 1000);
+ }
+}
+
+static void
+validate_idle_csts(struct nvmf_qpair *qp)
+{
+ uint64_t csts;
+ int error;
+
+ error = nvmf_read_property(qp, NVMF_PROP_CSTS, 4, &csts);
+ if (error != 0)
+ errc(1, error, "Failed to fetch CSTS");
+
+ if (NVMEV(NVME_CSTS_REG_RDY, csts) != 0)
+ errx(1, "Idle controller is ready");
+
+ if (NVMEV(NVME_CSTS_REG_CFS, csts) != 0)
+ errx(1, "Idle controller has fatal error");
+}
+
+static void
+enable_controller(struct nvmf_qpair *qp)
+{
+ uint64_t cc;
+ int error;
+
+ validate_idle_csts(qp);
+
+ error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
+ if (error != 0)
+ errc(1, error, "Failed to fetch CC");
+
+ /* 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)
+ errc(1, error, "Failed to set CC");
+
+ wait_for_ready(qp, true);
+}
+
+static struct nvmf_qpair *
+connect_admin_queue(struct nvmf_association *na,
+ const struct nvmf_qpair_params *params, const uint8_t hostid[16],
+ uint16_t cntlid, const char *hostnqn, const char *subnqn)
+{
+ struct nvmf_qpair *qp;
+ u_int mpsmin, mpsmax;
+ int error;
+
+ qp = nvmf_connect(na, params, 0, NVMF_MIN_ADMIN_MAX_SQ_SIZE, hostid,
+ cntlid, subnqn, hostnqn, 0);
+ if (qp == NULL)
+ return (NULL);
+
+ error = nvmf_read_property(qp, NVMF_PROP_CAP, 8, &cap);
+ if (error != 0)
+ errc(1, error, "Failed to fetch CAP");
+
+ /* Require the NVM command set. */
+ if (NVME_CAP_HI_CSS_NVM(cap >> 32) == 0)
+ errx(1, "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. */
+ enable_controller(qp);
+
+ return (qp);
+}
+
+static void
+shutdown_controller(struct nvmf_qpair *qp, bool abrupt)
+{
+ uint64_t cc;
+ int error;
+
+ error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
+ if (error != 0)
+ errc(1, error, "Failed to fetch CC");
+
+ if (abrupt)
+ cc |= NVMEF(NVME_CC_REG_SHN, NVME_SHN_ABRUPT);
+ else
+ cc |= NVMEF(NVME_CC_REG_SHN, NVME_SHN_NORMAL);
+
+ error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc);
+ if (error != 0)
+ errc(1, error, "Failed to set CC to trigger shutdown");
+}
+
+static void
+reset_controller(struct nvmf_qpair *qp)
+{
+ uint64_t cc;
+ int error;
+
+ error = nvmf_read_property(qp, NVMF_PROP_CC, 4, &cc);
+ if (error != 0)
+ errc(1, error, "Failed to fetch CC");
+
+ cc &= ~NVMEM(NVME_CC_REG_EN);
+
+ error = nvmf_write_property(qp, NVMF_PROP_CC, 4, cc);
+ if (error != 0)
+ errc(1, error, "Failed to set CC to reset controller");
+}
+
+int
+main(int ac, char **av)
+{
+ const char *transport;
+ char *address, *port;
+ struct nvmf_association_params aparams;
+ struct nvmf_qpair_params qparams;
+ struct nvmf_association *na;
+ struct nvmf_qpair *admin;
+ char hostnqn[NVMF_NQN_MAX_LEN];
+ uint8_t hostid[16];
+ enum nvmf_trtype trtype;
+ enum shutdown_mode mode, reenable_mode;
+ u_int cntlid, delay, reenable_delay;
+ int ch, error, s;
+ bool flow_control;
+
+ cntlid = NVMF_CNTLID_DYNAMIC;
+ delay = 0;
+ flow_control = false;
+ mode = NONE;
+ reenable_delay = 0;
+ reenable_mode = NONE;
+ transport = "tcp";
+ while ((ch = getopt(ac, av, "D:FGR:c:d:gt:")) != -1) {
+ switch (ch) {
+ case 'D':
+ reenable_delay = strtoumax(optarg, NULL, 0);
+ break;
+ case 'F':
+ flow_control = true;
+ break;
+ case 'G':
+ data_digests = true;
+ break;
+ case 'R':
+ reenable_mode = parse_mode(optarg);
+ break;
+ case 'c':
+ if (strcasecmp(optarg, "dynamic") == 0)
+ cntlid = NVMF_CNTLID_DYNAMIC;
+ else if (strcasecmp(optarg, "static") == 0)
+ cntlid = NVMF_CNTLID_STATIC_ANY;
+ else
+ cntlid = strtoul(optarg, NULL, 0);
+ break;
+ case 'd':
+ delay = strtoumax(optarg, NULL, 0);
+ break;
+ case 'g':
+ header_digests = true;
+ break;
+ case 't':
+ transport = optarg;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ av += optind;
+ ac -= optind;
+
+ if (ac != 3)
+ usage();
+
+ mode = parse_mode(av[0]);
+
+ if (mode == CLOSE && reenable_mode != NONE)
+ errx(1, "close mode doesn't support re-enabling");
+
+ address = av[1];
+ port = strrchr(address, ':');
+ if (port == NULL || port[1] == '\0')
+ errx(1, "Invalid address %s", address);
+ *port = '\0';
+ port++;
+
+ memset(&aparams, 0, sizeof(aparams));
+ aparams.sq_flow_control = flow_control;
+ if (strcasecmp(transport, "tcp") == 0) {
+ trtype = NVMF_TRTYPE_TCP;
+ tcp_association_params(&aparams);
+ } else
+ errx(1, "Invalid transport %s", transport);
+
+ error = nvmf_hostid_from_hostuuid(hostid);
+ if (error != 0)
+ errc(1, error, "Failed to generate hostid");
+ error = nvmf_nqn_from_hostuuid(hostnqn);
+ if (error != 0)
+ errc(1, error, "Failed to generate host NQN");
+
+ na = nvmf_allocate_association(trtype, false, &aparams);
+ if (na == NULL)
+ err(1, "Failed to create association");
+
+ s = open_socket(address, port);
+ memset(&qparams, 0, sizeof(qparams));
+ qparams.admin = true;
+ qparams.tcp.fd = s;
+
+ admin = connect_admin_queue(na, &qparams, hostid, cntlid, hostnqn,
+ av[2]);
+ if (admin == NULL)
+ errx(1, "Failed to create admin queue: %s",
+ nvmf_association_error(na));
+ nvmf_free_association(na);
+
+ switch (mode) {
+ case CLOSE:
+ break;
+ case NORMAL:
+ case ABRUPT:
+ shutdown_controller(admin, mode == ABRUPT);
+ if (reenable_mode != NONE)
+ wait_for_shutdown(admin);
+ break;
+ case RESET:
+ reset_controller(admin);
+ break;
+ case NONE:
+ __unreachable();
+ }
+
+ if (reenable_mode != NONE) {
+ wait_for_ready(admin, false);
+ if (reenable_delay != 0)
+ sleep(reenable_delay);
+ enable_controller(admin);
+ switch (reenable_mode) {
+ case CLOSE:
+ break;
+ case NORMAL:
+ case ABRUPT:
+ shutdown_controller(admin, mode == ABRUPT);
+ break;
+ case RESET:
+ reset_controller(admin);
+ break;
+ case NONE:
+ __unreachable();
+ }
+ }
+
+ if (delay != 0)
+ sleep(delay);
+
+ nvmf_free_qpair(admin);
+ close(s);
+ return (0);
+}
diff --git a/tests/sys/cam/ctl/nvmft/shutdown.head.sh b/tests/sys/cam/ctl/nvmft/shutdown.head.sh
new file mode 100644
--- /dev/null
+++ b/tests/sys/cam/ctl/nvmft/shutdown.head.sh
@@ -0,0 +1,57 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2026 Chelsio Communications, Inc.
+
+require()
+{
+ atf_set "require.config" "nqn address port"
+}
+
+fetch_vars()
+{
+ CMD=$(atf_get_srcdir)/nvmf_shutdown
+ NQN=$(atf_config_get nqn)
+ ADDRESS=$(atf_config_get address):$(atf_config_get port)
+}
+
+# $1 - mode
+# $2 - delay (optional)
+shutdown_test()
+{
+ local delay
+
+ if [ "$#" -eq 2 ]; then
+ delay="-d $2"
+ fi
+
+ fetch_vars
+
+ atf_check $CMD $delay $1 "$ADDRESS" "$NQN"
+}
+
+# $1 - mode
+# $2 - reenable mode
+# $3 - reenable delay (optional)
+reenable_test()
+{
+ local delay
+
+ if [ "$#" -eq 3 ]; then
+ delay="-D $3"
+ fi
+
+ fetch_vars
+
+ atf_check $CMD $delay -R $2 $1 "$ADDRESS" "$NQN"
+}
+
+# $1 - mode
+# $2 - reenable mode
+# $3 - reenable delay
+reenable_xfail()
+{
+ fetch_vars
+
+ atf_check -s exit:1 -e match:"Connection reset" \
+ $CMD -D $3 -R $2 $1 "$ADDRESS" "$NQN"
+}
diff --git a/tests/sys/cam/ctl/nvmft/shutdown.reenable.sh b/tests/sys/cam/ctl/nvmft/shutdown.reenable.sh
new file mode 100644
--- /dev/null
+++ b/tests/sys/cam/ctl/nvmft/shutdown.reenable.sh
@@ -0,0 +1,35 @@
+atf_test_case %%MODE%%_%%RMODE%%
+%%MODE%%_%%RMODE%%_head()
+{
+ atf_set "descr" "%%MODE%% reenable then %%RMODE%% with no delay"
+ require
+}
+
+%%MODE%%_%%RMODE%%_body()
+{
+ reenable_test %%MODE%% %%RMODE%%
+}
+
+atf_test_case %%MODE%%_%%RMODE%%_short_delay
+%%MODE%%_%%RMODE%%_short_delay_head()
+{
+ atf_set "descr" "%%MODE%% reenable then %%RMODE%% with 1 second delay"
+ require
+}
+
+%%MODE%%_%%RMODE%%_short_delay_body()
+{
+ reenable_test %%MODE%% %%RMODE%% 1
+}
+
+atf_test_case %%MODE%%_%%RMODE%%_long_delay
+%%MODE%%_%%RMODE%%_long_delay_head()
+{
+ atf_set "descr" "%%MODE%% reenable then %%RMODE%% with 130 second delay"
+ require
+}
+
+%%MODE%%_%%RMODE%%_long_delay_body()
+{
+ reenable_xfail %%MODE%% %%RMODE%% 130
+}
diff --git a/tests/sys/cam/ctl/nvmft/shutdown.simple.sh b/tests/sys/cam/ctl/nvmft/shutdown.simple.sh
new file mode 100644
--- /dev/null
+++ b/tests/sys/cam/ctl/nvmft/shutdown.simple.sh
@@ -0,0 +1,35 @@
+atf_test_case %%MODE%%
+%%MODE%%_head()
+{
+ atf_set "descr" "%%MODE%% with no delay"
+ require
+}
+
+%%MODE%%_body()
+{
+ shutdown_test %%MODE%%
+}
+
+atf_test_case %%MODE%%_short_delay
+%%MODE%%_short_delay_head()
+{
+ atf_set "descr" "%%MODE%% with 1 second delay"
+ require
+}
+
+%%MODE%%_short_delay_body()
+{
+ shutdown_test %%MODE%% 1
+}
+
+atf_test_case %%MODE%%_long_delay
+%%MODE%%_long_delay_head()
+{
+ atf_set "descr" "%%MODE%% with 130 second delay"
+ require
+}
+
+%%MODE%%_long_delay_body()
+{
+ shutdown_test %%MODE%% 130
+}

File Metadata

Mime Type
text/plain
Expires
Sat, Nov 14, 7:55 PM (3 h, 11 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
41731554
Default Alt Text
D60473.1794686109.diff (15 KB)

Event Timeline