From: Vladislav Bolkhovitin on 13 Apr 2010 09:10 This patch contains file scst_lib.c. Signed-off-by: Vladislav Bolkhovitin <vst(a)vlnb.net> --- scst_lib.c | 6337 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 6337 insertions(+) diff -uprN orig/linux-2.6.33/drivers/scst/scst_lib.c linux-2.6.33/drivers/scst/scst_lib.c --- orig/linux-2.6.33/drivers/scst/scst_lib.c +++ linux-2.6.33/drivers/scst/scst_lib.c @@ -0,0 +1,6337 @@ +/* + * scst_lib.c + * + * Copyright (C) 2004 - 2010 Vladislav Bolkhovitin <vst(a)vlnb.net> + * Copyright (C) 2004 - 2005 Leonid Stoljar + * Copyright (C) 2007 - 2010 ID7 Ltd. + * + * 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, version 2 + * of the License. + * + * 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/init.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#include <linux/cdrom.h> +#include <linux/unistd.h> +#include <linux/string.h> +#include <asm/kmap_types.h> +#include <linux/ctype.h> +#include <linux/delay.h> + +#include "scst.h" +#include "scst_priv.h" +#include "scst_mem.h" + +struct scsi_io_context { + unsigned int full_cdb_used:1; + void *data; + void (*done)(void *data, char *sense, int result, int resid); + char sense[SCST_SENSE_BUFFERSIZE]; + unsigned char full_cdb[0]; +}; +static struct kmem_cache *scsi_io_context_cache; + +/* get_trans_len_x extract x bytes from cdb as length starting from off */ +static int get_trans_len_1(struct scst_cmd *cmd, uint8_t off); +static int get_trans_len_1_256(struct scst_cmd *cmd, uint8_t off); +static int get_trans_len_2(struct scst_cmd *cmd, uint8_t off); +static int get_trans_len_3(struct scst_cmd *cmd, uint8_t off); +static int get_trans_len_4(struct scst_cmd *cmd, uint8_t off); + +/* for special commands */ +static int get_trans_len_block_limit(struct scst_cmd *cmd, uint8_t off); +static int get_trans_len_read_capacity(struct scst_cmd *cmd, uint8_t off); +static int get_trans_len_serv_act_in(struct scst_cmd *cmd, uint8_t off); +static int get_trans_len_single(struct scst_cmd *cmd, uint8_t off); +static int get_trans_len_none(struct scst_cmd *cmd, uint8_t off); +static int get_trans_len_read_pos(struct scst_cmd *cmd, uint8_t off); +static int get_trans_cdb_len_10(struct scst_cmd *cmd, uint8_t off); +static int get_trans_len_prevent_allow_medium_removal(struct scst_cmd *cmd, + uint8_t off); +static int get_trans_len_3_read_elem_stat(struct scst_cmd *cmd, uint8_t off); +static int get_trans_len_start_stop(struct scst_cmd *cmd, uint8_t off); + +/* ++=====================================-============-======- +| Command name | Operation | Type | +| | code | | +|-------------------------------------+------------+------+ + ++=========================================================+ +|Key: M = command implementation is mandatory. | +| O = command implementation is optional. | +| V = Vendor-specific | +| R = Reserved | +| ' '= DON'T use for this device | ++=========================================================+ +*/ + +#define SCST_CDB_MANDATORY 'M' /* mandatory */ +#define SCST_CDB_OPTIONAL 'O' /* optional */ +#define SCST_CDB_VENDOR 'V' /* vendor */ +#define SCST_CDB_RESERVED 'R' /* reserved */ +#define SCST_CDB_NOTSUPP ' ' /* don't use */ + +struct scst_sdbops { + uint8_t ops; /* SCSI-2 op codes */ + uint8_t devkey[16]; /* Key for every device type M,O,V,R + * type_disk devkey[0] + * type_tape devkey[1] + * type_printer devkey[2] + * type_proseccor devkey[3] + * type_worm devkey[4] + * type_cdrom devkey[5] + * type_scanner devkey[6] + * type_mod devkey[7] + * type_changer devkey[8] + * type_commdev devkey[9] + * type_reserv devkey[A] + * type_reserv devkey[B] + * type_raid devkey[C] + * type_enclosure devkey[D] + * type_reserv devkey[E] + * type_reserv devkey[F] + */ + const char *op_name; /* SCSI-2 op codes full name */ + uint8_t direction; /* init --> target: SCST_DATA_WRITE + * target --> init: SCST_DATA_READ + */ + uint16_t flags; /* opcode -- various flags */ + uint8_t off; /* length offset in cdb */ + int (*get_trans_len)(struct scst_cmd *cmd, uint8_t off) + __attribute__ ((aligned)); +} __attribute__((packed)); + +static int scst_scsi_op_list[256]; + +#define FLAG_NONE 0 + +static const struct scst_sdbops scst_scsi_op_table[] = { + /* + * +-------------------> TYPE_IS_DISK (0) + * | + * |+------------------> TYPE_IS_TAPE (1) + * || + * || +----------------> TYPE_IS_PROCESSOR (3) + * || | + * || | +--------------> TYPE_IS_CDROM (5) + * || | | + * || | | +------------> TYPE_IS_MOD (7) + * || | | | + * || | | |+-----------> TYPE_IS_CHANGER (8) + * || | | || + * || | | || +-------> TYPE_IS_RAID (C) + * || | | || | + * || | | || | + * 0123456789ABCDEF ---> TYPE_IS_???? */ + + /* 6-bytes length CDB */ + {0x00, "MMMMMMMMMMMMMMMM", "TEST UNIT READY", + /* let's be HQ to don't look dead under high load */ + SCST_DATA_NONE, SCST_SMALL_TIMEOUT|SCST_IMPLICIT_HQ| + SCST_REG_RESERVE_ALLOWED, + 0, get_trans_len_none}, + {0x01, " M ", "REWIND", + SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none}, + {0x01, "O V OO OO ", "REZERO UNIT", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x02, "VVVVVV V ", "REQUEST BLOCK ADDR", + SCST_DATA_NONE, SCST_SMALL_TIMEOUT, 0, get_trans_len_none}, + {0x03, "MMMMMMMMMMMMMMMM", "REQUEST SENSE", + SCST_DATA_READ, SCST_SMALL_TIMEOUT|SCST_SKIP_UA|SCST_LOCAL_CMD| + SCST_REG_RESERVE_ALLOWED, + 4, get_trans_len_1}, + {0x04, "M O O ", "FORMAT UNIT", + SCST_DATA_WRITE, SCST_LONG_TIMEOUT|SCST_UNKNOWN_LENGTH|SCST_WRITE_MEDIUM, + 0, get_trans_len_none}, + {0x04, " O ", "FORMAT", + SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none}, + {0x05, "VMVVVV V ", "READ BLOCK LIMITS", + SCST_DATA_READ, SCST_SMALL_TIMEOUT|SCST_REG_RESERVE_ALLOWED, + 0, get_trans_len_block_limit}, + {0x07, " O ", "INITIALIZE ELEMENT STATUS", + SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none}, + {0x07, "OVV O OV ", "REASSIGN BLOCKS", + SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none}, + {0x08, "O ", "READ(6)", + SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED, 4, get_trans_len_1_256}, + {0x08, " MV OO OV ", "READ(6)", + SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED, 2, get_trans_len_3}, + {0x08, " M ", "GET MESSAGE(6)", + SCST_DATA_READ, FLAG_NONE, 2, get_trans_len_3}, + {0x08, " O ", "RECEIVE", + SCST_DATA_READ, FLAG_NONE, 2, get_trans_len_3}, + {0x0A, "O ", "WRITE(6)", + SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM, + 4, get_trans_len_1_256}, + {0x0A, " M O OV ", "WRITE(6)", + SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM, + 2, get_trans_len_3}, + {0x0A, " M ", "PRINT", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x0A, " M ", "SEND MESSAGE(6)", + SCST_DATA_WRITE, FLAG_NONE, 2, get_trans_len_3}, + {0x0A, " M ", "SEND(6)", + SCST_DATA_WRITE, FLAG_NONE, 2, get_trans_len_3}, + {0x0B, "O OO OV ", "SEEK(6)", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x0B, " ", "TRACK SELECT", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x0B, " O ", "SLEW AND PRINT", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x0C, "VVVVVV V ", "SEEK BLOCK", + SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none}, + {0x0D, "VVVVVV V ", "PARTITION", + SCST_DATA_NONE, SCST_LONG_TIMEOUT|SCST_WRITE_MEDIUM, + 0, get_trans_len_none}, + {0x0F, "VOVVVV V ", "READ REVERSE", + SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED, 2, get_trans_len_3}, + {0x10, "VM V V ", "WRITE FILEMARKS", + SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none}, + {0x10, " O O ", "SYNCHRONIZE BUFFER", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x11, "VMVVVV ", "SPACE", + SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none}, + {0x12, "MMMMMMMMMMMMMMMM", "INQUIRY", + SCST_DATA_READ, SCST_SMALL_TIMEOUT|SCST_IMPLICIT_HQ|SCST_SKIP_UA| + SCST_REG_RESERVE_ALLOWED, + 4, get_trans_len_1}, + {0x13, "VOVVVV ", "VERIFY(6)", + SCST_DATA_NONE, SCST_TRANSFER_LEN_TYPE_FIXED| + SCST_VERIFY_BYTCHK_MISMATCH_ALLOWED, + 2, get_trans_len_3}, + {0x14, "VOOVVV ", "RECOVER BUFFERED DATA", + SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED, 2, get_trans_len_3}, + {0x15, "OMOOOOOOOOOOOOOO", "MODE SELECT(6)", + SCST_DATA_WRITE, SCST_LOCAL_CMD, 4, get_trans_len_1}, + {0x16, "MMMMMMMMMMMMMMMM", "RESERVE", + SCST_DATA_NONE, SCST_SMALL_TIMEOUT|SCST_LOCAL_CMD, + 0, get_trans_len_none}, + {0x17, "MMMMMMMMMMMMMMMM", "RELEASE", + SCST_DATA_NONE, SCST_SMALL_TIMEOUT|SCST_LOCAL_CMD|SCST_REG_RESERVE_ALLOWED, + 0, get_trans_len_none}, + {0x18, "OOOOOOOO ", "COPY", + SCST_DATA_WRITE, SCST_LONG_TIMEOUT, 2, get_trans_len_3}, + {0x19, "VMVVVV ", "ERASE", + SCST_DATA_NONE, SCST_LONG_TIMEOUT|SCST_WRITE_MEDIUM, + 0, get_trans_len_none}, + {0x1A, "OMOOOOOOOOOOOOOO", "MODE SENSE(6)", + SCST_DATA_READ, SCST_SMALL_TIMEOUT, 4, get_trans_len_1}, + {0x1B, " O ", "SCAN", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x1B, " O ", "LOAD UNLOAD", + SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none}, + {0x1B, " O ", "STOP PRINT", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x1B, "O OO O O ", "START STOP UNIT", + SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_start_stop}, + {0x1C, "OOOOOOOOOOOOOOOO", "RECEIVE DIAGNOSTIC RESULTS", + SCST_DATA_READ, FLAG_NONE, 3, get_trans_len_2}, + {0x1D, "MMMMMMMMMMMMMMMM", "SEND DIAGNOSTIC", + SCST_DATA_WRITE, FLAG_NONE, 4, get_trans_len_1}, + {0x1E, "OOOOOOOOOOOOOOOO", "PREVENT ALLOW MEDIUM REMOVAL", + SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, + get_trans_len_prevent_allow_medium_removal}, + {0x1F, " O ", "PORT STATUS", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + + /* 10-bytes length CDB */ + {0x23, "V VV V ", "READ FORMAT CAPACITY", + SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2}, + {0x24, "V VVM ", "SET WINDOW", + SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_3}, + {0x25, "M MM M ", "READ CAPACITY", + SCST_DATA_READ, SCST_IMPLICIT_HQ|SCST_REG_RESERVE_ALLOWED, + 0, get_trans_len_read_capacity}, + {0x25, " O ", "GET WINDOW", + SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_3}, + {0x28, "M MMMM ", "READ(10)", + SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED, 7, get_trans_len_2}, + {0x28, " O ", "GET MESSAGE(10)", + SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2}, + {0x29, "V VV O ", "READ GENERATION", + SCST_DATA_READ, FLAG_NONE, 8, get_trans_len_1}, + {0x2A, "O MO M ", "WRITE(10)", + SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM, + 7, get_trans_len_2}, + {0x2A, " O ", "SEND MESSAGE(10)", + SCST_DATA_WRITE, FLAG_NONE, 7, get_trans_len_2}, + {0x2A, " O ", "SEND(10)", + SCST_DATA_WRITE, FLAG_NONE, 7, get_trans_len_2}, + {0x2B, " O ", "LOCATE", + SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none}, + {0x2B, " O ", "POSITION TO ELEMENT", + SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none}, + {0x2B, "O OO O ", "SEEK(10)", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x2C, "V O O ", "ERASE(10)", + SCST_DATA_NONE, SCST_LONG_TIMEOUT|SCST_WRITE_MEDIUM, + 0, get_trans_len_none}, + {0x2D, "V O O ", "READ UPDATED BLOCK", + SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED, 0, get_trans_len_single}, + {0x2E, "O OO O ", "WRITE AND VERIFY(10)", + SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM, + 7, get_trans_len_2}, + {0x2F, "O OO O ", "VERIFY(10)", + SCST_DATA_NONE, SCST_TRANSFER_LEN_TYPE_FIXED| + SCST_VERIFY_BYTCHK_MISMATCH_ALLOWED, + 7, get_trans_len_2}, + {0x33, "O OO O ", "SET LIMITS(10)", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x34, " O ", "READ POSITION", + SCST_DATA_READ, SCST_SMALL_TIMEOUT, 7, get_trans_len_read_pos}, + {0x34, " O ", "GET DATA BUFFER STATUS", + SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2}, + {0x34, "O OO O ", "PRE-FETCH", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x35, "O OO O ", "SYNCHRONIZE CACHE", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x36, "O OO O ", "LOCK UNLOCK CACHE", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x37, "O O ", "READ DEFECT DATA(10)", + SCST_DATA_READ, FLAG_NONE, 8, get_trans_len_1}, + {0x37, " O ", "INIT ELEMENT STATUS WRANGE", + SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none}, + {0x38, " O O ", "MEDIUM SCAN", + SCST_DATA_READ, FLAG_NONE, 8, get_trans_len_1}, + {0x39, "OOOOOOOO ", "COMPARE", + SCST_DATA_WRITE, FLAG_NONE, 3, get_trans_len_3}, + {0x3A, "OOOOOOOO ", "COPY AND VERIFY", + SCST_DATA_WRITE, FLAG_NONE, 3, get_trans_len_3}, + {0x3B, "OOOOOOOOOOOOOOOO", "WRITE BUFFER", + SCST_DATA_WRITE, SCST_SMALL_TIMEOUT, 6, get_trans_len_3}, + {0x3C, "OOOOOOOOOOOOOOOO", "READ BUFFER", + SCST_DATA_READ, SCST_SMALL_TIMEOUT, 6, get_trans_len_3}, + {0x3D, " O O ", "UPDATE BLOCK", + SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED, + 0, get_trans_len_single}, + {0x3E, "O OO O ", "READ LONG", + SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2}, + {0x3F, "O O O ", "WRITE LONG", + SCST_DATA_WRITE, SCST_WRITE_MEDIUM, 7, get_trans_len_2}, + {0x40, "OOOOOOOOOO ", "CHANGE DEFINITION", + SCST_DATA_WRITE, SCST_SMALL_TIMEOUT, 8, get_trans_len_1}, + {0x41, "O O ", "WRITE SAME", + SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM, + 0, get_trans_len_single}, + {0x42, " O ", "READ SUB-CHANNEL", + SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2}, + {0x43, " O ", "READ TOC/PMA/ATIP", + SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2}, + {0x44, " M ", "REPORT DENSITY SUPPORT", + SCST_DATA_READ, SCST_REG_RESERVE_ALLOWED, 7, get_trans_len_2}, + {0x44, " O ", "READ HEADER", + SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2}, + {0x45, " O ", "PLAY AUDIO(10)", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x46, " O ", "GET CONFIGURATION", + SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2}, + {0x47, " O ", "PLAY AUDIO MSF", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x48, " O ", "PLAY AUDIO TRACK INDEX", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x49, " O ", "PLAY TRACK RELATIVE(10)", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x4A, " O ", "GET EVENT STATUS NOTIFICATION", + SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2}, + {0x4B, " O ", "PAUSE/RESUME", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x4C, "OOOOOOOOOOOOOOOO", "LOG SELECT", + SCST_DATA_WRITE, SCST_SMALL_TIMEOUT, 7, get_trans_len_2}, + {0x4D, "OOOOOOOOOOOOOOOO", "LOG SENSE", + SCST_DATA_READ, SCST_SMALL_TIMEOUT|SCST_REG_RESERVE_ALLOWED, + 7, get_trans_len_2}, + {0x4E, " O ", "STOP PLAY/SCAN", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x50, " ", "XDWRITE", + SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none}, + {0x51, " O ", "READ DISC INFORMATION", + SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2}, + {0x51, " ", "XPWRITE", + SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none}, + {0x52, " O ", "READ TRACK INFORMATION", + SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2}, + {0x53, " O ", "RESERVE TRACK", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x54, " O ", "SEND OPC INFORMATION", + SCST_DATA_WRITE, FLAG_NONE, 7, get_trans_len_2}, + {0x55, "OOOOOOOOOOOOOOOO", "MODE SELECT(10)", + SCST_DATA_WRITE, SCST_LOCAL_CMD, 7, get_trans_len_2}, + {0x56, "OOOOOOOOOOOOOOOO", "RESERVE(10)", + SCST_DATA_NONE, SCST_SMALL_TIMEOUT|SCST_LOCAL_CMD, + 0, get_trans_len_none}, + {0x57, "OOOOOOOOOOOOOOOO", "RELEASE(10)", + SCST_DATA_NONE, SCST_SMALL_TIMEOUT|SCST_LOCAL_CMD|SCST_REG_RESERVE_ALLOWED, + 0, get_trans_len_none}, + {0x58, " O ", "REPAIR TRACK", + SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none}, + {0x5A, "OOOOOOOOOOOOOOOO", "MODE SENSE(10)", + SCST_DATA_READ, SCST_SMALL_TIMEOUT, 7, get_trans_len_2}, + {0x5B, " O ", "CLOSE TRACK/SESSION", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x5C, " O ", "READ BUFFER CAPACITY", + SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_2}, + {0x5D, " O ", "SEND CUE SHEET", + SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_3}, + {0x5E, "OOOOO OOOO ", "PERSISTENT RESERV IN", + SCST_DATA_READ, FLAG_NONE, 5, get_trans_len_4}, + {0x5F, "OOOOO OOOO ", "PERSISTENT RESERV OUT", + SCST_DATA_WRITE, FLAG_NONE, 5, get_trans_len_4}, + + /* 16-bytes length CDB */ + {0x80, "O OO O ", "XDWRITE EXTENDED", + SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none}, + {0x80, " M ", "WRITE FILEMARKS", + SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none}, + {0x81, "O OO O ", "REBUILD", + SCST_DATA_WRITE, SCST_WRITE_MEDIUM, 10, get_trans_len_4}, + {0x82, "O OO O ", "REGENERATE", + SCST_DATA_WRITE, SCST_WRITE_MEDIUM, 10, get_trans_len_4}, + {0x83, "OOOOOOOOOOOOOOOO", "EXTENDED COPY", + SCST_DATA_WRITE, SCST_WRITE_MEDIUM, 10, get_trans_len_4}, + {0x84, "OOOOOOOOOOOOOOOO", "RECEIVE COPY RESULT", + SCST_DATA_WRITE, FLAG_NONE, 10, get_trans_len_4}, + {0x86, "OOOOOOOOOO ", "ACCESS CONTROL IN", + SCST_DATA_NONE, SCST_REG_RESERVE_ALLOWED, 0, get_trans_len_none}, + {0x87, "OOOOOOOOOO ", "ACCESS CONTROL OUT", + SCST_DATA_NONE, SCST_REG_RESERVE_ALLOWED, 0, get_trans_len_none}, + {0x88, "M MMMM ", "READ(16)", + SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED, 10, get_trans_len_4}, + {0x8A, "O OO O ", "WRITE(16)", + SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM, + 10, get_trans_len_4}, + {0x8C, "OOOOOOOOOO ", "READ ATTRIBUTE", + SCST_DATA_READ, FLAG_NONE, 10, get_trans_len_4}, + {0x8D, "OOOOOOOOOO ", "WRITE ATTRIBUTE", + SCST_DATA_WRITE, SCST_WRITE_MEDIUM, 10, get_trans_len_4}, + {0x8E, "O OO O ", "WRITE AND VERIFY(16)", + SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM, + 10, get_trans_len_4}, + {0x8F, "O OO O ", "VERIFY(16)", + SCST_DATA_NONE, SCST_TRANSFER_LEN_TYPE_FIXED| + SCST_VERIFY_BYTCHK_MISMATCH_ALLOWED, + 10, get_trans_len_4}, + {0x90, "O OO O ", "PRE-FETCH(16)", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x91, "O OO O ", "SYNCHRONIZE CACHE(16)", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x91, " M ", "SPACE(16)", + SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none}, + {0x92, "O OO O ", "LOCK UNLOCK CACHE(16)", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0x92, " O ", "LOCATE(16)", + SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none}, + {0x93, "O O ", "WRITE SAME(16)", + SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM, + 10, get_trans_len_4}, + {0x93, " M ", "ERASE(16)", + SCST_DATA_NONE, SCST_LONG_TIMEOUT|SCST_WRITE_MEDIUM, + 0, get_trans_len_none}, + {0x9E, "O ", "SERVICE ACTION IN", + SCST_DATA_READ, FLAG_NONE, 0, get_trans_len_serv_act_in}, + + /* 12-bytes length CDB */ + {0xA0, "VVVVVVVVVV M ", "REPORT LUNS", + SCST_DATA_READ, SCST_SMALL_TIMEOUT|SCST_IMPLICIT_HQ|SCST_SKIP_UA| + SCST_FULLY_LOCAL_CMD|SCST_LOCAL_CMD| + SCST_REG_RESERVE_ALLOWED, + 6, get_trans_len_4}, + {0xA1, " O ", "BLANK", + SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none}, + {0xA3, " O ", "SEND KEY", + SCST_DATA_WRITE, FLAG_NONE, 8, get_trans_len_2}, + {0xA3, "OOOOO OOOO ", "REPORT DEVICE IDENTIDIER", + SCST_DATA_READ, SCST_REG_RESERVE_ALLOWED, 6, get_trans_len_4}, + {0xA3, " M ", "MAINTENANCE(IN)", + SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_4}, + {0xA4, " O ", "REPORT KEY", + SCST_DATA_READ, FLAG_NONE, 8, get_trans_len_2}, + {0xA4, " O ", "MAINTENANCE(OUT)", + SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_4}, + {0xA5, " M ", "MOVE MEDIUM", + SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none}, + {0xA5, " O ", "PLAY AUDIO(12)", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0xA6, " O O ", "EXCHANGE/LOAD/UNLOAD MEDIUM", + SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none}, + {0xA7, " O ", "SET READ AHEAD", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0xA8, " O ", "GET MESSAGE(12)", + SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_4}, + {0xA8, "O OO O ", "READ(12)", + SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED, 6, get_trans_len_4}, + {0xA9, " O ", "PLAY TRACK RELATIVE(12)", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0xAA, "O OO O ", "WRITE(12)", + SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM, + 6, get_trans_len_4}, + {0xAA, " O ", "SEND MESSAGE(12)", + SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_4}, + {0xAC, " O ", "ERASE(12)", + SCST_DATA_NONE, SCST_WRITE_MEDIUM, 0, get_trans_len_none}, + {0xAC, " M ", "GET PERFORMANCE", + SCST_DATA_READ, SCST_UNKNOWN_LENGTH, 0, get_trans_len_none}, + {0xAD, " O ", "READ DVD STRUCTURE", + SCST_DATA_READ, FLAG_NONE, 8, get_trans_len_2}, + {0xAE, "O OO O ", "WRITE AND VERIFY(12)", + SCST_DATA_WRITE, SCST_TRANSFER_LEN_TYPE_FIXED|SCST_WRITE_MEDIUM, + 6, get_trans_len_4}, + {0xAF, "O OO O ", "VERIFY(12)", + SCST_DATA_NONE, SCST_TRANSFER_LEN_TYPE_FIXED| + SCST_VERIFY_BYTCHK_MISMATCH_ALLOWED, + 6, get_trans_len_4}, +#if 0 /* No need to support at all */ + {0xB0, " OO O ", "SEARCH DATA HIGH(12)", + SCST_DATA_WRITE, FLAG_NONE, 9, get_trans_len_1}, + {0xB1, " OO O ", "SEARCH DATA EQUAL(12)", + SCST_DATA_WRITE, FLAG_NONE, 9, get_trans_len_1}, + {0xB2, " OO O ", "SEARCH DATA LOW(12)", + SCST_DATA_WRITE, FLAG_NONE, 9, get_trans_len_1}, +#endif + {0xB3, " OO O ", "SET LIMITS(12)", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0xB5, " O ", "REQUEST VOLUME ELEMENT ADDRESS", + SCST_DATA_READ, FLAG_NONE, 9, get_trans_len_1}, + {0xB6, " O ", "SEND VOLUME TAG", + SCST_DATA_WRITE, FLAG_NONE, 9, get_trans_len_1}, + {0xB6, " M ", "SET STREAMING", + SCST_DATA_WRITE, FLAG_NONE, 9, get_trans_len_2}, + {0xB7, " O ", "READ DEFECT DATA(12)", + SCST_DATA_READ, FLAG_NONE, 9, get_trans_len_1}, + {0xB8, " O ", "READ ELEMENT STATUS", + SCST_DATA_READ, FLAG_NONE, 7, get_trans_len_3_read_elem_stat}, + {0xB9, " O ", "READ CD MSF", + SCST_DATA_READ, SCST_UNKNOWN_LENGTH, 0, get_trans_len_none}, + {0xBA, " O ", "SCAN", + SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_len_none}, + {0xBA, " O ", "REDUNDANCY GROUP(IN)", + SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_4}, + {0xBB, " O ", "SET SPEED", + SCST_DATA_NONE, FLAG_NONE, 0, get_trans_len_none}, + {0xBB, " O ", "REDUNDANCY GROUP(OUT)", + SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_4}, + {0xBC, " O ", "SPARE(IN)", + SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_4}, + {0xBD, " O ", "MECHANISM STATUS", + SCST_DATA_READ, FLAG_NONE, 8, get_trans_len_2}, + {0xBD, " O ", "SPARE(OUT)", + SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_4}, + {0xBE, " O ", "READ CD", + SCST_DATA_READ, SCST_TRANSFER_LEN_TYPE_FIXED, 6, get_trans_len_3}, + {0xBE, " O ", "VOLUME SET(IN)", + SCST_DATA_READ, FLAG_NONE, 6, get_trans_len_4}, + {0xBF, " O ", "SEND DVD STRUCTUE", + SCST_DATA_WRITE, FLAG_NONE, 8, get_trans_len_2}, + {0xBF, " O ", "VOLUME SET(OUT)", + SCST_DATA_WRITE, FLAG_NONE, 6, get_trans_len_4}, + {0xE7, " V ", "INIT ELEMENT STATUS WRANGE", + SCST_DATA_NONE, SCST_LONG_TIMEOUT, 0, get_trans_cdb_len_10} +}; + +#define SCST_CDB_TBL_SIZE ((int)ARRAY_SIZE(scst_scsi_op_table)) + +static void scst_free_tgt_dev(struct scst_tgt_dev *tgt_dev); +static void scst_check_internal_sense(struct scst_device *dev, int result, + uint8_t *sense, int sense_len); +static void scst_queue_report_luns_changed_UA(struct scst_session *sess, + int flags); +static void __scst_check_set_UA(struct scst_tgt_dev *tgt_dev, + const uint8_t *sense, int sense_len, int flags); +static void scst_alloc_set_UA(struct scst_tgt_dev *tgt_dev, + const uint8_t *sense, int sense_len, int flags); +static void scst_free_all_UA(struct scst_tgt_dev *tgt_dev); +static void scst_release_space(struct scst_cmd *cmd); +static void scst_unblock_cmds(struct scst_device *dev); +static void scst_clear_reservation(struct scst_tgt_dev *tgt_dev); +static struct scst_tgt_dev *scst_alloc_add_tgt_dev(struct scst_session *sess, + struct scst_acg_dev *acg_dev); +static void scst_tgt_retry_timer_fn(unsigned long arg); + +#ifdef CONFIG_SCST_DEBUG_TM +static void tm_dbg_init_tgt_dev(struct scst_tgt_dev *tgt_dev); +static void tm_dbg_deinit_tgt_dev(struct scst_tgt_dev *tgt_dev); +#else +static inline void tm_dbg_init_tgt_dev(struct scst_tgt_dev *tgt_dev) {} +static inline void tm_dbg_deinit_tgt_dev(struct scst_tgt_dev *tgt_dev) {} +#endif /* CONFIG_SCST_DEBUG_TM */ + +/** + * scst_alloc_sense() - allocate sense buffer for command + * + * Allocates, if necessary, sense buffer for command. Returns 0 on success + * and error code othrwise. Parameter "atomic" should be non-0 if the + * function called in atomic context. + */ +int scst_alloc_sense(struct scst_cmd *cmd, int atomic) +{ + int res = 0; + gfp_t gfp_mask = atomic ? GFP_ATOMIC : (GFP_KERNEL|__GFP_NOFAIL); + + if (cmd->sense != NULL) + goto memzero; + + cmd->sense = mempool_alloc(scst_sense_mempool, gfp_mask); + if (cmd->sense == NULL) { + PRINT_CRIT_ERROR("Sense memory allocation failed (op %x). " + "The sense data will be lost!!", cmd->cdb[0]); + res = -ENOMEM; + goto out; + } + + cmd->sense_buflen = SCST_SENSE_BUFFERSIZE; + +memzero: + cmd->sense_valid_len = 0; + memset(cmd->sense, 0, cmd->sense_buflen); + +out: + return res; +} +EXPORT_SYMBOL(scst_alloc_sense); + +/** + * scst_alloc_set_sense() - allocate and fill sense buffer for command + * + * Allocates, if necessary, sense buffer for command and copies in + * it data from the supplied sense buffer. Returns 0 on success + * and error code othrwise. + */ +int scst_alloc_set_sense(struct scst_cmd *cmd, int atomic, + const uint8_t *sense, unsigned int len) +{ + int res; + + /* + * We don't check here if the existing sense is valid or not, because + * we suppose the caller did it based on cmd->status. + */ + + res = scst_alloc_sense(cmd, atomic); + if (res != 0) { + PRINT_BUFFER("Lost sense", sense, len); + goto out; + } + + cmd->sense_valid_len = len; + if (cmd->sense_buflen < len) { + PRINT_WARNING("Sense truncated (needed %d), shall you increase " + "SCST_SENSE_BUFFERSIZE? Op: %x", len, cmd->cdb[0]); + cmd->sense_valid_len = cmd->sense_buflen; + } + + memcpy(cmd->sense, sense, cmd->sense_valid_len); + TRACE_BUFFER("Sense set", cmd->sense, cmd->sense_valid_len); + +out: + return res; +} +EXPORT_SYMBOL(scst_alloc_set_sense); + +/** + * scst_set_cmd_error_status() - set error SCSI status + * @cmd: SCST command + * @status: SCSI status to set + * + * Description: + * Sets error SCSI status in the command and prepares it for returning it. + * Returns 0 on success, error code otherwise. + */ +int scst_set_cmd_error_status(struct scst_cmd *cmd, int status) +{ + int res = 0; + + if (cmd->status != 0) { + TRACE_MGMT_DBG("cmd %p already has status %x set", cmd, + cmd->status); + res = -EEXIST; + goto out; + } + + cmd->status = status; + cmd->host_status = DID_OK; + + cmd->dbl_ua_orig_resp_data_len = cmd->resp_data_len; + cmd->dbl_ua_orig_data_direction = cmd->data_direction; + + cmd->data_direction = SCST_DATA_NONE; + cmd->resp_data_len = 0; + cmd->is_send_status = 1; + + cmd->completed = 1; + +out: + return res; +} +EXPORT_SYMBOL(scst_set_cmd_error_status); + +static int scst_set_lun_not_supported_request_sense(struct scst_cmd *cmd, + int key, int asc, int ascq) +{ + int res; + int sense_len; + + if (cmd->status != 0) { + TRACE_MGMT_DBG("cmd %p already has status %x set", cmd, + cmd->status); + res = -EEXIST; + goto out; + } + + if ((cmd->sg != NULL) && SCST_SENSE_VALID(sg_virt(cmd->sg))) { + TRACE_MGMT_DBG("cmd %p already has sense set", cmd); + res = -EEXIST; + goto out; + } + + if (cmd->sg == NULL) { + if (cmd->bufflen == 0) + cmd->bufflen = cmd->cdb[4]; + + cmd->sg = scst_alloc(cmd->bufflen, GFP_ATOMIC, &cmd->sg_cnt); + if (cmd->sg == NULL) { + PRINT_ERROR("Unable to alloc sg for REQUEST SENSE" + "(sense %x/%x/%x)", key, asc, ascq); + res = 1; + goto out; + } + } + + TRACE_MEM("sg %p alloced for sense for cmd %p (cnt %d, " + "len %d)", cmd->sg, cmd, cmd->sg_cnt, cmd->bufflen); + + sense_len = scst_set_sense(sg_virt(cmd->sg), + cmd->bufflen, cmd->cdb[1] & 1, key, asc, ascq); + scst_set_resp_data_len(cmd, sense_len); + + TRACE_BUFFER("Sense set", sg_virt(cmd->sg), sense_len); + + res = 0; + cmd->completed = 1; + +out: + return res; +} + +static int scst_set_lun_not_supported_inquiry(struct scst_cmd *cmd) +{ + int res; + uint8_t *buf; + int len; + + if (cmd->status != 0) { + TRACE_MGMT_DBG("cmd %p already has status %x set", cmd, + cmd->status); + res = -EEXIST; + goto out; + } + + if (cmd->sg == NULL) { + if (cmd->bufflen == 0) + cmd->bufflen = min_t(int, 36, (cmd->cdb[3] << 8) | cmd->cdb[4]); + + cmd->sg = scst_alloc(cmd->bufflen, GFP_ATOMIC, &cmd->sg_cnt); + if (cmd->sg == NULL) { + PRINT_ERROR("%s", "Unable to alloc sg for INQUIRY " + "for not supported LUN"); + res = 1; + goto out; + } + } + + TRACE_MEM("sg %p alloced INQUIRY for cmd %p (cnt %d, len %d)", + cmd->sg, cmd, cmd->sg_cnt, cmd->bufflen); + + buf = sg_virt(cmd->sg); + len = min_t(int, 36, cmd->bufflen); + + memset(buf, 0, len); + buf[0] = 0x7F; /* Peripheral qualifier 011b, Peripheral device type 1Fh */ + + TRACE_BUFFER("INQUIRY for not supported LUN set", buf, len); + + res = 0; + cmd->completed = 1; + +out: + return res; +} + +/** + * scst_set_cmd_error() - set error in the command and fill the sense buffer. + * + * Sets error in the command and fill the sense buffer. Returns 0 on success, + * error code otherwise. + */ +int scst_set_cmd_error(struct scst_cmd *cmd, int key, int asc, int ascq) +{ + int res; + + /* + * We need for LOGICAL UNIT NOT SUPPORTED special handling for + * REQUEST SENSE and INQUIRY. + */ + if ((key == ILLEGAL_REQUEST) && (asc == 0x25) && (ascq == 0)) { + if (cmd->cdb[0] == REQUEST_SENSE) + res = scst_set_lun_not_supported_request_sense(cmd, + key, asc, ascq); + else if (cmd->cdb[0] == INQUIRY) + res = scst_set_lun_not_supported_inquiry(cmd); + else + goto do_sense; + + if (res > 0) + goto do_sense; + else + goto out; + } + +do_sense: + res = scst_set_cmd_error_status(cmd, SAM_STAT_CHECK_CONDITION); + if (res != 0) + goto out; + + res = scst_alloc_sense(cmd, 1); + if (res != 0) { + PRINT_ERROR("Lost sense data (key %x, asc %x, ascq %x)", + key, asc, ascq); + goto out; + } + + cmd->sense_valid_len = scst_set_sense(cmd->sense, cmd->sense_buflen, + scst_get_cmd_dev_d_sense(cmd), key, asc, ascq); + TRACE_BUFFER("Sense set", cmd->sense, cmd->sense_valid_len); + +out: + return res; +} +EXPORT_SYMBOL(scst_set_cmd_error); + +/** + * scst_set_sense() - set sense from KEY/ASC/ASCQ numbers + * + * Sets the corresponding fields in the sense buffer taking sense type + * into account. Returns resulting sense length. + */ +int scst_set_sense(uint8_t *buffer, int len, bool d_sense, + int key, int asc, int ascq) +{ + int res; + + BUG_ON(len == 0); + + memset(buffer, 0, len); + + if (d_sense) { + /* Descriptor format */ + if (len < 8) { + PRINT_ERROR("Length %d of sense buffer too small to " + "fit sense %x:%x:%x", len, key, asc, ascq); + } + + buffer[0] = 0x72; /* Response Code */ + if (len > 1) + buffer[1] = key; /* Sense Key */ + if (len > 2) + buffer[2] = asc; /* ASC */ + if (len > 3) + buffer[3] = ascq; /* ASCQ */ + res = 8; + } else { + /* Fixed format */ + if (len < 18) { + PRINT_ERROR("Length %d of sense buffer too small to " + "fit sense %x:%x:%x", len, key, asc, ascq); + } + + buffer[0] = 0x70; /* Response Code */ + if (len > 2) + buffer[2] = key; /* Sense Key */ + if (len > 7) + buffer[7] = 0x0a; /* Additional Sense Length */ + if (len > 12) + buffer[12] = asc; /* ASC */ + if (len > 13) + buffer[13] = ascq; /* ASCQ */ + res = 18; + } + + TRACE_BUFFER("Sense set", buffer, res); + return res; +} +EXPORT_SYMBOL(scst_set_sense); + +/** + * scst_analyze_sense() - analyze sense + * + * Returns true if sense matches to (key, asc, ascq) and false otherwise. + * Valid_mask is one or several SCST_SENSE_*_VALID constants setting valid + * (key, asc, ascq) values. + */ +bool scst_analyze_sense(const uint8_t *sense, int len, unsigned int valid_mask, + int key, int asc, int ascq) +{ + bool res = false; + + /* Response Code */ + if ((sense[0] == 0x70) || (sense[0] == 0x71)) { + /* Fixed format */ + + /* Sense Key */ + if (valid_mask & SCST_SENSE_KEY_VALID) { + if (len < 3) + goto out; + if (sense[2] != key) + goto out; + } + + /* ASC */ + if (valid_mask & SCST_SENSE_ASC_VALID) { + if (len < 13) + goto out; + if (sense[12] != asc) + goto out; + } + + /* ASCQ */ + if (valid_mask & SCST_SENSE_ASCQ_VALID) { + if (len < 14) + goto out; + if (sense[13] != ascq) + goto out; + } + } else if ((sense[0] == 0x72) || (sense[0] == 0x73)) { + /* Descriptor format */ + + /* Sense Key */ + if (valid_mask & SCST_SENSE_KEY_VALID) { + if (len < 2) + goto out; + if (sense[1] != key) + goto out; + } + + /* ASC */ + if (valid_mask & SCST_SENSE_ASC_VALID) { + if (len < 3) + goto out; + if (sense[2] != asc) + goto out; + } + + /* ASCQ */ + if (valid_mask & SCST_SENSE_ASCQ_VALID) { + if (len < 4) + goto out; + if (sense[3] != ascq) + goto out; + } + } else + goto out; + + res = true; + +out: + return res; +} +EXPORT_SYMBOL(scst_analyze_sense); + +/** + * scst_is_ua_sense() - determine if the sense is UA sense + * + * Returns true if the sense is valid and carrying a Unit + * Attention or false otherwise. + */ +bool scst_is_ua_sense(const uint8_t *sense, int len) +{ + if (SCST_SENSE_VALID(sense)) + return scst_analyze_sense(sense, len, + SCST_SENSE_KEY_VALID, UNIT_ATTENTION, 0, 0); + else + return false; +} +EXPORT_SYMBOL(scst_is_ua_sense); + +bool scst_is_ua_global(const uint8_t *sense, int len) +{ + bool res; + + /* Changing it don't forget to change scst_requeue_ua() as well!! */ + + if (scst_analyze_sense(sense, len, SCST_SENSE_ALL_VALID, + SCST_LOAD_SENSE(scst_sense_reported_luns_data_changed))) + res = true; + else + res = false; + + return res; +} + +/** + * scst_check_convert_sense() - check sense type and convert it if needed + * + * Checks if sense in the sense buffer, if any, is in the correct format. + * If not, converts it in the correct format. + */ +void scst_check_convert_sense(struct scst_cmd *cmd) +{ + bool d_sense; + + if ((cmd->sense == NULL) || (cmd->status != SAM_STAT_CHECK_CONDITION)) + goto out; + + d_sense = scst_get_cmd_dev_d_sense(cmd); + if (d_sense && ((cmd->sense[0] == 0x70) || (cmd->sense[0] == 0x71))) { + TRACE_MGMT_DBG("Converting fixed sense to descriptor (cmd %p)", + cmd); + if ((cmd->sense_valid_len < 18)) { + PRINT_ERROR("Sense too small to convert (%d, " + "type: fixed)", cmd->sense_buflen); + goto out; + } + cmd->sense_valid_len = scst_set_sense(cmd->sense, cmd->sense_buflen, + d_sense, cmd->sense[2], cmd->sense[12], cmd->sense[13]); + } else if (!d_sense && ((cmd->sense[0] == 0x72) || + (cmd->sense[0] == 0x73))) { + TRACE_MGMT_DBG("Converting descriptor sense to fixed (cmd %p)", + cmd); + if ((cmd->sense_buflen < 18) || (cmd->sense_valid_len < 8)) { + PRINT_ERROR("Sense too small to convert (%d, " + "type: descryptor, valid %d)", + cmd->sense_buflen, cmd->sense_valid_len); + goto out; + } + cmd->sense_valid_len = scst_set_sense(cmd->sense, + cmd->sense_buflen, d_sense, + cmd->sense[1], cmd->sense[2], cmd->sense[3]); + } + +out: + return; +} +EXPORT_SYMBOL(scst_check_convert_sense); + +static int scst_set_cmd_error_sense(struct scst_cmd *cmd, uint8_t *sense, + unsigned int len) +{ + int res; + + res = scst_set_cmd_error_status(cmd, SAM_STAT_CHECK_CONDITION); + if (res != 0) + goto out; + + res = scst_alloc_set_sense(cmd, 1, sense, len); + +out: + return res; +} + +/** + * scst_set_busy() - set BUSY or TASK QUEUE FULL status + * + * Sets BUSY or TASK QUEUE FULL status depending on if this session has other + * outstanding commands or not. + */ +void scst_set_busy(struct scst_cmd *cmd) +{ + int c = atomic_read(&cmd->sess->sess_cmd_count); + + if ((c <= 1) || (cmd->sess->init_phase != SCST_SESS_IPH_READY)) { + scst_set_cmd_error_status(cmd, SAM_STAT_BUSY); + TRACE(TRACE_FLOW_CONTROL, "Sending BUSY status to initiator %s " + "(cmds count %d, queue_type %x, sess->init_phase %d)", + cmd->sess->initiator_name, c, + cmd->queue_type, cmd->sess->init_phase); + } else { + scst_set_cmd_error_status(cmd, SAM_STAT_TASK_SET_FULL); + TRACE(TRACE_FLOW_CONTROL, "Sending QUEUE_FULL status to " + "initiator %s (cmds count %d, queue_type %x, " + "sess->init_phase %d)", cmd->sess->initiator_name, c, + cmd->queue_type, cmd->sess->init_phase); + } + return; +} +EXPORT_SYMBOL(scst_set_busy); + +/** + * scst_set_initial_UA() - set initial Unit Attention + * + * Sets initial Unit Attention on all devices of the session, + * replacing default scst_sense_reset_UA + */ +void scst_set_initial_UA(struct scst_session *sess, int key, int asc, int ascq) +{ + int i; + + TRACE_MGMT_DBG("Setting for sess %p initial UA %x/%x/%x", sess, key, + asc, ascq); + + /* Protect sess_tgt_dev_list_hash */ + mutex_lock(&scst_mutex); + + for (i = 0; i < TGT_DEV_HASH_SIZE; i++) { + struct list_head *sess_tgt_dev_list_head = + &sess->sess_tgt_dev_list_hash[i]; + struct scst_tgt_dev *tgt_dev; + + list_for_each_entry(tgt_dev, sess_tgt_dev_list_head, + sess_tgt_dev_list_entry) { + spin_lock_bh(&tgt_dev->tgt_dev_lock); + if (!list_empty(&tgt_dev->UA_list)) { + struct scst_tgt_dev_UA *ua; + + ua = list_entry(tgt_dev->UA_list.next, + typeof(*ua), UA_list_entry); + if (scst_analyze_sense(ua->UA_sense_buffer, + ua->UA_valid_sense_len, + SCST_SENSE_ALL_VALID, + SCST_LOAD_SENSE(scst_sense_reset_UA))) { + ua->UA_valid_sense_len = scst_set_sense( + ua->UA_sense_buffer, + sizeof(ua->UA_sense_buffer), + tgt_dev->dev->d_sense, + key, asc, ascq); + } else + PRINT_ERROR("%s", + "The first UA isn't RESET UA"); + } else + PRINT_ERROR("%s", "There's no RESET UA to " + "replace"); + spin_unlock_bh(&tgt_dev->tgt_dev_lock); + } + } + + mutex_unlock(&scst_mutex); + return; +} +EXPORT_SYMBOL(scst_set_initial_UA); + +static struct scst_aen *scst_alloc_aen(struct scst_session *sess, + uint64_t unpacked_lun) +{ + struct scst_aen *aen; + + aen = mempool_alloc(scst_aen_mempool, GFP_KERNEL); + if (aen == NULL) { + PRINT_ERROR("AEN memory allocation failed. Corresponding " + "event notification will not be performed (initiator " + "%s)", sess->initiator_name); + goto out; + } + memset(aen, 0, sizeof(*aen)); + + aen->sess = sess; + scst_sess_get(sess); + + aen->lun = scst_pack_lun(unpacked_lun, sess->acg->addr_method); + +out: + return aen; +}; + +static void scst_free_aen(struct scst_aen *aen) +{ + + scst_sess_put(aen->sess); + mempool_free(aen, scst_aen_mempool); + return; +}; + +/* Must be called under scst_mutex */ +void scst_gen_aen_or_ua(struct scst_tgt_dev *tgt_dev, + int key, int asc, int ascq) +{ + struct scst_tgt_template *tgtt = tgt_dev->sess->tgt->tgtt; + uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN]; + int sl; + + if (tgtt->report_aen != NULL) { + struct scst_aen *aen; + int rc; + + aen = scst_alloc_aen(tgt_dev->sess, tgt_dev->lun); + if (aen == NULL) + goto queue_ua; + + aen->event_fn = SCST_AEN_SCSI; + aen->aen_sense_len = scst_set_sense(aen->aen_sense, + sizeof(aen->aen_sense), tgt_dev->dev->d_sense, + key, asc, ascq); + + TRACE_DBG("Calling target's %s report_aen(%p)", + tgtt->name, aen); + rc = tgtt->report_aen(aen); + TRACE_DBG("Target's %s report_aen(%p) returned %d", + tgtt->name, aen, rc); + if (rc == SCST_AEN_RES_SUCCESS) + goto out; + + scst_free_aen(aen); + } + +queue_ua: + TRACE_MGMT_DBG("AEN not supported, queuing plain UA (tgt_dev %p)", + tgt_dev); + sl = scst_set_sense(sense_buffer, sizeof(sense_buffer), + tgt_dev->dev->d_sense, key, asc, ascq); + scst_check_set_UA(tgt_dev, sense_buffer, sl, 0); + +out: + return; +} + +/** + * scst_capacity_data_changed() - notify SCST about device capacity change + * + * Notifies SCST core that dev has changed its capacity. Called under no locks. + */ +void scst_capacity_data_changed(struct scst_device *dev) +{ + struct scst_tgt_dev *tgt_dev; + + if (dev->type != TYPE_DISK) { + TRACE_MGMT_DBG("Device type %d isn't for CAPACITY DATA " + "CHANGED UA", dev->type); + goto out; + } + + TRACE_MGMT_DBG("CAPACITY DATA CHANGED (dev %p)", dev); + + mutex_lock(&scst_mutex); + + list_for_each_entry(tgt_dev, &dev->dev_tgt_dev_list, + dev_tgt_dev_list_entry) { + scst_gen_aen_or_ua(tgt_dev, + SCST_LOAD_SENSE(scst_sense_capacity_data_changed)); + } + + mutex_unlock(&scst_mutex); + +out: + return; +} +EXPORT_SYMBOL_GPL(scst_capacity_data_changed); + +static inline bool scst_is_report_luns_changed_type(int type) +{ + switch (type) { + case TYPE_DISK: + case TYPE_TAPE: + case TYPE_PRINTER: + case TYPE_PROCESSOR: + case TYPE_WORM: + case TYPE_ROM: + case TYPE_SCANNER: + case TYPE_MOD: + case TYPE_MEDIUM_CHANGER: + case TYPE_RAID: + case TYPE_ENCLOSURE: + return true; + default: + return false; + } +} + +/* scst_mutex supposed to be held */ +static void scst_queue_report_luns_changed_UA(struct scst_session *sess, + int flags) +{ + uint8_t sense_buffer[SCST_STANDARD_SENSE_LEN]; + struct list_head *shead; + struct scst_tgt_dev *tgt_dev; + int i; + + TRACE_MGMT_DBG("Queuing REPORTED LUNS DATA CHANGED UA " + "(sess %p)", sess); + + local_bh_disable(); + + for (i = 0; i < TGT_DEV_HASH_SIZE; i++) { + shead = &sess->sess_tgt_dev_list_hash[i]; + + list_for_each_entry(tgt_dev, shead, + sess_tgt_dev_list_entry) { + /* Lockdep triggers here a false positive.. */ + spin_lock(&tgt_dev->tgt_dev_lock); + } + } + + for (i = 0; i < TGT_DEV_HASH_SIZE; i++) { + shead = &sess->sess_tgt_dev_list_hash[i]; + + list_for_each_entry(tgt_dev, shead, + sess_tgt_dev_list_entry) { + int sl; + + if (!scst_is_report_luns_changed_type( + tgt_dev->dev->type)) + continue; + + sl = scst_set_sense(sense_buffer, sizeof(sense_buffer), + tgt_dev->dev->d_sense, + SCST_LOAD_SENSE(scst_sense_reported_luns_data_changed)); + + __scst_check_set_UA(tgt_dev, sense_buffer, + sl, flags | SCST_SET_UA_FLAG_GLOBAL); + } + } + + for (i = TGT_DEV_HASH_SIZE-1; i >= 0; i--) { + shead = &sess->sess_tgt_dev_list_hash[i]; + + list_for_each_entry_reverse(tgt_dev, + shead, sess_tgt_dev_list_entry) { + spin_unlock(&tgt_dev->tgt_dev_lock); + } + } + + local_bh_enable(); + return; +} + +/* The activity supposed to be suspended and scst_mutex held */ +static void scst_report_luns_changed_sess(struct scst_session *sess) +{ + int i; + struct scst_tgt_template *tgtt = sess->tgt->tgtt; + int d_sense = 0; + uint64_t lun = 0; + + TRACE_DBG("REPORTED LUNS DATA CHANGED (sess %p)", sess); + + for (i = 0; i < TGT_DEV_HASH_SIZE; i++) { + struct list_head *shead; + struct scst_tgt_dev *tgt_dev; + + shead = &sess->sess_tgt_dev_list_hash[i]; + + list_for_each_entry(tgt_dev, shead, + sess_tgt_dev_list_entry) { + if (scst_is_report_luns_changed_type( + tgt_dev->dev->type)) { + lun = tgt_dev->lun; + d_sense = tgt_dev->dev->d_sense; + goto found; + } + } + } + +found: + if (tgtt->report_aen != NULL) { + struct scst_aen *aen; + int rc; + + aen = scst_alloc_aen(sess, lun); + if (aen == NULL) + goto queue_ua; + + aen->event_fn = SCST_AEN_SCSI; + aen->aen_sense_len = scst_set_sense(aen->aen_sense, + sizeof(aen->aen_sense), d_sense, + SCST_LOAD_SENSE(scst_sense_reported_luns_data_changed)); + + TRACE_DBG("Calling target's %s report_aen(%p)", + tgtt->name, aen); + rc = tgtt->report_aen(aen); + TRACE_DBG("Target's %s report_aen(%p) returned %d", + tgtt->name, aen, rc); + if (rc == SCST_AEN_RES_SUCCESS) + goto out; + + scst_free_aen(aen); + } + +queue_ua: + scst_queue_report_luns_changed_UA(sess, 0); + +out: + return; +} + +/* The activity supposed to be suspended and scst_mutex held */ +void scst_report_luns_changed(struct scst_acg *acg) +{ + struct scst_session *sess; + + TRACE_MGMT_DBG("REPORTED LUNS DATA CHANGED (acg %s)", acg->acg_name); + + list_for_each_entry(sess, &acg->acg_sess_list, acg_sess_list_entry) { + scst_report_luns_changed_sess(sess); + } + return; +} + +/** + * scst_aen_done() - AEN processing done + * + * Notifies SCST that the driver has sent the AEN and it + * can be freed now. Don't forget to set the delivery status, if it + * isn't success, using scst_set_aen_delivery_status() before calling + * this function. + */ +void scst_aen_done(struct scst_aen *aen) +{ + + TRACE_MGMT_DBG("AEN %p (fn %d) done (initiator %s)", aen, + aen->event_fn, aen->sess->initiator_name); + + if (aen->delivery_status == SCST_AEN_RES_SUCCESS) + goto out_free; + + if (aen->event_fn != SCST_AEN_SCSI) + goto out_free; + + TRACE_MGMT_DBG("Delivery of SCSI AEN failed (initiator %s)", + aen->sess->initiator_name); + + if (scst_analyze_sense(aen->aen_sense, aen->aen_sense_len, + SCST_SENSE_ALL_VALID, SCST_LOAD_SENSE( + scst_sense_reported_luns_data_changed))) { + mutex_lock(&scst_mutex); + scst_queue_report_luns_changed_UA(aen->sess, + SCST_SET_UA_FLAG_AT_HEAD); + mutex_unlock(&scst_mutex); + } else { + struct list_head *shead; + struct scst_tgt_dev *tgt_dev; + uint64_t lun; + + lun = scst_unpack_lun((uint8_t *)&aen->lun, sizeof(aen->lun)); + + mutex_lock(&scst_mutex); + + /* tgt_dev might get dead, so we need to reseek it */ + shead = &aen->sess->sess_tgt_dev_list_hash[HASH_VAL(lun)]; + list_for_each_entry(tgt_dev, shead, + sess_tgt_dev_list_entry) { + if (tgt_dev->lun == lun) { + TRACE_MGMT_DBG("Requeuing failed AEN UA for " + "tgt_dev %p", tgt_dev); + scst_check_set_UA(tgt_dev, aen->aen_sense, + aen->aen_sense_len, + SCST_SET_UA_FLAG_AT_HEAD); + break; + } + } + + mutex_unlock(&scst_mutex); + } + +out_free: + scst_free_aen(aen); + return; +} +EXPORT_SYMBOL(scst_aen_done); + +void scst_requeue_ua(struct scst_cmd *cmd) +{ + + if (scst_analyze_sense(cmd->sense, cmd->sense_valid_len, + SCST_SENSE_ALL_VALID, + SCST_LOAD_SENSE(scst_sense_reported_luns_data_changed))) { + TRACE_MGMT_DBG("Requeuing REPORTED LUNS DATA CHANGED UA " + "for delivery failed cmd %p", cmd); + mutex_lock(&scst_mutex); + scst_queue_report_luns_changed_UA(cmd->sess, + SCST_SET_UA_FLAG_AT_HEAD); + mutex_unlock(&scst_mutex); + } else { + TRACE_MGMT_DBG("Requeuing UA for delivery failed cmd %p", cmd); + scst_check_set_UA(cmd->tgt_dev, cmd->sense, + cmd->sense_valid_len, SCST_SET_UA_FLAG_AT_HEAD); + } + return; +} + +/* The activity supposed to be suspended and scst_mutex held */ +static void scst_check_reassign_sess(struct scst_session *sess) +{ + struct scst_acg *acg, *old_acg; + struct scst_acg_dev *acg_dev; + int i; + struct list_head *shead; + struct scst_tgt_dev *tgt_dev; + bool luns_changed = false; + bool add_failed, something_freed, not_needed_freed = false; + + TRACE_MGMT_DBG("Checking reassignment for sess %p (initiator %s)", + sess, sess->initiator_name); + + acg = scst_find_acg(sess); + if (acg == sess->acg) { + TRACE_MGMT_DBG("No reassignment for sess %p", sess); + goto out; + } + + TRACE_MGMT_DBG("sess %p will be reassigned from acg %s to acg %s", + sess, sess->acg->acg_name, acg->acg_name); + + old_acg = sess->acg; + sess->acg = NULL; /* to catch implicit dependencies earlier */ + +retry_add: + add_failed = false; + list_for_each_entry(acg_dev, &acg->acg_dev_list, acg_dev_list_entry) { + unsigned int inq_changed_ua_needed = 0; + + for (i = 0; i < TGT_DEV_HASH_SIZE; i++) { + shead = &sess->sess_tgt_dev_list_hash[i]; + + list_for_each_entry(tgt_dev, shead, + sess_tgt_dev_list_entry) { + if ((tgt_dev->dev == acg_dev->dev) && + (tgt_dev->lun == acg_dev->lun) && + (tgt_dev->acg_dev->rd_only == acg_dev->rd_only)) { + TRACE_MGMT_DBG("sess %p: tgt_dev %p for " + "LUN %lld stays the same", + sess, tgt_dev, + (unsigned long long)tgt_dev->lun); + tgt_dev->acg_dev = acg_dev; + goto next; + } else if (tgt_dev->lun == acg_dev->lun) + inq_changed_ua_needed = 1; + } + } + + luns_changed = true; + + TRACE_MGMT_DBG("sess %p: Allocing new tgt_dev for LUN %lld", + sess, (unsigned long long)acg_dev->lun); + + tgt_dev = scst_alloc_add_tgt_dev(sess, acg_dev); + if (tgt_dev == NULL) { + add_failed = true; + break; + } + + tgt_dev->inq_changed_ua_needed = inq_changed_ua_needed || + not_needed_freed; +next: + continue; + } + + something_freed = false; + not_needed_freed = true; + for (i = 0; i < TGT_DEV_HASH_SIZE; i++) { + struct scst_tgt_dev *t; + shead = &sess->sess_tgt_dev_list_hash[i]; + + list_for_each_entry_safe(tgt_dev, t, shead, + sess_tgt_dev_list_entry) { + if (tgt_dev->acg_dev->acg != acg) { + TRACE_MGMT_DBG("sess %p: Deleting not used " + "tgt_dev %p for LUN %lld", + sess, tgt_dev, + (unsigned long long)tgt_dev->lun); + luns_changed = true; + something_freed = true; + scst_free_tgt_dev(tgt_dev); + } + } + } + + if (add_failed && something_freed) { + TRACE_MGMT_DBG("sess %p: Retrying adding new tgt_devs", sess); + goto retry_add; + } + + sess->acg = acg; + + TRACE_DBG("Moving sess %p from acg %s to acg %s", sess, + old_acg->acg_name, acg->acg_name); + list_move_tail(&sess->acg_sess_list_entry, &acg->acg_sess_list); + + if (luns_changed) { + scst_report_luns_changed_sess(sess); + + for (i = 0; i < TGT_DEV_HASH_SIZE; i++) { + shead = &sess->sess_tgt_dev_list_hash[i]; + + list_for_each_entry(tgt_dev, shead, + sess_tgt_dev_list_entry) { + if (tgt_dev->inq_changed_ua_needed) { + TRACE_MGMT_DBG("sess %p: Setting " + "INQUIRY DATA HAS CHANGED UA " + "(tgt_dev %p)", sess, tgt_dev); + + tgt_dev->inq_changed_ua_needed = 0; + + scst_gen_aen_or_ua(tgt_dev, + SCST_LOAD_SENSE(scst_sense_inquery_data_changed)); + } + } + } + } + +out: + return; +} + +/* The activity supposed to be suspended and scst_mutex held */ +void scst_check_reassign_sessions(void) +{ + struct scst_tgt_template *tgtt; + + list_for_each_entry(tgtt, &scst_template_list, scst_template_list_entry) { + struct scst_tgt *tgt; + list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) { + struct scst_session *sess; + list_for_each_entry(sess, &tgt->sess_list, + sess_list_entry) { + scst_check_reassign_sess(sess); + } + } + } + return; +} + +/** + * scst_get_cmd_abnormal_done_state() - get command's next abnormal done state + * + * Returns the next state of the SCSI target state machine in case if command's + * completed abnormally. + */ +int scst_get_cmd_abnormal_done_state(const struct scst_cmd *cmd) +{ + int res; + + switch (cmd->state) { + case SCST_CMD_STATE_INIT_WAIT: + case SCST_CMD_STATE_INIT: + case SCST_CMD_STATE_PRE_PARSE: + case SCST_CMD_STATE_DEV_PARSE: + if (cmd->preprocessing_only) { + res = SCST_CMD_STATE_PREPROCESSING_DONE; + break; + } /* else go through */ + case SCST_CMD_STATE_DEV_DONE: + if (cmd->internal) + res = SCST_CMD_STATE_FINISHED_INTERNAL; + else + res = SCST_CMD_STATE_PRE_XMIT_RESP; + break; + + case SCST_CMD_STATE_PRE_DEV_DONE: + case SCST_CMD_STATE_MODE_SELECT_CHECKS: + res = SCST_CMD_STATE_DEV_DONE; + break; + + case SCST_CMD_STATE_PRE_XMIT_RESP: + res = SCST_CMD_STATE_XMIT_RESP; + break; + + case SCST_CMD_STATE_PREPROCESSING_DONE: + case SCST_CMD_STATE_PREPROCESSING_DONE_CALLED: + if (cmd->tgt_dev == NULL) + res = SCST_CMD_STATE_PRE_XMIT_RESP; + else + res = SCST_CMD_STATE_PRE_DEV_DONE; + break; + + case SCST_CMD_STATE_PREPARE_SPACE: + if (cmd->preprocessing_only) { + res = SCST_CMD_STATE_PREPROCESSING_DONE; + break; + } /* else go through */ + case SCST_CMD_STATE_RDY_TO_XFER: + case SCST_CMD_STATE_DATA_WAIT: + case SCST_CMD_STATE_TGT_PRE_EXEC: + case SCST_CMD_STATE_SEND_FOR_EXEC: + case SCST_CMD_STATE_LOCAL_EXEC: + case SCST_CMD_STATE_REAL_EXEC: + case SCST_CMD_STATE_REAL_EXECUTING: + res = SCST_CMD_STATE_PRE_DEV_DONE; + break; + + default: + PRINT_CRIT_ERROR("Wrong cmd state %d (cmd %p, op %x)", + cmd->state, cmd, cmd->cdb[0]); + BUG(); + /* Invalid state to supress compiler's warning */ + res = SCST_CMD_STATE_LAST_ACTIVE; + } + return res; +} +EXPORT_SYMBOL_GPL(scst_get_cmd_abnormal_done_state); + +/** + * scst_set_cmd_abnormal_done_state() - set command's next abnormal done state + * + * Sets state of the SCSI target state machine in case if command's completed + * abnormally. + */ +void scst_set_cmd_abnormal_done_state(struct scst_cmd *cmd) +{ + +#ifdef CONFIG_SCST_EXTRACHECKS + switch (cmd->state) { + case SCST_CMD_STATE_XMIT_RESP: + case SCST_CMD_STATE_FINISHED: + case SCST_CMD_STATE_FINISHED_INTERNAL: + case SCST_CMD_STATE_XMIT_WAIT: + PRINT_CRIT_ERROR("Wrong cmd state %d (cmd %p, op %x)", + cmd->state, cmd, cmd->cdb[0]); + BUG(); + } +#endif + + cmd->state = scst_get_cmd_abnormal_done_state(cmd); + +#ifdef CONFIG_SCST_EXTRACHECKS + if (((cmd->state != SCST_CMD_STATE_PRE_XMIT_RESP) && + (cmd->state != SCST_CMD_STATE_PREPROCESSING_DONE)) && + (cmd->tgt_dev == NULL) && !cmd->internal) { + PRINT_CRIT_ERROR("Wrong not inited cmd state %d (cmd %p, " + "op %x)", cmd->state, cmd, cmd->cdb[0]); + BUG(); + } +#endif + return; +} +EXPORT_SYMBOL_GPL(scst_set_cmd_abnormal_done_state); + +/** + * scst_set_resp_data_len() - set response data length + * + * Sets response data length for cmd and truncates its SG vector accordingly. + * + * The cmd->resp_data_len must not be set directly, it must be set only + * using this function. Value of resp_data_len must be <= cmd->bufflen. + */ +void scst_set_resp_data_len(struct scst_cmd *cmd, int resp_data_len) +{ + int i, l; + + scst_check_restore_sg_buff(cmd); + cmd->resp_data_len = resp_data_len; + + if (resp_data_len == cmd->bufflen) + goto out; + + l = 0; + for (i = 0; i < cmd->sg_cnt; i++) { + l += cmd->sg[i].length; + if (l >= resp_data_len) { + int left = resp_data_len - (l - cmd->sg[i].length); +#ifdef CONFIG_SCST_DEBUG + TRACE(TRACE_SG_OP|TRACE_MEMORY, "cmd %p (tag %llu), " + "resp_data_len %d, i %d, cmd->sg[i].length %d, " + "left %d", + cmd, (long long unsigned int)cmd->tag, + resp_data_len, i, + cmd->sg[i].length, left); +#endif + cmd->orig_sg_cnt = cmd->sg_cnt; + cmd->orig_sg_entry = i; + cmd->orig_entry_len = cmd->sg[i].length; + cmd->sg_cnt = (left > 0) ? i+1 : i; + cmd->sg[i].length = left; + cmd->sg_buff_modified = 1; + break; + } + } + +out: + return; +} +EXPORT_SYMBOL_GPL(scst_set_resp_data_len); + +/* No locks */ +int scst_queue_retry_cmd(struct scst_cmd *cmd, int finished_cmds) +{ + struct scst_tgt *tgt = cmd->tgt; + int res = 0; + unsigned long flags; + + spin_lock_irqsave(&tgt->tgt_lock, flags); + tgt->retry_cmds++; + /* + * Memory barrier is needed here, because we need the exact order + * between the read and write between retry_cmds and finished_cmds to + * not miss the case when a command finished while we queuing it for + * retry after the finished_cmds check. + */ + smp_mb(); + TRACE_RETRY("TGT QUEUE FULL: incrementing retry_cmds %d", + tgt->retry_cmds); + if (finished_cmds != atomic_read(&tgt->finished_cmds)) { + /* At least one cmd finished, so try again */ + tgt->retry_cmds--; + TRACE_RETRY("Some command(s) finished, direct retry " + "(finished_cmds=%d, tgt->finished_cmds=%d, " + "retry_cmds=%d)", finished_cmds, + atomic_read(&tgt->finished_cmds), tgt->retry_cmds); + res = -1; + goto out_unlock_tgt; + } + + TRACE_RETRY("Adding cmd %p to retry cmd list", cmd); + list_add_tail(&cmd->cmd_list_entry, &tgt->retry_cmd_list); + + if (!tgt->retry_timer_active) { + tgt->retry_timer.expires = jiffies + SCST_TGT_RETRY_TIMEOUT; + add_timer(&tgt->retry_timer); + tgt->retry_timer_active = 1; + } + +out_unlock_tgt: + spin_unlock_irqrestore(&tgt->tgt_lock, flags); + return res; +} + +/* Returns 0 to continue, >0 to restart, <0 to break */ +static int scst_check_hw_pending_cmd(struct scst_cmd *cmd, + unsigned long cur_time, unsigned long max_time, + struct scst_session *sess, unsigned long *flags, + struct scst_tgt_template *tgtt) +{ + int res = -1; /* break */ + + TRACE_DBG("cmd %p, hw_pending %d, proc time %ld, " + "pending time %ld", cmd, cmd->cmd_hw_pending, + (long)(cur_time - cmd->start_time) / HZ, + (long)(cur_time - cmd->hw_pending_start) / HZ); + + if (time_before_eq(cur_time, cmd->start_time + max_time)) { + /* Cmds are ordered, so no need to check more */ + goto out; + } + + if (!cmd->cmd_hw_pending) { + res = 0; /* continue */ + goto out; + } + + if (time_before(cur_time, cmd->hw_pending_start + max_time)) { + /* Cmds are ordered, so no need to check more */ + goto out; + } + + TRACE_MGMT_DBG("Cmd %p HW pending for too long %ld (state %x)", + cmd, (cur_time - cmd->hw_pending_start) / HZ, + cmd->state); + + cmd->cmd_hw_pending = 0; + + spin_unlock_irqrestore(&sess->sess_list_lock, *flags); + tgtt->on_hw_pending_cmd_timeout(cmd); + spin_lock_irqsave(&sess->sess_list_lock, *flags); + + res = 1; /* restart */ + +out: + return res; +} + +static void scst_hw_pending_work_fn(struct delayed_work *work) +{ + struct scst_session *sess = container_of(work, struct scst_session, + hw_pending_work); + struct scst_tgt_template *tgtt = sess->tgt->tgtt; + struct scst_cmd *cmd; + unsigned long cur_time = jiffies; + unsigned long flags; + unsigned long max_time = tgtt->max_hw_pending_time * HZ; + + TRACE_DBG("HW pending work (sess %p, max time %ld)", sess, max_time/HZ); + + clear_bit(SCST_SESS_HW_PENDING_WORK_SCHEDULED, &sess->sess_aflags); + + spin_lock_irqsave(&sess->sess_list_lock, flags); + +restart: + list_for_each_entry(cmd, &sess->sess_cmd_list, sess_cmd_list_entry) { + int rc; + + rc = scst_check_hw_pending_cmd(cmd, cur_time, max_time, sess, + &flags, tgtt); + if (rc < 0) + break; + else if (rc == 0) + continue; + else + goto restart; + } + + if (!list_empty(&sess->sess_cmd_list)) { + /* + * For stuck cmds if there is no activity we might need to have + * one more run to release them, so reschedule once again. + */ + TRACE_DBG("Sched HW pending work for sess %p (max time %d)", + sess, tgtt->max_hw_pending_time); + set_bit(SCST_SESS_HW_PENDING_WORK_SCHEDULED, &sess->sess_aflags); + schedule_delayed_work(&sess->hw_pending_work, + tgtt->max_hw_pending_time * HZ); + } + + spin_unlock_irqrestore(&sess->sess_list_lock, flags); + return; +} + +bool scst_is_relative_target_port_id_unique(uint16_t id, struct scst_tgt *t) +{ + bool res = true; + struct scst_tgt_template *tgtt; + + mutex_lock(&scst_mutex); + list_for_each_entry(tgtt, &scst_template_list, + scst_template_list_entry) { + struct scst_tgt *tgt; + list_for_each_entry(tgt, &tgtt->tgt_list, tgt_list_entry) { + if (tgt == t) + continue; + if (id == tgt->rel_tgt_id) { + res = false; + break; + } + } + } + mutex_unlock(&scst_mutex); + return res; +} + +int gen_relative_target_port_id(uint16_t *id) +{ + int res = -EOVERFLOW; + static unsigned long rti = SCST_MIN_REL_TGT_ID, rti_prev; + + rti_prev = rti; + do { + if (scst_is_relative_target_port_id_unique(rti, NULL)) { + *id = (uint16_t)rti++; + res = 0; + goto out; + } + rti++; + if (rti > SCST_MAX_REL_TGT_ID) + rti = SCST_MIN_REL_TGT_ID; + } while (rti != rti_prev); + + PRINT_ERROR("%s", "Unable to create unique relative target port id"); + +out: + return res; +} + +int scst_alloc_tgt(struct scst_tgt_template *tgtt, struct scst_tgt **tgt) +{ + struct scst_tgt *t; + int res = 0; + + t = kzalloc(sizeof(*t), GFP_KERNEL); + if (t == NULL) { + TRACE(TRACE_OUT_OF_MEM, "%s", "Allocation of tgt failed"); + res = -ENOMEM; + goto out; + } + + INIT_LIST_HEAD(&t->sess_list); + init_waitqueue_head(&t->unreg_waitQ); + t->tgtt = tgtt; + t->sg_tablesize = tgtt->sg_tablesize; + spin_lock_init(&t->tgt_lock); + INIT_LIST_HEAD(&t->retry_cmd_list); + atomic_set(&t->finished_cmds, 0); + init_timer(&t->retry_timer); + t->retry_timer.data = (unsigned long)t; + t->retry_timer.function = scst_tgt_retry_timer_fn; + + *tgt = t; + +out: + return res; +} + +void scst_free_tgt(struct scst_tgt *tgt) +{ + + if (tgt->default_acg != NULL) + scst_free_acg(tgt->default_acg); + + kfree(tgt->tgt_name); + + kfree(tgt); + return; +} + +/* Called under scst_mutex and suspended activity */ +int scst_alloc_device(gfp_t gfp_mask, struct scst_device **out_dev) +{ + struct scst_device *dev; + int res = 0; + + dev = kzalloc(sizeof(*dev), gfp_mask); + if (dev == NULL) { + TRACE(TRACE_OUT_OF_MEM, "%s", + "Allocation of scst_device failed"); + res = -ENOMEM; + goto out; + } + + dev->handler = &scst_null_devtype; + atomic_set(&dev->dev_cmd_count, 0); + atomic_set(&dev->write_cmd_count, 0); + scst_init_mem_lim
|
Pages: 1 Prev: SCST external modules support Next: SCST sysfs interface |