1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2023 Chelsio Communications, Inc.
5 * Written by: John Baldwin <jhb@FreeBSD.org>
6 */
7
8#include <sys/param.h>
9#include <sys/linker.h>
10#include <sys/nv.h>
11#include <sys/time.h>
12#include <err.h>
13#include <errno.h>
14#include <fcntl.h>
15#include <libnvmf.h>
16#include <string.h>
17
18#include <cam/ctl/ctl.h>
19#include <cam/ctl/ctl_io.h>
20#include <cam/ctl/ctl_ioctl.h>
21
22#include "internal.h"
23
24static int ctl_fd = -1;
25static int ctl_port;
26
27static void
28open_ctl(void)
29{
30	if (ctl_fd > 0)
31		return;
32
33	ctl_fd = open(CTL_DEFAULT_DEV, O_RDWR);
34	if (ctl_fd == -1 && errno == ENOENT) {
35		if (kldload("ctl") == -1)
36			err(1, "Failed to load ctl.ko");
37		ctl_fd = open(CTL_DEFAULT_DEV, O_RDWR);
38	}
39	if (ctl_fd == -1)
40		err(1, "Failed to open %s", CTL_DEFAULT_DEV);
41}
42
43void
44init_ctl_port(const char *subnqn, const struct nvmf_association_params *params)
45{
46	char result_buf[256];
47	struct ctl_port_entry entry;
48	struct ctl_req req;
49	nvlist_t *nvl;
50
51	open_ctl();
52
53	nvl = nvlist_create(0);
54
55	nvlist_add_string(nvl, "subnqn", subnqn);
56
57	/* XXX: Hardcoded in discovery.c */
58	nvlist_add_stringf(nvl, "portid", "%u", 1);
59
60	nvlist_add_stringf(nvl, "max_io_qsize", "%u", params->max_io_qsize);
61
62	memset(&req, 0, sizeof(req));
63	strlcpy(req.driver, "nvmf", sizeof(req.driver));
64	req.reqtype = CTL_REQ_CREATE;
65	req.args = nvlist_pack(nvl, &req.args_len);
66	if (req.args == NULL)
67		errx(1, "Failed to pack nvlist for CTL_PORT/CTL_REQ_CREATE");
68	req.result = result_buf;
69	req.result_len = sizeof(result_buf);
70	if (ioctl(ctl_fd, CTL_PORT_REQ, &req) != 0)
71		err(1, "ioctl(CTL_PORT/CTL_REQ_CREATE)");
72	if (req.status == CTL_LUN_ERROR)
73		errx(1, "Failed to create CTL port: %s", req.error_str);
74	if (req.status != CTL_LUN_OK)
75		errx(1, "Failed to create CTL port: %d", req.status);
76
77	nvlist_destroy(nvl);
78	nvl = nvlist_unpack(result_buf, req.result_len, 0);
79	if (nvl == NULL)
80		errx(1, "Failed to unpack nvlist from CTL_PORT/CTL_REQ_CREATE");
81
82	ctl_port = nvlist_get_number(nvl, "port_id");
83	nvlist_destroy(nvl);
84
85	memset(&entry, 0, sizeof(entry));
86	entry.targ_port = ctl_port;
87	if (ioctl(ctl_fd, CTL_ENABLE_PORT, &entry) != 0)
88		errx(1, "ioctl(CTL_ENABLE_PORT)");
89}
90
91void
92shutdown_ctl_port(const char *subnqn)
93{
94	struct ctl_req req;
95	nvlist_t *nvl;
96
97	open_ctl();
98
99	nvl = nvlist_create(0);
100
101	nvlist_add_string(nvl, "subnqn", subnqn);
102
103	memset(&req, 0, sizeof(req));
104	strlcpy(req.driver, "nvmf", sizeof(req.driver));
105	req.reqtype = CTL_REQ_REMOVE;
106	req.args = nvlist_pack(nvl, &req.args_len);
107	if (req.args == NULL)
108		errx(1, "Failed to pack nvlist for CTL_PORT/CTL_REQ_REMOVE");
109	if (ioctl(ctl_fd, CTL_PORT_REQ, &req) != 0)
110		err(1, "ioctl(CTL_PORT/CTL_REQ_REMOVE)");
111	if (req.status == CTL_LUN_ERROR)
112		errx(1, "Failed to remove CTL port: %s", req.error_str);
113	if (req.status != CTL_LUN_OK)
114		errx(1, "Failed to remove CTL port: %d", req.status);
115
116	nvlist_destroy(nvl);
117}
118
119void
120ctl_handoff_qpair(struct nvmf_qpair *qp,
121    const struct nvmf_fabric_connect_cmd *cmd,
122    const struct nvmf_fabric_connect_data *data)
123{
124	struct ctl_nvmf req;
125	int error;
126
127	memset(&req, 0, sizeof(req));
128	req.type = CTL_NVMF_HANDOFF;
129	error = nvmf_handoff_controller_qpair(qp, &req.data.handoff);
130	if (error != 0) {
131		warnc(error, "Failed to prepare qpair for handoff");
132		return;
133	}
134
135	req.data.handoff.cmd = cmd;
136	req.data.handoff.data = data;
137	if (ioctl(ctl_fd, CTL_NVMF, &req) != 0)
138		warn("ioctl(CTL_NVMF/CTL_NVMF_HANDOFF)");
139}
140