NetBSD/sys/dev/iscsi/iscsi_test.c

1089 lines
25 KiB
C

/* $NetBSD: iscsi_test.c,v 1.2 2011/11/29 03:50:31 tls Exp $ */
/*-
* Copyright (c) 2006,2011 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Wasabi Systems, Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "iscsi_globals.h"
#ifdef ISCSI_TEST_MODE
STATIC test_pars_list_t test_list = TAILQ_HEAD_INITIALIZER(test_list);
/* ------------------- Local Stuff for PDU modifications -------------------- */
/*
* find_pdu_offset:
* Return pointer to PDU location to be modified, based on io vector.
* Also return maximum size of field.
*
* Parameter:
* pdu the PDU to modify
* offset the offset into the PDU
* ptr OUT: The pointer to the data
* maxlen OUT: Maximum length of field
*
* Returns 0 on success, nonzero if offset exceeds PDU size.
*/
STATIC int
find_pdu_offset(pdu_t *pdu, int offset, uint8_t **ptr, int *maxlen)
{
uint8_t *pt = NULL;
int i, maxl = 0;
for (i = 0; i < pdu->uio.uio_iovcnt; i++) {
pt = pdu->io_vec[i].iov_base;
maxl = pdu->io_vec[i].iov_len;
if (offset >= maxl) {
offset -= maxl;
} else {
maxl -= offset;
pt += offset;
offset = -1;
break;
}
}
DEB(1, ("Find pdu offset: offs=%d, ptr=%x, maxl=%d\n",
offset, (int) pt, maxl));
if (offset >= 0 || !maxl)
return 1;
*ptr = pt;
*maxlen = maxl;
return 0;
}
/*
* mod_pdu:
* Modify given PDU according to modification descriptor.
*
* Parameter:
* pdu the PDU to modify
* mp the modification descriptor
*/
STATIC void
mod_pdu(pdu_t *pdu, iscsi_pdu_mod_t *mp)
{
int add = mp->flags & ISCSITEST_MOD_FLAG_ADD_VAL;
int64_t val = *((int64_t *) mp->value);
uint8_t *ptr;
int len;
switch (mp->offset) {
case ISCSITEST_OFFSET_DATADIGEST:
ptr = (uint8_t *) &pdu->data_digest;
len = sizeof(pdu->data_digest);
break;
case ISCSITEST_OFFSET_HEADERDIGEST:
ptr = (uint8_t *) &pdu->pdu.HeaderDigest;
len = sizeof(pdu->pdu.HeaderDigest);
break;
case ISCSITEST_OFFSET_DATA:
if ((len = pdu->io_vec[1].iov_len) == 0)
return;
ptr = pdu->io_vec[1].iov_base;
break;
case ISCSITEST_OFFSET_DRV_CMDSN:
ptr = (uint8_t *) &pdu->connection->session->CmdSN;
len = sizeof(pdu->connection->session->CmdSN);
break;
default:
if (find_pdu_offset(pdu, mp->offset, &ptr, &len))
return;
break;
}
len = min(len, mp->size);
DEB(1, ("mod_pdu: mpoff=%d, size=%d, len=%d, val=%qx, *ptr=%qx\n",
mp->offset, mp->size, len, val, *((uint64_t *) ptr)));
if (!add) {
if (mp->flags & ISCSITEST_MOD_FLAG_REORDER)
val = htonq(val);
memcpy(ptr, &val, len);
} else if (len == mp->size) {
switch (len) {
case 1:
*ptr += *mp->value;
break;
case 2:
val += (int64_t) (ntohs(*((uint16_t *) ptr)));
*((uint16_t *) ptr) = htons((uint16_t) val);
break;
case 3:
val += (int64_t) (ntoh3(ptr));
hton3((uint32_t) val, ptr);
break;
case 4:
val += (int64_t) (ntohl(*((uint32_t *) ptr)));
*((uint32_t *) ptr) = htonl((uint32_t) val);
break;
case 8:
val += ntohq(*((uint64_t *) ptr));
*((uint64_t *) ptr) = htonq(val);
break;
default:
break;
}
}
DEB(1, ("mod_pdu: *ptr=%qx\n", *((uint64_t *) ptr)));
}
/*
* update_options:
* Update test options from current mod record.
*
* Parameter:
* tp the test parameters
* mod the mod descriptor
*/
STATIC void
update_options(test_pars_t *tp, mod_desc_t *mod)
{
if ((tp->options & ISCSITEST_OPT_DISABLE_CCB_TIMEOUT) &&
(mod->pars.options & ISCSITEST_OPT_ENABLE_CCB_TIMEOUT))
tp->options &= ~ISCSITEST_OPT_DISABLE_CCB_TIMEOUT;
else if (!(tp->options & ISCSITEST_OPT_DISABLE_CCB_TIMEOUT) &&
(mod->pars.options & ISCSITEST_OPT_DISABLE_CCB_TIMEOUT))
tp->options |= ISCSITEST_OPT_DISABLE_CCB_TIMEOUT;
if ((tp->options & ISCSITEST_OPT_DISABLE_CONN_TIMEOUT) &&
(mod->pars.options & ISCSITEST_OPT_ENABLE_CONN_TIMEOUT))
tp->options &= ~ISCSITEST_OPT_DISABLE_CONN_TIMEOUT;
else if (!(tp->options & ISCSITEST_OPT_DISABLE_CONN_TIMEOUT) &&
(mod->pars.options & ISCSITEST_OPT_DISABLE_CONN_TIMEOUT))
tp->options |= ISCSITEST_OPT_DISABLE_CONN_TIMEOUT;
if (mod->pars.options & ISCSITEST_OPT_USE_RANDOM_TX)
tp->lose_random[CNT_TX] = mod->pars.lose_random_tx;
if (mod->pars.options & ISCSITEST_OPT_USE_RANDOM_RX)
tp->lose_random[CNT_RX] = mod->pars.lose_random_rx;
}
/*
* test_get:
* Copy PDU to app
*
* Parameter:
* pdu the PDU to modify
* mod the modification descriptor
* error the PDU error indication
*/
STATIC void
test_get(pdu_t *pdu, mod_desc_t *mod, int error)
{
uint32_t size, cpy, dlen;
uint8_t *pptr;
pptr = mod->pdu_ptr;
size = mod->pars.pdu_size;
cpy = min(size, BHS_SIZE);
memcpy(pptr, &pdu->pdu, cpy);
size -= cpy;
pptr += cpy;
dlen = pdu->save_uio.uio_resid;
if (size && dlen) {
cpy = min(size, dlen);
memcpy(pptr, (char *) pdu->save_uio.uio_iov, cpy);
size -= cpy;
}
mod->pars.pdu_actual_size = BHS_SIZE + dlen;
switch (error) {
case 0:
mod->pars.status = ISCSI_STATUS_SUCCESS;
break;
case TEST_INVALID_HEADER_CRC:
mod->pars.status = ISCSI_STATUS_TEST_HEADER_CRC_ERROR;
break;
case TEST_INVALID_DATA_CRC:
mod->pars.status = ISCSI_STATUS_TEST_DATA_CRC_ERROR;
break;
case TEST_READ_ERROR:
mod->pars.status = ISCSI_STATUS_TEST_DATA_READ_ERROR;
break;
default:
mod->pars.status = ISCSI_STATUS_GENERAL_ERROR;
break;
}
}
/*
* check_loss:
* Check whether PDU is to be discarded randomly.
*
* Parameter:
* tp the test parameters
* rxtx whether this is a recieve or send PDU (CNT_TX or CNT_RX)
*
* Returns nonzero if the PDU is to be discarded, 0 for normal processing.
*/
STATIC int
check_loss(test_pars_t *tp, int rxtx)
{
return (tp->lose_random[rxtx]) ?
(cprng_fast32() % tp->lose_random[rxtx]) : 0;
}
/*
* test_mod:
* Check whether PDU is to be modified - calculate offset, modify PDU
* on match.
*
* Parameter:
* tp the test parameters
* pdu the PDU to modify
* kind the PDU kind
* rxtx whether this is a recieve or send PDU (CNT_TX or CNT_RX)
* err the PDU error indicator (set on digest/read errors, rx only)
*
* Returns nonzero if the PDU is to be discarded, 0 for normal processing.
*/
STATIC int
test_mod(test_pars_t *tp, pdu_t *pdu, iscsi_pdu_kind_t kind, int rxtx, int err)
{
mod_desc_t *mod;
uint32_t mpoff, off;
int i, rc = 0;
tp->pdu_count[kind][rxtx]++;
tp->pdu_count[ANY_PDU][rxtx]++;
do {
if ((mod = TAILQ_FIRST(&tp->mods)) == NULL) {
return check_loss(tp, rxtx);
}
if (mod->pars.which_pdu != ANY_PDU &&
mod->pars.which_pdu != kind) {
return check_loss(tp, rxtx);
}
mpoff = mod->pars.pdu_offset;
switch (mod->pars.which_offset) {
case ABSOLUTE_ANY:
off = tp->pdu_count[ANY_PDU][CNT_TX] +
tp->pdu_count[ANY_PDU][CNT_RX];
break;
case RELATIVE_ANY:
off = (tp->pdu_count[ANY_PDU][CNT_TX] +
tp->pdu_count[ANY_PDU][CNT_RX]) -
(tp->pdu_last[ANY_PDU][CNT_TX] + tp->pdu_last[ANY_PDU][CNT_RX]);
break;
case ABSOLUTE_PDUKIND:
off = tp->pdu_count[kind][rxtx];
break;
case RELATIVE_PDUKIND:
off = tp->pdu_count[kind][rxtx] - tp->pdu_last[kind][rxtx];
break;
case ABSOLUTE_TX:
if (rxtx != CNT_TX)
return check_loss(tp, rxtx);
off = tp->pdu_count[ANY_PDU][CNT_TX];
break;
case RELATIVE_TX:
if (rxtx != CNT_TX)
return check_loss(tp, rxtx);
off = tp->pdu_count[ANY_PDU][CNT_TX] -
tp->pdu_last[ANY_PDU][CNT_TX];
break;
case ABSOLUTE_RX:
if (rxtx != CNT_RX)
return check_loss(tp, rxtx);
off = tp->pdu_count[ANY_PDU][CNT_RX];
break;
case RELATIVE_RX:
if (rxtx != CNT_RX)
return check_loss(tp, rxtx);
off = tp->pdu_count[ANY_PDU][CNT_RX] -
tp->pdu_last[ANY_PDU][CNT_RX];
break;
default:
/* bad offset - skip this entry */
mpoff = off = 0;
break;
}
DEB(1, ("test_mod: kind=%d, rxtx=%d, pdukind=%d, mpoff=%d, "
"whichoff=%d, off=%d\n", kind, rxtx, mod->pars.which_pdu,
mpoff, mod->pars.which_offset, off));
if (!off || (mpoff != 0 && mpoff < off)) {
/* This might happen in some cases. Just discard the modification. */
CS_BEGIN;
TAILQ_REMOVE(&tp->mods, mod, link);
CS_END;
update_options(tp, mod);
if (mod->pars.options & ISCSITEST_OPT_WAIT_FOR_COMPLETION) {
mod->pars.status = ISCSI_STATUS_TEST_MODIFICATION_SKIPPED;
wakeup(mod);
}
free(mod, M_TEMP);
}
} while (mpoff && mpoff < off);
if (mpoff > off)
return check_loss(tp, rxtx);
DEB(1, ("test_mod: opt=%x, pdu_ptr=%x, num_mods=%d\n", mod->pars.options,
(int) mod->pdu_ptr, mod->pars.num_pdu_mods));
if (mod->pdu_ptr)
test_get(pdu, mod, err);
if (mod->pars.options & ISCSITEST_OPT_DISCARD_PDU)
rc = 1;
else if (check_loss(tp, rxtx))
rc = 1;
else if (mod->pars.num_pdu_mods) {
if (!(mod->pars.options & ISCSITEST_OPT_MOD_PERMANENT)) {
/*
* Note: if the PDU is later resent, the unmodified one will be
* used as resend_pdu restores the original io vector.
*/
pdu->mod_pdu = pdu->pdu;
pdu->io_vec[0].iov_base = &pdu->mod_pdu;
}
for (i = 0; i < mod->pars.num_pdu_mods; i++) {
mod_pdu(pdu, &mod->mods[i]);
}
}
if (rxtx == CNT_TX) {
if (mod->pars.options & ISCSITEST_OPT_NO_RESPONSE_PDU) {
ccb_t *ccb = pdu->owner;
DEB(1, ("test_mod: No response expected, completing CCB %x\n",
(int)ccb));
if (ccb != NULL &&
(ccb->disp == CCBDISP_WAIT || ccb->disp == CCBDISP_SCSIPI)) {
/* simulate timeout */
ccb->status = ISCSI_STATUS_TIMEOUT;
wake_ccb(ccb);
}
}
if ((mod->pars.options & ISCSITEST_SFLAG_UPDATE_FIELDS) &&
mod->pars.num_pdu_mods) {
connection_t *conn = pdu->connection;
if (conn->HeaderDigest &&
!(mod->pars.options & ISCSITEST_SFLAG_NO_HEADER_DIGEST))
pdu->pdu.HeaderDigest = gen_digest(&pdu->pdu, BHS_SIZE);
if (pdu->uio.uio_iovcnt > 1 && conn->DataDigest &&
!(mod->pars.options & ISCSITEST_SFLAG_NO_DATA_DIGEST))
pdu->data_digest = gen_digest_2(
pdu->io_vec[1].iov_base,
pdu->io_vec[1].iov_len,
pdu->io_vec[2].iov_base,
pdu->io_vec[2].iov_len);
}
}
CS_BEGIN;
TAILQ_REMOVE(&tp->mods, mod, link);
update_options(tp, mod);
/* we've modified a PDU - copy current count into last count */
memcpy(tp->pdu_last, tp->pdu_count, sizeof(tp->pdu_last));
CS_END;
if (mod->pars.options & ISCSITEST_OPT_WAIT_FOR_COMPLETION) {
wakeup(mod);
}
if (mod->pars.options & ISCSITEST_KILL_CONNECTION) {
kill_connection(tp->connection,
ISCSI_STATUS_TEST_CONNECTION_CLOSED,
NO_LOGOUT, TRUE);
}
free(mod, M_TEMP);
return rc;
}
/*
* free_negs:
* Free all negotiation elements attached to test parameter.
*
* Parameter:
* tp The test parameter
*/
STATIC void
free_negs(test_pars_t *tp)
{
neg_desc_t *np;
while ((np = TAILQ_FIRST(&tp->negs)) != NULL) {
TAILQ_REMOVE(&tp->negs, np, link);
free(np, M_TEMP);
}
}
/*
* free_mods:
* Free all modification elements attached to test parameter.
*
* Parameter:
* tp The test parameter
* status The status code to return for clients waiting on completion
*/
STATIC void
free_mods(test_pars_t *tp, uint32_t status)
{
mod_desc_t *mp;
while ((mp = TAILQ_FIRST(&tp->mods)) != NULL) {
TAILQ_REMOVE(&tp->mods, mp, link);
if (mp->pars.options & ISCSITEST_OPT_WAIT_FOR_COMPLETION) {
mp->pars.status = status;
wakeup(mp);
}
free(mp, M_TEMP);
}
}
/* ---------------- Global functions for PDU modifications ------------------ */
/*
* test_mode_rx:
* Check whether received PDU is to be modified - determine PDU kind,
* then call test_mod for further processing.
*
* Parameter:
* conn the connection
* pdu the PDU to modify
* error invalid frame if nonzero: don't do mods, frame will be
* discarded anyway.
*
* Returns nonzero if the PDU is to be discarded, 0 for normal processing.
*/
int
test_mode_rx(connection_t *conn, pdu_t *pdu, int error)
{
test_pars_t *tp;
iscsi_pdu_kind_t kind;
if ((tp = conn->test_pars) == NULL) {
return 0;
}
switch (pdu->pdu.Opcode & OPCODE_MASK) {
case TOP_NOP_In:
kind = NOP_IN_PDU;
break;
case TOP_SCSI_Response:
kind = RESPONSE_PDU;
break;
case TOP_SCSI_Task_Management:
kind = TASK_RSP_PDU;
break;
case TOP_Login_Response:
kind = LOGIN_RSP_PDU;
break;
case TOP_Text_Response:
kind = TEXT_RSP_PDU;
break;
case TOP_SCSI_Data_in:
kind = DATA_IN_PDU;
break;
case TOP_Logout_Response:
kind = LOGOUT_RSP_PDU;
break;
case TOP_R2T:
kind = R2T_PDU;
break;
case TOP_Asynchronous_Message:
kind = ASYNCH_PDU;
break;
case TOP_Reject:
kind = REJECT_PDU;
break;
default:
kind = INVALID_PDU;
break;
}
return test_mod(tp, pdu, kind, CNT_RX, error);
}
/*
* test_mode_tx:
* Check whether PDU to be sent is to be modified - determine PDU kind,
* then call test_mod for further processing.
*
* Parameter:
* conn the connection
* pdu the PDU to modify
*
* Returns nonzero if the PDU is to be discarded, 0 for normal processing.
*/
int
test_mode_tx(connection_t *conn, pdu_t *pdu)
{
test_pars_t *tp;
iscsi_pdu_kind_t kind;
if ((tp = conn->test_pars) == NULL)
return 0;
switch (pdu->pdu.Opcode & OPCODE_MASK) {
case IOP_NOP_Out:
kind = NOP_OUT_PDU;
break;
case IOP_SCSI_Command:
kind = COMMAND_PDU;
break;
case IOP_SCSI_Task_Management:
kind = TASK_REQ_PDU;
break;
case IOP_Login_Request:
kind = LOGIN_REQ_PDU;
break;
case IOP_Text_Request:
kind = TEXT_REQ_PDU;
break;
case IOP_SCSI_Data_out:
kind = DATA_OUT_PDU;
break;
case IOP_Logout_Request:
kind = LOGOUT_REQ_PDU;
break;
case IOP_SNACK_Request:
kind = SNACK_PDU;
break;
default:
kind = INVALID_PDU;
break;
}
return test_mod(tp, pdu, kind, CNT_TX, 0);
}
/*
* test_assign_connection:
* Check whether there is an unassigned test parameter, and assign it to the
* given connection.
* Note: The number of concurrent tests at any time is assumed to be
* very small (usually 1-2). The algorithm used therefore can be very
* very simple minded.
*
* Parameter:
* conn the connection
*/
void
test_assign_connection(connection_t *conn)
{
test_pars_t *tp;
for (tp = TAILQ_FIRST(&test_list); tp != NULL; tp = TAILQ_NEXT(tp, link)) {
if (tp->connection == NULL) {
tp->connection = conn;
conn->test_pars = tp;
DEB(1, ("Assigning session %d, connection %d to test %d\n",
conn->session->id, conn->id, tp->test_id));
return;
}
}
}
/*
* test_remove_connection:
* If the connection has test parameters assigned, remove and free those
* parameters.
*
* Parameter:
* conn the connection
*/
void
test_remove_connection(connection_t *conn)
{
test_pars_t *tp;
int s;
s = splbio();
if ((tp = conn->test_pars) != NULL) {
conn->test_pars = NULL;
TAILQ_REMOVE(&test_list, tp, link);
splx(s);
DEB(9, ("Remove test %d from connection %d\n", tp->test_id, conn->id));
free_negs(tp);
free_mods(tp, ISCSI_STATUS_TEST_CONNECTION_CLOSED);
free(tp, M_TEMP);
} else {
splx(s);
}
}
/* ----------------------- Local Stuff for IOCTLs --------------------------- */
/*
* find_test_id:
* Search the given test ID in the test list.
*
* Parameter:
* id The test ID
*
* Returns a pointer to the test pars is found, else NULL.
*/
STATIC test_pars_t *
find_test_id(uint32_t id)
{
test_pars_t *curr;
for (curr = TAILQ_FIRST(&test_list);
curr && curr->test_id != id;
curr = TAILQ_NEXT(curr, link)) {
}
return curr;
}
/*
* add_neg:
* Add negotiation element to test parameter.
* Helper for test_define and test_add_neg.
*
* Parameter:
* tp The test parameter
* addr The pointer to the descriptor
* len The descriptor size
*
* Returns 0 on success, else an ISCSI status code.
*/
STATIC uint32_t
add_neg(test_pars_t *tp, void *addr, uint32_t len)
{
neg_desc_t *negp;
uint32_t size;
/* len already includes the size of iscsi_test_negotiation_descriptor_t */
size = len + sizeof(neg_desc_t) -
sizeof(iscsi_test_negotiation_descriptor_t);
if ((negp = malloc(size, M_TEMP, M_WAITOK)) == NULL) {
return ISCSI_STATUS_NO_RESOURCES;
}
copyin(addr, &negp->entry, len);
TAILQ_INSERT_TAIL(&tp->negs, negp, link);
DEB(1, ("add_neg: size %d, phase %d, flags %x, data %02x...\n",
negp->entry.size, negp->entry.state, negp->entry.flags,
negp->entry.value[0]));
return 0;
}
/* ----------------- Global functions for IOCTLs --------------------------- */
/*
* test_define:
* Implements ISCSI_TEST_DEFINE ioctl.
*
* Parameter:
* par The IOCTL parameter.
*/
void
test_define(iscsi_test_define_parameters_t *par)
{
test_pars_t *tp;
session_t *sess = NULL;
connection_t *conn = NULL;
if (!par->test_id) {
par->status = ISCSI_STATUS_INVALID_ID;
return;
}
if (find_test_id(par->test_id) != NULL) {
par->status = ISCSI_STATUS_DUPLICATE_ID;
return;
}
if (par->session_id &&
(sess = find_session(par->session_id)) == NULL) {
par->status = ISCSI_STATUS_INVALID_SESSION_ID;
return;
}
if (sess != NULL) {
if (par->connection_id &&
(conn = find_connection(sess, par->connection_id)) == NULL) {
par->status = ISCSI_STATUS_INVALID_CONNECTION_ID;
return;
} else if (!par->connection_id) {
conn = TAILQ_FIRST(&sess->conn_list);
}
if (conn->test_pars != NULL) {
par->status = ISCSI_STATUS_TEST_ALREADY_ASSIGNED;
return;
}
}
if ((tp = malloc(sizeof(*tp), M_TEMP, M_WAITOK | M_ZERO)) == NULL) {
par->status = ISCSI_STATUS_NO_RESOURCES;
return;
}
TAILQ_INIT(&tp->negs);
TAILQ_INIT(&tp->mods);
if (par->neg_descriptor_size &&
(par->status = add_neg(tp, par->neg_descriptor_ptr,
par->neg_descriptor_size)) != 0) {
free(tp, M_TEMP);
return;
}
tp->test_id = par->test_id;
tp->options = par->options;
tp->lose_random[CNT_RX] = par->lose_random_rx;
tp->lose_random[CNT_TX] = par->lose_random_tx;
tp->connection = conn;
tp->firstburst_val = par->firstburst_val;
tp->maxburst_val = par->maxburst_val;
tp->r2t_val = par->r2t_val;
DEB(1, ("TestDefine: id=%d, opt=%x, negsize=%d, conn=%x\n",
tp->test_id, tp->options, par->neg_descriptor_size, (int)conn));
TAILQ_INSERT_TAIL(&test_list, tp, link);
if (conn != NULL) {
tp->connection = conn;
conn->test_pars = tp;
DEB(1, ("Assigning session %d, connection %d to test %d\n",
conn->session->id, conn->id, tp->test_id));
}
par->status = ISCSI_STATUS_SUCCESS;
}
/*
* test_add_neg:
* Implements ISCSI_TEST_ADD_NEGOTIATION ioctl.
*
* Parameter:
* par The IOCTL parameter.
*/
void
test_add_neg(iscsi_test_add_negotiation_parameters_t *par)
{
test_pars_t *tp;
if ((tp = find_test_id(par->test_id)) == NULL) {
par->status = ISCSI_STATUS_INVALID_ID;
return;
}
if (par->neg_descriptor_size <
sizeof(iscsi_test_negotiation_descriptor_t)) {
par->status = ISCSI_STATUS_PARAMETER_INVALID;
return;
}
par->status =
add_neg(tp, par->neg_descriptor_ptr, par->neg_descriptor_size);
}
/*
* test_add_mod:
* Implements ISCSI_TEST_ADD_MODIFICATION ioctl.
*
* Parameter:
* par The IOCTL parameter.
*/
void
test_add_mod(struct proc *p, iscsi_test_add_modification_parameters_t *par)
{
test_pars_t *tp;
mod_desc_t *modp;
uint32_t len;
uint32_t psize = par->pdu_size;
void *pdu_ptr = par->pdu_ptr;
if ((tp = find_test_id(par->test_id)) == NULL) {
par->status = ISCSI_STATUS_INVALID_ID;
return;
}
len = par->num_pdu_mods * sizeof(iscsi_pdu_mod_t);
if ((modp = malloc(len + sizeof(mod_desc_t),
M_TEMP, M_WAITOK | M_ZERO)) == NULL) {
par->status = ISCSI_STATUS_NO_RESOURCES;
return;
}
par->status = ISCSI_STATUS_SUCCESS;
modp->pars = *par;
if (len)
copyin(par->mod_ptr, modp->mods, len);
if (psize) {
if (pdu_ptr == NULL) {
free(modp, M_TEMP);
par->status = ISCSI_STATUS_PARAMETER_INVALID;
return;
}
if (pdu_ptr == NULL ||
(par->status = map_databuf(p, &pdu_ptr, psize)) != 0) {
free(modp, M_TEMP);
return;
}
modp->pdu_ptr = pdu_ptr;
/* force wait */
modp->pars.options |= ISCSITEST_OPT_WAIT_FOR_COMPLETION;
}
TAILQ_INSERT_TAIL(&tp->mods, modp, link);
if (modp->pars.options & ISCSITEST_OPT_WAIT_FOR_COMPLETION)
tsleep(modp, PWAIT, "test_completion", 0);
}
/*
* test_cancel:
* Implements ISCSI_TEST_CANCEL ioctl.
*
* Parameter:
* par The IOCTL parameter.
*/
void
test_cancel(iscsi_test_cancel_parameters_t *par)
{
test_pars_t *tp;
if ((tp = find_test_id(par->test_id)) == NULL) {
par->status = ISCSI_STATUS_INVALID_ID;
return;
}
DEB(1, ("Test Cancel, id %d\n", par->test_id));
CS_BEGIN;
if (tp->connection)
tp->connection->test_pars = NULL;
TAILQ_REMOVE(&test_list, tp, link);
CS_END;
free_negs(tp);
free_mods(tp, ISCSI_STATUS_TEST_CANCELED);
free(tp, M_TEMP);
par->status = ISCSI_STATUS_SUCCESS;
}
/*
* test_send_pdu:
* Implements ISCSI_TEST_SEND_PDU ioctl.
*
* Parameter:
* p The caller's context.
* par The IOCTL parameter.
*/
void
test_send_pdu(struct proc *p, iscsi_test_send_pdu_parameters_t *par)
{
static uint8_t pad_bytes[4] = { 0 };
test_pars_t *tp;
connection_t *conn;
pdu_t *pdu;
uint32_t psize = par->pdu_size;
void *pdu_ptr = par->pdu_ptr;
struct uio *uio;
uint32_t i, pad, dsl, size;
if ((tp = find_test_id(par->test_id)) == NULL) {
par->status = ISCSI_STATUS_INVALID_ID;
return;
}
if (!psize || pdu_ptr == NULL ||
((par->options & ISCSITEST_SFLAG_UPDATE_FIELDS) && psize < BHS_SIZE)) {
par->status = ISCSI_STATUS_PARAMETER_INVALID;
return;
}
if ((conn = tp->connection) == NULL) {
par->status = ISCSI_STATUS_TEST_INACTIVE;
return;
}
if ((pdu = get_pdu(conn)) == NULL) {
par->status = ISCSI_STATUS_TEST_CONNECTION_CLOSED;
return;
}
DEB(1, ("Test Send PDU, id %d\n", par->test_id));
if ((par->status = map_databuf(p, &pdu_ptr, psize)) != 0) {
free_pdu(pdu);
return;
}
i = 1;
if (!par->options) {
pdu->io_vec[0].iov_base = pdu_ptr;
pdu->io_vec[0].iov_len = size = psize;
} else {
memcpy(&pdu->pdu, pdu_ptr, BHS_SIZE);
if (!(pdu->pdu.Opcode & OP_IMMEDIATE))
conn->session->CmdSN++;
pdu->pdu.p.command.CmdSN = htonl(conn->session->CmdSN);
dsl = psize - BHS_SIZE;
size = BHS_SIZE;
hton3(dsl, pdu->pdu.DataSegmentLength);
if (conn->HeaderDigest &&
!(par->options & ISCSITEST_SFLAG_NO_HEADER_DIGEST)) {
pdu->pdu.HeaderDigest = gen_digest(&pdu->pdu, BHS_SIZE);
size += 4;
}
pdu->io_vec[0].iov_base = &pdu->pdu;
pdu->io_vec[0].iov_len = size;
if (dsl) {
pdu->io_vec[1].iov_base = &pdu_ptr[BHS_SIZE];
pdu->io_vec[1].iov_len = dsl;
i++;
size += dsl;
/* Pad to next multiple of 4 */
pad = (par->options & ISCSITEST_SFLAG_NO_PADDING) ? 0 : size & 0x03;
if (pad) {
pad = 4 - pad;
pdu->io_vec[i].iov_base = pad_bytes;
pdu->io_vec[i].iov_len = pad;
i++;
size += pad;
}
if (conn->DataDigest &&
!(par->options & ISCSITEST_SFLAG_NO_DATA_DIGEST)) {
pdu->data_digest = gen_digest_2(&pdu_ptr[BHS_SIZE], dsl,
pad_bytes, pad);
pdu->io_vec[i].iov_base = &pdu->data_digest;
pdu->io_vec[i].iov_len = 4;
i++;
size += 4;
}
}
}
uio = &pdu->uio;
uio->uio_iov = pdu->io_vec;
UIO_SETUP_SYSSPACE(uio);
uio->uio_rw = UIO_WRITE;
uio->uio_iovcnt = i;
uio->uio_resid = size;
pdu->disp = PDUDISP_SIGNAL;
pdu->flags = PDUF_BUSY | PDUF_NOUPDATE;
CS_BEGIN;
/* Enqueue for sending */
if (pdu->pdu.Opcode & OP_IMMEDIATE)
TAILQ_INSERT_HEAD(&conn->pdus_to_send, pdu, send_chain);
else
TAILQ_INSERT_TAIL(&conn->pdus_to_send, pdu, send_chain);
wakeup(&conn->pdus_to_send);
tsleep(pdu, PINOD, "test_send_pdu", 0);
CS_END;
unmap_databuf(p, pdu_ptr, psize);
par->status = ISCSI_STATUS_SUCCESS;
if (par->options & ISCSITEST_KILL_CONNECTION)
kill_connection(conn, ISCSI_STATUS_TEST_CONNECTION_CLOSED, NO_LOGOUT,
TRUE);
}
#endif