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/bus.h>
10#include <sys/conf.h>
11#include <sys/malloc.h>
12#include <dev/nvme/nvme.h>
13#include <dev/nvmf/nvmf.h>
14#include <dev/nvmf/nvmf_transport.h>
15#include <dev/nvmf/host/nvmf_var.h>
16
17static struct cdev *nvmf_cdev;
18
19static int
20nvmf_handoff_host(struct nvmf_handoff_host *hh)
21{
22	struct nvmf_ivars ivars;
23	device_t dev;
24	int error;
25
26	error = nvmf_init_ivars(&ivars, hh);
27	if (error != 0)
28		return (error);
29
30	bus_topo_lock();
31	dev = device_add_child(root_bus, "nvme", -1);
32	if (dev == NULL) {
33		bus_topo_unlock();
34		error = ENXIO;
35		goto out;
36	}
37
38	device_set_ivars(dev, &ivars);
39	error = device_probe_and_attach(dev);
40	device_set_ivars(dev, NULL);
41	if (error != 0)
42		device_delete_child(root_bus, dev);
43	bus_topo_unlock();
44
45out:
46	nvmf_free_ivars(&ivars);
47	return (error);
48}
49
50static bool
51nvmf_matches(device_t dev, char *name)
52{
53	struct nvmf_softc *sc = device_get_softc(dev);
54
55	if (strcmp(device_get_nameunit(dev), name) == 0)
56		return (true);
57	if (strcmp(sc->cdata->subnqn, name) == 0)
58		return (true);
59	return (false);
60}
61
62static int
63nvmf_disconnect_by_name(char *name)
64{
65	devclass_t dc;
66	device_t dev;
67	int error, unit;
68	bool found;
69
70	found = false;
71	error = 0;
72	bus_topo_lock();
73	dc = devclass_find("nvme");
74	if (dc == NULL)
75		goto out;
76
77	for (unit = 0; unit < devclass_get_maxunit(dc); unit++) {
78		dev = devclass_get_device(dc, unit);
79		if (dev == NULL)
80			continue;
81		if (device_get_driver(dev) != &nvme_nvmf_driver)
82			continue;
83		if (device_get_parent(dev) != root_bus)
84			continue;
85		if (name != NULL && !nvmf_matches(dev, name))
86			continue;
87
88		error = device_delete_child(root_bus, dev);
89		if (error != 0)
90			break;
91		found = true;
92	}
93out:
94	bus_topo_unlock();
95	if (error == 0 && !found)
96		error = ENOENT;
97	return (error);
98}
99
100static int
101nvmf_disconnect_host(const char **namep)
102{
103	char *name;
104	int error;
105
106	name = malloc(PATH_MAX, M_NVMF, M_WAITOK);
107	error = copyinstr(*namep, name, PATH_MAX, NULL);
108	if (error == 0)
109		error = nvmf_disconnect_by_name(name);
110	free(name, M_NVMF);
111	return (error);
112}
113
114static int
115nvmf_ctl_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, int flag,
116    struct thread *td)
117{
118	switch (cmd) {
119	case NVMF_HANDOFF_HOST:
120		return (nvmf_handoff_host((struct nvmf_handoff_host *)arg));
121	case NVMF_DISCONNECT_HOST:
122		return (nvmf_disconnect_host((const char **)arg));
123	case NVMF_DISCONNECT_ALL:
124		return (nvmf_disconnect_by_name(NULL));
125	default:
126		return (ENOTTY);
127	}
128}
129
130static struct cdevsw nvmf_ctl_cdevsw = {
131	.d_version = D_VERSION,
132	.d_ioctl = nvmf_ctl_ioctl
133};
134
135int
136nvmf_ctl_load(void)
137{
138	struct make_dev_args mda;
139	int error;
140
141	make_dev_args_init(&mda);
142	mda.mda_devsw = &nvmf_ctl_cdevsw;
143	mda.mda_uid = UID_ROOT;
144	mda.mda_gid = GID_WHEEL;
145	mda.mda_mode = 0600;
146	error = make_dev_s(&mda, &nvmf_cdev, "nvmf");
147	if (error != 0)
148		nvmf_cdev = NULL;
149	return (error);
150}
151
152void
153nvmf_ctl_unload(void)
154{
155	if (nvmf_cdev != NULL) {
156		destroy_dev(nvmf_cdev);
157		nvmf_cdev = NULL;
158	}
159}
160