1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2023-2024 Chelsio Communications, Inc.
5 * Written by: John Baldwin <jhb@FreeBSD.org>
6 */
7
8#include <err.h>
9#include <errno.h>
10#include <libnvmf.h>
11#include <stdlib.h>
12
13#include "internal.h"
14
15struct controller {
16	struct nvmf_qpair *qp;
17
18	uint64_t cap;
19	uint32_t vs;
20	uint32_t cc;
21	uint32_t csts;
22
23	bool shutdown;
24
25	struct nvme_controller_data cdata;
26};
27
28static bool
29update_cc(struct controller *c, uint32_t new_cc)
30{
31	uint32_t changes;
32
33	if (c->shutdown)
34		return (false);
35	if (!nvmf_validate_cc(c->qp, c->cap, c->cc, new_cc))
36		return (false);
37
38	changes = c->cc ^ new_cc;
39	c->cc = new_cc;
40
41	/* Handle shutdown requests. */
42	if (NVMEV(NVME_CC_REG_SHN, changes) != 0 &&
43	    NVMEV(NVME_CC_REG_SHN, new_cc) != 0) {
44		c->csts &= ~NVMEM(NVME_CSTS_REG_SHST);
45		c->csts |= NVMEF(NVME_CSTS_REG_SHST, NVME_SHST_COMPLETE);
46		c->shutdown = true;
47	}
48
49	if (NVMEV(NVME_CC_REG_EN, changes) != 0) {
50		if (NVMEV(NVME_CC_REG_EN, new_cc) == 0) {
51			/* Controller reset. */
52			c->csts = 0;
53			c->shutdown = true;
54		} else
55			c->csts |= NVMEF(NVME_CSTS_REG_RDY, 1);
56	}
57	return (true);
58}
59
60static void
61handle_property_get(const struct controller *c, const struct nvmf_capsule *nc,
62    const struct nvmf_fabric_prop_get_cmd *pget)
63{
64	struct nvmf_fabric_prop_get_rsp rsp;
65
66	nvmf_init_cqe(&rsp, nc, 0);
67
68	switch (le32toh(pget->ofst)) {
69	case NVMF_PROP_CAP:
70		if (pget->attrib.size != NVMF_PROP_SIZE_8)
71			goto error;
72		rsp.value.u64 = htole64(c->cap);
73		break;
74	case NVMF_PROP_VS:
75		if (pget->attrib.size != NVMF_PROP_SIZE_4)
76			goto error;
77		rsp.value.u32.low = htole32(c->vs);
78		break;
79	case NVMF_PROP_CC:
80		if (pget->attrib.size != NVMF_PROP_SIZE_4)
81			goto error;
82		rsp.value.u32.low = htole32(c->cc);
83		break;
84	case NVMF_PROP_CSTS:
85		if (pget->attrib.size != NVMF_PROP_SIZE_4)
86			goto error;
87		rsp.value.u32.low = htole32(c->csts);
88		break;
89	default:
90		goto error;
91	}
92
93	nvmf_send_response(nc, &rsp);
94	return;
95error:
96	nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
97}
98
99static void
100handle_property_set(struct controller *c, const struct nvmf_capsule *nc,
101    const struct nvmf_fabric_prop_set_cmd *pset)
102{
103	switch (le32toh(pset->ofst)) {
104	case NVMF_PROP_CC:
105		if (pset->attrib.size != NVMF_PROP_SIZE_4)
106			goto error;
107		if (!update_cc(c, le32toh(pset->value.u32.low)))
108			goto error;
109		break;
110	default:
111		goto error;
112	}
113
114	nvmf_send_success(nc);
115	return;
116error:
117	nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
118}
119
120static void
121handle_fabrics_command(struct controller *c,
122    const struct nvmf_capsule *nc, const struct nvmf_fabric_cmd *fc)
123{
124	switch (fc->fctype) {
125	case NVMF_FABRIC_COMMAND_PROPERTY_GET:
126		handle_property_get(c, nc,
127		    (const struct nvmf_fabric_prop_get_cmd *)fc);
128		break;
129	case NVMF_FABRIC_COMMAND_PROPERTY_SET:
130		handle_property_set(c, nc,
131		    (const struct nvmf_fabric_prop_set_cmd *)fc);
132		break;
133	case NVMF_FABRIC_COMMAND_CONNECT:
134		warnx("CONNECT command on connected queue");
135		nvmf_send_generic_error(nc, NVME_SC_COMMAND_SEQUENCE_ERROR);
136		break;
137	case NVMF_FABRIC_COMMAND_DISCONNECT:
138		warnx("DISCONNECT command on admin queue");
139		nvmf_send_error(nc, NVME_SCT_COMMAND_SPECIFIC,
140		    NVMF_FABRIC_SC_INVALID_QUEUE_TYPE);
141		break;
142	default:
143		warnx("Unsupported fabrics command %#x", fc->fctype);
144		nvmf_send_generic_error(nc, NVME_SC_INVALID_OPCODE);
145		break;
146	}
147}
148
149static void
150handle_identify_command(const struct controller *c,
151    const struct nvmf_capsule *nc, const struct nvme_command *cmd)
152{
153	uint8_t cns;
154
155	cns = le32toh(cmd->cdw10) & 0xFF;
156	switch (cns) {
157	case 1:
158		break;
159	default:
160		warnx("Unsupported CNS %#x for IDENTIFY", cns);
161		goto error;
162	}
163
164	nvmf_send_controller_data(nc, &c->cdata, sizeof(c->cdata));
165	return;
166error:
167	nvmf_send_generic_error(nc, NVME_SC_INVALID_FIELD);
168}
169
170void
171controller_handle_admin_commands(struct controller *c, handle_command *cb,
172    void *cb_arg)
173{
174	struct nvmf_qpair *qp = c->qp;
175	const struct nvme_command *cmd;
176	struct nvmf_capsule *nc;
177	int error;
178
179	for (;;) {
180		error = nvmf_controller_receive_capsule(qp, &nc);
181		if (error != 0) {
182			if (error != ECONNRESET)
183				warnc(error, "Failed to read command capsule");
184			break;
185		}
186
187		cmd = nvmf_capsule_sqe(nc);
188
189		/*
190		 * Only permit Fabrics commands while a controller is
191		 * disabled.
192		 */
193		if (NVMEV(NVME_CC_REG_EN, c->cc) == 0 &&
194		    cmd->opc != NVME_OPC_FABRICS_COMMANDS) {
195			warnx("Unsupported admin opcode %#x whiled disabled\n",
196			    cmd->opc);
197			nvmf_send_generic_error(nc,
198			    NVME_SC_COMMAND_SEQUENCE_ERROR);
199			nvmf_free_capsule(nc);
200			continue;
201		}
202
203		if (cb(nc, cmd, cb_arg)) {
204			nvmf_free_capsule(nc);
205			continue;
206		}
207
208		switch (cmd->opc) {
209		case NVME_OPC_FABRICS_COMMANDS:
210			handle_fabrics_command(c, nc,
211			    (const struct nvmf_fabric_cmd *)cmd);
212			break;
213		case NVME_OPC_IDENTIFY:
214			handle_identify_command(c, nc, cmd);
215			break;
216		default:
217			warnx("Unsupported admin opcode %#x", cmd->opc);
218			nvmf_send_generic_error(nc, NVME_SC_INVALID_OPCODE);
219			break;
220		}
221		nvmf_free_capsule(nc);
222	}
223}
224
225struct controller *
226init_controller(struct nvmf_qpair *qp,
227    const struct nvme_controller_data *cdata)
228{
229	struct controller *c;
230
231	c = calloc(1, sizeof(*c));
232	c->qp = qp;
233	c->cap = nvmf_controller_cap(c->qp);
234	c->vs = cdata->ver;
235	c->cdata = *cdata;
236
237	return (c);
238}
239
240void
241free_controller(struct controller *c)
242{
243	free(c);
244}
245