| /******************************************************************************* |
| * This file contains main functions related to iSCSI DataSequenceInOrder=No |
| * and DataPDUInOrder=No. |
| * |
| \u00a9 Copyright 2007-2011 RisingTide Systems LLC. |
| * |
| * Licensed to the Linux Foundation under the General Public License (GPL) version 2. |
| * |
| * Author: Nicholas A. Bellinger <nab@linux-iscsi.org> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| ******************************************************************************/ |
| |
| #include <linux/slab.h> |
| #include <linux/random.h> |
| |
| #include "iscsi_target_core.h" |
| #include "iscsi_target_util.h" |
| #include "iscsi_target_seq_pdu_list.h" |
| |
| #define OFFLOAD_BUF_SIZE 32768 |
| |
| void iscsit_dump_seq_list(struct iscsi_cmd *cmd) |
| { |
| int i; |
| struct iscsi_seq *seq; |
| |
| pr_debug("Dumping Sequence List for ITT: 0x%08x:\n", |
| cmd->init_task_tag); |
| |
| for (i = 0; i < cmd->seq_count; i++) { |
| seq = &cmd->seq_list[i]; |
| pr_debug("i: %d, pdu_start: %d, pdu_count: %d," |
| " offset: %d, xfer_len: %d, seq_send_order: %d," |
| " seq_no: %d\n", i, seq->pdu_start, seq->pdu_count, |
| seq->offset, seq->xfer_len, seq->seq_send_order, |
| seq->seq_no); |
| } |
| } |
| |
| void iscsit_dump_pdu_list(struct iscsi_cmd *cmd) |
| { |
| int i; |
| struct iscsi_pdu *pdu; |
| |
| pr_debug("Dumping PDU List for ITT: 0x%08x:\n", |
| cmd->init_task_tag); |
| |
| for (i = 0; i < cmd->pdu_count; i++) { |
| pdu = &cmd->pdu_list[i]; |
| pr_debug("i: %d, offset: %d, length: %d," |
| " pdu_send_order: %d, seq_no: %d\n", i, pdu->offset, |
| pdu->length, pdu->pdu_send_order, pdu->seq_no); |
| } |
| } |
| |
| static void iscsit_ordered_seq_lists( |
| struct iscsi_cmd *cmd, |
| u8 type) |
| { |
| u32 i, seq_count = 0; |
| |
| for (i = 0; i < cmd->seq_count; i++) { |
| if (cmd->seq_list[i].type != SEQTYPE_NORMAL) |
| continue; |
| cmd->seq_list[i].seq_send_order = seq_count++; |
| } |
| } |
| |
| static void iscsit_ordered_pdu_lists( |
| struct iscsi_cmd *cmd, |
| u8 type) |
| { |
| u32 i, pdu_send_order = 0, seq_no = 0; |
| |
| for (i = 0; i < cmd->pdu_count; i++) { |
| redo: |
| if (cmd->pdu_list[i].seq_no == seq_no) { |
| cmd->pdu_list[i].pdu_send_order = pdu_send_order++; |
| continue; |
| } |
| seq_no++; |
| pdu_send_order = 0; |
| goto redo; |
| } |
| } |
| |
| /* |
| * Generate count random values into array. |
| * Use 0x80000000 to mark generates valued in array[]. |
| */ |
| static void iscsit_create_random_array(u32 *array, u32 count) |
| { |
| int i, j, k; |
| |
| if (count == 1) { |
| array[0] = 0; |
| return; |
| } |
| |
| for (i = 0; i < count; i++) { |
| redo: |
| get_random_bytes(&j, sizeof(u32)); |
| j = (1 + (int) (9999 + 1) - j) % count; |
| for (k = 0; k < i + 1; k++) { |
| j |= 0x80000000; |
| if ((array[k] & 0x80000000) && (array[k] == j)) |
| goto redo; |
| } |
| array[i] = j; |
| } |
| |
| for (i = 0; i < count; i++) |
| array[i] &= ~0x80000000; |
| } |
| |
| static int iscsit_randomize_pdu_lists( |
| struct iscsi_cmd *cmd, |
| u8 type) |
| { |
| int i = 0; |
| u32 *array, pdu_count, seq_count = 0, seq_no = 0, seq_offset = 0; |
| |
| for (pdu_count = 0; pdu_count < cmd->pdu_count; pdu_count++) { |
| redo: |
| if (cmd->pdu_list[pdu_count].seq_no == seq_no) { |
| seq_count++; |
| continue; |
| } |
| array = kzalloc(seq_count * sizeof(u32), GFP_KERNEL); |
| if (!array) { |
| pr_err("Unable to allocate memory" |
| " for random array.\n"); |
| return -1; |
| } |
| iscsit_create_random_array(array, seq_count); |
| |
| for (i = 0; i < seq_count; i++) |
| cmd->pdu_list[seq_offset+i].pdu_send_order = array[i]; |
| |
| kfree(array); |
| |
| seq_offset += seq_count; |
| seq_count = 0; |
| seq_no++; |
| goto redo; |
| } |
| |
| if (seq_count) { |
| array = kzalloc(seq_count * sizeof(u32), GFP_KERNEL); |
| if (!array) { |
| pr_err("Unable to allocate memory for" |
| " random array.\n"); |
| return -1; |
| } |
| iscsit_create_random_array(array, seq_count); |
| |
| for (i = 0; i < seq_count; i++) |
| cmd->pdu_list[seq_offset+i].pdu_send_order = array[i]; |
| |
| kfree(array); |
| } |
| |
| return 0; |
| } |
| |
| static int iscsit_randomize_seq_lists( |
| struct iscsi_cmd *cmd, |
| u8 type) |
| { |
| int i, j = 0; |
| u32 *array, seq_count = cmd->seq_count; |
| |
| if ((type == PDULIST_IMMEDIATE) || (type == PDULIST_UNSOLICITED)) |
| seq_count--; |
| else if (type == PDULIST_IMMEDIATE_AND_UNSOLICITED) |
| seq_count -= 2; |
| |
| if (!seq_count) |
| return 0; |
| |
| array = kzalloc(seq_count * sizeof(u32), GFP_KERNEL); |
| if (!array) { |
| pr_err("Unable to allocate memory for random array.\n"); |
| return -1; |
| } |
| iscsit_create_random_array(array, seq_count); |
| |
| for (i = 0; i < cmd->seq_count; i++) { |
| if (cmd->seq_list[i].type != SEQTYPE_NORMAL) |
| continue; |
| cmd->seq_list[i].seq_send_order = array[j++]; |
| } |
| |
| kfree(array); |
| return 0; |
| } |
| |
| static void iscsit_determine_counts_for_list( |
| struct iscsi_cmd *cmd, |
| struct iscsi_build_list *bl, |
| u32 *seq_count, |
| u32 *pdu_count) |
| { |
| int check_immediate = 0; |
| u32 burstlength = 0, offset = 0; |
| u32 unsolicited_data_length = 0; |
| struct iscsi_conn *conn = cmd->conn; |
| |
| if ((bl->type == PDULIST_IMMEDIATE) || |
| (bl->type == PDULIST_IMMEDIATE_AND_UNSOLICITED)) |
| check_immediate = 1; |
| |
| if ((bl->type == PDULIST_UNSOLICITED) || |
| (bl->type == PDULIST_IMMEDIATE_AND_UNSOLICITED)) |
| unsolicited_data_length = (cmd->data_length > |
| conn->sess->sess_ops->FirstBurstLength) ? |
| conn->sess->sess_ops->FirstBurstLength : cmd->data_length; |
| |
| while (offset < cmd->data_length) { |
| *pdu_count += 1; |
| |
| if (check_immediate) { |
| check_immediate = 0; |
| offset += bl->immediate_data_length; |
| *seq_count += 1; |
| if (unsolicited_data_length) |
| unsolicited_data_length -= |
| bl->immediate_data_length; |
| continue; |
| } |
| if (unsolicited_data_length > 0) { |
| if ((offset + conn->conn_ops->MaxRecvDataSegmentLength) |
| >= cmd->data_length) { |
| unsolicited_data_length -= |
| (cmd->data_length - offset); |
| offset += (cmd->data_length - offset); |
| continue; |
| } |
| if ((offset + conn->conn_ops->MaxRecvDataSegmentLength) |
| >= conn->sess->sess_ops->FirstBurstLength) { |
| unsolicited_data_length -= |
| (conn->sess->sess_ops->FirstBurstLength - |
| offset); |
| offset += (conn->sess->sess_ops->FirstBurstLength - |
| offset); |
| burstlength = 0; |
| *seq_count += 1; |
| continue; |
| } |
| |
| offset += conn->conn_ops->MaxRecvDataSegmentLength; |
| unsolicited_data_length -= |
| conn->conn_ops->MaxRecvDataSegmentLength; |
| continue; |
| } |
| if ((offset + conn->conn_ops->MaxRecvDataSegmentLength) >= |
| cmd->data_length) { |
| offset += (cmd->data_length - offset); |
| continue; |
| } |
| if ((burstlength + conn->conn_ops->MaxRecvDataSegmentLength) >= |
| conn->sess->sess_ops->MaxBurstLength) { |
| offset += (conn->sess->sess_ops->MaxBurstLength - |
| burstlength); |
| burstlength = 0; |
| *seq_count += 1; |
| continue; |
| } |
| |
| burstlength += conn->conn_ops->MaxRecvDataSegmentLength; |
| offset += conn->conn_ops->MaxRecvDataSegmentLength; |
| } |
| } |
| |
| |
| /* |
| * Builds PDU and/or Sequence list, called while DataSequenceInOrder=No |
| * and DataPDUInOrder=No. |
| */ |
| static int iscsit_build_pdu_and_seq_list( |
| struct iscsi_cmd *cmd, |
| struct iscsi_build_list *bl) |
| { |
| int check_immediate = 0, datapduinorder, datasequenceinorder; |
| u32 burstlength = 0, offset = 0, i = 0; |
| u32 pdu_count = 0, seq_no = 0, unsolicited_data_length = 0; |
| struct iscsi_conn *conn = cmd->conn; |
| struct iscsi_pdu *pdu = cmd->pdu_list; |
| struct iscsi_seq *seq = cmd->seq_list; |
| |
| datapduinorder = conn->sess->sess_ops->DataPDUInOrder; |
| datasequenceinorder = conn->sess->sess_ops->DataSequenceInOrder; |
| |
| if ((bl->type == PDULIST_IMMEDIATE) || |
| (bl->type == PDULIST_IMMEDIATE_AND_UNSOLICITED)) |
| check_immediate = 1; |
| |
| if ((bl->type == PDULIST_UNSOLICITED) || |
| (bl->type == PDULIST_IMMEDIATE_AND_UNSOLICITED)) |
| unsolicited_data_length = (cmd->data_length > |
| conn->sess->sess_ops->FirstBurstLength) ? |
| conn->sess->sess_ops->FirstBurstLength : cmd->data_length; |
| |
| while (offset < cmd->data_length) { |
| pdu_count++; |
| if (!datapduinorder) { |
| pdu[i].offset = offset; |
| pdu[i].seq_no = seq_no; |
| } |
| if (!datasequenceinorder && (pdu_count == 1)) { |
| seq[seq_no].pdu_start = i; |
| seq[seq_no].seq_no = seq_no; |
| seq[seq_no].offset = offset; |
| seq[seq_no].orig_offset = offset; |
| } |
| |
| if (check_immediate) { |
| check_immediate = 0; |
| if (!datapduinorder) { |
| pdu[i].type = PDUTYPE_IMMEDIATE; |
| pdu[i++].length = bl->immediate_data_length; |
| } |
| if (!datasequenceinorder) { |
| seq[seq_no].type = SEQTYPE_IMMEDIATE; |
| seq[seq_no].pdu_count = 1; |
| seq[seq_no].xfer_len = |
| bl->immediate_data_length; |
| } |
| offset += bl->immediate_data_length; |
| pdu_count = 0; |
| seq_no++; |
| if (unsolicited_data_length) |
| unsolicited_data_length -= |
| bl->immediate_data_length; |
| continue; |
| } |
| if (unsolicited_data_length > 0) { |
| if ((offset + |
| conn->conn_ops->MaxRecvDataSegmentLength) >= |
| cmd->data_length) { |
| if (!datapduinorder) { |
| pdu[i].type = PDUTYPE_UNSOLICITED; |
| pdu[i].length = |
| (cmd->data_length - offset); |
| } |
| if (!datasequenceinorder) { |
| seq[seq_no].type = SEQTYPE_UNSOLICITED; |
| seq[seq_no].pdu_count = pdu_count; |
| seq[seq_no].xfer_len = (burstlength + |
| (cmd->data_length - offset)); |
| } |
| unsolicited_data_length -= |
| (cmd->data_length - offset); |
| offset += (cmd->data_length - offset); |
| continue; |
| } |
| if ((offset + |
| conn->conn_ops->MaxRecvDataSegmentLength) >= |
| conn->sess->sess_ops->FirstBurstLength) { |
| if (!datapduinorder) { |
| pdu[i].type = PDUTYPE_UNSOLICITED; |
| pdu[i++].length = |
| (conn->sess->sess_ops->FirstBurstLength - |
| offset); |
| } |
| if (!datasequenceinorder) { |
| seq[seq_no].type = SEQTYPE_UNSOLICITED; |
| seq[seq_no].pdu_count = pdu_count; |
| seq[seq_no].xfer_len = (burstlength + |
| (conn->sess->sess_ops->FirstBurstLength - |
| offset)); |
| } |
| unsolicited_data_length -= |
| (conn->sess->sess_ops->FirstBurstLength - |
| offset); |
| offset += (conn->sess->sess_ops->FirstBurstLength - |
| offset); |
| burstlength = 0; |
| pdu_count = 0; |
| seq_no++; |
| continue; |
| } |
| |
| if (!datapduinorder) { |
| pdu[i].type = PDUTYPE_UNSOLICITED; |
| pdu[i++].length = |
| conn->conn_ops->MaxRecvDataSegmentLength; |
| } |
| burstlength += conn->conn_ops->MaxRecvDataSegmentLength; |
| offset += conn->conn_ops->MaxRecvDataSegmentLength; |
| unsolicited_data_length -= |
| conn->conn_ops->MaxRecvDataSegmentLength; |
| continue; |
| } |
| if ((offset + conn->conn_ops->MaxRecvDataSegmentLength) >= |
| cmd->data_length) { |
| if (!datapduinorder) { |
| pdu[i].type = PDUTYPE_NORMAL; |
| pdu[i].length = (cmd->data_length - offset); |
| } |
| if (!datasequenceinorder) { |
| seq[seq_no].type = SEQTYPE_NORMAL; |
| seq[seq_no].pdu_count = pdu_count; |
| seq[seq_no].xfer_len = (burstlength + |
| (cmd->data_length - offset)); |
| } |
| offset += (cmd->data_length - offset); |
| continue; |
| } |
| if ((burstlength + conn->conn_ops->MaxRecvDataSegmentLength) >= |
| conn->sess->sess_ops->MaxBurstLength) { |
| if (!datapduinorder) { |
| pdu[i].type = PDUTYPE_NORMAL; |
| pdu[i++].length = |
| (conn->sess->sess_ops->MaxBurstLength - |
| burstlength); |
| } |
| if (!datasequenceinorder) { |
| seq[seq_no].type = SEQTYPE_NORMAL; |
| seq[seq_no].pdu_count = pdu_count; |
| seq[seq_no].xfer_len = (burstlength + |
| (conn->sess->sess_ops->MaxBurstLength - |
| burstlength)); |
| } |
| offset += (conn->sess->sess_ops->MaxBurstLength - |
| burstlength); |
| burstlength = 0; |
| pdu_count = 0; |
| seq_no++; |
| continue; |
| } |
| |
| if (!datapduinorder) { |
| pdu[i].type = PDUTYPE_NORMAL; |
| pdu[i++].length = |
| conn->conn_ops->MaxRecvDataSegmentLength; |
| } |
| burstlength += conn->conn_ops->MaxRecvDataSegmentLength; |
| offset += conn->conn_ops->MaxRecvDataSegmentLength; |
| } |
| |
| if (!datasequenceinorder) { |
| if (bl->data_direction & ISCSI_PDU_WRITE) { |
| if (bl->randomize & RANDOM_R2T_OFFSETS) { |
| if (iscsit_randomize_seq_lists(cmd, bl->type) |
| < 0) |
| return -1; |
| } else |
| iscsit_ordered_seq_lists(cmd, bl->type); |
| } else if (bl->data_direction & ISCSI_PDU_READ) { |
| if (bl->randomize & RANDOM_DATAIN_SEQ_OFFSETS) { |
| if (iscsit_randomize_seq_lists(cmd, bl->type) |
| < 0) |
| return -1; |
| } else |
| iscsit_ordered_seq_lists(cmd, bl->type); |
| } |
| #if 0 |
| iscsit_dump_seq_list(cmd); |
| #endif |
| } |
| if (!datapduinorder) { |
| if (bl->data_direction & ISCSI_PDU_WRITE) { |
| if (bl->randomize & RANDOM_DATAOUT_PDU_OFFSETS) { |
| if (iscsit_randomize_pdu_lists(cmd, bl->type) |
| < 0) |
| return -1; |
| } else |
| iscsit_ordered_pdu_lists(cmd, bl->type); |
| } else if (bl->data_direction & ISCSI_PDU_READ) { |
| if (bl->randomize & RANDOM_DATAIN_PDU_OFFSETS) { |
| if (iscsit_randomize_pdu_lists(cmd, bl->type) |
| < 0) |
| return -1; |
| } else |
| iscsit_ordered_pdu_lists(cmd, bl->type); |
| } |
| #if 0 |
| iscsit_dump_pdu_list(cmd); |
| #endif |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Only called while DataSequenceInOrder=No or DataPDUInOrder=No. |
| */ |
| int iscsit_do_build_list( |
| struct iscsi_cmd *cmd, |
| struct iscsi_build_list *bl) |
| { |
| u32 pdu_count = 0, seq_count = 1; |
| struct iscsi_conn *conn = cmd->conn; |
| struct iscsi_pdu *pdu = NULL; |
| struct iscsi_seq *seq = NULL; |
| |
| iscsit_determine_counts_for_list(cmd, bl, &seq_count, &pdu_count); |
| |
| if (!conn->sess->sess_ops->DataSequenceInOrder) { |
| seq = kzalloc(seq_count * sizeof(struct iscsi_seq), GFP_ATOMIC); |
| if (!seq) { |
| pr_err("Unable to allocate struct iscsi_seq list\n"); |
| return -1; |
| } |
| cmd->seq_list = seq; |
| cmd->seq_count = seq_count; |
| } |
| |
| if (!conn->sess->sess_ops->DataPDUInOrder) { |
| pdu = kzalloc(pdu_count * sizeof(struct iscsi_pdu), GFP_ATOMIC); |
| if (!pdu) { |
| pr_err("Unable to allocate struct iscsi_pdu list.\n"); |
| kfree(seq); |
| return -1; |
| } |
| cmd->pdu_list = pdu; |
| cmd->pdu_count = pdu_count; |
| } |
| |
| return iscsit_build_pdu_and_seq_list(cmd, bl); |
| } |
| |
| struct iscsi_pdu *iscsit_get_pdu_holder( |
| struct iscsi_cmd *cmd, |
| u32 offset, |
| u32 length) |
| { |
| u32 i; |
| struct iscsi_pdu *pdu = NULL; |
| |
| if (!cmd->pdu_list) { |
| pr_err("struct iscsi_cmd->pdu_list is NULL!\n"); |
| return NULL; |
| } |
| |
| pdu = &cmd->pdu_list[0]; |
| |
| for (i = 0; i < cmd->pdu_count; i++) |
| if ((pdu[i].offset == offset) && (pdu[i].length == length)) |
| return &pdu[i]; |
| |
| pr_err("Unable to locate PDU holder for ITT: 0x%08x, Offset:" |
| " %u, Length: %u\n", cmd->init_task_tag, offset, length); |
| return NULL; |
| } |
| |
| struct iscsi_pdu *iscsit_get_pdu_holder_for_seq( |
| struct iscsi_cmd *cmd, |
| struct iscsi_seq *seq) |
| { |
| u32 i; |
| struct iscsi_conn *conn = cmd->conn; |
| struct iscsi_pdu *pdu = NULL; |
| |
| if (!cmd->pdu_list) { |
| pr_err("struct iscsi_cmd->pdu_list is NULL!\n"); |
| return NULL; |
| } |
| |
| if (conn->sess->sess_ops->DataSequenceInOrder) { |
| redo: |
| pdu = &cmd->pdu_list[cmd->pdu_start]; |
| |
| for (i = 0; pdu[i].seq_no != cmd->seq_no; i++) { |
| #if 0 |
| pr_debug("pdu[i].seq_no: %d, pdu[i].pdu" |
| "_send_order: %d, pdu[i].offset: %d," |
| " pdu[i].length: %d\n", pdu[i].seq_no, |
| pdu[i].pdu_send_order, pdu[i].offset, |
| pdu[i].length); |
| #endif |
| if (pdu[i].pdu_send_order == cmd->pdu_send_order) { |
| cmd->pdu_send_order++; |
| return &pdu[i]; |
| } |
| } |
| |
| cmd->pdu_start += cmd->pdu_send_order; |
| cmd->pdu_send_order = 0; |
| cmd->seq_no++; |
| |
| if (cmd->pdu_start < cmd->pdu_count) |
| goto redo; |
| |
| pr_err("Command ITT: 0x%08x unable to locate" |
| " struct iscsi_pdu for cmd->pdu_send_order: %u.\n", |
| cmd->init_task_tag, cmd->pdu_send_order); |
| return NULL; |
| } else { |
| if (!seq) { |
| pr_err("struct iscsi_seq is NULL!\n"); |
| return NULL; |
| } |
| #if 0 |
| pr_debug("seq->pdu_start: %d, seq->pdu_count: %d," |
| " seq->seq_no: %d\n", seq->pdu_start, seq->pdu_count, |
| seq->seq_no); |
| #endif |
| pdu = &cmd->pdu_list[seq->pdu_start]; |
| |
| if (seq->pdu_send_order == seq->pdu_count) { |
| pr_err("Command ITT: 0x%08x seq->pdu_send" |
| "_order: %u equals seq->pdu_count: %u\n", |
| cmd->init_task_tag, seq->pdu_send_order, |
| seq->pdu_count); |
| return NULL; |
| } |
| |
| for (i = 0; i < seq->pdu_count; i++) { |
| if (pdu[i].pdu_send_order == seq->pdu_send_order) { |
| seq->pdu_send_order++; |
| return &pdu[i]; |
| } |
| } |
| |
| pr_err("Command ITT: 0x%08x unable to locate iscsi" |
| "_pdu_t for seq->pdu_send_order: %u.\n", |
| cmd->init_task_tag, seq->pdu_send_order); |
| return NULL; |
| } |
| |
| return NULL; |
| } |
| |
| struct iscsi_seq *iscsit_get_seq_holder( |
| struct iscsi_cmd *cmd, |
| u32 offset, |
| u32 length) |
| { |
| u32 i; |
| |
| if (!cmd->seq_list) { |
| pr_err("struct iscsi_cmd->seq_list is NULL!\n"); |
| return NULL; |
| } |
| |
| for (i = 0; i < cmd->seq_count; i++) { |
| #if 0 |
| pr_debug("seq_list[i].orig_offset: %d, seq_list[i]." |
| "xfer_len: %d, seq_list[i].seq_no %u\n", |
| cmd->seq_list[i].orig_offset, cmd->seq_list[i].xfer_len, |
| cmd->seq_list[i].seq_no); |
| #endif |
| if ((cmd->seq_list[i].orig_offset + |
| cmd->seq_list[i].xfer_len) >= |
| (offset + length)) |
| return &cmd->seq_list[i]; |
| } |
| |
| pr_err("Unable to locate Sequence holder for ITT: 0x%08x," |
| " Offset: %u, Length: %u\n", cmd->init_task_tag, offset, |
| length); |
| return NULL; |
| } |