Changeset View
Changeset View
Standalone View
Standalone View
sys/cam/ctl/ctl_frontend_ioctl.c
/*- | /*- | ||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | |||||
* | |||||
* Copyright (c) 2003-2009 Silicon Graphics International Corp. | * Copyright (c) 2003-2009 Silicon Graphics International Corp. | ||||
* Copyright (c) 2012 The FreeBSD Foundation | * Copyright (c) 2012 The FreeBSD Foundation | ||||
* Copyright (c) 2015 Alexander Motin <mav@FreeBSD.org> | * Copyright (c) 2015 Alexander Motin <mav@FreeBSD.org> | ||||
* Copyright (c) 2017 Jakub Wojciech Klama <jceel@FreeBSD.org> | |||||
* All rights reserved. | * All rights reserved. | ||||
* | * | ||||
* Redistribution and use in source and binary forms, with or without | * Redistribution and use in source and binary forms, with or without | ||||
* modification, are permitted provided that the following conditions | * modification, are permitted provided that the following conditions | ||||
* are met: | * are met: | ||||
* 1. Redistributions of source code must retain the above copyright | * 1. Redistributions of source code must retain the above copyright | ||||
* notice, this list of conditions and the following disclaimer, | * notice, this list of conditions and the following disclaimer, | ||||
* without modification, immediately at the beginning of the file. | * without modification, immediately at the beginning of the file. | ||||
Show All 23 Lines | |||||
#include <sys/lock.h> | #include <sys/lock.h> | ||||
#include <sys/module.h> | #include <sys/module.h> | ||||
#include <sys/mutex.h> | #include <sys/mutex.h> | ||||
#include <sys/condvar.h> | #include <sys/condvar.h> | ||||
#include <sys/malloc.h> | #include <sys/malloc.h> | ||||
#include <sys/conf.h> | #include <sys/conf.h> | ||||
#include <sys/queue.h> | #include <sys/queue.h> | ||||
#include <sys/sysctl.h> | #include <sys/sysctl.h> | ||||
#include <sys/nv.h> | |||||
#include <sys/dnv.h> | |||||
#include <cam/cam.h> | #include <cam/cam.h> | ||||
#include <cam/scsi/scsi_all.h> | #include <cam/scsi/scsi_all.h> | ||||
#include <cam/scsi/scsi_da.h> | #include <cam/scsi/scsi_da.h> | ||||
#include <cam/ctl/ctl_io.h> | #include <cam/ctl/ctl_io.h> | ||||
#include <cam/ctl/ctl.h> | #include <cam/ctl/ctl.h> | ||||
#include <cam/ctl/ctl_frontend.h> | #include <cam/ctl/ctl_frontend.h> | ||||
#include <cam/ctl/ctl_util.h> | #include <cam/ctl/ctl_util.h> | ||||
Show All 11 Lines | |||||
} ctl_fe_ioctl_state; | } ctl_fe_ioctl_state; | ||||
struct ctl_fe_ioctl_params { | struct ctl_fe_ioctl_params { | ||||
struct cv sem; | struct cv sem; | ||||
struct mtx ioctl_mtx; | struct mtx ioctl_mtx; | ||||
ctl_fe_ioctl_state state; | ctl_fe_ioctl_state state; | ||||
}; | }; | ||||
struct cfi_softc { | struct cfi_port { | ||||
TAILQ_ENTRY(cfi_port) link; | |||||
uint32_t cur_tag_num; | uint32_t cur_tag_num; | ||||
struct cdev * dev; | |||||
struct ctl_port port; | struct ctl_port port; | ||||
}; | }; | ||||
struct cfi_softc { | |||||
TAILQ_HEAD(, cfi_port) ports; | |||||
}; | |||||
static struct cfi_softc cfi_softc; | static struct cfi_softc cfi_softc; | ||||
static int cfi_init(void); | static int cfi_init(void); | ||||
static int cfi_shutdown(void); | static int cfi_shutdown(void); | ||||
static void cfi_datamove(union ctl_io *io); | static void cfi_datamove(union ctl_io *io); | ||||
static void cfi_done(union ctl_io *io); | static void cfi_done(union ctl_io *io); | ||||
static int cfi_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, | |||||
struct thread *td); | |||||
static void cfi_ioctl_port_create(struct ctl_req *req); | |||||
static void cfi_ioctl_port_remove(struct ctl_req *req); | |||||
static struct cdevsw cfi_cdevsw = { | |||||
.d_version = D_VERSION, | |||||
.d_flags = 0, | |||||
.d_ioctl = ctl_ioctl_io | |||||
}; | |||||
static struct ctl_frontend cfi_frontend = | static struct ctl_frontend cfi_frontend = | ||||
{ | { | ||||
.name = "ioctl", | .name = "ioctl", | ||||
.init = cfi_init, | .init = cfi_init, | ||||
.ioctl = cfi_ioctl, | |||||
.shutdown = cfi_shutdown, | .shutdown = cfi_shutdown, | ||||
}; | }; | ||||
CTL_FRONTEND_DECLARE(ctlioctl, cfi_frontend); | CTL_FRONTEND_DECLARE(ctlioctl, cfi_frontend); | ||||
static int | static int | ||||
cfi_init(void) | cfi_init(void) | ||||
{ | { | ||||
struct cfi_softc *isoftc = &cfi_softc; | struct cfi_softc *isoftc = &cfi_softc; | ||||
struct cfi_port *cfi; | |||||
struct ctl_port *port; | struct ctl_port *port; | ||||
int error = 0; | int error = 0; | ||||
memset(isoftc, 0, sizeof(*isoftc)); | memset(isoftc, 0, sizeof(*isoftc)); | ||||
TAILQ_INIT(&isoftc->ports); | |||||
port = &isoftc->port; | cfi = malloc(sizeof(*cfi), M_CTL, M_WAITOK | M_ZERO); | ||||
port = &cfi->port; | |||||
port->frontend = &cfi_frontend; | port->frontend = &cfi_frontend; | ||||
port->port_type = CTL_PORT_IOCTL; | port->port_type = CTL_PORT_IOCTL; | ||||
port->num_requested_ctl_io = 100; | port->num_requested_ctl_io = 100; | ||||
port->port_name = "ioctl"; | port->port_name = "ioctl"; | ||||
port->fe_datamove = cfi_datamove; | port->fe_datamove = cfi_datamove; | ||||
port->fe_done = cfi_done; | port->fe_done = cfi_done; | ||||
port->physical_port = 0; | |||||
port->targ_port = -1; | port->targ_port = -1; | ||||
port->max_initiators = 1; | |||||
if ((error = ctl_port_register(port)) != 0) { | if ((error = ctl_port_register(port)) != 0) { | ||||
printf("%s: ioctl port registration failed\n", __func__); | printf("%s: ioctl port registration failed\n", __func__); | ||||
return (error); | return (error); | ||||
} | } | ||||
ctl_port_online(port); | ctl_port_online(port); | ||||
TAILQ_INSERT_TAIL(&isoftc->ports, cfi, link); | |||||
return (0); | return (0); | ||||
} | } | ||||
static int | static int | ||||
cfi_shutdown(void) | cfi_shutdown(void) | ||||
{ | { | ||||
struct cfi_softc *isoftc = &cfi_softc; | struct cfi_softc *isoftc = &cfi_softc; | ||||
struct ctl_port *port = &isoftc->port; | struct cfi_port *cfi, *temp; | ||||
int error = 0; | struct ctl_port *port; | ||||
int error; | |||||
TAILQ_FOREACH_SAFE(cfi, &isoftc->ports, link, temp) { | |||||
mav: This code is incomplete. It will leak different resources. | |||||
port = &cfi->port; | |||||
ctl_port_offline(port); | ctl_port_offline(port); | ||||
if ((error = ctl_port_deregister(port)) != 0) | error = ctl_port_deregister(port); | ||||
printf("%s: ioctl port deregistration failed\n", __func__); | if (error != 0) { | ||||
printf("%s: ctl_frontend_deregister() failed\n", | |||||
__func__); | |||||
return (error); | return (error); | ||||
} | } | ||||
TAILQ_REMOVE(&isoftc->ports, cfi, link); | |||||
free(cfi, M_CTL); | |||||
} | |||||
return (0); | |||||
} | |||||
static void | |||||
cfi_ioctl_port_create(struct ctl_req *req) | |||||
{ | |||||
struct cfi_softc *isoftc = &cfi_softc; | |||||
struct cfi_port *cfi; | |||||
struct ctl_port *port; | |||||
struct make_dev_args args; | |||||
const char *val; | |||||
int retval; | |||||
int pp = -1, vp = 0; | |||||
val = dnvlist_get_string(req->args_nvl, "pp", NULL); | |||||
if (val != NULL) | |||||
pp = strtol(val, NULL, 10); | |||||
val = dnvlist_get_string(req->args_nvl, "vp", NULL); | |||||
if (val != NULL) | |||||
vp = strtol(val, NULL, 10); | |||||
if (pp != -1) { | |||||
/* Check for duplicates */ | |||||
TAILQ_FOREACH(cfi, &isoftc->ports, link) { | |||||
if (pp == cfi->port.physical_port && | |||||
vp == cfi->port.virtual_port) { | |||||
req->status = CTL_LUN_ERROR; | |||||
snprintf(req->error_str, sizeof(req->error_str), | |||||
"port %d already exists", pp); | |||||
return; | |||||
} | |||||
} | |||||
} else { | |||||
/* Find free port number */ | |||||
TAILQ_FOREACH(cfi, &isoftc->ports, link) { | |||||
pp = MAX(pp, cfi->port.physical_port); | |||||
} | |||||
pp++; | |||||
} | |||||
cfi = malloc(sizeof(*cfi), M_CTL, M_WAITOK | M_ZERO); | |||||
port = &cfi->port; | |||||
port->frontend = &cfi_frontend; | |||||
port->port_type = CTL_PORT_IOCTL; | |||||
port->num_requested_ctl_io = 100; | |||||
port->port_name = "ioctl"; | |||||
port->fe_datamove = cfi_datamove; | |||||
port->fe_done = cfi_done; | |||||
port->physical_port = pp; | |||||
port->virtual_port = vp; | |||||
port->targ_port = -1; | |||||
retval = ctl_port_register(port); | |||||
if (retval != 0) { | |||||
req->status = CTL_LUN_ERROR; | |||||
snprintf(req->error_str, sizeof(req->error_str), | |||||
"ctl_port_register() failed with error %d", retval); | |||||
free(port, M_CTL); | |||||
return; | |||||
} | |||||
req->result_nvl = nvlist_create(0); | |||||
nvlist_add_number(req->result_nvl, "port_id", port->targ_port); | |||||
ctl_port_online(port); | |||||
make_dev_args_init(&args); | |||||
args.mda_devsw = &cfi_cdevsw; | |||||
args.mda_uid = UID_ROOT; | |||||
args.mda_gid = GID_OPERATOR; | |||||
args.mda_mode = 0600; | |||||
args.mda_si_drv1 = NULL; | |||||
args.mda_si_drv2 = cfi; | |||||
retval = make_dev_s(&args, &cfi->dev, "cam/ctl%d.%d", pp, vp); | |||||
if (retval != 0) { | |||||
req->status = CTL_LUN_ERROR; | |||||
snprintf(req->error_str, sizeof(req->error_str), | |||||
"make_dev_s() failed with error %d", retval); | |||||
free(port, M_CTL); | |||||
return; | |||||
} | |||||
req->status = CTL_LUN_OK; | |||||
TAILQ_INSERT_TAIL(&isoftc->ports, cfi, link); | |||||
} | |||||
static void | |||||
cfi_ioctl_port_remove(struct ctl_req *req) | |||||
{ | |||||
struct cfi_softc *isoftc = &cfi_softc; | |||||
struct cfi_port *cfi = NULL; | |||||
const char *val; | |||||
int port_id = -1; | |||||
val = dnvlist_get_string(req->args_nvl, "port_id", NULL); | |||||
if (val != NULL) | |||||
port_id = strtol(val, NULL, 10); | |||||
if (port_id == -1) { | |||||
req->status = CTL_LUN_ERROR; | |||||
snprintf(req->error_str, sizeof(req->error_str), | |||||
"port_id not provided"); | |||||
return; | |||||
} | |||||
TAILQ_FOREACH(cfi, &isoftc->ports, link) { | |||||
if (cfi->port.targ_port == port_id) | |||||
break; | |||||
} | |||||
if (cfi == NULL) { | |||||
req->status = CTL_LUN_ERROR; | |||||
snprintf(req->error_str, sizeof(req->error_str), | |||||
"cannot find port %d", port_id); | |||||
return; | |||||
Done Inline ActionsWhat happen if somebody try to destroy default ioctl port? It has no dedicated device to destroy, and without that there may be running requests during port destruction. Also attempt to recreate that port will cause behavior different from original. mav: What happen if somebody try to destroy default ioctl port? It has no dedicated device to… | |||||
} | |||||
if (cfi->port.physical_port == 0 && cfi->port.virtual_port == 0) { | |||||
req->status = CTL_LUN_ERROR; | |||||
snprintf(req->error_str, sizeof(req->error_str), | |||||
"cannot destroy default ioctl port"); | |||||
return; | |||||
} | |||||
ctl_port_offline(&cfi->port); | |||||
ctl_port_deregister(&cfi->port); | |||||
TAILQ_REMOVE(&isoftc->ports, cfi, link); | |||||
destroy_dev(cfi->dev); | |||||
free(cfi, M_CTL); | |||||
req->status = CTL_LUN_OK; | |||||
} | |||||
static int | |||||
cfi_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, | |||||
struct thread *td) | |||||
{ | |||||
struct ctl_req *req; | |||||
if (cmd == CTL_PORT_REQ) { | |||||
req = (struct ctl_req *)addr; | |||||
switch (req->reqtype) { | |||||
case CTL_REQ_CREATE: | |||||
cfi_ioctl_port_create(req); | |||||
break; | |||||
case CTL_REQ_REMOVE: | |||||
cfi_ioctl_port_remove(req); | |||||
break; | |||||
default: | |||||
req->status = CTL_LUN_ERROR; | |||||
snprintf(req->error_str, sizeof(req->error_str), | |||||
"Unsupported request type %d", req->reqtype); | |||||
} | |||||
return (0); | |||||
} | |||||
return (ENOTTY); | |||||
} | |||||
/* | /* | ||||
* Data movement routine for the CTL ioctl frontend port. | * Data movement routine for the CTL ioctl frontend port. | ||||
*/ | */ | ||||
static int | static int | ||||
ctl_ioctl_do_datamove(struct ctl_scsiio *ctsio) | ctl_ioctl_do_datamove(struct ctl_scsiio *ctsio) | ||||
{ | { | ||||
struct ctl_sg_entry *ext_sglist, *kern_sglist; | struct ctl_sg_entry *ext_sglist, *kern_sglist; | ||||
struct ctl_sg_entry ext_entry, kern_entry; | struct ctl_sg_entry ext_entry, kern_entry; | ||||
▲ Show 20 Lines • Show All 240 Lines • ▼ Show 20 Lines | do { | ||||
} | } | ||||
} while (done == 0); | } while (done == 0); | ||||
mtx_destroy(¶ms.ioctl_mtx); | mtx_destroy(¶ms.ioctl_mtx); | ||||
cv_destroy(¶ms.sem); | cv_destroy(¶ms.sem); | ||||
return (CTL_RETVAL_COMPLETE); | return (CTL_RETVAL_COMPLETE); | ||||
} | } | ||||
Done Inline ActionsThis won't be correct if we allow default port to be destroyed. mav: This won't be correct if we allow default port to be destroyed. | |||||
int | int | ||||
ctl_ioctl_io(struct cdev *dev, u_long cmd, caddr_t addr, int flag, | ctl_ioctl_io(struct cdev *dev, u_long cmd, caddr_t addr, int flag, | ||||
struct thread *td) | struct thread *td) | ||||
{ | { | ||||
struct cfi_port *cfi; | |||||
union ctl_io *io; | union ctl_io *io; | ||||
void *pool_tmp, *sc_tmp; | void *pool_tmp, *sc_tmp; | ||||
int retval = 0; | int retval = 0; | ||||
if (cmd != CTL_IO) | |||||
return (ENOTTY); | |||||
cfi = dev->si_drv2 == NULL | |||||
? TAILQ_FIRST(&cfi_softc.ports) | |||||
: dev->si_drv2; | |||||
/* | /* | ||||
* If we haven't been "enabled", don't allow any SCSI I/O | * If we haven't been "enabled", don't allow any SCSI I/O | ||||
* to this FETD. | * to this FETD. | ||||
*/ | */ | ||||
if ((cfi_softc.port.status & CTL_PORT_STATUS_ONLINE) == 0) | if ((cfi->port.status & CTL_PORT_STATUS_ONLINE) == 0) | ||||
return (EPERM); | return (EPERM); | ||||
io = ctl_alloc_io(cfi_softc.port.ctl_pool_ref); | io = ctl_alloc_io(cfi->port.ctl_pool_ref); | ||||
Not Done Inline ActionsWe'll need to invent some way to pass tag value from user level. Though that can be done later. mav: We'll need to invent some way to pass tag value from user level. Though that can be done later. | |||||
/* | /* | ||||
* Need to save the pool reference so it doesn't get | * Need to save the pool reference so it doesn't get | ||||
* spammed by the user's ctl_io. | * spammed by the user's ctl_io. | ||||
*/ | */ | ||||
pool_tmp = io->io_hdr.pool; | pool_tmp = io->io_hdr.pool; | ||||
sc_tmp = CTL_SOFTC(io); | sc_tmp = CTL_SOFTC(io); | ||||
memcpy(io, (void *)addr, sizeof(*io)); | memcpy(io, (void *)addr, sizeof(*io)); | ||||
io->io_hdr.pool = pool_tmp; | io->io_hdr.pool = pool_tmp; | ||||
CTL_SOFTC(io) = sc_tmp; | CTL_SOFTC(io) = sc_tmp; | ||||
/* | /* | ||||
* No status yet, so make sure the status is set properly. | * No status yet, so make sure the status is set properly. | ||||
*/ | */ | ||||
io->io_hdr.status = CTL_STATUS_NONE; | io->io_hdr.status = CTL_STATUS_NONE; | ||||
/* | /* | ||||
* The user sets the initiator ID, target and LUN IDs. | * The user sets the initiator ID, target and LUN IDs. | ||||
*/ | */ | ||||
io->io_hdr.nexus.targ_port = cfi_softc.port.targ_port; | io->io_hdr.nexus.targ_port = cfi->port.targ_port; | ||||
io->io_hdr.flags |= CTL_FLAG_USER_REQ; | io->io_hdr.flags |= CTL_FLAG_USER_REQ; | ||||
if ((io->io_hdr.io_type == CTL_IO_SCSI) && | if ((io->io_hdr.io_type == CTL_IO_SCSI) && | ||||
(io->scsiio.tag_type != CTL_TAG_UNTAGGED)) | (io->scsiio.tag_type != CTL_TAG_UNTAGGED)) | ||||
io->scsiio.tag_num = cfi_softc.cur_tag_num++; | io->scsiio.tag_num = cfi->cur_tag_num++; | ||||
retval = cfi_submit_wait(io); | retval = cfi_submit_wait(io); | ||||
if (retval == 0) | if (retval == 0) | ||||
memcpy((void *)addr, io, sizeof(*io)); | memcpy((void *)addr, io, sizeof(*io)); | ||||
ctl_free_io(io); | ctl_free_io(io); | ||||
return (retval); | return (retval); | ||||
} | } |
This code is incomplete. It will leak different resources.