1
0
mirror of https://git.FreeBSD.org/src.git synced 2025-01-19 15:33:56 +00:00

Add support for MaxBurstLength and Expected Data transfer Length parameters.

Before this change target could send R2T request for write transfer of any
size, that could violate iSCSI RFC, which allows initiator to limit maximum
R2T size by negotiating MaxBurstLength connection parameter.

Also report an error in case of write underflow, when initiator provides
less data than initiator expects.  Previously in such case our target
sent R2T request for non-existing data, violating the RFC, and confusing
some initiators.  SCSI specs don't explicitly define how write underflows
should be handled and there are different oppinions, but reporting error
is hopefully better then violating iSCSI RFC with unpredictable results.

MFC after:	2 weeks
This commit is contained in:
Alexander Motin 2014-10-06 12:20:46 +00:00
parent 3615981425
commit 04f10b6c96
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=272613
2 changed files with 65 additions and 16 deletions

View File

@ -154,6 +154,8 @@ static uint32_t cfiscsi_lun_map(void *arg, uint32_t lun);
static int cfiscsi_ioctl(struct cdev *dev,
u_long cmd, caddr_t addr, int flag, struct thread *td);
static void cfiscsi_datamove(union ctl_io *io);
static void cfiscsi_datamove_in(union ctl_io *io);
static void cfiscsi_datamove_out(union ctl_io *io);
static void cfiscsi_done(union ctl_io *io);
static bool cfiscsi_pdu_update_cmdsn(const struct icl_pdu *request);
static void cfiscsi_pdu_handle_nop_out(struct icl_pdu *request);
@ -824,7 +826,7 @@ cfiscsi_handle_data_segment(struct icl_pdu *request, struct cfiscsi_data_wait *c
return (true);
}
if (io->scsiio.ext_data_filled == io->scsiio.kern_data_len &&
if (io->scsiio.ext_data_filled == cdw->cdw_r2t_end &&
(bhsdo->bhsdo_flags & BHSDO_FLAGS_F) == 0) {
CFISCSI_SESSION_WARN(cs, "got the final packet without "
"the F flag; flags = 0x%x; dropping connection",
@ -834,7 +836,7 @@ cfiscsi_handle_data_segment(struct icl_pdu *request, struct cfiscsi_data_wait *c
return (true);
}
if (io->scsiio.ext_data_filled != io->scsiio.kern_data_len &&
if (io->scsiio.ext_data_filled != cdw->cdw_r2t_end &&
(bhsdo->bhsdo_flags & BHSDO_FLAGS_F) != 0) {
if ((request->ip_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) ==
ISCSI_BHS_OPCODE_SCSI_DATA_OUT) {
@ -842,7 +844,7 @@ cfiscsi_handle_data_segment(struct icl_pdu *request, struct cfiscsi_data_wait *c
"transmitted size was %zd bytes instead of %d; "
"dropping connection",
(size_t)io->scsiio.ext_data_filled,
io->scsiio.kern_data_len);
cdw->cdw_r2t_end);
ctl_set_data_phase_error(&io->scsiio);
cfiscsi_session_terminate(cs);
return (true);
@ -855,7 +857,7 @@ cfiscsi_handle_data_segment(struct icl_pdu *request, struct cfiscsi_data_wait *c
}
}
if (io->scsiio.ext_data_filled == io->scsiio.kern_data_len) {
if (io->scsiio.ext_data_filled == cdw->cdw_r2t_end) {
#if 0
CFISCSI_SESSION_DEBUG(cs, "no longer expecting Data-Out with target "
"transfer tag 0x%x", cdw->cdw_target_transfer_tag);
@ -911,8 +913,13 @@ cfiscsi_pdu_handle_data_out(struct icl_pdu *request)
CFISCSI_SESSION_LOCK(cs);
TAILQ_REMOVE(&cs->cs_waiting_for_data_out, cdw, cdw_next);
CFISCSI_SESSION_UNLOCK(cs);
done = (io->scsiio.ext_data_filled != cdw->cdw_r2t_end ||
io->scsiio.ext_data_filled == io->scsiio.kern_data_len);
uma_zfree(cfiscsi_data_wait_zone, cdw);
io->scsiio.be_move_done(io);
if (done)
io->scsiio.be_move_done(io);
else
cfiscsi_datamove_out(io);
}
icl_pdu_free(request);
@ -2567,6 +2574,8 @@ cfiscsi_datamove_out(union ctl_io *io)
const struct iscsi_bhs_scsi_command *bhssc;
struct iscsi_bhs_r2t *bhsr2t;
struct cfiscsi_data_wait *cdw;
struct ctl_sg_entry ctl_sg_entry, *ctl_sglist;
uint32_t expected_len, r2t_off, r2t_len;
uint32_t target_transfer_tag;
bool done;
@ -2585,9 +2594,16 @@ cfiscsi_datamove_out(union ctl_io *io)
PDU_TOTAL_TRANSFER_LEN(request) = io->scsiio.kern_total_len;
/*
* We hadn't received anything during this datamove yet.
* Report write underflow as error since CTL and backends don't
* really support it, and SCSI does not tell how to do it right.
*/
io->scsiio.ext_data_filled = 0;
expected_len = ntohl(bhssc->bhssc_expected_data_transfer_length);
if (io->scsiio.kern_rel_offset + io->scsiio.kern_data_len >
expected_len) {
io->scsiio.io_hdr.port_status = 43;
io->scsiio.be_move_done(io);
return;
}
target_transfer_tag =
atomic_fetchadd_32(&cs->cs_target_transfer_tag, 1);
@ -2609,8 +2625,35 @@ cfiscsi_datamove_out(union ctl_io *io)
cdw->cdw_ctl_io = io;
cdw->cdw_target_transfer_tag = target_transfer_tag;
cdw->cdw_initiator_task_tag = bhssc->bhssc_initiator_task_tag;
cdw->cdw_r2t_end = io->scsiio.kern_data_len;
if (cs->cs_immediate_data && io->scsiio.kern_rel_offset <
/* Set initial data pointer for the CDW respecting ext_data_filled. */
if (io->scsiio.kern_sg_entries > 0) {
ctl_sglist = (struct ctl_sg_entry *)io->scsiio.kern_data_ptr;
} else {
ctl_sglist = &ctl_sg_entry;
ctl_sglist->addr = io->scsiio.kern_data_ptr;
ctl_sglist->len = io->scsiio.kern_data_len;
}
cdw->cdw_sg_index = 0;
cdw->cdw_sg_addr = ctl_sglist[cdw->cdw_sg_index].addr;
cdw->cdw_sg_len = ctl_sglist[cdw->cdw_sg_index].len;
r2t_off = io->scsiio.ext_data_filled;
while (r2t_off > 0) {
if (r2t_off >= cdw->cdw_sg_len) {
r2t_off -= cdw->cdw_sg_len;
cdw->cdw_sg_index++;
cdw->cdw_sg_addr = ctl_sglist[cdw->cdw_sg_index].addr;
cdw->cdw_sg_len = ctl_sglist[cdw->cdw_sg_index].len;
continue;
}
cdw->cdw_sg_addr += r2t_off;
cdw->cdw_sg_len -= r2t_off;
r2t_off = 0;
}
if (cs->cs_immediate_data &&
io->scsiio.kern_rel_offset + io->scsiio.ext_data_filled <
icl_pdu_data_segment_length(request)) {
done = cfiscsi_handle_data_segment(request, cdw);
if (done) {
@ -2620,6 +2663,11 @@ cfiscsi_datamove_out(union ctl_io *io)
}
}
r2t_off = io->scsiio.kern_rel_offset + io->scsiio.ext_data_filled;
r2t_len = MIN(io->scsiio.kern_data_len - io->scsiio.ext_data_filled,
cs->cs_max_burst_length);
cdw->cdw_r2t_end = io->scsiio.ext_data_filled + r2t_len;
CFISCSI_SESSION_LOCK(cs);
TAILQ_INSERT_TAIL(&cs->cs_waiting_for_data_out, cdw, cdw_next);
CFISCSI_SESSION_UNLOCK(cs);
@ -2659,16 +2707,13 @@ cfiscsi_datamove_out(union ctl_io *io)
* The ext_data_filled is to account for unsolicited
* (immediate) data that might have already arrived.
*/
bhsr2t->bhsr2t_buffer_offset =
htonl(io->scsiio.kern_rel_offset + io->scsiio.ext_data_filled);
bhsr2t->bhsr2t_buffer_offset = htonl(r2t_off);
/*
* This is the total length (sum of S/G lengths) this call
* to cfiscsi_datamove() is supposed to handle.
*
* XXX: Limit it to MaxBurstLength.
* to cfiscsi_datamove() is supposed to handle, limited by
* MaxBurstLength.
*/
bhsr2t->bhsr2t_desired_data_transfer_length =
htonl(io->scsiio.kern_data_len - io->scsiio.ext_data_filled);
bhsr2t->bhsr2t_desired_data_transfer_length = htonl(r2t_len);
cfiscsi_pdu_queue(response);
}
@ -2678,8 +2723,11 @@ cfiscsi_datamove(union ctl_io *io)
if ((io->io_hdr.flags & CTL_FLAG_DATA_MASK) == CTL_FLAG_DATA_IN)
cfiscsi_datamove_in(io);
else
else {
/* We hadn't received anything during this datamove yet. */
io->scsiio.ext_data_filled = 0;
cfiscsi_datamove_out(io);
}
}
static void

View File

@ -56,6 +56,7 @@ struct cfiscsi_data_wait {
int cdw_sg_index;
char *cdw_sg_addr;
size_t cdw_sg_len;
uint32_t cdw_r2t_end;
};
#define CFISCSI_SESSION_STATE_INVALID 0