#include <sys/param.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/malloc.h>
#include <sys/nv.h>
#include <dev/nvme/nvme.h>
#include <dev/nvmf/nvmf.h>
#include <dev/nvmf/nvmf_transport.h>
#include <dev/nvmf/host/nvmf_var.h>
static struct cdev *nvmf_cdev;
static int
nvmf_handoff_host(struct nvmf_ioc_nv *nv)
{
nvlist_t *nvl;
device_t dev;
int error;
error = nvmf_copyin_handoff(nv, &nvl);
if (error != 0)
return (error);
bus_topo_lock();
dev = device_add_child(root_bus, "nvme", DEVICE_UNIT_ANY);
if (dev == NULL) {
bus_topo_unlock();
error = ENXIO;
goto out;
}
device_set_ivars(dev, nvl);
error = device_probe_and_attach(dev);
device_set_ivars(dev, NULL);
if (error != 0)
device_delete_child(root_bus, dev);
bus_topo_unlock();
out:
nvlist_destroy(nvl);
return (error);
}
static bool
nvmf_matches(device_t dev, char *name)
{
struct nvmf_softc *sc = device_get_softc(dev);
if (strcmp(device_get_nameunit(dev), name) == 0)
return (true);
if (strcmp(sc->cdata->subnqn, name) == 0)
return (true);
return (false);
}
static int
nvmf_disconnect_by_name(char *name)
{
devclass_t dc;
device_t dev;
int error, unit;
bool found;
found = false;
error = 0;
bus_topo_lock();
dc = devclass_find("nvme");
if (dc == NULL)
goto out;
for (unit = 0; unit < devclass_get_maxunit(dc); unit++) {
dev = devclass_get_device(dc, unit);
if (dev == NULL)
continue;
if (device_get_driver(dev) != &nvme_nvmf_driver)
continue;
if (device_get_parent(dev) != root_bus)
continue;
if (name != NULL && !nvmf_matches(dev, name))
continue;
error = device_delete_child(root_bus, dev);
if (error != 0)
break;
found = true;
}
out:
bus_topo_unlock();
if (error == 0 && !found)
error = ENOENT;
return (error);
}
static int
nvmf_disconnect_host(const char **namep)
{
char *name;
int error;
name = malloc(PATH_MAX, M_NVMF, M_WAITOK);
error = copyinstr(*namep, name, PATH_MAX, NULL);
if (error == 0)
error = nvmf_disconnect_by_name(name);
free(name, M_NVMF);
return (error);
}
static int
nvmf_ctl_ioctl(struct cdev *dev, u_long cmd, caddr_t arg, int flag,
struct thread *td)
{
switch (cmd) {
case NVMF_HANDOFF_HOST:
return (nvmf_handoff_host((struct nvmf_ioc_nv *)arg));
case NVMF_DISCONNECT_HOST:
return (nvmf_disconnect_host((const char **)arg));
case NVMF_DISCONNECT_ALL:
return (nvmf_disconnect_by_name(NULL));
default:
return (ENOTTY);
}
}
static struct cdevsw nvmf_ctl_cdevsw = {
.d_version = D_VERSION,
.d_ioctl = nvmf_ctl_ioctl
};
int
nvmf_ctl_load(void)
{
struct make_dev_args mda;
int error;
make_dev_args_init(&mda);
mda.mda_devsw = &nvmf_ctl_cdevsw;
mda.mda_uid = UID_ROOT;
mda.mda_gid = GID_WHEEL;
mda.mda_mode = 0600;
error = make_dev_s(&mda, &nvmf_cdev, "nvmf");
if (error != 0)
nvmf_cdev = NULL;
return (error);
}
void
nvmf_ctl_unload(void)
{
if (nvmf_cdev != NULL) {
destroy_dev(nvmf_cdev);
nvmf_cdev = NULL;
}
}