| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * vvvvvvvvvvvvvvvvvvvvvvv Original vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv |
| * Copyright (C) 1992 Eric Youngdale |
| * Simulate a host adapter with 2 disks attached. Do a lot of checking |
| * to make sure that we are not getting blocks mixed up, and PANIC if |
| * anything out of the ordinary is seen. |
| * ^^^^^^^^^^^^^^^^^^^^^^^ Original ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| * |
| * Copyright (C) 2001 - 2021 Douglas Gilbert |
| * |
| * For documentation see http://sg.danny.cz/sg/scsi_debug.html |
| */ |
| |
| |
| #define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__ |
| |
| #include <linux/module.h> |
| #include <linux/align.h> |
| #include <linux/kernel.h> |
| #include <linux/errno.h> |
| #include <linux/jiffies.h> |
| #include <linux/slab.h> |
| #include <linux/types.h> |
| #include <linux/string.h> |
| #include <linux/fs.h> |
| #include <linux/init.h> |
| #include <linux/proc_fs.h> |
| #include <linux/vmalloc.h> |
| #include <linux/moduleparam.h> |
| #include <linux/scatterlist.h> |
| #include <linux/blkdev.h> |
| #include <linux/crc-t10dif.h> |
| #include <linux/spinlock.h> |
| #include <linux/interrupt.h> |
| #include <linux/atomic.h> |
| #include <linux/hrtimer.h> |
| #include <linux/uuid.h> |
| #include <linux/t10-pi.h> |
| #include <linux/msdos_partition.h> |
| #include <linux/random.h> |
| #include <linux/xarray.h> |
| #include <linux/prefetch.h> |
| #include <linux/debugfs.h> |
| #include <linux/async.h> |
| #include <linux/cleanup.h> |
| |
| #include <net/checksum.h> |
| |
| #include <asm/unaligned.h> |
| |
| #include <scsi/scsi.h> |
| #include <scsi/scsi_cmnd.h> |
| #include <scsi/scsi_device.h> |
| #include <scsi/scsi_host.h> |
| #include <scsi/scsicam.h> |
| #include <scsi/scsi_eh.h> |
| #include <scsi/scsi_tcq.h> |
| #include <scsi/scsi_dbg.h> |
| |
| #include "sd.h" |
| #include "scsi_logging.h" |
| |
| /* make sure inq_product_rev string corresponds to this version */ |
| #define SDEBUG_VERSION "0191" /* format to fit INQUIRY revision field */ |
| static const char *sdebug_version_date = "20210520"; |
| |
| #define MY_NAME "scsi_debug" |
| |
| /* Additional Sense Code (ASC) */ |
| #define NO_ADDITIONAL_SENSE 0x0 |
| #define OVERLAP_ATOMIC_COMMAND_ASC 0x0 |
| #define OVERLAP_ATOMIC_COMMAND_ASCQ 0x23 |
| #define LOGICAL_UNIT_NOT_READY 0x4 |
| #define LOGICAL_UNIT_COMMUNICATION_FAILURE 0x8 |
| #define UNRECOVERED_READ_ERR 0x11 |
| #define PARAMETER_LIST_LENGTH_ERR 0x1a |
| #define INVALID_OPCODE 0x20 |
| #define LBA_OUT_OF_RANGE 0x21 |
| #define INVALID_FIELD_IN_CDB 0x24 |
| #define INVALID_FIELD_IN_PARAM_LIST 0x26 |
| #define WRITE_PROTECTED 0x27 |
| #define UA_RESET_ASC 0x29 |
| #define UA_CHANGED_ASC 0x2a |
| #define TARGET_CHANGED_ASC 0x3f |
| #define LUNS_CHANGED_ASCQ 0x0e |
| #define INSUFF_RES_ASC 0x55 |
| #define INSUFF_RES_ASCQ 0x3 |
| #define POWER_ON_RESET_ASCQ 0x0 |
| #define POWER_ON_OCCURRED_ASCQ 0x1 |
| #define BUS_RESET_ASCQ 0x2 /* scsi bus reset occurred */ |
| #define MODE_CHANGED_ASCQ 0x1 /* mode parameters changed */ |
| #define CAPACITY_CHANGED_ASCQ 0x9 |
| #define SAVING_PARAMS_UNSUP 0x39 |
| #define TRANSPORT_PROBLEM 0x4b |
| #define THRESHOLD_EXCEEDED 0x5d |
| #define LOW_POWER_COND_ON 0x5e |
| #define MISCOMPARE_VERIFY_ASC 0x1d |
| #define MICROCODE_CHANGED_ASCQ 0x1 /* with TARGET_CHANGED_ASC */ |
| #define MICROCODE_CHANGED_WO_RESET_ASCQ 0x16 |
| #define WRITE_ERROR_ASC 0xc |
| #define UNALIGNED_WRITE_ASCQ 0x4 |
| #define WRITE_BOUNDARY_ASCQ 0x5 |
| #define READ_INVDATA_ASCQ 0x6 |
| #define READ_BOUNDARY_ASCQ 0x7 |
| #define ATTEMPT_ACCESS_GAP 0x9 |
| #define INSUFF_ZONE_ASCQ 0xe |
| /* see drivers/scsi/sense_codes.h */ |
| |
| /* Additional Sense Code Qualifier (ASCQ) */ |
| #define ACK_NAK_TO 0x3 |
| |
| /* Default values for driver parameters */ |
| #define DEF_NUM_HOST 1 |
| #define DEF_NUM_TGTS 1 |
| #define DEF_MAX_LUNS 1 |
| /* With these defaults, this driver will make 1 host with 1 target |
| * (id 0) containing 1 logical unit (lun 0). That is 1 device. |
| */ |
| #define DEF_ATO 1 |
| #define DEF_CDB_LEN 10 |
| #define DEF_JDELAY 1 /* if > 0 unit is a jiffy */ |
| #define DEF_DEV_SIZE_PRE_INIT 0 |
| #define DEF_DEV_SIZE_MB 8 |
| #define DEF_ZBC_DEV_SIZE_MB 128 |
| #define DEF_DIF 0 |
| #define DEF_DIX 0 |
| #define DEF_PER_HOST_STORE false |
| #define DEF_D_SENSE 0 |
| #define DEF_EVERY_NTH 0 |
| #define DEF_FAKE_RW 0 |
| #define DEF_GUARD 0 |
| #define DEF_HOST_LOCK 0 |
| #define DEF_LBPU 0 |
| #define DEF_LBPWS 0 |
| #define DEF_LBPWS10 0 |
| #define DEF_LBPRZ 1 |
| #define DEF_LOWEST_ALIGNED 0 |
| #define DEF_NDELAY 0 /* if > 0 unit is a nanosecond */ |
| #define DEF_NO_LUN_0 0 |
| #define DEF_NUM_PARTS 0 |
| #define DEF_OPTS 0 |
| #define DEF_OPT_BLKS 1024 |
| #define DEF_PHYSBLK_EXP 0 |
| #define DEF_OPT_XFERLEN_EXP 0 |
| #define DEF_PTYPE TYPE_DISK |
| #define DEF_RANDOM false |
| #define DEF_REMOVABLE false |
| #define DEF_SCSI_LEVEL 7 /* INQUIRY, byte2 [6->SPC-4; 7->SPC-5] */ |
| #define DEF_SECTOR_SIZE 512 |
| #define DEF_UNMAP_ALIGNMENT 0 |
| #define DEF_UNMAP_GRANULARITY 1 |
| #define DEF_UNMAP_MAX_BLOCKS 0xFFFFFFFF |
| #define DEF_UNMAP_MAX_DESC 256 |
| #define DEF_VIRTUAL_GB 0 |
| #define DEF_VPD_USE_HOSTNO 1 |
| #define DEF_WRITESAME_LENGTH 0xFFFF |
| #define DEF_ATOMIC_WR 0 |
| #define DEF_ATOMIC_WR_MAX_LENGTH 8192 |
| #define DEF_ATOMIC_WR_ALIGN 2 |
| #define DEF_ATOMIC_WR_GRAN 2 |
| #define DEF_ATOMIC_WR_MAX_LENGTH_BNDRY (DEF_ATOMIC_WR_MAX_LENGTH) |
| #define DEF_ATOMIC_WR_MAX_BNDRY 128 |
| #define DEF_STRICT 0 |
| #define DEF_STATISTICS false |
| #define DEF_SUBMIT_QUEUES 1 |
| #define DEF_TUR_MS_TO_READY 0 |
| #define DEF_UUID_CTL 0 |
| #define JDELAY_OVERRIDDEN -9999 |
| |
| /* Default parameters for ZBC drives */ |
| #define DEF_ZBC_ZONE_SIZE_MB 128 |
| #define DEF_ZBC_MAX_OPEN_ZONES 8 |
| #define DEF_ZBC_NR_CONV_ZONES 1 |
| |
| #define SDEBUG_LUN_0_VAL 0 |
| |
| /* bit mask values for sdebug_opts */ |
| #define SDEBUG_OPT_NOISE 1 |
| #define SDEBUG_OPT_MEDIUM_ERR 2 |
| #define SDEBUG_OPT_TIMEOUT 4 |
| #define SDEBUG_OPT_RECOVERED_ERR 8 |
| #define SDEBUG_OPT_TRANSPORT_ERR 16 |
| #define SDEBUG_OPT_DIF_ERR 32 |
| #define SDEBUG_OPT_DIX_ERR 64 |
| #define SDEBUG_OPT_MAC_TIMEOUT 128 |
| #define SDEBUG_OPT_SHORT_TRANSFER 0x100 |
| #define SDEBUG_OPT_Q_NOISE 0x200 |
| #define SDEBUG_OPT_ALL_TSF 0x400 /* ignore */ |
| #define SDEBUG_OPT_RARE_TSF 0x800 |
| #define SDEBUG_OPT_N_WCE 0x1000 |
| #define SDEBUG_OPT_RESET_NOISE 0x2000 |
| #define SDEBUG_OPT_NO_CDB_NOISE 0x4000 |
| #define SDEBUG_OPT_HOST_BUSY 0x8000 |
| #define SDEBUG_OPT_CMD_ABORT 0x10000 |
| #define SDEBUG_OPT_ALL_NOISE (SDEBUG_OPT_NOISE | SDEBUG_OPT_Q_NOISE | \ |
| SDEBUG_OPT_RESET_NOISE) |
| #define SDEBUG_OPT_ALL_INJECTING (SDEBUG_OPT_RECOVERED_ERR | \ |
| SDEBUG_OPT_TRANSPORT_ERR | \ |
| SDEBUG_OPT_DIF_ERR | SDEBUG_OPT_DIX_ERR | \ |
| SDEBUG_OPT_SHORT_TRANSFER | \ |
| SDEBUG_OPT_HOST_BUSY | \ |
| SDEBUG_OPT_CMD_ABORT) |
| #define SDEBUG_OPT_RECOV_DIF_DIX (SDEBUG_OPT_RECOVERED_ERR | \ |
| SDEBUG_OPT_DIF_ERR | SDEBUG_OPT_DIX_ERR) |
| |
| /* As indicated in SAM-5 and SPC-4 Unit Attentions (UAs) are returned in |
| * priority order. In the subset implemented here lower numbers have higher |
| * priority. The UA numbers should be a sequence starting from 0 with |
| * SDEBUG_NUM_UAS being 1 higher than the highest numbered UA. */ |
| #define SDEBUG_UA_POR 0 /* Power on, reset, or bus device reset */ |
| #define SDEBUG_UA_POOCCUR 1 /* Power on occurred */ |
| #define SDEBUG_UA_BUS_RESET 2 |
| #define SDEBUG_UA_MODE_CHANGED 3 |
| #define SDEBUG_UA_CAPACITY_CHANGED 4 |
| #define SDEBUG_UA_LUNS_CHANGED 5 |
| #define SDEBUG_UA_MICROCODE_CHANGED 6 /* simulate firmware change */ |
| #define SDEBUG_UA_MICROCODE_CHANGED_WO_RESET 7 |
| #define SDEBUG_NUM_UAS 8 |
| |
| /* when 1==SDEBUG_OPT_MEDIUM_ERR, a medium error is simulated at this |
| * sector on read commands: */ |
| #define OPT_MEDIUM_ERR_ADDR 0x1234 /* that's sector 4660 in decimal */ |
| #define OPT_MEDIUM_ERR_NUM 10 /* number of consecutive medium errs */ |
| |
| /* SDEBUG_CANQUEUE is the maximum number of commands that can be queued |
| * (for response) per submit queue at one time. Can be reduced by max_queue |
| * option. Command responses are not queued when jdelay=0 and ndelay=0. The |
| * per-device DEF_CMD_PER_LUN can be changed via sysfs: |
| * /sys/class/scsi_device/<h:c:t:l>/device/queue_depth |
| * but cannot exceed SDEBUG_CANQUEUE . |
| */ |
| #define SDEBUG_CANQUEUE_WORDS 3 /* a WORD is bits in a long */ |
| #define SDEBUG_CANQUEUE (SDEBUG_CANQUEUE_WORDS * BITS_PER_LONG) |
| #define DEF_CMD_PER_LUN SDEBUG_CANQUEUE |
| |
| /* UA - Unit Attention; SA - Service Action; SSU - Start Stop Unit */ |
| #define F_D_IN 1 /* Data-in command (e.g. READ) */ |
| #define F_D_OUT 2 /* Data-out command (e.g. WRITE) */ |
| #define F_D_OUT_MAYBE 4 /* WRITE SAME, NDOB bit */ |
| #define F_D_UNKN 8 |
| #define F_RL_WLUN_OK 0x10 /* allowed with REPORT LUNS W-LUN */ |
| #define F_SKIP_UA 0x20 /* bypass UAs (e.g. INQUIRY command) */ |
| #define F_DELAY_OVERR 0x40 /* for commands like INQUIRY */ |
| #define F_SA_LOW 0x80 /* SA is in cdb byte 1, bits 4 to 0 */ |
| #define F_SA_HIGH 0x100 /* SA is in cdb bytes 8 and 9 */ |
| #define F_INV_OP 0x200 /* invalid opcode (not supported) */ |
| #define F_FAKE_RW 0x400 /* bypass resp_*() when fake_rw set */ |
| #define F_M_ACCESS 0x800 /* media access, reacts to SSU state */ |
| #define F_SSU_DELAY 0x1000 /* SSU command delay (long-ish) */ |
| #define F_SYNC_DELAY 0x2000 /* SYNCHRONIZE CACHE delay */ |
| |
| /* Useful combinations of the above flags */ |
| #define FF_RESPOND (F_RL_WLUN_OK | F_SKIP_UA | F_DELAY_OVERR) |
| #define FF_MEDIA_IO (F_M_ACCESS | F_FAKE_RW) |
| #define FF_SA (F_SA_HIGH | F_SA_LOW) |
| #define F_LONG_DELAY (F_SSU_DELAY | F_SYNC_DELAY) |
| |
| #define SDEBUG_MAX_PARTS 4 |
| |
| #define SDEBUG_MAX_CMD_LEN 32 |
| |
| #define SDEB_XA_NOT_IN_USE XA_MARK_1 |
| |
| static struct kmem_cache *queued_cmd_cache; |
| |
| #define TO_QUEUED_CMD(scmd) ((void *)(scmd)->host_scribble) |
| #define ASSIGN_QUEUED_CMD(scmnd, qc) { (scmnd)->host_scribble = (void *) qc; } |
| |
| /* Zone types (zbcr05 table 25) */ |
| enum sdebug_z_type { |
| ZBC_ZTYPE_CNV = 0x1, |
| ZBC_ZTYPE_SWR = 0x2, |
| ZBC_ZTYPE_SWP = 0x3, |
| /* ZBC_ZTYPE_SOBR = 0x4, */ |
| ZBC_ZTYPE_GAP = 0x5, |
| }; |
| |
| /* enumeration names taken from table 26, zbcr05 */ |
| enum sdebug_z_cond { |
| ZBC_NOT_WRITE_POINTER = 0x0, |
| ZC1_EMPTY = 0x1, |
| ZC2_IMPLICIT_OPEN = 0x2, |
| ZC3_EXPLICIT_OPEN = 0x3, |
| ZC4_CLOSED = 0x4, |
| ZC6_READ_ONLY = 0xd, |
| ZC5_FULL = 0xe, |
| ZC7_OFFLINE = 0xf, |
| }; |
| |
| struct sdeb_zone_state { /* ZBC: per zone state */ |
| enum sdebug_z_type z_type; |
| enum sdebug_z_cond z_cond; |
| bool z_non_seq_resource; |
| unsigned int z_size; |
| sector_t z_start; |
| sector_t z_wp; |
| }; |
| |
| enum sdebug_err_type { |
| ERR_TMOUT_CMD = 0, /* make specific scsi command timeout */ |
| ERR_FAIL_QUEUE_CMD = 1, /* make specific scsi command's */ |
| /* queuecmd return failed */ |
| ERR_FAIL_CMD = 2, /* make specific scsi command's */ |
| /* queuecmd return succeed but */ |
| /* with errors set in scsi_cmnd */ |
| ERR_ABORT_CMD_FAILED = 3, /* control return FAILED from */ |
| /* scsi_debug_abort() */ |
| ERR_LUN_RESET_FAILED = 4, /* control return FAILED from */ |
| /* scsi_debug_device_reseLUN_RESET_FAILEDt() */ |
| }; |
| |
| struct sdebug_err_inject { |
| int type; |
| struct list_head list; |
| int cnt; |
| unsigned char cmd; |
| struct rcu_head rcu; |
| |
| union { |
| /* |
| * For ERR_FAIL_QUEUE_CMD |
| */ |
| int queuecmd_ret; |
| |
| /* |
| * For ERR_FAIL_CMD |
| */ |
| struct { |
| unsigned char host_byte; |
| unsigned char driver_byte; |
| unsigned char status_byte; |
| unsigned char sense_key; |
| unsigned char asc; |
| unsigned char asq; |
| }; |
| }; |
| }; |
| |
| struct sdebug_dev_info { |
| struct list_head dev_list; |
| unsigned int channel; |
| unsigned int target; |
| u64 lun; |
| uuid_t lu_name; |
| struct sdebug_host_info *sdbg_host; |
| unsigned long uas_bm[1]; |
| atomic_t stopped; /* 1: by SSU, 2: device start */ |
| bool used; |
| |
| /* For ZBC devices */ |
| bool zoned; |
| unsigned int zcap; |
| unsigned int zsize; |
| unsigned int zsize_shift; |
| unsigned int nr_zones; |
| unsigned int nr_conv_zones; |
| unsigned int nr_seq_zones; |
| unsigned int nr_imp_open; |
| unsigned int nr_exp_open; |
| unsigned int nr_closed; |
| unsigned int max_open; |
| ktime_t create_ts; /* time since bootup that this device was created */ |
| struct sdeb_zone_state *zstate; |
| |
| struct dentry *debugfs_entry; |
| struct spinlock list_lock; |
| struct list_head inject_err_list; |
| }; |
| |
| struct sdebug_target_info { |
| bool reset_fail; |
| struct dentry *debugfs_entry; |
| }; |
| |
| struct sdebug_host_info { |
| struct list_head host_list; |
| int si_idx; /* sdeb_store_info (per host) xarray index */ |
| struct Scsi_Host *shost; |
| struct device dev; |
| struct list_head dev_info_list; |
| }; |
| |
| /* There is an xarray of pointers to this struct's objects, one per host */ |
| struct sdeb_store_info { |
| rwlock_t macc_data_lck; /* for media data access on this store */ |
| rwlock_t macc_meta_lck; /* for atomic media meta access on this store */ |
| rwlock_t macc_sector_lck; /* per-sector media data access on this store */ |
| u8 *storep; /* user data storage (ram) */ |
| struct t10_pi_tuple *dif_storep; /* protection info */ |
| void *map_storep; /* provisioning map */ |
| }; |
| |
| #define dev_to_sdebug_host(d) \ |
| container_of(d, struct sdebug_host_info, dev) |
| |
| #define shost_to_sdebug_host(shost) \ |
| dev_to_sdebug_host(shost->dma_dev) |
| |
| enum sdeb_defer_type {SDEB_DEFER_NONE = 0, SDEB_DEFER_HRT = 1, |
| SDEB_DEFER_WQ = 2, SDEB_DEFER_POLL = 3}; |
| |
| struct sdebug_defer { |
| struct hrtimer hrt; |
| struct execute_work ew; |
| ktime_t cmpl_ts;/* time since boot to complete this cmd */ |
| int issuing_cpu; |
| bool aborted; /* true when blk_abort_request() already called */ |
| enum sdeb_defer_type defer_t; |
| }; |
| |
| struct sdebug_device_access_info { |
| bool atomic_write; |
| u64 lba; |
| u32 num; |
| struct scsi_cmnd *self; |
| }; |
| |
| struct sdebug_queued_cmd { |
| /* corresponding bit set in in_use_bm[] in owning struct sdebug_queue |
| * instance indicates this slot is in use. |
| */ |
| struct sdebug_defer sd_dp; |
| struct scsi_cmnd *scmd; |
| struct sdebug_device_access_info *i; |
| }; |
| |
| struct sdebug_scsi_cmd { |
| spinlock_t lock; |
| }; |
| |
| static atomic_t sdebug_cmnd_count; /* number of incoming commands */ |
| static atomic_t sdebug_completions; /* count of deferred completions */ |
| static atomic_t sdebug_miss_cpus; /* submission + completion cpus differ */ |
| static atomic_t sdebug_a_tsf; /* 'almost task set full' counter */ |
| static atomic_t sdeb_inject_pending; |
| static atomic_t sdeb_mq_poll_count; /* bumped when mq_poll returns > 0 */ |
| |
| struct opcode_info_t { |
| u8 num_attached; /* 0 if this is it (i.e. a leaf); use 0xff */ |
| /* for terminating element */ |
| u8 opcode; /* if num_attached > 0, preferred */ |
| u16 sa; /* service action */ |
| u32 flags; /* OR-ed set of SDEB_F_* */ |
| int (*pfp)(struct scsi_cmnd *, struct sdebug_dev_info *); |
| const struct opcode_info_t *arrp; /* num_attached elements or NULL */ |
| u8 len_mask[16]; /* len_mask[0]-->cdb_len, then mask for cdb */ |
| /* 1 to min(cdb_len, 15); ignore cdb[15...] */ |
| }; |
| |
| /* SCSI opcodes (first byte of cdb) of interest mapped onto these indexes */ |
| enum sdeb_opcode_index { |
| SDEB_I_INVALID_OPCODE = 0, |
| SDEB_I_INQUIRY = 1, |
| SDEB_I_REPORT_LUNS = 2, |
| SDEB_I_REQUEST_SENSE = 3, |
| SDEB_I_TEST_UNIT_READY = 4, |
| SDEB_I_MODE_SENSE = 5, /* 6, 10 */ |
| SDEB_I_MODE_SELECT = 6, /* 6, 10 */ |
| SDEB_I_LOG_SENSE = 7, |
| SDEB_I_READ_CAPACITY = 8, /* 10; 16 is in SA_IN(16) */ |
| SDEB_I_READ = 9, /* 6, 10, 12, 16 */ |
| SDEB_I_WRITE = 10, /* 6, 10, 12, 16 */ |
| SDEB_I_START_STOP = 11, |
| SDEB_I_SERV_ACT_IN_16 = 12, /* add ...SERV_ACT_IN_12 if needed */ |
| SDEB_I_SERV_ACT_OUT_16 = 13, /* add ...SERV_ACT_OUT_12 if needed */ |
| SDEB_I_MAINT_IN = 14, |
| SDEB_I_MAINT_OUT = 15, |
| SDEB_I_VERIFY = 16, /* VERIFY(10), VERIFY(16) */ |
| SDEB_I_VARIABLE_LEN = 17, /* READ(32), WRITE(32), WR_SCAT(32) */ |
| SDEB_I_RESERVE = 18, /* 6, 10 */ |
| SDEB_I_RELEASE = 19, /* 6, 10 */ |
| SDEB_I_ALLOW_REMOVAL = 20, /* PREVENT ALLOW MEDIUM REMOVAL */ |
| SDEB_I_REZERO_UNIT = 21, /* REWIND in SSC */ |
| SDEB_I_ATA_PT = 22, /* 12, 16 */ |
| SDEB_I_SEND_DIAG = 23, |
| SDEB_I_UNMAP = 24, |
| SDEB_I_WRITE_BUFFER = 25, |
| SDEB_I_WRITE_SAME = 26, /* 10, 16 */ |
| SDEB_I_SYNC_CACHE = 27, /* 10, 16 */ |
| SDEB_I_COMP_WRITE = 28, |
| SDEB_I_PRE_FETCH = 29, /* 10, 16 */ |
| SDEB_I_ZONE_OUT = 30, /* 0x94+SA; includes no data xfer */ |
| SDEB_I_ZONE_IN = 31, /* 0x95+SA; all have data-in */ |
| SDEB_I_ATOMIC_WRITE_16 = 32, |
| SDEB_I_LAST_ELEM_P1 = 33, /* keep this last (previous + 1) */ |
| }; |
| |
| |
| static const unsigned char opcode_ind_arr[256] = { |
| /* 0x0; 0x0->0x1f: 6 byte cdbs */ |
| SDEB_I_TEST_UNIT_READY, SDEB_I_REZERO_UNIT, 0, SDEB_I_REQUEST_SENSE, |
| 0, 0, 0, 0, |
| SDEB_I_READ, 0, SDEB_I_WRITE, 0, 0, 0, 0, 0, |
| 0, 0, SDEB_I_INQUIRY, 0, 0, SDEB_I_MODE_SELECT, SDEB_I_RESERVE, |
| SDEB_I_RELEASE, |
| 0, 0, SDEB_I_MODE_SENSE, SDEB_I_START_STOP, 0, SDEB_I_SEND_DIAG, |
| SDEB_I_ALLOW_REMOVAL, 0, |
| /* 0x20; 0x20->0x3f: 10 byte cdbs */ |
| 0, 0, 0, 0, 0, SDEB_I_READ_CAPACITY, 0, 0, |
| SDEB_I_READ, 0, SDEB_I_WRITE, 0, 0, 0, 0, SDEB_I_VERIFY, |
| 0, 0, 0, 0, SDEB_I_PRE_FETCH, SDEB_I_SYNC_CACHE, 0, 0, |
| 0, 0, 0, SDEB_I_WRITE_BUFFER, 0, 0, 0, 0, |
| /* 0x40; 0x40->0x5f: 10 byte cdbs */ |
| 0, SDEB_I_WRITE_SAME, SDEB_I_UNMAP, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, SDEB_I_LOG_SENSE, 0, 0, |
| 0, 0, 0, 0, 0, SDEB_I_MODE_SELECT, SDEB_I_RESERVE, |
| SDEB_I_RELEASE, |
| 0, 0, SDEB_I_MODE_SENSE, 0, 0, 0, 0, 0, |
| /* 0x60; 0x60->0x7d are reserved, 0x7e is "extended cdb" */ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, SDEB_I_VARIABLE_LEN, |
| /* 0x80; 0x80->0x9f: 16 byte cdbs */ |
| 0, 0, 0, 0, 0, SDEB_I_ATA_PT, 0, 0, |
| SDEB_I_READ, SDEB_I_COMP_WRITE, SDEB_I_WRITE, 0, |
| 0, 0, 0, SDEB_I_VERIFY, |
| SDEB_I_PRE_FETCH, SDEB_I_SYNC_CACHE, 0, SDEB_I_WRITE_SAME, |
| SDEB_I_ZONE_OUT, SDEB_I_ZONE_IN, 0, 0, |
| 0, 0, 0, 0, |
| SDEB_I_ATOMIC_WRITE_16, 0, SDEB_I_SERV_ACT_IN_16, SDEB_I_SERV_ACT_OUT_16, |
| /* 0xa0; 0xa0->0xbf: 12 byte cdbs */ |
| SDEB_I_REPORT_LUNS, SDEB_I_ATA_PT, 0, SDEB_I_MAINT_IN, |
| SDEB_I_MAINT_OUT, 0, 0, 0, |
| SDEB_I_READ, 0 /* SDEB_I_SERV_ACT_OUT_12 */, SDEB_I_WRITE, |
| 0 /* SDEB_I_SERV_ACT_IN_12 */, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, |
| /* 0xc0; 0xc0->0xff: vendor specific */ |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| }; |
| |
| /* |
| * The following "response" functions return the SCSI mid-level's 4 byte |
| * tuple-in-an-int. To handle commands with an IMMED bit, for a faster |
| * command completion, they can mask their return value with |
| * SDEG_RES_IMMED_MASK . |
| */ |
| #define SDEG_RES_IMMED_MASK 0x40000000 |
| |
| static int resp_inquiry(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_report_luns(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_requests(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_mode_sense(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_mode_select(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_log_sense(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_readcap(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_read_dt0(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_write_dt0(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_write_scat(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_start_stop(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_readcap16(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_get_lba_status(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_get_stream_status(struct scsi_cmnd *scp, |
| struct sdebug_dev_info *devip); |
| static int resp_report_tgtpgs(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_unmap(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_rsup_opcodes(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_rsup_tmfs(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_verify(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_write_same_10(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_write_same_16(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_comp_write(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_write_buffer(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_sync_cache(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_pre_fetch(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_report_zones(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_atomic_write(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_open_zone(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_close_zone(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_finish_zone(struct scsi_cmnd *, struct sdebug_dev_info *); |
| static int resp_rwp_zone(struct scsi_cmnd *, struct sdebug_dev_info *); |
| |
| static int sdebug_do_add_host(bool mk_new_store); |
| static int sdebug_add_host_helper(int per_host_idx); |
| static void sdebug_do_remove_host(bool the_end); |
| static int sdebug_add_store(void); |
| static void sdebug_erase_store(int idx, struct sdeb_store_info *sip); |
| static void sdebug_erase_all_stores(bool apart_from_first); |
| |
| static void sdebug_free_queued_cmd(struct sdebug_queued_cmd *sqcp); |
| |
| /* |
| * The following are overflow arrays for cdbs that "hit" the same index in |
| * the opcode_info_arr array. The most time sensitive (or commonly used) cdb |
| * should be placed in opcode_info_arr[], the others should be placed here. |
| */ |
| static const struct opcode_info_t msense_iarr[] = { |
| {0, 0x1a, 0, F_D_IN, NULL, NULL, |
| {6, 0xe8, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, |
| }; |
| |
| static const struct opcode_info_t mselect_iarr[] = { |
| {0, 0x15, 0, F_D_OUT, NULL, NULL, |
| {6, 0xf1, 0, 0, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, |
| }; |
| |
| static const struct opcode_info_t read_iarr[] = { |
| {0, 0x28, 0, F_D_IN | FF_MEDIA_IO, resp_read_dt0, NULL,/* READ(10) */ |
| {10, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xc7, 0, 0, |
| 0, 0, 0, 0} }, |
| {0, 0x8, 0, F_D_IN | FF_MEDIA_IO, resp_read_dt0, NULL, /* READ(6) */ |
| {6, 0xff, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, |
| {0, 0xa8, 0, F_D_IN | FF_MEDIA_IO, resp_read_dt0, NULL,/* READ(12) */ |
| {12, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, |
| 0xc7, 0, 0, 0, 0} }, |
| }; |
| |
| static const struct opcode_info_t write_iarr[] = { |
| {0, 0x2a, 0, F_D_OUT | FF_MEDIA_IO, resp_write_dt0, /* WRITE(10) */ |
| NULL, {10, 0xfb, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xc7, |
| 0, 0, 0, 0, 0, 0} }, |
| {0, 0xa, 0, F_D_OUT | FF_MEDIA_IO, resp_write_dt0, /* WRITE(6) */ |
| NULL, {6, 0xff, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0} }, |
| {0, 0xaa, 0, F_D_OUT | FF_MEDIA_IO, resp_write_dt0, /* WRITE(12) */ |
| NULL, {12, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xbf, 0xc7, 0, 0, 0, 0} }, |
| }; |
| |
| static const struct opcode_info_t verify_iarr[] = { |
| {0, 0x2f, 0, F_D_OUT_MAYBE | FF_MEDIA_IO, resp_verify,/* VERIFY(10) */ |
| NULL, {10, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xff, 0xff, 0xc7, |
| 0, 0, 0, 0, 0, 0} }, |
| }; |
| |
| static const struct opcode_info_t sa_in_16_iarr[] = { |
| {0, 0x9e, 0x12, F_SA_LOW | F_D_IN, resp_get_lba_status, NULL, |
| {16, 0x12, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0, 0xc7} }, /* GET LBA STATUS(16) */ |
| {0, 0x9e, 0x16, F_SA_LOW | F_D_IN, resp_get_stream_status, NULL, |
| {16, 0x16, 0, 0, 0xff, 0xff, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, |
| 0, 0} }, /* GET STREAM STATUS */ |
| }; |
| |
| static const struct opcode_info_t vl_iarr[] = { /* VARIABLE LENGTH */ |
| {0, 0x7f, 0xb, F_SA_HIGH | F_D_OUT | FF_MEDIA_IO, resp_write_dt0, |
| NULL, {32, 0xc7, 0, 0, 0, 0, 0x3f, 0x18, 0x0, 0xb, 0xfa, |
| 0, 0xff, 0xff, 0xff, 0xff} }, /* WRITE(32) */ |
| {0, 0x7f, 0x11, F_SA_HIGH | F_D_OUT | FF_MEDIA_IO, resp_write_scat, |
| NULL, {32, 0xc7, 0, 0, 0, 0, 0x3f, 0x18, 0x0, 0x11, 0xf8, |
| 0, 0xff, 0xff, 0x0, 0x0} }, /* WRITE SCATTERED(32) */ |
| }; |
| |
| static const struct opcode_info_t maint_in_iarr[] = { /* MAINT IN */ |
| {0, 0xa3, 0xc, F_SA_LOW | F_D_IN, resp_rsup_opcodes, NULL, |
| {12, 0xc, 0x87, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, |
| 0xc7, 0, 0, 0, 0} }, /* REPORT SUPPORTED OPERATION CODES */ |
| {0, 0xa3, 0xd, F_SA_LOW | F_D_IN, resp_rsup_tmfs, NULL, |
| {12, 0xd, 0x80, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, |
| 0, 0} }, /* REPORTED SUPPORTED TASK MANAGEMENT FUNCTIONS */ |
| }; |
| |
| static const struct opcode_info_t write_same_iarr[] = { |
| {0, 0x93, 0, F_D_OUT_MAYBE | FF_MEDIA_IO, resp_write_same_16, NULL, |
| {16, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0x3f, 0xc7} }, /* WRITE SAME(16) */ |
| }; |
| |
| static const struct opcode_info_t reserve_iarr[] = { |
| {0, 0x16, 0, F_D_OUT, NULL, NULL, /* RESERVE(6) */ |
| {6, 0x1f, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, |
| }; |
| |
| static const struct opcode_info_t release_iarr[] = { |
| {0, 0x17, 0, F_D_OUT, NULL, NULL, /* RELEASE(6) */ |
| {6, 0x1f, 0xff, 0, 0, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, |
| }; |
| |
| static const struct opcode_info_t sync_cache_iarr[] = { |
| {0, 0x91, 0, F_SYNC_DELAY | F_M_ACCESS, resp_sync_cache, NULL, |
| {16, 0x6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0x3f, 0xc7} }, /* SYNC_CACHE (16) */ |
| }; |
| |
| static const struct opcode_info_t pre_fetch_iarr[] = { |
| {0, 0x90, 0, F_SYNC_DELAY | FF_MEDIA_IO, resp_pre_fetch, NULL, |
| {16, 0x2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0x3f, 0xc7} }, /* PRE-FETCH (16) */ |
| }; |
| |
| static const struct opcode_info_t zone_out_iarr[] = { /* ZONE OUT(16) */ |
| {0, 0x94, 0x1, F_SA_LOW | F_M_ACCESS, resp_close_zone, NULL, |
| {16, 0x1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0, 0, 0xff, 0xff, 0x1, 0xc7} }, /* CLOSE ZONE */ |
| {0, 0x94, 0x2, F_SA_LOW | F_M_ACCESS, resp_finish_zone, NULL, |
| {16, 0x2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0, 0, 0xff, 0xff, 0x1, 0xc7} }, /* FINISH ZONE */ |
| {0, 0x94, 0x4, F_SA_LOW | F_M_ACCESS, resp_rwp_zone, NULL, |
| {16, 0x4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0, 0, 0xff, 0xff, 0x1, 0xc7} }, /* RESET WRITE POINTER */ |
| }; |
| |
| static const struct opcode_info_t zone_in_iarr[] = { /* ZONE IN(16) */ |
| {0, 0x95, 0x6, F_SA_LOW | F_D_IN | F_M_ACCESS, NULL, NULL, |
| {16, 0x6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0x3f, 0xc7} }, /* REPORT ZONES */ |
| }; |
| |
| |
| /* This array is accessed via SDEB_I_* values. Make sure all are mapped, |
| * plus the terminating elements for logic that scans this table such as |
| * REPORT SUPPORTED OPERATION CODES. */ |
| static const struct opcode_info_t opcode_info_arr[SDEB_I_LAST_ELEM_P1 + 1] = { |
| /* 0 */ |
| {0, 0, 0, F_INV_OP | FF_RESPOND, NULL, NULL, /* unknown opcodes */ |
| {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, |
| {0, 0x12, 0, FF_RESPOND | F_D_IN, resp_inquiry, NULL, /* INQUIRY */ |
| {6, 0xe3, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, |
| {0, 0xa0, 0, FF_RESPOND | F_D_IN, resp_report_luns, NULL, |
| {12, 0xe3, 0xff, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0xc7, 0, 0, |
| 0, 0} }, /* REPORT LUNS */ |
| {0, 0x3, 0, FF_RESPOND | F_D_IN, resp_requests, NULL, |
| {6, 0xe1, 0, 0, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, |
| {0, 0x0, 0, F_M_ACCESS | F_RL_WLUN_OK, NULL, NULL,/* TEST UNIT READY */ |
| {6, 0, 0, 0, 0, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, |
| /* 5 */ |
| {ARRAY_SIZE(msense_iarr), 0x5a, 0, F_D_IN, /* MODE SENSE(10) */ |
| resp_mode_sense, msense_iarr, {10, 0xf8, 0xff, 0xff, 0, 0, 0, |
| 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0} }, |
| {ARRAY_SIZE(mselect_iarr), 0x55, 0, F_D_OUT, /* MODE SELECT(10) */ |
| resp_mode_select, mselect_iarr, {10, 0xf1, 0, 0, 0, 0, 0, 0xff, |
| 0xff, 0xc7, 0, 0, 0, 0, 0, 0} }, |
| {0, 0x4d, 0, F_D_IN, resp_log_sense, NULL, /* LOG SENSE */ |
| {10, 0xe3, 0xff, 0xff, 0, 0xff, 0xff, 0xff, 0xff, 0xc7, 0, 0, 0, |
| 0, 0, 0} }, |
| {0, 0x25, 0, F_D_IN, resp_readcap, NULL, /* READ CAPACITY(10) */ |
| {10, 0xe1, 0xff, 0xff, 0xff, 0xff, 0, 0, 0x1, 0xc7, 0, 0, 0, 0, |
| 0, 0} }, |
| {ARRAY_SIZE(read_iarr), 0x88, 0, F_D_IN | FF_MEDIA_IO, /* READ(16) */ |
| resp_read_dt0, read_iarr, {16, 0xfe, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7} }, |
| /* 10 */ |
| {ARRAY_SIZE(write_iarr), 0x8a, 0, F_D_OUT | FF_MEDIA_IO, |
| resp_write_dt0, write_iarr, /* WRITE(16) */ |
| {16, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7} }, |
| {0, 0x1b, 0, F_SSU_DELAY, resp_start_stop, NULL,/* START STOP UNIT */ |
| {6, 0x1, 0, 0xf, 0xf7, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, |
| {ARRAY_SIZE(sa_in_16_iarr), 0x9e, 0x10, F_SA_LOW | F_D_IN, |
| resp_readcap16, sa_in_16_iarr, /* SA_IN(16), READ CAPACITY(16) */ |
| {16, 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0x1, 0xc7} }, |
| {0, 0x9f, 0x12, F_SA_LOW | F_D_OUT | FF_MEDIA_IO, resp_write_scat, |
| NULL, {16, 0x12, 0xf9, 0x0, 0xff, 0xff, 0, 0, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0xc7} }, /* SA_OUT(16), WRITE SCAT(16) */ |
| {ARRAY_SIZE(maint_in_iarr), 0xa3, 0xa, F_SA_LOW | F_D_IN, |
| resp_report_tgtpgs, /* MAINT IN, REPORT TARGET PORT GROUPS */ |
| maint_in_iarr, {12, 0xea, 0, 0, 0, 0, 0xff, 0xff, 0xff, |
| 0xff, 0, 0xc7, 0, 0, 0, 0} }, |
| /* 15 */ |
| {0, 0, 0, F_INV_OP | FF_RESPOND, NULL, NULL, /* MAINT OUT */ |
| {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, |
| {ARRAY_SIZE(verify_iarr), 0x8f, 0, |
| F_D_OUT_MAYBE | FF_MEDIA_IO, resp_verify, /* VERIFY(16) */ |
| verify_iarr, {16, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xc7} }, |
| {ARRAY_SIZE(vl_iarr), 0x7f, 0x9, F_SA_HIGH | F_D_IN | FF_MEDIA_IO, |
| resp_read_dt0, vl_iarr, /* VARIABLE LENGTH, READ(32) */ |
| {32, 0xc7, 0, 0, 0, 0, 0x3f, 0x18, 0x0, 0x9, 0xfe, 0, 0xff, 0xff, |
| 0xff, 0xff} }, |
| {ARRAY_SIZE(reserve_iarr), 0x56, 0, F_D_OUT, |
| NULL, reserve_iarr, /* RESERVE(10) <no response function> */ |
| {10, 0xff, 0xff, 0xff, 0, 0, 0, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, |
| 0} }, |
| {ARRAY_SIZE(release_iarr), 0x57, 0, F_D_OUT, |
| NULL, release_iarr, /* RELEASE(10) <no response function> */ |
| {10, 0x13, 0xff, 0xff, 0, 0, 0, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, |
| 0} }, |
| /* 20 */ |
| {0, 0x1e, 0, 0, NULL, NULL, /* ALLOW REMOVAL */ |
| {6, 0, 0, 0, 0x3, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, |
| {0, 0x1, 0, 0, resp_start_stop, NULL, /* REWIND ?? */ |
| {6, 0x1, 0, 0, 0, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, |
| {0, 0, 0, F_INV_OP | FF_RESPOND, NULL, NULL, /* ATA_PT */ |
| {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, |
| {0, 0x1d, F_D_OUT, 0, NULL, NULL, /* SEND DIAGNOSTIC */ |
| {6, 0xf7, 0, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, |
| {0, 0x42, 0, F_D_OUT | FF_MEDIA_IO, resp_unmap, NULL, /* UNMAP */ |
| {10, 0x1, 0, 0, 0, 0, 0x3f, 0xff, 0xff, 0xc7, 0, 0, 0, 0, 0, 0} }, |
| /* 25 */ |
| {0, 0x3b, 0, F_D_OUT_MAYBE, resp_write_buffer, NULL, |
| {10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc7, 0, 0, |
| 0, 0, 0, 0} }, /* WRITE_BUFFER */ |
| {ARRAY_SIZE(write_same_iarr), 0x41, 0, F_D_OUT_MAYBE | FF_MEDIA_IO, |
| resp_write_same_10, write_same_iarr, /* WRITE SAME(10) */ |
| {10, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xc7, 0, |
| 0, 0, 0, 0, 0} }, |
| {ARRAY_SIZE(sync_cache_iarr), 0x35, 0, F_SYNC_DELAY | F_M_ACCESS, |
| resp_sync_cache, sync_cache_iarr, |
| {10, 0x7, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xc7, 0, 0, |
| 0, 0, 0, 0} }, /* SYNC_CACHE (10) */ |
| {0, 0x89, 0, F_D_OUT | FF_MEDIA_IO, resp_comp_write, NULL, |
| {16, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, |
| 0, 0xff, 0x3f, 0xc7} }, /* COMPARE AND WRITE */ |
| {ARRAY_SIZE(pre_fetch_iarr), 0x34, 0, F_SYNC_DELAY | FF_MEDIA_IO, |
| resp_pre_fetch, pre_fetch_iarr, |
| {10, 0x2, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xc7, 0, 0, |
| 0, 0, 0, 0} }, /* PRE-FETCH (10) */ |
| |
| /* 30 */ |
| {ARRAY_SIZE(zone_out_iarr), 0x94, 0x3, F_SA_LOW | F_M_ACCESS, |
| resp_open_zone, zone_out_iarr, /* ZONE_OUT(16), OPEN ZONE) */ |
| {16, 0x3 /* SA */, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0x0, 0x0, 0xff, 0xff, 0x1, 0xc7} }, |
| {ARRAY_SIZE(zone_in_iarr), 0x95, 0x0, F_SA_LOW | F_M_ACCESS, |
| resp_report_zones, zone_in_iarr, /* ZONE_IN(16), REPORT ZONES) */ |
| {16, 0x0 /* SA */, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0xc7} }, |
| /* 31 */ |
| {0, 0x0, 0x0, F_D_OUT | FF_MEDIA_IO, |
| resp_atomic_write, NULL, /* ATOMIC WRITE 16 */ |
| {16, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff} }, |
| /* sentinel */ |
| {0xff, 0, 0, 0, NULL, NULL, /* terminating element */ |
| {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, |
| }; |
| |
| static int sdebug_num_hosts; |
| static int sdebug_add_host = DEF_NUM_HOST; /* in sysfs this is relative */ |
| static int sdebug_ato = DEF_ATO; |
| static int sdebug_cdb_len = DEF_CDB_LEN; |
| static int sdebug_jdelay = DEF_JDELAY; /* if > 0 then unit is jiffies */ |
| static int sdebug_dev_size_mb = DEF_DEV_SIZE_PRE_INIT; |
| static int sdebug_dif = DEF_DIF; |
| static int sdebug_dix = DEF_DIX; |
| static int sdebug_dsense = DEF_D_SENSE; |
| static int sdebug_every_nth = DEF_EVERY_NTH; |
| static int sdebug_fake_rw = DEF_FAKE_RW; |
| static unsigned int sdebug_guard = DEF_GUARD; |
| static int sdebug_host_max_queue; /* per host */ |
| static int sdebug_lowest_aligned = DEF_LOWEST_ALIGNED; |
| static int sdebug_max_luns = DEF_MAX_LUNS; |
| static int sdebug_max_queue = SDEBUG_CANQUEUE; /* per submit queue */ |
| static unsigned int sdebug_medium_error_start = OPT_MEDIUM_ERR_ADDR; |
| static int sdebug_medium_error_count = OPT_MEDIUM_ERR_NUM; |
| static int sdebug_ndelay = DEF_NDELAY; /* if > 0 then unit is nanoseconds */ |
| static int sdebug_no_lun_0 = DEF_NO_LUN_0; |
| static int sdebug_no_uld; |
| static int sdebug_num_parts = DEF_NUM_PARTS; |
| static int sdebug_num_tgts = DEF_NUM_TGTS; /* targets per host */ |
| static int sdebug_opt_blks = DEF_OPT_BLKS; |
| static int sdebug_opts = DEF_OPTS; |
| static int sdebug_physblk_exp = DEF_PHYSBLK_EXP; |
| static int sdebug_opt_xferlen_exp = DEF_OPT_XFERLEN_EXP; |
| static int sdebug_ptype = DEF_PTYPE; /* SCSI peripheral device type */ |
| static int sdebug_scsi_level = DEF_SCSI_LEVEL; |
| static int sdebug_sector_size = DEF_SECTOR_SIZE; |
| static int sdeb_tur_ms_to_ready = DEF_TUR_MS_TO_READY; |
| static int sdebug_virtual_gb = DEF_VIRTUAL_GB; |
| static int sdebug_vpd_use_hostno = DEF_VPD_USE_HOSTNO; |
| static unsigned int sdebug_lbpu = DEF_LBPU; |
| static unsigned int sdebug_lbpws = DEF_LBPWS; |
| static unsigned int sdebug_lbpws10 = DEF_LBPWS10; |
| static unsigned int sdebug_lbprz = DEF_LBPRZ; |
| static unsigned int sdebug_unmap_alignment = DEF_UNMAP_ALIGNMENT; |
| static unsigned int sdebug_unmap_granularity = DEF_UNMAP_GRANULARITY; |
| static unsigned int sdebug_unmap_max_blocks = DEF_UNMAP_MAX_BLOCKS; |
| static unsigned int sdebug_unmap_max_desc = DEF_UNMAP_MAX_DESC; |
| static unsigned int sdebug_write_same_length = DEF_WRITESAME_LENGTH; |
| static unsigned int sdebug_atomic_wr = DEF_ATOMIC_WR; |
| static unsigned int sdebug_atomic_wr_max_length = DEF_ATOMIC_WR_MAX_LENGTH; |
| static unsigned int sdebug_atomic_wr_align = DEF_ATOMIC_WR_ALIGN; |
| static unsigned int sdebug_atomic_wr_gran = DEF_ATOMIC_WR_GRAN; |
| static unsigned int sdebug_atomic_wr_max_length_bndry = |
| DEF_ATOMIC_WR_MAX_LENGTH_BNDRY; |
| static unsigned int sdebug_atomic_wr_max_bndry = DEF_ATOMIC_WR_MAX_BNDRY; |
| static int sdebug_uuid_ctl = DEF_UUID_CTL; |
| static bool sdebug_random = DEF_RANDOM; |
| static bool sdebug_per_host_store = DEF_PER_HOST_STORE; |
| static bool sdebug_removable = DEF_REMOVABLE; |
| static bool sdebug_clustering; |
| static bool sdebug_host_lock = DEF_HOST_LOCK; |
| static bool sdebug_strict = DEF_STRICT; |
| static bool sdebug_any_injecting_opt; |
| static bool sdebug_no_rwlock; |
| static bool sdebug_verbose; |
| static bool have_dif_prot; |
| static bool write_since_sync; |
| static bool sdebug_statistics = DEF_STATISTICS; |
| static bool sdebug_wp; |
| static bool sdebug_allow_restart; |
| static enum { |
| BLK_ZONED_NONE = 0, |
| BLK_ZONED_HA = 1, |
| BLK_ZONED_HM = 2, |
| } sdeb_zbc_model = BLK_ZONED_NONE; |
| static char *sdeb_zbc_model_s; |
| |
| enum sam_lun_addr_method {SAM_LUN_AM_PERIPHERAL = 0x0, |
| SAM_LUN_AM_FLAT = 0x1, |
| SAM_LUN_AM_LOGICAL_UNIT = 0x2, |
| SAM_LUN_AM_EXTENDED = 0x3}; |
| static enum sam_lun_addr_method sdebug_lun_am = SAM_LUN_AM_PERIPHERAL; |
| static int sdebug_lun_am_i = (int)SAM_LUN_AM_PERIPHERAL; |
| |
| static unsigned int sdebug_store_sectors; |
| static sector_t sdebug_capacity; /* in sectors */ |
| |
| /* old BIOS stuff, kernel may get rid of them but some mode sense pages |
| may still need them */ |
| static int sdebug_heads; /* heads per disk */ |
| static int sdebug_cylinders_per; /* cylinders per surface */ |
| static int sdebug_sectors_per; /* sectors per cylinder */ |
| |
| static LIST_HEAD(sdebug_host_list); |
| static DEFINE_MUTEX(sdebug_host_list_mutex); |
| |
| static struct xarray per_store_arr; |
| static struct xarray *per_store_ap = &per_store_arr; |
| static int sdeb_first_idx = -1; /* invalid index ==> none created */ |
| static int sdeb_most_recent_idx = -1; |
| static DEFINE_RWLOCK(sdeb_fake_rw_lck); /* need a RW lock when fake_rw=1 */ |
| |
| static unsigned long map_size; |
| static int num_aborts; |
| static int num_dev_resets; |
| static int num_target_resets; |
| static int num_bus_resets; |
| static int num_host_resets; |
| static int dix_writes; |
| static int dix_reads; |
| static int dif_errors; |
| |
| /* ZBC global data */ |
| static bool sdeb_zbc_in_use; /* true for host-aware and host-managed disks */ |
| static int sdeb_zbc_zone_cap_mb; |
| static int sdeb_zbc_zone_size_mb; |
| static int sdeb_zbc_max_open = DEF_ZBC_MAX_OPEN_ZONES; |
| static int sdeb_zbc_nr_conv = DEF_ZBC_NR_CONV_ZONES; |
| |
| static int submit_queues = DEF_SUBMIT_QUEUES; /* > 1 for multi-queue (mq) */ |
| static int poll_queues; /* iouring iopoll interface.*/ |
| |
| static atomic_long_t writes_by_group_number[64]; |
| |
| static char sdebug_proc_name[] = MY_NAME; |
| static const char *my_name = MY_NAME; |
| |
| static const struct bus_type pseudo_lld_bus; |
| |
| static struct device_driver sdebug_driverfs_driver = { |
| .name = sdebug_proc_name, |
| .bus = &pseudo_lld_bus, |
| }; |
| |
| static const int check_condition_result = |
| SAM_STAT_CHECK_CONDITION; |
| |
| static const int illegal_condition_result = |
| (DID_ABORT << 16) | SAM_STAT_CHECK_CONDITION; |
| |
| static const int device_qfull_result = |
| (DID_ABORT << 16) | SAM_STAT_TASK_SET_FULL; |
| |
| static const int condition_met_result = SAM_STAT_CONDITION_MET; |
| |
| static struct dentry *sdebug_debugfs_root; |
| static ASYNC_DOMAIN_EXCLUSIVE(sdebug_async_domain); |
| |
| static void sdebug_err_free(struct rcu_head *head) |
| { |
| struct sdebug_err_inject *inject = |
| container_of(head, typeof(*inject), rcu); |
| |
| kfree(inject); |
| } |
| |
| static void sdebug_err_add(struct scsi_device *sdev, struct sdebug_err_inject *new) |
| { |
| struct sdebug_dev_info *devip = (struct sdebug_dev_info *)sdev->hostdata; |
| struct sdebug_err_inject *err; |
| |
| spin_lock(&devip->list_lock); |
| list_for_each_entry_rcu(err, &devip->inject_err_list, list) { |
| if (err->type == new->type && err->cmd == new->cmd) { |
| list_del_rcu(&err->list); |
| call_rcu(&err->rcu, sdebug_err_free); |
| } |
| } |
| |
| list_add_tail_rcu(&new->list, &devip->inject_err_list); |
| spin_unlock(&devip->list_lock); |
| } |
| |
| static int sdebug_err_remove(struct scsi_device *sdev, const char *buf, size_t count) |
| { |
| struct sdebug_dev_info *devip = (struct sdebug_dev_info *)sdev->hostdata; |
| struct sdebug_err_inject *err; |
| int type; |
| unsigned char cmd; |
| |
| if (sscanf(buf, "- %d %hhx", &type, &cmd) != 2) { |
| kfree(buf); |
| return -EINVAL; |
| } |
| |
| spin_lock(&devip->list_lock); |
| list_for_each_entry_rcu(err, &devip->inject_err_list, list) { |
| if (err->type == type && err->cmd == cmd) { |
| list_del_rcu(&err->list); |
| call_rcu(&err->rcu, sdebug_err_free); |
| spin_unlock(&devip->list_lock); |
| kfree(buf); |
| return count; |
| } |
| } |
| spin_unlock(&devip->list_lock); |
| |
| kfree(buf); |
| return -EINVAL; |
| } |
| |
| static int sdebug_error_show(struct seq_file *m, void *p) |
| { |
| struct scsi_device *sdev = (struct scsi_device *)m->private; |
| struct sdebug_dev_info *devip = (struct sdebug_dev_info *)sdev->hostdata; |
| struct sdebug_err_inject *err; |
| |
| seq_puts(m, "Type\tCount\tCommand\n"); |
| |
| rcu_read_lock(); |
| list_for_each_entry_rcu(err, &devip->inject_err_list, list) { |
| switch (err->type) { |
| case ERR_TMOUT_CMD: |
| case ERR_ABORT_CMD_FAILED: |
| case ERR_LUN_RESET_FAILED: |
| seq_printf(m, "%d\t%d\t0x%x\n", err->type, err->cnt, |
| err->cmd); |
| break; |
| |
| case ERR_FAIL_QUEUE_CMD: |
| seq_printf(m, "%d\t%d\t0x%x\t0x%x\n", err->type, |
| err->cnt, err->cmd, err->queuecmd_ret); |
| break; |
| |
| case ERR_FAIL_CMD: |
| seq_printf(m, "%d\t%d\t0x%x\t0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n", |
| err->type, err->cnt, err->cmd, |
| err->host_byte, err->driver_byte, |
| err->status_byte, err->sense_key, |
| err->asc, err->asq); |
| break; |
| } |
| } |
| rcu_read_unlock(); |
| |
| return 0; |
| } |
| |
| static int sdebug_error_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, sdebug_error_show, inode->i_private); |
| } |
| |
| static ssize_t sdebug_error_write(struct file *file, const char __user *ubuf, |
| size_t count, loff_t *ppos) |
| { |
| char *buf; |
| unsigned int inject_type; |
| struct sdebug_err_inject *inject; |
| struct scsi_device *sdev = (struct scsi_device *)file->f_inode->i_private; |
| |
| buf = kzalloc(count + 1, GFP_KERNEL); |
| if (!buf) |
| return -ENOMEM; |
| |
| if (copy_from_user(buf, ubuf, count)) { |
| kfree(buf); |
| return -EFAULT; |
| } |
| |
| if (buf[0] == '-') |
| return sdebug_err_remove(sdev, buf, count); |
| |
| if (sscanf(buf, "%d", &inject_type) != 1) { |
| kfree(buf); |
| return -EINVAL; |
| } |
| |
| inject = kzalloc(sizeof(struct sdebug_err_inject), GFP_KERNEL); |
| if (!inject) { |
| kfree(buf); |
| return -ENOMEM; |
| } |
| |
| switch (inject_type) { |
| case ERR_TMOUT_CMD: |
| case ERR_ABORT_CMD_FAILED: |
| case ERR_LUN_RESET_FAILED: |
| if (sscanf(buf, "%d %d %hhx", &inject->type, &inject->cnt, |
| &inject->cmd) != 3) |
| goto out_error; |
| break; |
| |
| case ERR_FAIL_QUEUE_CMD: |
| if (sscanf(buf, "%d %d %hhx %x", &inject->type, &inject->cnt, |
| &inject->cmd, &inject->queuecmd_ret) != 4) |
| goto out_error; |
| break; |
| |
| case ERR_FAIL_CMD: |
| if (sscanf(buf, "%d %d %hhx %hhx %hhx %hhx %hhx %hhx %hhx", |
| &inject->type, &inject->cnt, &inject->cmd, |
| &inject->host_byte, &inject->driver_byte, |
| &inject->status_byte, &inject->sense_key, |
| &inject->asc, &inject->asq) != 9) |
| goto out_error; |
| break; |
| |
| default: |
| goto out_error; |
| break; |
| } |
| |
| kfree(buf); |
| sdebug_err_add(sdev, inject); |
| |
| return count; |
| |
| out_error: |
| kfree(buf); |
| kfree(inject); |
| return -EINVAL; |
| } |
| |
| static const struct file_operations sdebug_error_fops = { |
| .open = sdebug_error_open, |
| .read = seq_read, |
| .write = sdebug_error_write, |
| .release = single_release, |
| }; |
| |
| static int sdebug_target_reset_fail_show(struct seq_file *m, void *p) |
| { |
| struct scsi_target *starget = (struct scsi_target *)m->private; |
| struct sdebug_target_info *targetip = |
| (struct sdebug_target_info *)starget->hostdata; |
| |
| if (targetip) |
| seq_printf(m, "%c\n", targetip->reset_fail ? 'Y' : 'N'); |
| |
| return 0; |
| } |
| |
| static int sdebug_target_reset_fail_open(struct inode *inode, struct file *file) |
| { |
| return single_open(file, sdebug_target_reset_fail_show, inode->i_private); |
| } |
| |
| static ssize_t sdebug_target_reset_fail_write(struct file *file, |
| const char __user *ubuf, size_t count, loff_t *ppos) |
| { |
| int ret; |
| struct scsi_target *starget = |
| (struct scsi_target *)file->f_inode->i_private; |
| struct sdebug_target_info *targetip = |
| (struct sdebug_target_info *)starget->hostdata; |
| |
| if (targetip) { |
| ret = kstrtobool_from_user(ubuf, count, &targetip->reset_fail); |
| return ret < 0 ? ret : count; |
| } |
| return -ENODEV; |
| } |
| |
| static const struct file_operations sdebug_target_reset_fail_fops = { |
| .open = sdebug_target_reset_fail_open, |
| .read = seq_read, |
| .write = sdebug_target_reset_fail_write, |
| .release = single_release, |
| }; |
| |
| static int sdebug_target_alloc(struct scsi_target *starget) |
| { |
| struct sdebug_target_info *targetip; |
| |
| targetip = kzalloc(sizeof(struct sdebug_target_info), GFP_KERNEL); |
| if (!targetip) |
| return -ENOMEM; |
| |
| async_synchronize_full_domain(&sdebug_async_domain); |
| |
| targetip->debugfs_entry = debugfs_create_dir(dev_name(&starget->dev), |
| sdebug_debugfs_root); |
| |
| debugfs_create_file("fail_reset", 0600, targetip->debugfs_entry, starget, |
| &sdebug_target_reset_fail_fops); |
| |
| starget->hostdata = targetip; |
| |
| return 0; |
| } |
| |
| static void sdebug_tartget_cleanup_async(void *data, async_cookie_t cookie) |
| { |
| struct sdebug_target_info *targetip = data; |
| |
| debugfs_remove(targetip->debugfs_entry); |
| kfree(targetip); |
| } |
| |
| static void sdebug_target_destroy(struct scsi_target *starget) |
| { |
| struct sdebug_target_info *targetip; |
| |
| targetip = (struct sdebug_target_info *)starget->hostdata; |
| if (targetip) { |
| starget->hostdata = NULL; |
| async_schedule_domain(sdebug_tartget_cleanup_async, targetip, |
| &sdebug_async_domain); |
| } |
| } |
| |
| /* Only do the extra work involved in logical block provisioning if one or |
| * more of the lbpu, lbpws or lbpws10 parameters are given and we are doing |
| * real reads and writes (i.e. not skipping them for speed). |
| */ |
| static inline bool scsi_debug_lbp(void) |
| { |
| return 0 == sdebug_fake_rw && |
| (sdebug_lbpu || sdebug_lbpws || sdebug_lbpws10); |
| } |
| |
| static inline bool scsi_debug_atomic_write(void) |
| { |
| return sdebug_fake_rw == 0 && sdebug_atomic_wr; |
| } |
| |
| static void *lba2fake_store(struct sdeb_store_info *sip, |
| unsigned long long lba) |
| { |
| struct sdeb_store_info *lsip = sip; |
| |
| lba = do_div(lba, sdebug_store_sectors); |
| if (!sip || !sip->storep) { |
| WARN_ON_ONCE(true); |
| lsip = xa_load(per_store_ap, 0); /* should never be NULL */ |
| } |
| return lsip->storep + lba * sdebug_sector_size; |
| } |
| |
| static struct t10_pi_tuple *dif_store(struct sdeb_store_info *sip, |
| sector_t sector) |
| { |
| sector = sector_div(sector, sdebug_store_sectors); |
| |
| return sip->dif_storep + sector; |
| } |
| |
| static void sdebug_max_tgts_luns(void) |
| { |
| struct sdebug_host_info *sdbg_host; |
| struct Scsi_Host *hpnt; |
| |
| mutex_lock(&sdebug_host_list_mutex); |
| list_for_each_entry(sdbg_host, &sdebug_host_list, host_list) { |
| hpnt = sdbg_host->shost; |
| if ((hpnt->this_id >= 0) && |
| (sdebug_num_tgts > hpnt->this_id)) |
| hpnt->max_id = sdebug_num_tgts + 1; |
| else |
| hpnt->max_id = sdebug_num_tgts; |
| /* sdebug_max_luns; */ |
| hpnt->max_lun = SCSI_W_LUN_REPORT_LUNS + 1; |
| } |
| mutex_unlock(&sdebug_host_list_mutex); |
| } |
| |
| enum sdeb_cmd_data {SDEB_IN_DATA = 0, SDEB_IN_CDB = 1}; |
| |
| /* Set in_bit to -1 to indicate no bit position of invalid field */ |
| static void mk_sense_invalid_fld(struct scsi_cmnd *scp, |
| enum sdeb_cmd_data c_d, |
| int in_byte, int in_bit) |
| { |
| unsigned char *sbuff; |
| u8 sks[4]; |
| int sl, asc; |
| |
| sbuff = scp->sense_buffer; |
| if (!sbuff) { |
| sdev_printk(KERN_ERR, scp->device, |
| "%s: sense_buffer is NULL\n", __func__); |
| return; |
| } |
| asc = c_d ? INVALID_FIELD_IN_CDB : INVALID_FIELD_IN_PARAM_LIST; |
| memset(sbuff, 0, SCSI_SENSE_BUFFERSIZE); |
| scsi_build_sense(scp, sdebug_dsense, ILLEGAL_REQUEST, asc, 0); |
| memset(sks, 0, sizeof(sks)); |
| sks[0] = 0x80; |
| if (c_d) |
| sks[0] |= 0x40; |
| if (in_bit >= 0) { |
| sks[0] |= 0x8; |
| sks[0] |= 0x7 & in_bit; |
| } |
| put_unaligned_be16(in_byte, sks + 1); |
| if (sdebug_dsense) { |
| sl = sbuff[7] + 8; |
| sbuff[7] = sl; |
| sbuff[sl] = 0x2; |
| sbuff[sl + 1] = 0x6; |
| memcpy(sbuff + sl + 4, sks, 3); |
| } else |
| memcpy(sbuff + 15, sks, 3); |
| if (sdebug_verbose) |
| sdev_printk(KERN_INFO, scp->device, "%s: [sense_key,asc,ascq" |
| "]: [0x5,0x%x,0x0] %c byte=%d, bit=%d\n", |
| my_name, asc, c_d ? 'C' : 'D', in_byte, in_bit); |
| } |
| |
| static void mk_sense_buffer(struct scsi_cmnd *scp, int key, int asc, int asq) |
| { |
| if (!scp->sense_buffer) { |
| sdev_printk(KERN_ERR, scp->device, |
| "%s: sense_buffer is NULL\n", __func__); |
| return; |
| } |
| memset(scp->sense_buffer, 0, SCSI_SENSE_BUFFERSIZE); |
| |
| scsi_build_sense(scp, sdebug_dsense, key, asc, asq); |
| |
| if (sdebug_verbose) |
| sdev_printk(KERN_INFO, scp->device, |
| "%s: [sense_key,asc,ascq]: [0x%x,0x%x,0x%x]\n", |
| my_name, key, asc, asq); |
| } |
| |
| static void mk_sense_invalid_opcode(struct scsi_cmnd *scp) |
| { |
| mk_sense_buffer(scp, ILLEGAL_REQUEST, INVALID_OPCODE, 0); |
| } |
| |
| static int scsi_debug_ioctl(struct scsi_device *dev, unsigned int cmd, |
| void __user *arg) |
| { |
| if (sdebug_verbose) { |
| if (0x1261 == cmd) |
| sdev_printk(KERN_INFO, dev, |
| "%s: BLKFLSBUF [0x1261]\n", __func__); |
| else if (0x5331 == cmd) |
| sdev_printk(KERN_INFO, dev, |
| "%s: CDROM_GET_CAPABILITY [0x5331]\n", |
| __func__); |
| else |
| sdev_printk(KERN_INFO, dev, "%s: cmd=0x%x\n", |
| __func__, cmd); |
| } |
| return -EINVAL; |
| /* return -ENOTTY; // correct return but upsets fdisk */ |
| } |
| |
| static void config_cdb_len(struct scsi_device *sdev) |
| { |
| switch (sdebug_cdb_len) { |
| case 6: /* suggest 6 byte READ, WRITE and MODE SENSE/SELECT */ |
| sdev->use_10_for_rw = false; |
| sdev->use_16_for_rw = false; |
| sdev->use_10_for_ms = false; |
| break; |
| case 10: /* suggest 10 byte RWs and 6 byte MODE SENSE/SELECT */ |
| sdev->use_10_for_rw = true; |
| sdev->use_16_for_rw = false; |
| sdev->use_10_for_ms = false; |
| break; |
| case 12: /* suggest 10 byte RWs and 10 byte MODE SENSE/SELECT */ |
| sdev->use_10_for_rw = true; |
| sdev->use_16_for_rw = false; |
| sdev->use_10_for_ms = true; |
| break; |
| case 16: |
| sdev->use_10_for_rw = false; |
| sdev->use_16_for_rw = true; |
| sdev->use_10_for_ms = true; |
| break; |
| case 32: /* No knobs to suggest this so same as 16 for now */ |
| sdev->use_10_for_rw = false; |
| sdev->use_16_for_rw = true; |
| sdev->use_10_for_ms = true; |
| break; |
| default: |
| pr_warn("unexpected cdb_len=%d, force to 10\n", |
| sdebug_cdb_len); |
| sdev->use_10_for_rw = true; |
| sdev->use_16_for_rw = false; |
| sdev->use_10_for_ms = false; |
| sdebug_cdb_len = 10; |
| break; |
| } |
| } |
| |
| static void all_config_cdb_len(void) |
| { |
| struct sdebug_host_info *sdbg_host; |
| struct Scsi_Host *shost; |
| struct scsi_device *sdev; |
| |
| mutex_lock(&sdebug_host_list_mutex); |
| list_for_each_entry(sdbg_host, &sdebug_host_list, host_list) { |
| shost = sdbg_host->shost; |
| shost_for_each_device(sdev, shost) { |
| config_cdb_len(sdev); |
| } |
| } |
| mutex_unlock(&sdebug_host_list_mutex); |
| } |
| |
| static void clear_luns_changed_on_target(struct sdebug_dev_info *devip) |
| { |
| struct sdebug_host_info *sdhp = devip->sdbg_host; |
| struct sdebug_dev_info *dp; |
| |
| list_for_each_entry(dp, &sdhp->dev_info_list, dev_list) { |
| if ((devip->sdbg_host == dp->sdbg_host) && |
| (devip->target == dp->target)) { |
| clear_bit(SDEBUG_UA_LUNS_CHANGED, dp->uas_bm); |
| } |
| } |
| } |
| |
| static int make_ua(struct scsi_cmnd *scp, struct sdebug_dev_info *devip) |
| { |
| int k; |
| |
| k = find_first_bit(devip->uas_bm, SDEBUG_NUM_UAS); |
| if (k != SDEBUG_NUM_UAS) { |
| const char *cp = NULL; |
| |
| switch (k) { |
| case SDEBUG_UA_POR: |
| mk_sense_buffer(scp, UNIT_ATTENTION, UA_RESET_ASC, |
| POWER_ON_RESET_ASCQ); |
| if (sdebug_verbose) |
| cp = "power on reset"; |
| break; |
| case SDEBUG_UA_POOCCUR: |
| mk_sense_buffer(scp, UNIT_ATTENTION, UA_RESET_ASC, |
| POWER_ON_OCCURRED_ASCQ); |
| if (sdebug_verbose) |
| cp = "power on occurred"; |
| break; |
| case SDEBUG_UA_BUS_RESET: |
| mk_sense_buffer(scp, UNIT_ATTENTION, UA_RESET_ASC, |
| BUS_RESET_ASCQ); |
| if (sdebug_verbose) |
| cp = "bus reset"; |
| break; |
| case SDEBUG_UA_MODE_CHANGED: |
| mk_sense_buffer(scp, UNIT_ATTENTION, UA_CHANGED_ASC, |
| MODE_CHANGED_ASCQ); |
| if (sdebug_verbose) |
| cp = "mode parameters changed"; |
| break; |
| case SDEBUG_UA_CAPACITY_CHANGED: |
| mk_sense_buffer(scp, UNIT_ATTENTION, UA_CHANGED_ASC, |
| CAPACITY_CHANGED_ASCQ); |
| if (sdebug_verbose) |
| cp = "capacity data changed"; |
| break; |
| case SDEBUG_UA_MICROCODE_CHANGED: |
| mk_sense_buffer(scp, UNIT_ATTENTION, |
| TARGET_CHANGED_ASC, |
| MICROCODE_CHANGED_ASCQ); |
| if (sdebug_verbose) |
| cp = "microcode has been changed"; |
| break; |
| case SDEBUG_UA_MICROCODE_CHANGED_WO_RESET: |
| mk_sense_buffer(scp, UNIT_ATTENTION, |
| TARGET_CHANGED_ASC, |
| MICROCODE_CHANGED_WO_RESET_ASCQ); |
| if (sdebug_verbose) |
| cp = "microcode has been changed without reset"; |
| break; |
| case SDEBUG_UA_LUNS_CHANGED: |
| /* |
| * SPC-3 behavior is to report a UNIT ATTENTION with |
| * ASC/ASCQ REPORTED LUNS DATA HAS CHANGED on every LUN |
| * on the target, until a REPORT LUNS command is |
| * received. SPC-4 behavior is to report it only once. |
| * NOTE: sdebug_scsi_level does not use the same |
| * values as struct scsi_device->scsi_level. |
| */ |
| if (sdebug_scsi_level >= 6) /* SPC-4 and above */ |
| clear_luns_changed_on_target(devip); |
| mk_sense_buffer(scp, UNIT_ATTENTION, |
| TARGET_CHANGED_ASC, |
| LUNS_CHANGED_ASCQ); |
| if (sdebug_verbose) |
| cp = "reported luns data has changed"; |
| break; |
| default: |
| pr_warn("unexpected unit attention code=%d\n", k); |
| if (sdebug_verbose) |
| cp = "unknown"; |
| break; |
| } |
| clear_bit(k, devip->uas_bm); |
| if (sdebug_verbose) |
| sdev_printk(KERN_INFO, scp->device, |
| "%s reports: Unit attention: %s\n", |
| my_name, cp); |
| return check_condition_result; |
| } |
| return 0; |
| } |
| |
| /* Build SCSI "data-in" buffer. Returns 0 if ok else (DID_ERROR << 16). */ |
| static int fill_from_dev_buffer(struct scsi_cmnd *scp, unsigned char *arr, |
| int arr_len) |
| { |
| int act_len; |
| struct scsi_data_buffer *sdb = &scp->sdb; |
| |
| if (!sdb->length) |
| return 0; |
| if (scp->sc_data_direction != DMA_FROM_DEVICE) |
| return DID_ERROR << 16; |
| |
| act_len = sg_copy_from_buffer(sdb->table.sgl, sdb->table.nents, |
| arr, arr_len); |
| scsi_set_resid(scp, scsi_bufflen(scp) - act_len); |
| |
| return 0; |
| } |
| |
| /* Partial build of SCSI "data-in" buffer. Returns 0 if ok else |
| * (DID_ERROR << 16). Can write to offset in data-in buffer. If multiple |
| * calls, not required to write in ascending offset order. Assumes resid |
| * set to scsi_bufflen() prior to any calls. |
| */ |
| static int p_fill_from_dev_buffer(struct scsi_cmnd *scp, const void *arr, |
| int arr_len, unsigned int off_dst) |
| { |
| unsigned int act_len, n; |
| struct scsi_data_buffer *sdb = &scp->sdb; |
| off_t skip = off_dst; |
| |
| if (sdb->length <= off_dst) |
| return 0; |
| if (scp->sc_data_direction != DMA_FROM_DEVICE) |
| return DID_ERROR << 16; |
| |
| act_len = sg_pcopy_from_buffer(sdb->table.sgl, sdb->table.nents, |
| arr, arr_len, skip); |
| pr_debug("%s: off_dst=%u, scsi_bufflen=%u, act_len=%u, resid=%d\n", |
| __func__, off_dst, scsi_bufflen(scp), act_len, |
| scsi_get_resid(scp)); |
| n = scsi_bufflen(scp) - (off_dst + act_len); |
| scsi_set_resid(scp, min_t(u32, scsi_get_resid(scp), n)); |
| return 0; |
| } |
| |
| /* Fetches from SCSI "data-out" buffer. Returns number of bytes fetched into |
| * 'arr' or -1 if error. |
| */ |
| static int fetch_to_dev_buffer(struct scsi_cmnd *scp, unsigned char *arr, |
| int arr_len) |
| { |
| if (!scsi_bufflen(scp)) |
| return 0; |
| if (scp->sc_data_direction != DMA_TO_DEVICE) |
| return -1; |
| |
| return scsi_sg_copy_to_buffer(scp, arr, arr_len); |
| } |
| |
| |
| static char sdebug_inq_vendor_id[9] = "Linux "; |
| static char sdebug_inq_product_id[17] = "scsi_debug "; |
| static char sdebug_inq_product_rev[5] = SDEBUG_VERSION; |
| /* Use some locally assigned NAAs for SAS addresses. */ |
| static const u64 naa3_comp_a = 0x3222222000000000ULL; |
| static const u64 naa3_comp_b = 0x3333333000000000ULL; |
| static const u64 naa3_comp_c = 0x3111111000000000ULL; |
| |
| /* Device identification VPD page. Returns number of bytes placed in arr */ |
| static int inquiry_vpd_83(unsigned char *arr, int port_group_id, |
| int target_dev_id, int dev_id_num, |
| const char *dev_id_str, int dev_id_str_len, |
| const uuid_t *lu_name) |
| { |
| int num, port_a; |
| char b[32]; |
| |
| port_a = target_dev_id + 1; |
| /* T10 vendor identifier field format (faked) */ |
| arr[0] = 0x2; /* ASCII */ |
| arr[1] = 0x1; |
| arr[2] = 0x0; |
| memcpy(&arr[4], sdebug_inq_vendor_id, 8); |
| memcpy(&arr[12], sdebug_inq_product_id, 16); |
| memcpy(&arr[28], dev_id_str, dev_id_str_len); |
| num = 8 + 16 + dev_id_str_len; |
| arr[3] = num; |
| num += 4; |
| if (dev_id_num >= 0) { |
| if (sdebug_uuid_ctl) { |
| /* Locally assigned UUID */ |
| arr[num++] = 0x1; /* binary (not necessarily sas) */ |
| arr[num++] = 0xa; /* PIV=0, lu, naa */ |
| arr[num++] = 0x0; |
| arr[num++] = 0x12; |
| arr[num++] = 0x10; /* uuid type=1, locally assigned */ |
| arr[num++] = 0x0; |
| memcpy(arr + num, lu_name, 16); |
| num += 16; |
| } else { |
| /* NAA-3, Logical unit identifier (binary) */ |
| arr[num++] = 0x1; /* binary (not necessarily sas) */ |
| arr[num++] = 0x3; /* PIV=0, lu, naa */ |
| arr[num++] = 0x0; |
| arr[num++] = 0x8; |
| put_unaligned_be64(naa3_comp_b + dev_id_num, arr + num); |
| num += 8; |
| } |
| /* Target relative port number */ |
| arr[num++] = 0x61; /* proto=sas, binary */ |
| arr[num++] = 0x94; /* PIV=1, target port, rel port */ |
| arr[num++] = 0x0; /* reserved */ |
| arr[num++] = 0x4; /* length */ |
| arr[num++] = 0x0; /* reserved */ |
| arr[num++] = 0x0; /* reserved */ |
| arr[num++] = 0x0; |
| arr[num++] = 0x1; /* relative port A */ |
| } |
| /* NAA-3, Target port identifier */ |
| arr[num++] = 0x61; /* proto=sas, binary */ |
| arr[num++] = 0x93; /* piv=1, target port, naa */ |
| arr[num++] = 0x0; |
| arr[num++] = 0x8; |
| put_unaligned_be64(naa3_comp_a + port_a, arr + num); |
| num += 8; |
| /* NAA-3, Target port group identifier */ |
| arr[num++] = 0x61; /* proto=sas, binary */ |
| arr[num++] = 0x95; /* piv=1, target port group id */ |
| arr[num++] = 0x0; |
| arr[num++] = 0x4; |
| arr[num++] = 0; |
| arr[num++] = 0; |
| put_unaligned_be16(port_group_id, arr + num); |
| num += 2; |
| /* NAA-3, Target device identifier */ |
| arr[num++] = 0x61; /* proto=sas, binary */ |
| arr[num++] = 0xa3; /* piv=1, target device, naa */ |
| arr[num++] = 0x0; |
| arr[num++] = 0x8; |
| put_unaligned_be64(naa3_comp_a + target_dev_id, arr + num); |
| num += 8; |
| /* SCSI name string: Target device identifier */ |
| arr[num++] = 0x63; /* proto=sas, UTF-8 */ |
| arr[num++] = 0xa8; /* piv=1, target device, SCSI name string */ |
| arr[num++] = 0x0; |
| arr[num++] = 24; |
| memcpy(arr + num, "naa.32222220", 12); |
| num += 12; |
| snprintf(b, sizeof(b), "%08X", target_dev_id); |
| memcpy(arr + num, b, 8); |
| num += 8; |
| memset(arr + num, 0, 4); |
| num += 4; |
| return num; |
| } |
| |
| static unsigned char vpd84_data[] = { |
| /* from 4th byte */ 0x22,0x22,0x22,0x0,0xbb,0x0, |
| 0x22,0x22,0x22,0x0,0xbb,0x1, |
| 0x22,0x22,0x22,0x0,0xbb,0x2, |
| }; |
| |
| /* Software interface identification VPD page */ |
| static int inquiry_vpd_84(unsigned char *arr) |
| { |
| memcpy(arr, vpd84_data, sizeof(vpd84_data)); |
| return sizeof(vpd84_data); |
| } |
| |
| /* Management network addresses VPD page */ |
| static int inquiry_vpd_85(unsigned char *arr) |
| { |
| int num = 0; |
| const char *na1 = "https://www.kernel.org/config"; |
| const char *na2 = "http://www.kernel.org/log"; |
| int plen, olen; |
| |
| arr[num++] = 0x1; /* lu, storage config */ |
| arr[num++] = 0x0; /* reserved */ |
| arr[num++] = 0x0; |
| olen = strlen(na1); |
| plen = olen + 1; |
| if (plen % 4) |
| plen = ((plen / 4) + 1) * 4; |
| arr[num++] = plen; /* length, null termianted, padded */ |
| memcpy(arr + num, na1, olen); |
| memset(arr + num + olen, 0, plen - olen); |
| num += plen; |
| |
| arr[num++] = 0x4; /* lu, logging */ |
| arr[num++] = 0x0; /* reserved */ |
| arr[num++] = 0x0; |
| olen = strlen(na2); |
| plen = olen + 1; |
| if (plen % 4) |
| plen = ((plen / 4) + 1) * 4; |
| arr[num++] = plen; /* length, null terminated, padded */ |
| memcpy(arr + num, na2, olen); |
| memset(arr + num + olen, 0, plen - olen); |
| num += plen; |
| |
| return num; |
| } |
| |
| /* SCSI ports VPD page */ |
| static int inquiry_vpd_88(unsigned char *arr, int target_dev_id) |
| { |
| int num = 0; |
| int port_a, port_b; |
| |
| port_a = target_dev_id + 1; |
| port_b = port_a + 1; |
| arr[num++] = 0x0; /* reserved */ |
| arr[num++] = 0x0; /* reserved */ |
| arr[num++] = 0x0; |
| arr[num++] = 0x1; /* relative port 1 (primary) */ |
| memset(arr + num, 0, 6); |
| num += 6; |
| arr[num++] = 0x0; |
| arr[num++] = 12; /* length tp descriptor */ |
| /* naa-5 target port identifier (A) */ |
| arr[num++] = 0x61; /* proto=sas, binary */ |
| arr[num++] = 0x93; /* PIV=1, target port, NAA */ |
| arr[num++] = 0x0; /* reserved */ |
| arr[num++] = 0x8; /* length */ |
| put_unaligned_be64(naa3_comp_a + port_a, arr + num); |
| num += 8; |
| arr[num++] = 0x0; /* reserved */ |
| arr[num++] = 0x0; /* reserved */ |
| arr[num++] = 0x0; |
| arr[num++] = 0x2; /* relative port 2 (secondary) */ |
| memset(arr + num, 0, 6); |
| num += 6; |
| arr[num++] = 0x0; |
| arr[num++] = 12; /* length tp descriptor */ |
| /* naa-5 target port identifier (B) */ |
| arr[num++] = 0x61; /* proto=sas, binary */ |
| arr[num++] = 0x93; /* PIV=1, target port, NAA */ |
| arr[num++] = 0x0; /* reserved */ |
| arr[num++] = 0x8; /* length */ |
| put_unaligned_be64(naa3_comp_a + port_b, arr + num); |
| num += 8; |
| |
| return num; |
| } |
| |
| |
| static unsigned char vpd89_data[] = { |
| /* from 4th byte */ 0,0,0,0, |
| 'l','i','n','u','x',' ',' ',' ', |
| 'S','A','T',' ','s','c','s','i','_','d','e','b','u','g',' ',' ', |
| '1','2','3','4', |
| 0x34,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0, |
| 0xec,0,0,0, |
| 0x5a,0xc,0xff,0x3f,0x37,0xc8,0x10,0,0,0,0,0,0x3f,0,0,0, |
| 0,0,0,0,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x20,0x20,0x20,0x20, |
| 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0,0,0,0x40,0x4,0,0x2e,0x33, |
| 0x38,0x31,0x20,0x20,0x20,0x20,0x54,0x53,0x38,0x33,0x30,0x30,0x33,0x31, |
| 0x53,0x41, |
| 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, |
| 0x20,0x20, |
| 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, |
| 0x10,0x80, |
| 0,0,0,0x2f,0,0,0,0x2,0,0x2,0x7,0,0xff,0xff,0x1,0, |
| 0x3f,0,0xc1,0xff,0x3e,0,0x10,0x1,0xb0,0xf8,0x50,0x9,0,0,0x7,0, |
| 0x3,0,0x78,0,0x78,0,0xf0,0,0x78,0,0,0,0,0,0,0, |
| 0,0,0,0,0,0,0,0,0x2,0,0,0,0,0,0,0, |
| 0x7e,0,0x1b,0,0x6b,0x34,0x1,0x7d,0x3,0x40,0x69,0x34,0x1,0x3c,0x3,0x40, |
| 0x7f,0x40,0,0,0,0,0xfe,0xfe,0,0,0,0,0,0xfe,0,0, |
| 0,0,0,0,0,0,0,0,0xb0,0xf8,0x50,0x9,0,0,0,0, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
| 0x1,0,0xb0,0xf8,0x50,0x9,0xb0,0xf8,0x50,0x9,0x20,0x20,0x2,0,0xb6,0x42, |
| 0,0x80,0x8a,0,0x6,0x3c,0xa,0x3c,0xff,0xff,0xc6,0x7,0,0x1,0,0x8, |
| 0xf0,0xf,0,0x10,0x2,0,0x30,0,0,0,0,0,0,0,0x6,0xfe, |
| 0,0,0x2,0,0x50,0,0x8a,0,0x4f,0x95,0,0,0x21,0,0xb,0, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0xa5,0x51, |
| }; |
| |
| /* ATA Information VPD page */ |
| static int inquiry_vpd_89(unsigned char *arr) |
| { |
| memcpy(arr, vpd89_data, sizeof(vpd89_data)); |
| return sizeof(vpd89_data); |
| } |
| |
| |
| static unsigned char vpdb0_data[] = { |
| /* from 4th byte */ 0,0,0,4, 0,0,0x4,0, 0,0,0,64, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
| 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, |
| }; |
| |
| /* Block limits VPD page (SBC-3) */ |
| static int inquiry_vpd_b0(unsigned char *arr) |
| { |
| unsigned int gran; |
| |
| memcpy(arr, vpdb0_data, sizeof(vpdb0_data)); |
| |
| /* Optimal transfer length granularity */ |
| if (sdebug_opt_xferlen_exp != 0 && |
| sdebug_physblk_exp < sdebug_opt_xferlen_exp) |
| gran = 1 << sdebug_opt_xferlen_exp; |
| else |
| gran = 1 << sdebug_physblk_exp; |
| put_unaligned_be16(gran, arr + 2); |
| |
| /* Maximum Transfer Length */ |
| if (sdebug_store_sectors > 0x400) |
| put_unaligned_be32(sdebug_store_sectors, arr + 4); |
| |
| /* Optimal Transfer Length */ |
| put_unaligned_be32(sdebug_opt_blks, &arr[8]); |
| |
| if (sdebug_lbpu) { |
| /* Maximum Unmap LBA Count */ |
| put_unaligned_be32(sdebug_unmap_max_blocks, &arr[16]); |
| |
| /* Maximum Unmap Block Descriptor Count */ |
| put_unaligned_be32(sdebug_unmap_max_desc, &arr[20]); |
| } |
| |
| /* Unmap Granularity Alignment */ |
| if (sdebug_unmap_alignment) { |
| put_unaligned_be32(sdebug_unmap_alignment, &arr[28]); |
| arr[28] |= 0x80; /* UGAVALID */ |
| } |
| |
| /* Optimal Unmap Granularity */ |
| put_unaligned_be32(sdebug_unmap_granularity, &arr[24]); |
| |
| /* Maximum WRITE SAME Length */ |
| put_unaligned_be64(sdebug_write_same_length, &arr[32]); |
| |
| if (sdebug_atomic_wr) { |
| put_unaligned_be32(sdebug_atomic_wr_max_length, &arr[40]); |
| put_unaligned_be32(sdebug_atomic_wr_align, &arr[44]); |
| put_unaligned_be32(sdebug_atomic_wr_gran, &arr[48]); |
| put_unaligned_be32(sdebug_atomic_wr_max_length_bndry, &arr[52]); |
| put_unaligned_be32(sdebug_atomic_wr_max_bndry, &arr[56]); |
| } |
| |
| return 0x3c; /* Mandatory page length for Logical Block Provisioning */ |
| } |
| |
| /* Block device characteristics VPD page (SBC-3) */ |
| static int inquiry_vpd_b1(struct sdebug_dev_info *devip, unsigned char *arr) |
| { |
| memset(arr, 0, 0x3c); |
| arr[0] = 0; |
| arr[1] = 1; /* non rotating medium (e.g. solid state) */ |
| arr[2] = 0; |
| arr[3] = 5; /* less than 1.8" */ |
| |
| return 0x3c; |
| } |
| |
| /* Logical block provisioning VPD page (SBC-4) */ |
| static int inquiry_vpd_b2(unsigned char *arr) |
| { |
| memset(arr, 0, 0x4); |
| arr[0] = 0; /* threshold exponent */ |
| if (sdebug_lbpu) |
| arr[1] = 1 << 7; |
| if (sdebug_lbpws) |
| arr[1] |= 1 << 6; |
| if (sdebug_lbpws10) |
| arr[1] |= 1 << 5; |
| if (sdebug_lbprz && scsi_debug_lbp()) |
| arr[1] |= (sdebug_lbprz & 0x7) << 2; /* sbc4r07 and later */ |
| /* anc_sup=0; dp=0 (no provisioning group descriptor) */ |
| /* minimum_percentage=0; provisioning_type=0 (unknown) */ |
| /* threshold_percentage=0 */ |
| return 0x4; |
| } |
| |
| /* Zoned block device characteristics VPD page (ZBC mandatory) */ |
| static int inquiry_vpd_b6(struct sdebug_dev_info *devip, unsigned char *arr) |
| { |
| memset(arr, 0, 0x3c); |
| arr[0] = 0x1; /* set URSWRZ (unrestricted read in seq. wr req zone) */ |
| /* |
| * Set Optimal number of open sequential write preferred zones and |
| * Optimal number of non-sequentially written sequential write |
| * preferred zones fields to 'not reported' (0xffffffff). Leave other |
| * fields set to zero, apart from Max. number of open swrz_s field. |
| */ |
| put_unaligned_be32(0xffffffff, &arr[4]); |
| put_unaligned_be32(0xffffffff, &arr[8]); |
| if (sdeb_zbc_model == BLK_ZONED_HM && devip->max_open) |
| put_unaligned_be32(devip->max_open, &arr[12]); |
| else |
| put_unaligned_be32(0xffffffff, &arr[12]); |
| if (devip->zcap < devip->zsize) { |
| arr[19] = ZBC_CONSTANT_ZONE_START_OFFSET; |
| put_unaligned_be64(devip->zsize, &arr[20]); |
| } else { |
| arr[19] = 0; |
| } |
| return 0x3c; |
| } |
| |
| #define SDEBUG_BLE_LEN_AFTER_B4 28 /* thus vpage 32 bytes long */ |
| |
| enum { MAXIMUM_NUMBER_OF_STREAMS = 6, PERMANENT_STREAM_COUNT = 5 }; |
| |
| /* Block limits extension VPD page (SBC-4) */ |
| static int inquiry_vpd_b7(unsigned char *arrb4) |
| { |
| memset(arrb4, 0, SDEBUG_BLE_LEN_AFTER_B4); |
| arrb4[1] = 1; /* Reduced stream control support (RSCS) */ |
| put_unaligned_be16(MAXIMUM_NUMBER_OF_STREAMS, &arrb4[2]); |
| return SDEBUG_BLE_LEN_AFTER_B4; |
| } |
| |
| #define SDEBUG_LONG_INQ_SZ 96 |
| #define SDEBUG_MAX_INQ_ARR_SZ 584 |
| |
| static int resp_inquiry(struct scsi_cmnd *scp, struct sdebug_dev_info *devip) |
| { |
| unsigned char pq_pdt; |
| unsigned char *arr; |
| unsigned char *cmd = scp->cmnd; |
| u32 alloc_len, n; |
| int ret; |
| bool have_wlun, is_disk, is_zbc, is_disk_zbc; |
| |
| alloc_len = get_unaligned_be16(cmd + 3); |
| arr = kzalloc(SDEBUG_MAX_INQ_ARR_SZ, GFP_ATOMIC); |
| if (! arr) |
| return DID_REQUEUE << 16; |
| is_disk = (sdebug_ptype == TYPE_DISK); |
| is_zbc = devip->zoned; |
| is_disk_zbc = (is_disk || is_zbc); |
| have_wlun = scsi_is_wlun(scp->device->lun); |
| if (have_wlun) |
| pq_pdt = TYPE_WLUN; /* present, wlun */ |
| else if (sdebug_no_lun_0 && (devip->lun == SDEBUG_LUN_0_VAL)) |
| pq_pdt = 0x7f; /* not present, PQ=3, PDT=0x1f */ |
| else |
| pq_pdt = (sdebug_ptype & 0x1f); |
| arr[0] = pq_pdt; |
| if (0x2 & cmd[1]) { /* CMDDT bit set */ |
| mk_sense_invalid_fld(scp, SDEB_IN_CDB, 1, 1); |
| kfree(arr); |
| return check_condition_result; |
| } else if (0x1 & cmd[1]) { /* EVPD bit set */ |
| int lu_id_num, port_group_id, target_dev_id; |
| u32 len; |
| char lu_id_str[6]; |
| int host_no = devip->sdbg_host->shost->host_no; |
| |
| arr[1] = cmd[2]; |
| port_group_id = (((host_no + 1) & 0x7f) << 8) + |
| (devip->channel & 0x7f); |
| if (sdebug_vpd_use_hostno == 0) |
| host_no = 0; |
| lu_id_num = have_wlun ? -1 : (((host_no + 1) * 2000) + |
| (devip->target * 1000) + devip->lun); |
| target_dev_id = ((host_no + 1) * 2000) + |
| (devip->target * 1000) - 3; |
| len = scnprintf(lu_id_str, 6, "%d", lu_id_num); |
| if (0 == cmd[2]) { /* supported vital product data pages */ |
| n = 4; |
| arr[n++] = 0x0; /* this page */ |
| arr[n++] = 0x80; /* unit serial number */ |
| arr[n++] = 0x83; /* device identification */ |
| arr[n++] = 0x84; /* software interface ident. */ |
| arr[n++] = 0x85; /* management network addresses */ |
| arr[n++] = 0x86; /* extended inquiry */ |
| arr[n++] = 0x87; /* mode page policy */ |
| arr[n++] = 0x88; /* SCSI ports */ |
| if (is_disk_zbc) { /* SBC or ZBC */ |
| arr[n++] = 0x89; /* ATA information */ |
| arr[n++] = 0xb0; /* Block limits */ |
| arr[n++] = 0xb1; /* Block characteristics */ |
| if (is_disk) |
| arr[n++] = 0xb2; /* LB Provisioning */ |
| if (is_zbc) |
| arr[n++] = 0xb6; /* ZB dev. char. */ |
| arr[n++] = 0xb7; /* Block limits extension */ |
| } |
| arr[3] = n - 4; /* number of supported VPD pages */ |
| } else if (0x80 == cmd[2]) { /* unit serial number */ |
| arr[3] = len; |
| memcpy(&arr[4], lu_id_str, len); |
| } else if (0x83 == cmd[2]) { /* device identification */ |
| arr[3] = inquiry_vpd_83(&arr[4], port_group_id, |
| target_dev_id, lu_id_num, |
| lu_id_str, len, |
| &devip->lu_name); |
| } else if (0x84 == cmd[2]) { /* Software interface ident. */ |
| arr[3] = inquiry_vpd_84(&arr[4]); |
| } else if (0x85 == cmd[2]) { /* Management network addresses */ |
| arr[3] = inquiry_vpd_85(&arr[4]); |
| } else if (0x86 == cmd[2]) { /* extended inquiry */ |
| arr[3] = 0x3c; /* number of following entries */ |
| if (sdebug_dif == T10_PI_TYPE3_PROTECTION) |
| arr[4] = 0x4; /* SPT: GRD_CHK:1 */ |
| else if (have_dif_prot) |
| arr[4] = 0x5; /* SPT: GRD_CHK:1, REF_CHK:1 */ |
| else |
| arr[4] = 0x0; /* no protection stuff */ |
| /* |
| * GROUP_SUP=1; HEADSUP=1 (HEAD OF QUEUE); ORDSUP=1 |
| * (ORDERED queuing); SIMPSUP=1 (SIMPLE queuing). |
| */ |
| arr[5] = 0x17; |
| } else if (0x87 == cmd[2]) { /* mode page policy */ |
| arr[3] = 0x8; /* number of following entries */ |
| arr[4] = 0x2; /* disconnect-reconnect mp */ |
| arr[6] = 0x80; /* mlus, shared */ |
| arr[8] = 0x18; /* protocol specific lu */ |
| arr[10] = 0x82; /* mlus, per initiator port */ |
| } else if (0x88 == cmd[2]) { /* SCSI Ports */ |
| arr[3] = inquiry_vpd_88(&arr[4], target_dev_id); |
| } else if (is_disk_zbc && 0x89 == cmd[2]) { /* ATA info */ |
| n = inquiry_vpd_89(&arr[4]); |
| put_unaligned_be16(n, arr + 2); |
| } else if (is_disk_zbc && 0xb0 == cmd[2]) { /* Block limits */ |
| arr[3] = inquiry_vpd_b0(&arr[4]); |
| } else if (is_disk_zbc && 0xb1 == cmd[2]) { /* Block char. */ |
| arr[3] = inquiry_vpd_b1(devip, &arr[4]); |
| } else if (is_disk && 0xb2 == cmd[2]) { /* LB Prov. */ |
| arr[3] = inquiry_vpd_b2(&arr[4]); |
| } else if (is_zbc && cmd[2] == 0xb6) { /* ZB dev. charact. */ |
| arr[3] = inquiry_vpd_b6(devip, &arr[4]); |
| } else if (cmd[2] == 0xb7) { /* block limits extension page */ |
| arr[3] = inquiry_vpd_b7(&arr[4]); |
| } else { |
| mk_sense_invalid_fld(scp, SDEB_IN_CDB, 2, -1); |
| kfree(arr); |
| return check_condition_result; |
| } |
| len = min_t(u32, get_unaligned_be16(arr + 2) + 4, alloc_len); |
| ret = fill_from_dev_buffer(scp, arr, |
| min_t(u32, len, SDEBUG_MAX_INQ_ARR_SZ)); |
| kfree(arr); |
| return ret; |
| } |
| /* drops through here for a standard inquiry */ |
| arr[1] = sdebug_removable ? 0x80 : 0; /* Removable disk */ |
| arr[2] = sdebug_scsi_level; |
| arr[3] = 2; /* response_data_format==2 */ |
| arr[4] = SDEBUG_LONG_INQ_SZ - 5; |
| arr[5] = (int)have_dif_prot; /* PROTECT bit */ |
| if (sdebug_vpd_use_hostno == 0) |
| arr[5] |= 0x10; /* claim: implicit TPGS */ |
| arr[6] = 0x10; /* claim: MultiP */ |
| /* arr[6] |= 0x40; ... claim: EncServ (enclosure services) */ |
| arr[7] = 0xa; /* claim: LINKED + CMDQUE */ |
| memcpy(&arr[8], sdebug_inq_vendor_id, 8); |
| memcpy(&arr[16], sdebug_inq_product_id, 16); |
| memcpy(&arr[32], sdebug_inq_product_rev, 4); |
| /* Use Vendor Specific area to place driver date in ASCII hex */ |
| memcpy(&arr[36], sdebug_version_date, 8); |
| /* version descriptors (2 bytes each) follow */ |
| put_unaligned_be16(0xc0, arr + 58); /* SAM-6 no version claimed */ |
| put_unaligned_be16(0x5c0, arr + 60); /* SPC-5 no version claimed */ |
| n = 62; |
| if (is_disk) { /* SBC-4 no version claimed */ |
| put_unaligned_be16(0x600, arr + n); |
| n += 2; |
| } else if (sdebug_ptype == TYPE_TAPE) { /* SSC-4 rev 3 */ |
| put_unaligned_be16(0x525, arr + n); |
| n += 2; |
| } else if (is_zbc) { /* ZBC BSR INCITS 536 revision 05 */ |
| put_unaligned_be16(0x624, arr + n); |
| n += 2; |
| } |
| put_unaligned_be16(0x2100, arr + n); /* SPL-4 no version claimed */ |
| ret = fill_from_dev_buffer(scp, arr, |
| min_t(u32, alloc_len, SDEBUG_LONG_INQ_SZ)); |
| kfree(arr); |
| return ret; |
| } |
| |
| /* See resp_iec_m_pg() for how this data is manipulated */ |
| static unsigned char iec_m_pg[] = {0x1c, 0xa, 0x08, 0, 0, 0, 0, 0, |
| 0, 0, 0x0, 0x0}; |
| |
| static int resp_requests(struct scsi_cmnd *scp, |
| struct sdebug_dev_info *devip) |
| { |
| unsigned char *cmd = scp->cmnd; |
| unsigned char arr[SCSI_SENSE_BUFFERSIZE]; /* assume >= 18 bytes */ |
| bool dsense = !!(cmd[1] & 1); |
| u32 alloc_len = cmd[4]; |
| u32 len = 18; |
| int stopped_state = atomic_read(&devip->stopped); |
| |
| memset(arr, 0, sizeof(arr)); |
| if (stopped_state > 0) { /* some "pollable" data [spc6r02: 5.12.2] */ |
| if (dsense) { |
| arr[0] = 0x72; |
| arr[1] = NOT_READY; |
| arr[2] = LOGICAL_UNIT_NOT_READY; |
| arr[3] = (stopped_state == 2) ? 0x1 : 0x2; |
| len = 8; |
| } else { |
| arr[0] = 0x70; |
| arr[2] = NOT_READY; /* NO_SENSE in sense_key */ |
| arr[7] = 0xa; /* 18 byte sense buffer */ |
| arr[12] = LOGICAL_UNIT_NOT_READY; |
| arr[13] = (stopped_state == 2) ? 0x1 : 0x2; |
| } |
| } else if ((iec_m_pg[2] & 0x4) && (6 == (iec_m_pg[3] & 0xf))) { |
| /* Information exceptions control mode page: TEST=1, MRIE=6 */ |
| if (dsense) { |
| arr[0] = 0x72; |
| arr[1] = 0x0; /* NO_SENSE in sense_key */ |
| arr[2] = THRESHOLD_EXCEEDED; |
| arr[3] = 0xff; /* Failure prediction(false) */ |
| len = 8; |
| } else { |
| arr[0] = 0x70; |
| arr[2] = 0x0; /* NO_SENSE in sense_key */ |
| arr[7] = 0xa; /* 18 byte sense buffer */ |
| arr[12] = THRESHOLD_EXCEEDED; |
| arr[13] = 0xff; /* Failure prediction(false) */ |
| } |
| } else { /* nothing to report */ |
| if (dsense) { |
| len = 8; |
| memset(arr, 0, len); |
| arr[0] = 0x72; |
| } else { |
| memset(arr, 0, len); |
| arr[0] = 0x70; |
| arr[7] = 0xa; |
| } |
| } |
| return fill_from_dev_buffer(scp, arr, min_t(u32, len, alloc_len)); |
| } |
| |
| static int resp_start_stop(struct scsi_cmnd *scp, struct sdebug_dev_info *devip) |
| { |
| unsigned char *cmd = scp->cmnd; |
| int power_cond, want_stop, stopped_state; |
| bool changing; |
| |
| power_cond = (cmd[4] & 0xf0) >> 4; |
| if (power_cond) { |
| mk_sense_invalid_fld(scp, SDEB_IN_CDB, 4, 7); |
| return check_condition_result; |
| } |
| want_stop = !(cmd[4] & 1); |
| stopped_state = atomic_read(&devip->stopped); |
| if (stopped_state == 2) { |
| ktime_t now_ts = ktime_get_boottime(); |
| |
| if (ktime_to_ns(now_ts) > ktime_to_ns(devip->create_ts)) { |
| u64 diff_ns = ktime_to_ns(ktime_sub(now_ts, devip->create_ts)); |
| |
| if (diff_ns >= ((u64)sdeb_tur_ms_to_ready * 1000000)) { |
| /* tur_ms_to_ready timer extinguished */ |
| atomic_set(&devip->stopped, 0); |
| stopped_state = 0; |
| } |
| } |
| if (stopped_state == 2) { |
| if (want_stop) { |
| stopped_state = 1; /* dummy up success */ |
| } else { /* Disallow tur_ms_to_ready delay to be overridden */ |
| mk_sense_invalid_fld(scp, SDEB_IN_CDB, 4, 0 /* START bit */); |
| return check_condition_result; |
| } |
| } |
| } |
| changing = (stopped_state != want_stop); |
| if (changing) |
| atomic_xchg(&devip->stopped, want_stop); |
| if (!changing || (cmd[1] & 0x1)) /* state unchanged or IMMED bit set in cdb */ |
| return SDEG_RES_IMMED_MASK; |
| else |
| return 0; |
| } |
| |
| static sector_t get_sdebug_capacity(void) |
| { |
| static const unsigned int gibibyte = 1073741824; |
| |
| if (sdebug_virtual_gb > 0) |
| return (sector_t)sdebug_virtual_gb * |
| (gibibyte / sdebug_sector_size); |
| else |
| return sdebug_store_sectors; |
| } |
| |
| #define SDEBUG_READCAP_ARR_SZ 8 |
| static int resp_readcap(struct scsi_cmnd *scp, |
| struct sdebug_dev_info *devip) |
| { |
| unsigned char arr[SDEBUG_READCAP_ARR_SZ]; |
| unsigned int capac; |
| |
| /* following just in case virtual_gb changed */ |
| sdebug_capacity = get_sdebug_capacity(); |
| memset(arr, 0, SDEBUG_READCAP_ARR_SZ); |
| if (sdebug_capacity < 0xffffffff) { |
| capac = (unsigned int)sdebug_capacity - 1; |
| put_unaligned_be32(capac, arr + 0); |
| } else |
| put_unaligned_be32(0xffffffff, arr + 0); |
| put_unaligned_be16(sdebug_sector_size, arr + 6); |
| return fill_from_dev_buffer(scp, arr, SDEBUG_READCAP_ARR_SZ); |
| } |
| |
| #define SDEBUG_READCAP16_ARR_SZ 32 |
| static int resp_readcap16(struct scsi_cmnd *scp, |
| struct sdebug_dev_info *devip) |
| { |
| unsigned char *cmd = scp->cmnd; |
| unsigned char arr[SDEBUG_READCAP16_ARR_SZ]; |
| u32 alloc_len; |
| |
| alloc_len = get_unaligned_be32(cmd + 10); |
| /* following just in case virtual_gb changed */ |
| sdebug_capacity = get_sdebug_capacity(); |
| memset(arr, 0, SDEBUG_READCAP16_ARR_SZ); |
| put_unaligned_be64((u64)(sdebug_capacity - 1), arr + 0); |
| put_unaligned_be32(sdebug_sector_size, arr + 8); |
| arr[13] = sdebug_physblk_exp & 0xf; |
| arr[14] = (sdebug_lowest_aligned >> 8) & 0x3f; |
| |
| if (scsi_debug_lbp()) { |
| arr[14] |= 0x80; /* LBPME */ |
| /* from sbc4r07, this LBPRZ field is 1 bit, but the LBPRZ in |
| * the LB Provisioning VPD page is 3 bits. Note that lbprz=2 |
| * in the wider field maps to 0 in this field. |
| */ |
| if (sdebug_lbprz & 1) /* precisely what the draft requires */ |
| arr[14] |= 0x40; |
| } |
| |
| /* |
| * Since the scsi_debug READ CAPACITY implementation always reports the |
| * total disk capacity, set RC BASIS = 1 for host-managed ZBC devices. |
| */ |
| if (devip->zoned) |
| arr[12] |= 1 << 4; |
| |
| arr[15] = sdebug_lowest_aligned & 0xff; |
| |
| if (have_dif_prot) { |
| arr[12] = (sdebug_dif - 1) << 1; /* P_TYPE */ |
| arr[12] |= 1; /* PROT_EN */ |
| } |
| |
| return fill_from_dev_buffer(scp, arr, |
| min_t(u32, alloc_len, SDEBUG_READCAP16_ARR_SZ)); |
| } |
| |
| #define SDEBUG_MAX_TGTPGS_ARR_SZ 1412 |
| |
| static int resp_report_tgtpgs(struct scsi_cmnd *scp, |
| struct sdebug_dev_info *devip) |
| { |
| unsigned char *cmd = scp->cmnd; |
| unsigned char *arr; |
| int host_no = devip->sdbg_host->shost->host_no; |
| int port_group_a, port_group_b, port_a, port_b; |
| u32 alen, n, rlen; |
| int ret; |
| |
| alen = get_unaligned_be32(cmd + 6); |
| arr = kzalloc(SDEBUG_MAX_TGTPGS_ARR_SZ, GFP_ATOMIC); |
| if (! arr) |
| return DID_REQUEUE << 16; |
| /* |
| * EVPD page 0x88 states we have two ports, one |
| * real and a fake port with no device connected. |
| * So we create two port groups with one port each |
| * and set the group with port B to unavailable. |
| */ |
| port_a = 0x1; /* relative port A */ |
| port_b = 0x2; /* relative port B */ |
| port_group_a = (((host_no + 1) & 0x7f) << 8) + |
| (devip->channel & 0x7f); |
| port_group_b = (((host_no + 1) & 0x7f) << 8) + |
| (devip->channel & 0x7f) + 0x80; |
| |
| /* |
| * The asymmetric access state is cycled according to the host_id. |
| */ |
| n = 4; |
| if (sdebug_vpd_use_hostno == 0) { |
| arr[n++] = host_no % 3; /* Asymm access state */ |
| arr[n++] = 0x0F; /* claim: all states are supported */ |
| } else { |
| arr[n++] = 0x0; /* Active/Optimized path */ |
| arr[n++] = 0x01; /* only support active/optimized paths */ |
| } |
| put_unaligned_be16(port_group_a, arr + n); |
| n += 2; |
| arr[n++] = 0; /* Reserved */ |
| arr[n++] = 0; /* Status code */ |
| arr[n++] = 0; /* Vendor unique */ |
| arr[n++] = 0x1; /* One port per group */ |
| arr[n++] = 0; /* Reserved */ |
| arr[n++] = 0; /* Reserved */ |
| put_unaligned_be16(port_a, arr + n); |
| n += 2; |
| arr[n++] = 3; /* Port unavailable */ |
| arr[n++] = 0x08; /* claim: only unavailalbe paths are supported */ |
| put_unaligned_be16(port_group_b, arr + n); |
| n += 2; |
| arr[n++] = 0; /* Reserved */ |
| arr[n++] = 0; /* Status code */ |
| arr[n++] = 0; /* Vendor unique */ |
| arr[n++] = 0x1; /* One port per group */ |
| arr[n++] = 0; /* Reserved */ |
| arr[n++] = 0; /* Reserved */ |
| put_unaligned_be16(port_b, arr + n); |
| n += 2; |
| |
| rlen = n - 4; |
| put_unaligned_be32(rlen, arr + 0); |
| |
| /* |
| * Return the smallest value of either |
| * - The allocated length |
| * - The constructed command length |
| * - The maximum array size |
| */ |
| rlen = min(alen, n); |
| ret = fill_from_dev_buffer(scp, arr, |
| min_t(u32, rlen, SDEBUG_MAX_TGTPGS_ARR_SZ)); |
| kfree(arr); |
| return ret; |
| } |
| |
| static int resp_rsup_opcodes(struct scsi_cmnd *scp, |
| struct sdebug_dev_info *devip) |
| { |
| bool rctd; |
| u8 reporting_opts, req_opcode, sdeb_i, supp; |
| u16 req_sa, u; |
| u32 alloc_len, a_len; |
| int k, offset, len, errsts, count, bump, na; |
| const struct opcode_info_t *oip; |
| const struct opcode_info_t *r_oip; |
| u8 *arr; |
| u8 *cmd = scp->cmnd; |
| |
| rctd = !!(cmd[2] & 0x80); |
| reporting_opts = cmd[2] & 0x7; |
| req_opcode = cmd[3]; |
| req_sa = get_unaligned_be16(cmd + 4); |
| alloc_len = get_unaligned_be32(cmd + 6); |
| if (alloc_len < 4 || alloc_len > 0xffff) { |
| mk_sense_invalid_fld(scp, SDEB_IN_CDB, 6, -1); |
| return check_condition_result; |
| } |
| if (alloc_len > 8192) |
| a_len = 8192; |
| else |
| a_len = alloc_len; |
| arr = kzalloc((a_len < 256) ? 320 : a_len + 64, GFP_ATOMIC); |
| if (NULL == arr) { |
| mk_sense_buffer(scp, ILLEGAL_REQUEST, INSUFF_RES_ASC, |
| INSUFF_RES_ASCQ); |
| return check_condition_result; |
| } |
| switch (reporting_opts) { |
| case 0: /* all commands */ |
| /* count number of commands */ |
| for (count = 0, oip = opcode_info_arr; |
| oip->num_attached != 0xff; ++oip) { |
| if (F_INV_OP & oip->flags) |
| continue; |
| count += (oip->num_attached + 1); |
| } |
| bump = rctd ? 20 : 8; |
| put_unaligned_be32(count * bump, arr); |
| for (offset = 4, oip = opcode_info_arr; |
| oip->num_attached != 0xff && offset < a_len; ++oip) { |
| if (F_INV_OP & oip->flags) |
| continue; |
| na = oip->num_attached; |
| arr[offset] = oip->opcode; |
| put_unaligned_be16(oip->sa, arr + offset + 2); |
| if (rctd) |
| arr[offset + 5] |= 0x2; |
| if (FF_SA & oip->flags) |
| arr[offset + 5] |= 0x1; |
| put_unaligned_be16(oip->len_mask[0], arr + offset + 6); |
| if (rctd) |
| put_unaligned_be16(0xa, arr + offset + 8); |
| r_oip = oip; |
| for (k = 0, oip = oip->arrp; k < na; ++k, ++oip) { |
| if (F_INV_OP & oip->flags) |
| continue; |
| offset += bump; |
| arr[offset] = oip->opcode; |
| put_unaligned_be16(oip->sa, arr + offset + 2); |
| if (rctd) |
| arr[offset + 5] |= 0x2; |
| if (FF_SA & oip->flags) |
| arr[offset + 5] |= 0x1; |
| put_unaligned_be16(oip->len_mask[0], |
| arr + offset + 6); |
| if (rctd) |
| put_unaligned_be16(0xa, |
| arr + offset + 8); |
| } |
| oip = r_oip; |
| offset += bump; |
| } |
| break; |
| case 1: /* one command: opcode only */ |
| case 2: /* one command: opcode plus service action */ |
| case 3: /* one command: if sa==0 then opcode only else opcode+sa */ |
| sdeb_i = opcode_ind_arr[req_opcode]; |
| oip = &opcode_info_arr[sdeb_i]; |
| if (F_INV_OP & oip->flags) { |
| supp = 1; |
| offset = 4; |
| } else { |
| if (1 == reporting_opts) { |
| if (FF_SA & oip->flags) { |
| mk_sense_invalid_fld(scp, SDEB_IN_CDB, |
| 2, 2); |
| kfree(arr); |
| return check_condition_result; |
| } |
| req_sa = 0; |
| } else if (2 == reporting_opts && |
| 0 == (FF_SA & oip->flags)) { |
| mk_sense_invalid_fld(scp, SDEB_IN_CDB, 4, -1); |
| kfree(arr); /* point at requested sa */ |
| return check_condition_result; |
| } |
| if (0 == (FF_SA & oip->flags) && |
| req_opcode == oip->opcode) |
| supp = 3; |
| else if (0 == (FF_SA & oip->flags)) { |
| na = oip->num_attached; |
| for (k = 0, oip = oip->arrp; k < na; |
| ++k, ++oip) { |
| if (req_opcode == oip->opcode) |
| break; |
| } |
| supp = (k >= na) ? 1 : 3; |
| } else if (req_sa != oip->sa) { |
| na = oip->num_attached; |
| for (k = 0, oip = oip->arrp; k < na; |
| ++k, ++oip) { |
| if (req_sa == oip->sa) |
| break; |
| } |
| supp = (k >= na) ? 1 : 3; |
| } else |
| supp = 3; |
| if (3 == supp) { |
| u = oip->len_mask[0]; |
| put_unaligned_be16(u, arr + 2); |
| arr[4] = oip->opcode; |
| for (k = 1; k < u; ++k) |
| arr[4 + k] = (k < 16) ? |
| oip->len_mask[k] : 0xff; |
| offset = 4 + u; |
| } else |
| offset = 4; |
| } |
| arr[1] = (rctd ? 0x80 : 0) | supp; |
| if (rctd) { |
| put_unaligned_be16(0xa, arr + offset); |
| offset += 12; |
| } |
| break; |
| default: |
| mk_sense_invalid_fld(scp, SDEB_IN_CDB, 2, 2); |
| kfree(arr); |
| return check_condition_result; |
| } |
| offset = (offset < a_len) ? offset : a_len; |
| len = (offset < alloc_len) ? offset : alloc_len; |
| errsts = fill_from_dev_buffer(scp, arr, len); |
| kfree(arr); |
| return errsts; |
| } |
| |
| static int resp_rsup_tmfs(struct scsi_cmnd *scp, |
| struct sdebug_dev_info *devip) |
| { |
| bool repd; |
| u32 alloc_len, len; |
| u8 arr[16]; |
| u8 *cmd = scp->cmnd; |
| |
| memset(arr, 0, sizeof(arr)); |
| repd = !!(cmd[2] & 0x80); |
| alloc_len = get_unaligned_be32(cmd + 6); |
| if |