From: Vladislav Bolkhovitin on 13 Apr 2010 09:20 This patch contains SCST vdisk dev handlers. This dev handler allows to create virtual disks and CDROMs from files on file system. Signed-off-by: Vladislav Bolkhovitin <vst(a)vlnb.net> --- scst_vdisk.c | 3813 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3813 insertions(+) diff -uprN orig/linux-2.6.33/drivers/scst/dev_handlers/scst_vdisk.c linux-2.6.33/drivers/scst/dev_handlers/scst_vdisk.c --- orig/linux-2.6.33/drivers/scst/dev_handlers/scst_vdisk.c +++ linux-2.6.33/drivers/scst/dev_handlers/scst_vdisk.c @@ -0,0 +1,3813 @@ +/* + * scst_vdisk.c + * + * Copyright (C) 2004 - 2010 Vladislav Bolkhovitin <vst(a)vlnb.net> + * Copyright (C) 2004 - 2005 Leonid Stoljar + * Copyright (C) 2007 Ming Zhang <blackmagic02881 at gmail dot com> + * Copyright (C) 2007 Ross Walker <rswwalker at hotmail dot com> + * Copyright (C) 2007 - 2010 ID7 Ltd. + * + * SCSI disk (type 0) and CDROM (type 5) dev handler using files + * on file systems or block devices (VDISK) + * + * 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/file.h> +#include <linux/fs.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/unistd.h> +#include <linux/smp_lock.h> +#include <linux/spinlock.h> +#include <linux/init.h> +#include <linux/uio.h> +#include <linux/list.h> +#include <linux/ctype.h> +#include <linux/writeback.h> +#include <linux/vmalloc.h> +#include <asm/atomic.h> +#include <linux/kthread.h> +#include <linux/sched.h> +#include <linux/version.h> +#include <asm/div64.h> + +#define LOG_PREFIX "dev_vdisk" + +#include "scst.h" + +#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) + +#define TRACE_ORDER 0x80000000 + +static struct scst_trace_log vdisk_local_trace_tbl[] = { + { TRACE_ORDER, "order" }, + { 0, NULL } +}; +#define trace_log_tbl vdisk_local_trace_tbl + +#define VDISK_TRACE_TLB_HELP ", order" + +#endif + +#include "scst_dev_handler.h" + +/* 8 byte ASCII Vendor */ +#define SCST_FIO_VENDOR "SCST_FIO" +#define SCST_BIO_VENDOR "SCST_BIO" +/* 4 byte ASCII Product Revision Level - left aligned */ +#define SCST_FIO_REV " 200" + +#define MAX_USN_LEN (20+1) /* For '\0' */ + +#define INQ_BUF_SZ 128 +#define EVPD 0x01 +#define CMDDT 0x02 + +#define MSENSE_BUF_SZ 256 +#define DBD 0x08 /* disable block descriptor */ +#define WP 0x80 /* write protect */ +#define DPOFUA 0x10 /* DPOFUA bit */ +#define WCE 0x04 /* write cache enable */ + +#define PF 0x10 /* page format */ +#define SP 0x01 /* save pages */ +#define PS 0x80 /* parameter saveable */ + +#define BYTE 8 +#define DEF_DISK_BLOCKSIZE_SHIFT 9 +#define DEF_DISK_BLOCKSIZE (1 << DEF_DISK_BLOCKSIZE_SHIFT) +#define DEF_CDROM_BLOCKSIZE_SHIFT 11 +#define DEF_CDROM_BLOCKSIZE (1 << DEF_CDROM_BLOCKSIZE_SHIFT) +#define DEF_SECTORS 56 +#define DEF_HEADS 255 +#define LEN_MEM (32 * 1024) +#define DEF_RD_ONLY 0 +#define DEF_WRITE_THROUGH 0 +#define DEF_NV_CACHE 0 +#define DEF_O_DIRECT 0 +#define DEF_REMOVABLE 0 + +#define VDISK_NULLIO_SIZE (3LL*1024*1024*1024*1024/2) + +#define DEF_TST SCST_CONTR_MODE_SEP_TASK_SETS +/* + * Since we can't control backstorage device's reordering, we have to always + * report unrestricted reordering. + */ +#define DEF_QUEUE_ALG_WT SCST_CONTR_MODE_QUEUE_ALG_UNRESTRICTED_REORDER +#define DEF_QUEUE_ALG SCST_CONTR_MODE_QUEUE_ALG_UNRESTRICTED_REORDER +#define DEF_SWP 0 +#define DEF_TAS 0 + +#define DEF_DSENSE SCST_CONTR_MODE_FIXED_SENSE + +static unsigned int random_values[256] = { + 9862592UL, 3744545211UL, 2348289082UL, 4036111983UL, + 435574201UL, 3110343764UL, 2383055570UL, 1826499182UL, + 4076766377UL, 1549935812UL, 3696752161UL, 1200276050UL, + 3878162706UL, 1783530428UL, 2291072214UL, 125807985UL, + 3407668966UL, 547437109UL, 3961389597UL, 969093968UL, + 56006179UL, 2591023451UL, 1849465UL, 1614540336UL, + 3699757935UL, 479961779UL, 3768703953UL, 2529621525UL, + 4157893312UL, 3673555386UL, 4091110867UL, 2193909423UL, + 2800464448UL, 3052113233UL, 450394455UL, 3424338713UL, + 2113709130UL, 4082064373UL, 3708640918UL, 3841182218UL, + 3141803315UL, 1032476030UL, 1166423150UL, 1169646901UL, + 2686611738UL, 575517645UL, 2829331065UL, 1351103339UL, + 2856560215UL, 2402488288UL, 867847666UL, 8524618UL, + 704790297UL, 2228765657UL, 231508411UL, 1425523814UL, + 2146764591UL, 1287631730UL, 4142687914UL, 3879884598UL, + 729945311UL, 310596427UL, 2263511876UL, 1983091134UL, + 3500916580UL, 1642490324UL, 3858376049UL, 695342182UL, + 780528366UL, 1372613640UL, 1100993200UL, 1314818946UL, + 572029783UL, 3775573540UL, 776262915UL, 2684520905UL, + 1007252738UL, 3505856396UL, 1974886670UL, 3115856627UL, + 4194842288UL, 2135793908UL, 3566210707UL, 7929775UL, + 1321130213UL, 2627281746UL, 3587067247UL, 2025159890UL, + 2587032000UL, 3098513342UL, 3289360258UL, 130594898UL, + 2258149812UL, 2275857755UL, 3966929942UL, 1521739999UL, + 4191192765UL, 958953550UL, 4153558347UL, 1011030335UL, + 524382185UL, 4099757640UL, 498828115UL, 2396978754UL, + 328688935UL, 826399828UL, 3174103611UL, 3921966365UL, + 2187456284UL, 2631406787UL, 3930669674UL, 4282803915UL, + 1776755417UL, 374959755UL, 2483763076UL, 844956392UL, + 2209187588UL, 3647277868UL, 291047860UL, 3485867047UL, + 2223103546UL, 2526736133UL, 3153407604UL, 3828961796UL, + 3355731910UL, 2322269798UL, 2752144379UL, 519897942UL, + 3430536488UL, 1801511593UL, 1953975728UL, 3286944283UL, + 1511612621UL, 1050133852UL, 409321604UL, 1037601109UL, + 3352316843UL, 4198371381UL, 617863284UL, 994672213UL, + 1540735436UL, 2337363549UL, 1242368492UL, 665473059UL, + 2330728163UL, 3443103219UL, 2291025133UL, 3420108120UL, + 2663305280UL, 1608969839UL, 2278959931UL, 1389747794UL, + 2226946970UL, 2131266900UL, 3856979144UL, 1894169043UL, + 2692697628UL, 3797290626UL, 3248126844UL, 3922786277UL, + 343705271UL, 3739749888UL, 2191310783UL, 2962488787UL, + 4119364141UL, 1403351302UL, 2984008923UL, 3822407178UL, + 1932139782UL, 2323869332UL, 2793574182UL, 1852626483UL, + 2722460269UL, 1136097522UL, 1005121083UL, 1805201184UL, + 2212824936UL, 2979547931UL, 4133075915UL, 2585731003UL, + 2431626071UL, 134370235UL, 3763236829UL, 1171434827UL, + 2251806994UL, 1289341038UL, 3616320525UL, 392218563UL, + 1544502546UL, 2993937212UL, 1957503701UL, 3579140080UL, + 4270846116UL, 2030149142UL, 1792286022UL, 366604999UL, + 2625579499UL, 790898158UL, 770833822UL, 815540197UL, + 2747711781UL, 3570468835UL, 3976195842UL, 1257621341UL, + 1198342980UL, 1860626190UL, 3247856686UL, 351473955UL, + 993440563UL, 340807146UL, 1041994520UL, 3573925241UL, + 480246395UL, 2104806831UL, 1020782793UL, 3362132583UL, + 2272911358UL, 3440096248UL, 2356596804UL, 259492703UL, + 3899500740UL, 252071876UL, 2177024041UL, 4284810959UL, + 2775999888UL, 2653420445UL, 2876046047UL, 1025771859UL, + 1994475651UL, 3564987377UL, 4112956647UL, 1821511719UL, + 3113447247UL, 455315102UL, 1585273189UL, 2311494568UL, + 774051541UL, 1898115372UL, 2637499516UL, 247231365UL, + 1475014417UL, 803585727UL, 3911097303UL, 1714292230UL, + 476579326UL, 2496900974UL, 3397613314UL, 341202244UL, + 807790202UL, 4221326173UL, 499979741UL, 1301488547UL, + 1056807896UL, 3525009458UL, 1174811641UL, 3049738746UL, +}; + +struct scst_vdisk_dev { + uint32_t block_size; + uint64_t nblocks; + int block_shift; + loff_t file_size; /* in bytes */ + + /* + * This lock can be taken on both SIRQ and thread context, but in + * all cases for each particular instance it's taken consistenly either + * on SIRQ or thread context. Mix of them is forbidden. + */ + spinlock_t flags_lock; + + /* + * Below flags are protected by flags_lock or suspended activity + * with scst_vdisk_mutex. + */ + unsigned int rd_only:1; + unsigned int wt_flag:1; + unsigned int nv_cache:1; + unsigned int o_direct_flag:1; + unsigned int media_changed:1; + unsigned int prevent_allow_medium_removal:1; + unsigned int nullio:1; + unsigned int blockio:1; + unsigned int cdrom_empty:1; + unsigned int removable:1; + + int virt_id; + char name[16+1]; /* Name of the virtual device, + must be <= SCSI Model + 1 */ + char *filename; /* File name, protected by + scst_mutex and suspended activities */ + unsigned int t10_dev_id_set:1; /* true if t10_dev_id manually set */ + char t10_dev_id[16+8+2]; /* T10 device ID */ + char usn[MAX_USN_LEN]; + struct scst_device *dev; + struct list_head vdev_list_entry; + + struct mutex vdev_sysfs_mutex; + struct scst_dev_type *vdev_devt; +}; + +struct scst_vdisk_tgt_dev { + /* + * Used without locking since SCST core ensures that only commands + * with the same ORDERED type per tgt_dev can be processed + * simultaneously. + */ + enum scst_cmd_queue_type last_write_cmd_queue_type; +}; + +struct scst_vdisk_thr { + struct scst_thr_data_hdr hdr; + struct file *fd; + struct block_device *bdev; + struct iovec *iv; + int iv_count; +}; + +/* Context RA patch supposed to be applied on the kernel */ +#define DEF_NUM_THREADS 8 +static int num_threads = DEF_NUM_THREADS; + +module_param_named(num_threads, num_threads, int, S_IRUGO); +MODULE_PARM_DESC(num_threads, "vdisk threads count"); + +static int vdisk_attach(struct scst_device *dev); +static void vdisk_detach(struct scst_device *dev); +static int vdisk_attach_tgt(struct scst_tgt_dev *tgt_dev); +static void vdisk_detach_tgt(struct scst_tgt_dev *tgt_dev); +static int vdisk_parse(struct scst_cmd *); +static int vdisk_do_job(struct scst_cmd *cmd); +static int vcdrom_parse(struct scst_cmd *); +static int vcdrom_exec(struct scst_cmd *cmd); +static void vdisk_exec_read(struct scst_cmd *cmd, + struct scst_vdisk_thr *thr, loff_t loff); +static void vdisk_exec_write(struct scst_cmd *cmd, + struct scst_vdisk_thr *thr, loff_t loff); +static void blockio_exec_rw(struct scst_cmd *cmd, struct scst_vdisk_thr *thr, + u64 lba_start, int write); +static void vdisk_exec_verify(struct scst_cmd *cmd, + struct scst_vdisk_thr *thr, loff_t loff); +static void vdisk_exec_read_capacity(struct scst_cmd *cmd); +static void vdisk_exec_read_capacity16(struct scst_cmd *cmd); +static void vdisk_exec_inquiry(struct scst_cmd *cmd); +static void vdisk_exec_request_sense(struct scst_cmd *cmd); +static void vdisk_exec_mode_sense(struct scst_cmd *cmd); +static void vdisk_exec_mode_select(struct scst_cmd *cmd); +static void vdisk_exec_log(struct scst_cmd *cmd); +static void vdisk_exec_read_toc(struct scst_cmd *cmd); +static void vdisk_exec_prevent_allow_medium_removal(struct scst_cmd *cmd); +static int vdisk_fsync(struct scst_vdisk_thr *thr, loff_t loff, + loff_t len, struct scst_cmd *cmd, struct scst_device *dev); +static ssize_t vdisk_add_fileio_device(const char *device_name, char *params); +static ssize_t vdisk_add_blockio_device(const char *device_name, char *params); +static ssize_t vdisk_add_nullio_device(const char *device_name, char *params); +static ssize_t vdisk_del_device(const char *device_name); +static ssize_t vcdrom_add_device(const char *device_name, char *params); +static ssize_t vcdrom_del_device(const char *device_name); +static int vdisk_task_mgmt_fn(struct scst_mgmt_cmd *mcmd, + struct scst_tgt_dev *tgt_dev); +static uint64_t vdisk_gen_dev_id_num(const char *virt_dev_name); + +/** SYSFS **/ + +static ssize_t vdev_sysfs_size_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); +static ssize_t vdisk_sysfs_blocksize_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); +static ssize_t vdisk_sysfs_rd_only_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); +static ssize_t vdisk_sysfs_wt_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); +static ssize_t vdisk_sysfs_nv_cache_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); +static ssize_t vdisk_sysfs_o_direct_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); +static ssize_t vdisk_sysfs_removable_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); +static ssize_t vdev_sysfs_filename_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); +static ssize_t vdisk_sysfs_resync_size_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count); +static ssize_t vdev_sysfs_t10_dev_id_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count); +static ssize_t vdev_sysfs_t10_dev_id_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); +static ssize_t vdev_sysfs_usn_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf); + +static ssize_t vcdrom_sysfs_filename_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count); + +static struct kobj_attribute vdev_size_attr = + __ATTR(size_mb, S_IRUGO, vdev_sysfs_size_show, NULL); +static struct kobj_attribute vdisk_blocksize_attr = + __ATTR(blocksize, S_IRUGO, vdisk_sysfs_blocksize_show, NULL); +static struct kobj_attribute vdisk_rd_only_attr = + __ATTR(read_only, S_IRUGO, vdisk_sysfs_rd_only_show, NULL); +static struct kobj_attribute vdisk_wt_attr = + __ATTR(write_through, S_IRUGO, vdisk_sysfs_wt_show, NULL); +static struct kobj_attribute vdisk_nv_cache_attr = + __ATTR(nv_cache, S_IRUGO, vdisk_sysfs_nv_cache_show, NULL); +static struct kobj_attribute vdisk_o_direct_attr = + __ATTR(o_direct, S_IRUGO, vdisk_sysfs_o_direct_show, NULL); +static struct kobj_attribute vdisk_removable_attr = + __ATTR(removable, S_IRUGO, vdisk_sysfs_removable_show, NULL); +static struct kobj_attribute vdisk_filename_attr = + __ATTR(filename, S_IRUGO, vdev_sysfs_filename_show, NULL); +static struct kobj_attribute vdisk_resync_size_attr = + __ATTR(resync_size, S_IWUSR, NULL, vdisk_sysfs_resync_size_store); +static struct kobj_attribute vdev_t10_dev_id_attr = + __ATTR(t10_dev_id, S_IWUSR|S_IRUGO, vdev_sysfs_t10_dev_id_show, + vdev_sysfs_t10_dev_id_store); +static struct kobj_attribute vdev_usn_attr = + __ATTR(usn, S_IRUGO, vdev_sysfs_usn_show, NULL); + +static struct kobj_attribute vcdrom_filename_attr = + __ATTR(filename, S_IRUGO|S_IWUSR, vdev_sysfs_filename_show, + vcdrom_sysfs_filename_store); + +static const struct attribute *vdisk_fileio_attrs[] = { + &vdev_size_attr.attr, + &vdisk_blocksize_attr.attr, + &vdisk_rd_only_attr.attr, + &vdisk_wt_attr.attr, + &vdisk_nv_cache_attr.attr, + &vdisk_o_direct_attr.attr, + &vdisk_removable_attr.attr, + &vdisk_filename_attr.attr, + &vdisk_resync_size_attr.attr, + &vdev_t10_dev_id_attr.attr, + &vdev_usn_attr.attr, + NULL, +}; + +static const struct attribute *vdisk_blockio_attrs[] = { + &vdev_size_attr.attr, + &vdisk_blocksize_attr.attr, + &vdisk_rd_only_attr.attr, + &vdisk_removable_attr.attr, + &vdisk_filename_attr.attr, + &vdisk_resync_size_attr.attr, + &vdev_t10_dev_id_attr.attr, + &vdev_usn_attr.attr, + NULL, +}; + +static const struct attribute *vdisk_nullio_attrs[] = { + &vdev_size_attr.attr, + &vdisk_blocksize_attr.attr, + &vdisk_rd_only_attr.attr, + &vdisk_removable_attr.attr, + &vdev_t10_dev_id_attr.attr, + &vdev_usn_attr.attr, + NULL, +}; + +static const struct attribute *vcdrom_attrs[] = { + &vdev_size_attr.attr, + &vcdrom_filename_attr.attr, + &vdev_t10_dev_id_attr.attr, + &vdev_usn_attr.attr, + NULL, +}; + +/* Protects vdisks addition/deletion and related activities, like search */ +static DEFINE_MUTEX(scst_vdisk_mutex); +static DEFINE_RWLOCK(vdisk_t10_dev_id_rwlock); + +/* Protected by scst_vdisk_mutex */ +static LIST_HEAD(vdev_list); + +static struct kmem_cache *vdisk_thr_cachep; + +/* + * Be careful changing "name" field, since it is the name of the corresponding + * /sys/kernel/scst_tgt entry, hence a part of user space ABI. + */ + +static struct scst_dev_type vdisk_file_devtype = { + .name = "vdisk_fileio", + .type = TYPE_DISK, + .exec_sync = 1, + .threads_num = -1, + .parse_atomic = 1, + .exec_atomic = 0, + .dev_done_atomic = 1, + .attach = vdisk_attach, + .detach = vdisk_detach, + .attach_tgt = vdisk_attach_tgt, + .detach_tgt = vdisk_detach_tgt, + .parse = vdisk_parse, + .exec = vdisk_do_job, + .task_mgmt_fn = vdisk_task_mgmt_fn, + .add_device = vdisk_add_fileio_device, + .del_device = vdisk_del_device, + .dev_attrs = vdisk_fileio_attrs, + .add_device_parameters_help = "filename, blocksize, write_through, " + "nv_cache, o_direct, read_only, removable", +#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) + .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS, + .trace_flags = &trace_flag, + .trace_tbl = vdisk_local_trace_tbl, + .trace_tbl_help = VDISK_TRACE_TLB_HELP, +#endif +}; + +static struct kmem_cache *blockio_work_cachep; + +static struct scst_dev_type vdisk_blk_devtype = { + .name = "vdisk_blockio", + .type = TYPE_DISK, + .threads_num = 0, + .parse_atomic = 1, + .exec_atomic = 0, + .dev_done_atomic = 1, + .attach = vdisk_attach, + .detach = vdisk_detach, + .attach_tgt = vdisk_attach_tgt, + .detach_tgt = vdisk_detach_tgt, + .parse = vdisk_parse, + .exec = vdisk_do_job, + .task_mgmt_fn = vdisk_task_mgmt_fn, + .add_device = vdisk_add_blockio_device, + .del_device = vdisk_del_device, + .dev_attrs = vdisk_blockio_attrs, + .add_device_parameters_help = "filename, blocksize, read_only, " + "removable", +#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) + .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS, + .trace_flags = &trace_flag, + .trace_tbl = vdisk_local_trace_tbl, + .trace_tbl_help = VDISK_TRACE_TLB_HELP, +#endif +}; + +static struct scst_dev_type vdisk_null_devtype = { + .name = "vdisk_nullio", + .type = TYPE_DISK, + .threads_num = 0, + .parse_atomic = 1, + .exec_atomic = 1, + .dev_done_atomic = 1, + .attach = vdisk_attach, + .detach = vdisk_detach, + .attach_tgt = vdisk_attach_tgt, + .detach_tgt = vdisk_detach_tgt, + .parse = vdisk_parse, + .exec = vdisk_do_job, + .task_mgmt_fn = vdisk_task_mgmt_fn, + .add_device = vdisk_add_nullio_device, + .del_device = vdisk_del_device, + .dev_attrs = vdisk_nullio_attrs, + .add_device_parameters_help = "blocksize, read_only, removable", +#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) + .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS, + .trace_flags = &trace_flag, + .trace_tbl = vdisk_local_trace_tbl, + .trace_tbl_help = VDISK_TRACE_TLB_HELP, +#endif +}; + +static struct scst_dev_type vcdrom_devtype = { + .name = "vcdrom", + .type = TYPE_ROM, + .exec_sync = 1, + .threads_num = -1, + .parse_atomic = 1, + .exec_atomic = 0, + .dev_done_atomic = 1, + .attach = vdisk_attach, + .detach = vdisk_detach, + .attach_tgt = vdisk_attach_tgt, + .detach_tgt = vdisk_detach_tgt, + .parse = vcdrom_parse, + .exec = vcdrom_exec, + .task_mgmt_fn = vdisk_task_mgmt_fn, + .add_device = vcdrom_add_device, + .del_device = vcdrom_del_device, + .dev_attrs = vcdrom_attrs, + .add_device_parameters_help = NULL, +#if defined(CONFIG_SCST_DEBUG) || defined(CONFIG_SCST_TRACING) + .default_trace_flags = SCST_DEFAULT_DEV_LOG_FLAGS, + .trace_flags = &trace_flag, + .trace_tbl = vdisk_local_trace_tbl, + .trace_tbl_help = VDISK_TRACE_TLB_HELP, +#endif +}; + +static struct scst_vdisk_thr nullio_thr_data; + +static const char *vdev_get_filename(const struct scst_vdisk_dev *virt_dev) +{ + if (virt_dev->filename != NULL) + return virt_dev->filename; + else + return "none"; +} + +/* Returns fd, use IS_ERR(fd) to get error status */ +static struct file *vdev_open_fd(const struct scst_vdisk_dev *virt_dev) +{ + int open_flags = 0; + struct file *fd; + + if (virt_dev->dev->rd_only) + open_flags |= O_RDONLY; + else + open_flags |= O_RDWR; + if (virt_dev->o_direct_flag) + open_flags |= O_DIRECT; + if (virt_dev->wt_flag && !virt_dev->nv_cache) + open_flags |= O_SYNC; + TRACE_DBG("Opening file %s, flags 0x%x", + virt_dev->filename, open_flags); + fd = filp_open(virt_dev->filename, O_LARGEFILE | open_flags, 0600); + return fd; +} + +/* Returns 0 on success and file size in *file_size, error code otherwise */ +static int vdisk_get_file_size(const char *filename, bool blockio, + loff_t *file_size) +{ + struct inode *inode; + int res = 0; + struct file *fd; + + *file_size = 0; + + fd = filp_open(filename, O_LARGEFILE | O_RDONLY, 0600); + if (IS_ERR(fd)) { + res = PTR_ERR(fd); + PRINT_ERROR("filp_open(%s) returned error %d", filename, res); + goto out; + } + + inode = fd->f_dentry->d_inode; + + if (blockio && !S_ISBLK(inode->i_mode)) { + PRINT_ERROR("File %s is NOT a block device", filename); + res = -EINVAL; + goto out_close; + } + + if (S_ISREG(inode->i_mode)) + /* Nothing to do*/; + else if (S_ISBLK(inode->i_mode)) + inode = inode->i_bdev->bd_inode; + else { + res = -EINVAL; + goto out_close; + } + + *file_size = inode->i_size; + +out_close: + filp_close(fd, NULL); + +out: + return res; +} + +static int vdisk_attach(struct scst_device *dev) +{ + int res = 0; + loff_t err; + struct scst_vdisk_dev *virt_dev = NULL, *vv; + + TRACE_DBG("virt_id %d (%s)", dev->virt_id, dev->virt_name); + + if (dev->virt_id == 0) { + PRINT_ERROR("%s", "Not a virtual device"); + res = -EINVAL; + goto out; + } + + /* + * scst_vdisk_mutex must be already taken before + * scst_register_virtual_device() + */ + list_for_each_entry(vv, &vdev_list, vdev_list_entry) { + if (strcmp(vv->name, dev->virt_name) == 0) { + virt_dev = vv; + break; + } + } + + if (virt_dev == NULL) { + PRINT_ERROR("Device %s not found", dev->virt_name); + res = -EINVAL; + goto out; + } + + virt_dev->dev = dev; + + dev->rd_only = virt_dev->rd_only; + + if (!virt_dev->cdrom_empty) { + if (virt_dev->nullio) + err = VDISK_NULLIO_SIZE; + else { + res = vdisk_get_file_size(virt_dev->filename, + virt_dev->blockio, &err); + if (res != 0) + goto out; + } + virt_dev->file_size = err; + TRACE_DBG("size of file: %lld", (long long unsigned int)err); + } else + virt_dev->file_size = 0; + + virt_dev->nblocks = virt_dev->file_size >> virt_dev->block_shift; + + if (!virt_dev->cdrom_empty) { + PRINT_INFO("Attached SCSI target virtual %s %s " + "(file=\"%s\", fs=%lldMB, bs=%d, nblocks=%lld," + " cyln=%lld%s)", + (dev->type == TYPE_DISK) ? "disk" : "cdrom", + virt_dev->name, vdev_get_filename(virt_dev), + virt_dev->file_size >> 20, virt_dev->block_size, + (long long unsigned int)virt_dev->nblocks, + (long long unsigned int)virt_dev->nblocks/64/32, + virt_dev->nblocks < 64*32 + ? " !WARNING! cyln less than 1" : ""); + } else { + PRINT_INFO("Attached empty SCSI target virtual cdrom %s", + virt_dev->name); + } + + dev->dh_priv = virt_dev; + + dev->tst = DEF_TST; + dev->d_sense = DEF_DSENSE; + if (virt_dev->wt_flag && !virt_dev->nv_cache) + dev->queue_alg = DEF_QUEUE_ALG_WT; + else + dev->queue_alg = DEF_QUEUE_ALG; + dev->swp = DEF_SWP; + dev->tas = DEF_TAS; + +out: + return res; +} + +/* scst_mutex supposed to be held */ +static void vdisk_detach(struct scst_device *dev) +{ + struct scst_vdisk_dev *virt_dev = + (struct scst_vdisk_dev *)dev->dh_priv; + + TRACE_DBG("virt_id %d", dev->virt_id); + + PRINT_INFO("Detached SCSI target virtual device %s (\"%s\")", + virt_dev->name, vdev_get_filename(virt_dev)); + + /* virt_dev will be freed by the caller */ + dev->dh_priv = NULL; + return; +} + +static void vdisk_free_thr_data(struct scst_thr_data_hdr *d) +{ + struct scst_vdisk_thr *thr = + container_of(d, struct scst_vdisk_thr, hdr); + + if (thr->fd) + filp_close(thr->fd, NULL); + + kfree(thr->iv); + + kmem_cache_free(vdisk_thr_cachep, thr); + return; +} + +static struct scst_vdisk_thr *vdisk_init_thr_data( + struct scst_tgt_dev *tgt_dev) +{ + struct scst_vdisk_thr *res; + struct scst_vdisk_dev *virt_dev = + (struct scst_vdisk_dev *)tgt_dev->dev->dh_priv; + + EXTRACHECKS_BUG_ON(virt_dev->nullio); + + res = kmem_cache_zalloc(vdisk_thr_cachep, GFP_KERNEL); + if (res == NULL) { + TRACE(TRACE_OUT_OF_MEM, "%s", "Unable to allocate struct " + "scst_vdisk_thr"); + goto out; + } + + if (!virt_dev->cdrom_empty) { + res->fd = vdev_open_fd(virt_dev); + if (IS_ERR(res->fd)) { + PRINT_ERROR("filp_open(%s) returned an error %ld", + virt_dev->filename, PTR_ERR(res->fd)); + goto out_free; + } + if (virt_dev->blockio) + res->bdev = res->fd->f_dentry->d_inode->i_bdev; + else + res->bdev = NULL; + } else + res->fd = NULL; + + scst_add_thr_data(tgt_dev, &res->hdr, vdisk_free_thr_data); + +out: + return res; + +out_free: + kmem_cache_free(vdisk_thr_cachep, res); + res = NULL; + goto out; +} + +static int vdisk_attach_tgt(struct scst_tgt_dev *tgt_dev) +{ + struct scst_vdisk_tgt_dev *ftgt_dev; + int res = 0; + + ftgt_dev = kzalloc(sizeof(*ftgt_dev), GFP_KERNEL); + if (ftgt_dev == NULL) { + TRACE(TRACE_OUT_OF_MEM, "%s", "Allocation of per-session " + "virtual device failed"); + res = -ENOMEM; + goto out; + } + + tgt_dev->dh_priv = ftgt_dev; + +out: + return res; +} + +static void vdisk_detach_tgt(struct scst_tgt_dev *tgt_dev) +{ + struct scst_vdisk_tgt_dev *ftgt_dev = + (struct scst_vdisk_tgt_dev *)tgt_dev->dh_priv; + + scst_del_all_thr_data(tgt_dev); + + kfree(ftgt_dev); + tgt_dev->dh_priv = NULL; + return; +} + +static inline int vdisk_sync_queue_type(enum scst_cmd_queue_type qt) +{ + switch (qt) { + case SCST_CMD_QUEUE_ORDERED: + case SCST_CMD_QUEUE_HEAD_OF_QUEUE: + return 1; + default: + return 0; + } +} + +static inline int vdisk_need_pre_sync(enum scst_cmd_queue_type cur, + enum scst_cmd_queue_type last) +{ + if (vdisk_sync_queue_type(cur)) + if (!vdisk_sync_queue_type(last)) + return 1; + return 0; +} + +static int vdisk_do_job(struct scst_cmd *cmd) +{ + int rc, res; + uint64_t lba_start = 0; + loff_t data_len = 0; + uint8_t *cdb = cmd->cdb; + int opcode = cdb[0]; + loff_t loff; + struct scst_device *dev = cmd->dev; + struct scst_tgt_dev *tgt_dev = cmd->tgt_dev; + struct scst_vdisk_dev *virt_dev = + (struct scst_vdisk_dev *)dev->dh_priv; + struct scst_thr_data_hdr *d; + struct scst_vdisk_thr *thr = NULL; + int fua = 0; + + switch (cmd->queue_type) { + case SCST_CMD_QUEUE_ORDERED: + TRACE(TRACE_ORDER, "ORDERED cmd %p (op %x)", cmd, cmd->cdb[0]); + break; + case SCST_CMD_QUEUE_HEAD_OF_QUEUE: + TRACE(TRACE_ORDER, "HQ cmd %p (op %x)", cmd, cmd->cdb[0]); + break; + default: + break; + } + + rc = scst_check_local_events(cmd); + if (unlikely(rc != 0)) + goto out_done; + + cmd->status = 0; + cmd->msg_status = 0; + cmd->host_status = DID_OK; + cmd->driver_status = 0; + + if (!virt_dev->nullio) { + d = scst_find_thr_data(tgt_dev); + if (unlikely(d == NULL)) { + thr = vdisk_init_thr_data(tgt_dev); + if (thr == NULL) { + scst_set_busy(cmd); + goto out_compl; + } + scst_thr_data_get(&thr->hdr); + } else + thr = container_of(d, struct scst_vdisk_thr, hdr); + } else { + thr = &nullio_thr_data; + scst_thr_data_get(&thr->hdr); + } + + switch (opcode) { + case READ_6: + case WRITE_6: + case VERIFY_6: + lba_start = (((cdb[1] & 0x1f) << (BYTE * 2)) + + (cdb[2] << (BYTE * 1)) + + (cdb[3] << (BYTE * 0))); + data_len = cmd->bufflen; + break; + case READ_10: + case READ_12: + case WRITE_10: + case WRITE_12: + case VERIFY: + case WRITE_VERIFY: + case WRITE_VERIFY_12: + case VERIFY_12: + lba_start |= ((u64)cdb[2]) << 24; + lba_start |= ((u64)cdb[3]) << 16; + lba_start |= ((u64)cdb[4]) << 8; + lba_start |= ((u64)cdb[5]); + data_len = cmd->bufflen; + break; + case READ_16: + case WRITE_16: + case WRITE_VERIFY_16: + case VERIFY_16: + lba_start |= ((u64)cdb[2]) << 56; + lba_start |= ((u64)cdb[3]) << 48; + lba_start |= ((u64)cdb[4]) << 40; + lba_start |= ((u64)cdb[5]) << 32; + lba_start |= ((u64)cdb[6]) << 24; + lba_start |= ((u64)cdb[7]) << 16; + lba_start |= ((u64)cdb[8]) << 8; + lba_start |= ((u64)cdb[9]); + data_len = cmd->bufflen; + break; + case SYNCHRONIZE_CACHE: + lba_start |= ((u64)cdb[2]) << 24; + lba_start |= ((u64)cdb[3]) << 16; + lba_start |= ((u64)cdb[4]) << 8; + lba_start |= ((u64)cdb[5]); + data_len = ((cdb[7] << (BYTE * 1)) + (cdb[8] << (BYTE * 0))) + << virt_dev->block_shift; + if (data_len == 0) + data_len = virt_dev->file_size - + ((loff_t)lba_start << virt_dev->block_shift); + break; + } + + loff = (loff_t)lba_start << virt_dev->block_shift; + TRACE_DBG("cmd %p, lba_start %lld, loff %lld, data_len %lld", cmd, + (long long unsigned int)lba_start, + (long long unsigned int)loff, + (long long unsigned int)data_len); + if (unlikely(loff < 0) || unlikely(data_len < 0) || + unlikely((loff + data_len) > virt_dev->file_size)) { + PRINT_INFO("Access beyond the end of the device " + "(%lld of %lld, len %lld)", + (long long unsigned int)loff, + (long long unsigned int)virt_dev->file_size, + (long long unsigned int)data_len); + scst_set_cmd_error(cmd, SCST_LOAD_SENSE( + scst_sense_block_out_range_error)); + goto out_compl; + } + + switch (opcode) { + case WRITE_10: + case WRITE_12: + case WRITE_16: + fua = (cdb[1] & 0x8); + if (fua) { + TRACE(TRACE_ORDER, "FUA: loff=%lld, " + "data_len=%lld", (long long unsigned int)loff, + (long long unsigned int)data_len); + } + break; + } + + switch (opcode) { + case READ_6: + case READ_10: + case READ_12: + case READ_16: + if (virt_dev->blockio) { + blockio_exec_rw(cmd, thr, lba_start, 0); + goto out_thr; + } else + vdisk_exec_read(cmd, thr, loff); + break; + case WRITE_6: + case WRITE_10: + case WRITE_12: + case WRITE_16: + { + int do_fsync = vdisk_sync_queue_type(cmd->queue_type); + struct scst_vdisk_tgt_dev *ftgt_dev = + (struct scst_vdisk_tgt_dev *)tgt_dev->dh_priv; + enum scst_cmd_queue_type last_queue_type = + ftgt_dev->last_write_cmd_queue_type; + ftgt_dev->last_write_cmd_queue_type = cmd->queue_type; + if (vdisk_need_pre_sync(cmd->queue_type, last_queue_type)) { + TRACE(TRACE_ORDER, "ORDERED WRITE(%d): loff=%lld, " + "data_len=%lld", cmd->queue_type, + (long long unsigned int)loff, + (long long unsigned int)data_len); + do_fsync = 1; + if (vdisk_fsync(thr, 0, 0, cmd, dev) != 0) + goto out_compl; + } + if (virt_dev->blockio) { + blockio_exec_rw(cmd, thr, lba_start, 1); + goto out_thr; + } else + vdisk_exec_write(cmd, thr, loff); + /* O_SYNC flag is used for WT devices */ + if (do_fsync || fua) + vdisk_fsync(thr, loff, data_len, cmd, dev); + break; + } + case WRITE_VERIFY: + case WRITE_VERIFY_12: + case WRITE_VERIFY_16: + { + int do_fsync = vdisk_sync_queue_type(cmd->queue_type); + struct scst_vdisk_tgt_dev *ftgt_dev = + (struct scst_vdisk_tgt_dev *) tgt_dev->dh_priv; + enum scst_cmd_queue_type last_queue_type = + ftgt_dev->last_write_cmd_queue_type; + ftgt_dev->last_write_cmd_queue_type = cmd->queue_type; + if (vdisk_need_pre_sync(cmd->queue_type, last_queue_type)) { + TRACE(TRACE_ORDER, "ORDERED " + "WRITE_VERIFY(%d): loff=%lld," + " data_len=%lld", cmd->queue_type, + (long long unsigned int)loff, + (long long unsigned int)data_len); + do_fsync = 1; + if (vdisk_fsync(thr, 0, 0, cmd, dev) != 0) + goto out_compl; + } + /* ToDo: BLOCKIO VERIFY */ + vdisk_exec_write(cmd, thr, loff); + /* O_SYNC flag is used for WT devices */ + if (scsi_status_is_good(cmd->status)) + vdisk_exec_verify(cmd, thr, loff); + else if (do_fsync) + vdisk_fsync(thr, loff, data_len, cmd, dev); + break; + } + case SYNCHRONIZE_CACHE: + { + int immed = cdb[1] & 0x2; + TRACE(TRACE_ORDER, "SYNCHRONIZE_CACHE: " + "loff=%lld, data_len=%lld, immed=%d", + (long long unsigned int)loff, + (long long unsigned int)data_len, immed); + if (immed) { + scst_cmd_get(cmd); /* to protect dev */ + cmd->completed = 1; + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, + SCST_CONTEXT_SAME); + vdisk_fsync(thr, loff, data_len, NULL, dev); + /* ToDo: vdisk_fsync() error processing */ + scst_cmd_put(cmd); + goto out_thr; + } else { + vdisk_fsync(thr, loff, data_len, cmd, dev); + break; + } + } + case VERIFY_6: + case VERIFY: + case VERIFY_12: + case VERIFY_16: + vdisk_exec_verify(cmd, thr, loff); + break; + case MODE_SENSE: + case MODE_SENSE_10: + vdisk_exec_mode_sense(cmd); + break; + case MODE_SELECT: + case MODE_SELECT_10: + vdisk_exec_mode_select(cmd); + break; + case LOG_SELECT: + case LOG_SENSE: + vdisk_exec_log(cmd); + break; + case ALLOW_MEDIUM_REMOVAL: + vdisk_exec_prevent_allow_medium_removal(cmd); + break; + case READ_TOC: + vdisk_exec_read_toc(cmd); + break; + case START_STOP: + vdisk_fsync(thr, 0, virt_dev->file_size, cmd, dev); + break; + case RESERVE: + case RESERVE_10: + case RELEASE: + case RELEASE_10: + case TEST_UNIT_READY: + break; + case INQUIRY: + vdisk_exec_inquiry(cmd); + break; + case REQUEST_SENSE: + vdisk_exec_request_sense(cmd); + break; + case READ_CAPACITY: + vdisk_exec_read_capacity(cmd); + break; + case SERVICE_ACTION_IN: + if ((cmd->cdb[1] & 0x1f) == SAI_READ_CAPACITY_16) { + vdisk_exec_read_capacity16(cmd); + break; + } + /* else go through */ + case REPORT_LUNS: + default: + TRACE_DBG("Invalid opcode %d", opcode); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_opcode)); + } + +out_compl: + cmd->completed = 1; + +out_done: + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); + +out_thr: + if (likely(thr != NULL)) + scst_thr_data_put(&thr->hdr); + + res = SCST_EXEC_COMPLETED; + return res; +} + +static int vdisk_get_block_shift(struct scst_cmd *cmd) +{ + struct scst_vdisk_dev *virt_dev = + (struct scst_vdisk_dev *)cmd->dev->dh_priv; + return virt_dev->block_shift; +} + +static int vdisk_parse(struct scst_cmd *cmd) +{ + scst_sbc_generic_parse(cmd, vdisk_get_block_shift); + return SCST_CMD_STATE_DEFAULT; +} + +static int vcdrom_parse(struct scst_cmd *cmd) +{ + scst_cdrom_generic_parse(cmd, vdisk_get_block_shift); + return SCST_CMD_STATE_DEFAULT; +} + +static int vcdrom_exec(struct scst_cmd *cmd) +{ + int res = SCST_EXEC_COMPLETED; + int opcode = cmd->cdb[0]; + struct scst_vdisk_dev *virt_dev = + (struct scst_vdisk_dev *)cmd->dev->dh_priv; + + cmd->status = 0; + cmd->msg_status = 0; + cmd->host_status = DID_OK; + cmd->driver_status = 0; + + if (virt_dev->cdrom_empty && (opcode != INQUIRY)) { + TRACE_DBG("%s", "CDROM empty"); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_not_ready)); + goto out_done; + } + + if (virt_dev->media_changed && scst_is_ua_command(cmd)) { + spin_lock(&virt_dev->flags_lock); + if (virt_dev->media_changed) { + virt_dev->media_changed = 0; + TRACE_DBG("%s", "Reporting media changed"); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_medium_changed_UA)); + spin_unlock(&virt_dev->flags_lock); + goto out_done; + } + spin_unlock(&virt_dev->flags_lock); + } + + res = vdisk_do_job(cmd); + +out: + return res; + +out_done: + cmd->scst_cmd_done(cmd, SCST_CMD_STATE_DEFAULT, SCST_CONTEXT_SAME); + goto out; +} + +static uint64_t vdisk_gen_dev_id_num(const char *virt_dev_name) +{ + unsigned int dev_id_num, i; + + for (dev_id_num = 0, i = 0; i < strlen(virt_dev_name); i++) { + unsigned int rv = random_values[(int)(virt_dev_name[i])]; + /* Do some rotating of the bits */ + dev_id_num ^= ((rv << i) | (rv >> (32 - i))); + } + + return ((uint64_t)scst_get_setup_id() << 32) | dev_id_num; +} + +static void vdisk_exec_inquiry(struct scst_cmd *cmd) +{ + int32_t length, i, resp_len = 0; + uint8_t *address; + uint8_t *buf; + struct scst_vdisk_dev *virt_dev = + (struct scst_vdisk_dev *)cmd->dev->dh_priv; + + /* ToDo: Performance Boost: + * 1. remove kzalloc, buf + * 2. do all checks before touching *address + * 3. zero *address + * 4. write directly to *address + */ + + buf = kzalloc(INQ_BUF_SZ, + scst_cmd_atomic(cmd) ? GFP_ATOMIC : GFP_KERNEL); + if (buf == NULL) { + scst_set_busy(cmd); + goto out; + } + + length = scst_get_buf_first(cmd, &address); + TRACE_DBG("length %d", length); + if (unlikely(length <= 0)) { + if (length < 0) { + PRINT_ERROR("scst_get_buf_first() failed: %d", length); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_hardw_error)); + } + goto out_free; + } + + if (cmd->cdb[1] & CMDDT) { + TRACE_DBG("%s", "INQUIRY: CMDDT is unsupported"); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb)); + goto out_put; + } + + buf[0] = cmd->dev->type; /* type dev */ + if (virt_dev->removable) + buf[1] = 0x80; /* removable */ + /* Vital Product */ + if (cmd->cdb[1] & EVPD) { + if (0 == cmd->cdb[2]) { + /* supported vital product data pages */ + buf[3] = 3; + buf[4] = 0x0; /* this page */ + buf[5] = 0x80; /* unit serial number */ + buf[6] = 0x83; /* device identification */ + resp_len = buf[3] + 4; + } else if (0x80 == cmd->cdb[2]) { + /* unit serial number */ + int usn_len = strlen(virt_dev->usn); + buf[1] = 0x80; + buf[3] = usn_len; + strncpy(&buf[4], virt_dev->usn, usn_len); + resp_len = buf[3] + 4; + } else if (0x83 == cmd->cdb[2]) { + /* device identification */ + int num = 4; + + buf[1] = 0x83; + /* T10 vendor identifier field format (faked) */ + buf[num + 0] = 0x2; /* ASCII */ + buf[num + 1] = 0x1; /* Vendor ID */ + if (virt_dev->blockio) + memcpy(&buf[num + 4], SCST_BIO_VENDOR, 8); + else + memcpy(&buf[num + 4], SCST_FIO_VENDOR, 8); + + read_lock_bh(&vdisk_t10_dev_id_rwlock); + i = strlen(virt_dev->t10_dev_id); + memcpy(&buf[num + 12], virt_dev->t10_dev_id, i); + read_unlock_bh(&vdisk_t10_dev_id_rwlock); + + buf[num + 3] = 8 + i; + num += buf[num + 3]; + + num += 4; + + /* Binary */ + buf[num + 0] = 0x01; + + /* EUI-64 */ + buf[num + 1] = 0x02; + buf[num + 2] = 0x00; + buf[num + 3] = 0x08; + + /* IEEE id */ + buf[num + 4] = virt_dev->t10_dev_id[0]; + buf[num + 5] = virt_dev->t10_dev_id[1]; + buf[num + 6] = virt_dev->t10_dev_id[2]; + + /* IEEE ext id */ + buf[num + 7] = virt_dev->t10_dev_id[3]; + buf[num + 8] = virt_dev->t10_dev_id[4]; + buf[num + 9] = virt_dev->t10_dev_id[5]; + buf[num + 10] = virt_dev->t10_dev_id[6]; + buf[num + 11] = virt_dev->t10_dev_id[7]; + num += buf[num + 3]; + + resp_len = num; + buf[2] = (resp_len >> 8) & 0xFF; + buf[3] = resp_len & 0xFF; + resp_len += 4; + } else { + TRACE_DBG("INQUIRY: Unsupported EVPD page %x", + cmd->cdb[2]); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb)); + goto out_put; + } + } else { + int len; + + if (cmd->cdb[2] != 0) { + TRACE_DBG("INQUIRY: Unsupported page %x", cmd->cdb[2]); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb)); + goto out_put; + } + + buf[2] = 5; /* Device complies to SPC-3 */ + buf[3] = 0x12; /* HiSup + data in format specified in SPC */ + buf[4] = 31;/* n - 4 = 35 - 4 = 31 for full 36 byte data */ + buf[6] = 1; /* MultiP 1 */ + buf[7] = 2; /* CMDQUE 1, BQue 0 => commands queuing supported */ + + /* + * 8 byte ASCII Vendor Identification of the target + * - left aligned. + */ + if (virt_dev->blockio) + memcpy(&buf[8], SCST_BIO_VENDOR, 8); + else + memcpy(&buf[8], SCST_FIO_VENDOR, 8); + + /* + * 16 byte ASCII Product Identification of the target - left + * aligned. + */ + memset(&buf[16], ' ', 16); + len = min(strlen(virt_dev->name), (size_t)16); + memcpy(&buf[16], virt_dev->name, len); + + /* + * 4 byte ASCII Product Revision Level of the target - left + * aligned. + */ + memcpy(&buf[32], SCST_FIO_REV, 4); + resp_len = buf[4] + 5; + } + + BUG_ON(resp_len >= INQ_BUF_SZ); + if (length > resp_len) + length = resp_len; + memcpy(address, buf, length); + +out_put: + scst_put_buf(cmd, address); + if (length < cmd->resp_data_len) + scst_set_resp_data_len(cmd, length); + +out_free: + kfree(buf); + +out: + return; +} + +static void vdisk_exec_request_sense(struct scst_cmd *cmd) +{ + int32_t length, sl; + uint8_t *address; + uint8_t b[SCST_STANDARD_SENSE_LEN]; + + sl = scst_set_sense(b, sizeof(b), cmd->dev->d_sense, + SCST_LOAD_SENSE(scst_sense_no_sense)); + + length = scst_get_buf_first(cmd, &address); + TRACE_DBG("length %d", length); + if (length < 0) { + PRINT_ERROR("scst_get_buf_first() failed: %d)", length); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_hardw_error)); + goto out; + } + + length = min(sl, length); + memcpy(address, b, length); + scst_set_resp_data_len(cmd, length); + + scst_put_buf(cmd, address); + +out: + return; +} + +/* + * <<Following mode pages info copied from ST318451LW with some corrections>> + * + * ToDo: revise them + */ +static int vdisk_err_recov_pg(unsigned char *p, int pcontrol, + struct scst_vdisk_dev *virt_dev) +{ /* Read-Write Error Recovery page for mode_sense */ + const unsigned char err_recov_pg[] = {0x1, 0xa, 0xc0, 11, 240, 0, 0, 0, + 5, 0, 0xff, 0xff}; + + memcpy(p, err_recov_pg, sizeof(err_recov_pg)); + if (1 == pcontrol) + memset(p + 2, 0, sizeof(err_recov_pg) - 2); + return sizeof(err_recov_pg); +} + +static int vdisk_disconnect_pg(unsigned char *p, int pcontrol, + struct scst_vdisk_dev *virt_dev) +{ /* Disconnect-Reconnect page for mode_sense */ + const unsigned char disconnect_pg[] = {0x2, 0xe, 128, 128, 0, 10, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + + memcpy(p, disconnect_pg, sizeof(disconnect_pg)); + if (1 == pcontrol) + memset(p + 2, 0, sizeof(disconnect_pg) - 2); + return sizeof(disconnect_pg); +} + +static int vdisk_rigid_geo_pg(unsigned char *p, int pcontrol, + struct scst_vdisk_dev *virt_dev) +{ + unsigned char geo_m_pg[] = {0x04, 0x16, 0, 0, 0, DEF_HEADS, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0x3a, 0x98/* 15K RPM */, 0, 0}; + int32_t ncyl, n, rem; + uint64_t dividend; + + memcpy(p, geo_m_pg, sizeof(geo_m_pg)); + /* + * Divide virt_dev->nblocks by (DEF_HEADS * DEF_SECTORS) and store + * the quotient in ncyl and the remainder in rem. + */ + dividend = virt_dev->nblocks; + rem = do_div(dividend, DEF_HEADS * DEF_SECTORS); + ncyl = dividend; + if (rem != 0) + ncyl++; + memcpy(&n, p + 2, sizeof(u32)); + n = n | (cpu_to_be32(ncyl) >> 8); + memcpy(p + 2, &n, sizeof(u32)); + if (1 == pcontrol) + memset(p + 2, 0, sizeof(geo_m_pg) - 2); + return sizeof(geo_m_pg); +} + +static int vdisk_format_pg(unsigned char *p, int pcontrol, + struct scst_vdisk_dev *virt_dev) +{ /* Format device page for mode_sense */ + const unsigned char format_pg[] = {0x3, 0x16, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0x40, 0, 0, 0}; + + memcpy(p, format_pg, sizeof(format_pg)); + p[10] = (DEF_SECTORS >> 8) & 0xff; + p[11] = DEF_SECTORS & 0xff; + p[12] = (virt_dev->block_size >> 8) & 0xff; + p[13] = virt_dev->block_size & 0xff; + if (1 == pcontrol) + memset(p + 2, 0, sizeof(format_pg) - 2); + return sizeof(format_pg); +} + +static int vdisk_caching_pg(unsigned char *p, int pcontrol, + struct scst_vdisk_dev *virt_dev) +{ /* Caching page for mode_sense */ + const unsigned char caching_pg[] = {0x8, 18, 0x10, 0, 0xff, 0xff, 0, 0, + 0xff, 0xff, 0xff, 0xff, 0x80, 0x14, 0, 0, 0, 0, 0, 0}; + + memcpy(p, caching_pg, sizeof(caching_pg)); + p[2] |= !(virt_dev->wt_flag || virt_dev->nv_cache) ? WCE : 0; + if (1 == pcontrol) + memset(p + 2, 0, sizeof(caching_pg) - 2); + return sizeof(caching_pg); +} + +static int vdisk_ctrl_m_pg(unsigned char *p, int pcontrol, + struct scst_vdisk_dev *virt_dev) +{ /* Control mode page for mode_sense */ + const unsigned char ctrl_m_pg[] = {0xa, 0xa, 0, 0, 0, 0, 0, 0, + 0, 0, 0x2, 0x4b}; + + memcpy(p, ctrl_m_pg, sizeof(ctrl_m_pg)); + switch (pcontrol) { + case 0: + p[2] |= virt_dev->dev->tst << 5; + p[2] |= virt_dev->dev->d_sense << 2; + p[3] |= virt_dev->dev->queue_alg << 4; + p[4] |= virt_dev->dev->swp << 3; + p[5] |= virt_dev->dev->tas << 6; + break; + case 1: + memset(p + 2, 0, sizeof(ctrl_m_pg) - 2); +#if 0 /* + * It's too early to implement it, since we can't control the + * backstorage device parameters. ToDo + */ + p[2] |= 7 << 5; /* TST */ + p[3] |= 0xF << 4; /* QUEUE ALGORITHM MODIFIER */ +#endif + p[2] |= 1 << 2; /* D_SENSE */ + p[4] |= 1 << 3; /* SWP */ + p[5] |= 1 << 6; /* TAS */ + break; + case 2: + p[2] |= DEF_TST << 5; + p[2] |= DEF_DSENSE << 2; + if (virt_dev->wt_flag || virt_dev->nv_cache) + p[3] |= DEF_QUEUE_ALG_WT << 4; + else + p[3] |= DEF_QUEUE_ALG << 4; + p[4] |= DEF_SWP << 3; + p[5] |= DEF_TAS << 6; + break; + default: + BUG(); + } + return sizeof(ctrl_m_pg); +} + +static int vdisk_iec_m_pg(unsigned char *p, int pcontrol, + struct scst_vdisk_dev *virt_dev) +{ /* Informational Exceptions control mode page for mode_sense */ + const unsigned char iec_m_pg[] = {0x1c, 0xa, 0x08, 0, 0, 0, 0, 0, + 0, 0, 0x0, 0x0}; + memcpy(p, iec_m_pg, sizeof(iec_m_pg)); + if (1 == pcontrol) + memset(p + 2, 0, sizeof(iec_m_pg) - 2); + return sizeof(iec_m_pg); +} + +static void vdisk_exec_mode_sense(struct scst_cmd *cmd) +{ + int32_t length; + uint8_t *address; + uint8_t *buf; + struct scst_vdisk_dev *virt_dev; + uint32_t blocksize; + uint64_t nblocks; + unsigned char dbd, type; + int pcontrol, pcode, subpcode; + unsigned char dev_spec; + int msense_6, offset = 0, len; + unsigned char *bp; + + buf = kzalloc(MSENSE_BUF_SZ, + scst_cmd_atomic(cmd) ? GFP_ATOMIC : GFP_KERNEL); + if (buf == NULL) { + scst_set_busy(cmd); + goto out; + } + + virt_dev = (struct scst_vdisk_dev *)cmd->dev->dh_priv; + blocksize = virt_dev->block_size; + nblocks = virt_dev->nblocks; + + type = cmd->dev->type; /* type dev */ + dbd = cmd->cdb[1] & DBD; + pcontrol = (cmd->cdb[2] & 0xc0) >> 6; + pcode = cmd->cdb[2] & 0x3f; + subpcode = cmd->cdb[3]; + msense_6 = (MODE_SENSE == cmd->cdb[0]); + dev_spec = ((virt_dev->dev->rd_only || + cmd->tgt_dev->acg_dev->rd_only) ? WP : 0) | DPOFUA; + + length = scst_get_buf_first(cmd, &address); + if (unlikely(length <= 0)) { + if (length < 0) { + PRINT_ERROR("scst_get_buf_first() failed: %d", length); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_hardw_error)); + } + goto out_free; + } + + if (0x3 == pcontrol) { + TRACE_DBG("%s", "MODE SENSE: Saving values not supported"); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_saving_params_unsup)); + goto out_put; + } + + if (msense_6) { + buf[1] = type; + buf[2] = dev_spec; + offset = 4; + } else { + buf[2] = type; + buf[3] = dev_spec; + offset = 8; + } + + if (0 != subpcode) { + /* TODO: Control Extension page */ + TRACE_DBG("%s", "MODE SENSE: Only subpage 0 is supported"); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb)); + goto out_put; + } + + if (!dbd) { + /* Create block descriptor */ + buf[offset - 1] = 0x08; /* block descriptor length */ + if (nblocks >> 32) { + buf[offset + 0] = 0xFF; + buf[offset + 1] = 0xFF; + buf[offset + 2] = 0xFF; + buf[offset + 3] = 0xFF; + } else { + /* num blks */ + buf[offset + 0] = (nblocks >> (BYTE * 3)) & 0xFF; + buf[offset + 1] = (nblocks >> (BYTE * 2)) & 0xFF; + buf[offset + 2] = (nblocks >> (BYTE * 1)) & 0xFF; + buf[offset + 3] = (nblocks >> (BYTE * 0)) & 0xFF; + } + buf[offset + 4] = 0; /* density code */ + buf[offset + 5] = (blocksize >> (BYTE * 2)) & 0xFF;/* blklen */ + buf[offset + 6] = (blocksize >> (BYTE * 1)) & 0xFF; + buf[offset + 7] = (blocksize >> (BYTE * 0)) & 0xFF; + + offset += 8; /* increment offset */ + } + + bp = buf + offset; + + switch (pcode) { + case 0x1: /* Read-Write error recovery page, direct access */ + len = vdisk_err_recov_pg(bp, pcontrol, virt_dev); + break; + case 0x2: /* Disconnect-Reconnect page, all devices */ + len = vdisk_disconnect_pg(bp, pcontrol, virt_dev); + break; + case 0x3: /* Format device page, direct access */ + len = vdisk_format_pg(bp, pcontrol, virt_dev); + break; + case 0x4: /* Rigid disk geometry */ + len = vdisk_rigid_geo_pg(bp, pcontrol, virt_dev); + break; + case 0x8: /* Caching page, direct access */ + len = vdisk_caching_pg(bp, pcontrol, virt_dev); + break; + case 0xa: /* Control Mode page, all devices */ + len = vdisk_ctrl_m_pg(bp, pcontrol, virt_dev); + break; + case 0x1c: /* Informational Exceptions Mode page, all devices */ + len = vdisk_iec_m_pg(bp, pcontrol, virt_dev); + break; + case 0x3f: /* Read all Mode pages */ + len = vdisk_err_recov_pg(bp, pcontrol, virt_dev); + len += vdisk_disconnect_pg(bp + len, pcontrol, virt_dev); + len += vdisk_format_pg(bp + len, pcontrol, virt_dev); + len += vdisk_caching_pg(bp + len, pcontrol, virt_dev); + len += vdisk_ctrl_m_pg(bp + len, pcontrol, virt_dev); + len += vdisk_iec_m_pg(bp + len, pcontrol, virt_dev); + len += vdisk_rigid_geo_pg(bp + len, pcontrol, virt_dev); + break; + default: + TRACE_DBG("MODE SENSE: Unsupported page %x", pcode); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb)); + goto out_put; + } + + offset += len; + + if (msense_6) + buf[0] = offset - 1; + else { + buf[0] = ((offset - 2) >> 8) & 0xff; + buf[1] = (offset - 2) & 0xff; + } + + if (offset > length) + offset = length; + memcpy(address, buf, offset); + +out_put: + scst_put_buf(cmd, address); + if (offset < cmd->resp_data_len) + scst_set_resp_data_len(cmd, offset); + +out_free: + kfree(buf); + +out: + return; +} + +static int vdisk_set_wt(struct scst_vdisk_dev *virt_dev, int wt) +{ + int res = 0; + + if ((virt_dev->wt_flag == wt) || virt_dev->nullio || virt_dev->nv_cache) + goto out; + + spin_lock(&virt_dev->flags_lock); + virt_dev->wt_flag = wt; + spin_unlock(&virt_dev->flags_lock); + + scst_dev_del_all_thr_data(virt_dev->dev); + +out: + return res; +} + +static void vdisk_ctrl_m_pg_select(unsigned char *p, + struct scst_vdisk_dev *virt_dev) +{ + struct scst_device *dev = virt_dev->dev; + int old_swp = dev->swp, old_tas = dev->tas, old_dsense = dev->d_sense; + +#if 0 + /* Not implemented yet, see comment in vdisk_ctrl_m_pg() */ + dev->tst = p[2] >> 5; + dev->queue_alg = p[3] >> 4; +#endif + dev->swp = (p[4] & 0x8) >> 3; + dev->tas = (p[5] & 0x40) >> 6; + dev->d_sense = (p[2] & 0x4) >> 2; + + PRINT_INFO("Device %s: new control mode page parameters: SWP %x " + "(was %x), TAS %x (was %x), D_SENSE %d (was %d)", + virt_dev->name, dev->swp, old_swp, dev->tas, old_tas, + dev->d_sense, old_dsense); + return; +} + +static void vdisk_exec_mode_select(struct scst_cmd *cmd) +{ + int32_t length; + uint8_t *address; + struct scst_vdisk_dev *virt_dev; + int mselect_6, offset; + + virt_dev = (struct scst_vdisk_dev *)cmd->dev->dh_priv; + mselect_6 = (MODE_SELECT == cmd->cdb[0]); + + length = scst_get_buf_first(cmd, &address); + if (unlikely(length <= 0)) { + if (length < 0) { + PRINT_ERROR("scst_get_buf_first() failed: %d", length); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_hardw_error)); + } + goto out; + } + + if (!(cmd->cdb[1] & PF) || (cmd->cdb[1] & SP)) { + TRACE(TRACE_MINOR|TRACE_SCSI, "MODE SELECT: Unsupported " + "value(s) of PF and/or SP bits (cdb[1]=%x)", + cmd->cdb[1]); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb)); + goto out_put; + } + + if (mselect_6) + offset = 4; + else + offset = 8; + + if (address[offset - 1] == 8) { + offset += 8; + } else if (address[offset - 1] != 0) { + PRINT_ERROR("%s", "MODE SELECT: Wrong parameters list " + "lenght"); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list)); + goto out_put; + } + + while (length > offset + 2) { + if (address[offset] & PS) { + PRINT_ERROR("%s", "MODE SELECT: Illegal PS bit"); + scst_set_cmd_error(cmd, SCST_LOAD_SENSE( + scst_sense_invalid_field_in_parm_list)); + goto out_put; + } + if ((address[offset] & 0x3f) == 0x8) { + /* Caching page */ + if (address[offset + 1] != 18) { + PRINT_ERROR("%s", "MODE SELECT: Invalid " + "caching page request"); + scst_set_cmd_error(cmd, SCST_LOAD_SENSE( + scst_sense_invalid_field_in_parm_list)); + goto out_put; + } + if (vdisk_set_wt(virt_dev, + (address[offset + 2] & WCE) ? 0 : 1) != 0) { + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_hardw_error)); + goto out_put; + } + break; + } else if ((address[offset] & 0x3f) == 0xA) { + /* Control page */ + if (address[offset + 1] != 0xA) { + PRINT_ERROR("%s", "MODE SELECT: Invalid " + "control page request"); + scst_set_cmd_error(cmd, SCST_LOAD_SENSE( + scst_sense_invalid_field_in_parm_list)); + goto out_put; + } + vdisk_ctrl_m_pg_select(&address[offset], virt_dev); + } else { + PRINT_ERROR("MODE SELECT: Invalid request %x", + address[offset] & 0x3f); + scst_set_cmd_error(cmd, SCST_LOAD_SENSE( + scst_sense_invalid_field_in_parm_list)); + goto out_put; + } + offset += address[offset + 1]; + } + +out_put: + scst_put_buf(cmd, address); + +out: + return; +} + +static void vdisk_exec_log(struct scst_cmd *cmd) +{ + + /* No log pages are supported */ + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb)); + return; +} + +static void vdisk_exec_read_capacity(struct scst_cmd *cmd) +{ + int32_t length; + uint8_t *address; + struct scst_vdisk_dev *virt_dev; + uint32_t blocksize; + uint64_t nblocks; + uint8_t buffer[READ_CAP_LEN]; + + virt_dev = (struct scst_vdisk_dev *)cmd->dev->dh_priv; + blocksize = virt_dev->block_size; + nblocks = virt_dev->nblocks; + + /* last block on the virt_dev is (nblocks-1) */ + memset(buffer, 0, sizeof(buffer)); + if (nblocks >> 32) { + buffer[0] = 0xFF; + buffer[1] = 0xFF; + buffer[2] = 0xFF; + buffer[3] = 0xFF; + } else { + buffer[0] = ((nblocks - 1) >> (BYTE * 3)) & 0xFF; + buffer[1] = ((nblocks - 1) >> (BYTE * 2)) & 0xFF; + buffer[2] = ((nblocks - 1) >> (BYTE * 1)) & 0xFF; + buffer[3] = ((nblocks - 1) >> (BYTE * 0)) & 0xFF; + } + buffer[4] = (blocksize >> (BYTE * 3)) & 0xFF; + buffer[5] = (blocksize >> (BYTE * 2)) & 0xFF; + buffer[6] = (blocksize >> (BYTE * 1)) & 0xFF; + buffer[7] = (blocksize >> (BYTE * 0)) & 0xFF; + + length = scst_get_buf_first(cmd, &address); + if (unlikely(length <= 0)) { + if (length < 0) { + PRINT_ERROR("scst_get_buf_first() failed: %d", length); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_hardw_error)); + } + goto out; + } + + if (length > READ_CAP_LEN) + length = READ_CAP_LEN; + memcpy(address, buffer, length); + + scst_put_buf(cmd, address); + + if (length < cmd->resp_data_len) + scst_set_resp_data_len(cmd, length); + +out: + return; +} + +static void vdisk_exec_read_capacity16(struct scst_cmd *cmd) +{ + int32_t length; + uint8_t *address; + struct scst_vdisk_dev *virt_dev; + uint32_t blocksize; + uint64_t nblocks; + uint8_t buffer[READ_CAP16_LEN]; + + virt_dev = (struct scst_vdisk_dev *)cmd->dev->dh_priv; + blocksize = virt_dev->block_size; + nblocks = virt_dev->nblocks - 1; + + memset(buffer, 0, sizeof(buffer)); + + buffer[0] = nblocks >> 56; + buffer[1] = (nblocks >> 48) & 0xFF; + buffer[2] = (nblocks >> 40) & 0xFF; + buffer[3] = (nblocks >> 32) & 0xFF; + buffer[4] = (nblocks >> 24) & 0xFF; + buffer[5] = (nblocks >> 16) & 0xFF; + buffer[6] = (nblocks >> 8) & 0xFF; + buffer[7] = nblocks & 0xFF; + + buffer[8] = (blocksize >> (BYTE * 3)) & 0xFF; + buffer[9] = (blocksize >> (BYTE * 2)) & 0xFF; + buffer[10] = (blocksize >> (BYTE * 1)) & 0xFF; + buffer[11] = (blocksize >> (BYTE * 0)) & 0xFF; + + switch (blocksize) { + case 512: + buffer[13] = 3; + break; + case 1024: + buffer[13] = 2; + break; + case 2048: + buffer[13] = 1; + break; + default: + PRINT_ERROR("%s: Unexpected block size %d", + cmd->op_name, blocksize); + /* go through */ + case 4096: + buffer[13] = 0; + break; + } + + length = scst_get_buf_first(cmd, &address); + if (unlikely(length <= 0)) { + if (length < 0) { + PRINT_ERROR("scst_get_buf_first() failed: %d", length); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_hardw_error)); + } + goto out; + } + + /* + * Some versions of Windows have a bug, which makes them consider + * response of READ CAPACITY(16) longer than 12 bytes as a faulty one. + * As the result, such Windows'es refuse to see SCST exported + * devices >2TB in size. This is fixed by MS in latter Windows + * versions, probably, by some hotfix. + * + * But if you're using such buggy Windows and experience this problem, + * change this '1' to '0'. + */ +#if 0 /* there are too many such hosts */ + if (length > READ_CAP16_LEN) + length = READ_CAP16_LEN; +#else + if (length > 12) + length = 12; +#endif + memcpy(address, buffer, length); + + scst_put_buf(cmd, address); + + if (length < cmd->resp_data_len) + scst_set_resp_data_len(cmd, length); + +out: + return; +} + +static void vdisk_exec_read_toc(struct scst_cmd *cmd) +{ + int32_t length, off = 0; + uint8_t *address; + struct scst_vdisk_dev *virt_dev; + uint32_t nblocks; + uint8_t buffer[4+8+8] = { 0x00, 0x0a, 0x01, 0x01, 0x00, 0x14, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + if (cmd->dev->type != TYPE_ROM) { + PRINT_ERROR("%s", "READ TOC for non-CDROM device"); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_opcode)); + goto out; + } + + if (cmd->cdb[2] & 0x0e/*Format*/) { + PRINT_ERROR("%s", "READ TOC: invalid requested data format"); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb)); + goto out; + } + + if ((cmd->cdb[6] != 0 && (cmd->cdb[2] & 0x01)) || + (cmd->cdb[6] > 1 && cmd->cdb[6] != 0xAA)) { + PRINT_ERROR("READ TOC: invalid requested track number %x", + cmd->cdb[6]); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb)); + goto out; + } + + length = scst_get_buf_first(cmd, &address); + if (unlikely(length <= 0)) { + if (length < 0) { + PRINT_ERROR("scst_get_buf_first() failed: %d", length); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_hardw_error)); + } + goto out; + } + + virt_dev = (struct scst_vdisk_dev *)cmd->dev->dh_priv; + /* ToDo when you have > 8TB ROM device. */ + nblocks = (uint32_t)virt_dev->nblocks; + + /* Header */ + memset(buffer, 0, sizeof(buffer)); + buffer[2] = 0x01; /* First Track/Session */ + buffer[3] = 0x01; /* Last Track/Session */ + off = 4; + if (cmd->cdb[6] <= 1) { + /* Fistr TOC Track Descriptor */ + /* ADDR 0x10 - Q Sub-channel encodes current position data + CONTROL 0x04 - Data track, recoreded uninterrupted */ + buffer[off+1] = 0x14; + /* Track Number */ + buffer[off+2] = 0x01; + off += 8; + } + if (!(cmd->cdb[2] & 0x01)) { + /* Lead-out area TOC Track Descriptor */ + buffer[off+1] = 0x14; + /* Track Number */ + buffer[off+2] = 0xAA; + /* Track Start Address */ + buffer[off+4] = (nblocks >> (BYTE * 3)) & 0xFF; + buffer[off+5] = (nblocks >> (BYTE * 2)) & 0xFF; + buffer[off+6] = (nblocks >> (BYTE * 1)) & 0xFF; + buffer[off+7] = (nblocks >> (BYTE * 0)) & 0xFF; + off += 8; + } + + buffer[1] = off - 2; /* Data Length */ + + if (off > length) + off = length; + memcpy(address, buffer, off); + + scst_put_buf(cmd, address); + + if (off < cmd->resp_data_len) + scst_set_resp_data_len(cmd, off); + +out: + return; +} + +static void vdisk_exec_prevent_allow_medium_removal(struct scst_cmd *cmd) +{ + struct scst_vdisk_dev *virt_dev = + (struct scst_vdisk_dev *)cmd->dev->dh_priv; + + TRACE_DBG("PERSIST/PREVENT 0x%02x", cmd->cdb[4]); + + if (cmd->dev->type == TYPE_ROM) { + spin_lock(&virt_dev->flags_lock); + virt_dev->prevent_allow_medium_removal = + cmd->cdb[4] & 0x01 ? 1 : 0; + spin_unlock(&virt_dev->flags_lock); + } + + return; +} + +static int vdisk_fsync(struct scst_vdisk_thr *thr, loff_t loff, + loff_t len, struct scst_cmd *cmd, struct scst_device *dev) +{ + int res = 0; + struct scst_vdisk_dev *virt_dev = + (struct scst_vdisk_dev *)dev->dh_priv; + struct file *file = thr->fd; + + /* Hopefully, the compiler will generate the single comparison */ + if (virt_dev->nv_cache || virt_dev->blockio || virt_dev->wt_flag || + virt_dev->o_direct_flag || virt_dev->nullio) + goto out; + +#if 0 /* For sparse files we might need to sync metadata as well */ + res = generic_write_sync(file, loff, len); +#else + res = filemap_write_and_wait_range(file->f_mapping, loff, len); +#endif + if (unlikely(res != 0)) { + PRINT_ERROR("sync range failed (%d)", res); + if (cmd != NULL) { + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_write_error)); + } + } + + /* ToDo: flush the device cache, if needed */ + +out: + return res; +} + +static struct iovec *vdisk_alloc_iv(struct scst_cmd *cmd, + struct scst_vdisk_thr *thr) +{ + int iv_count; + + iv_count = min_t(int, scst_get_buf_count(cmd), UIO_MAXIOV); + if (iv_count > thr->iv_count) { + kfree(thr->iv); + /* It can't be called in atomic context */ + thr->iv = kmalloc(sizeof(*thr->iv) * iv_count, GFP_KERNEL); + if (thr->iv == NULL) { + PRINT_ERROR("Unable to allocate iv (%d)", iv_count); + scst_set_busy(cmd); + goto out; + } + thr->iv_count = iv_count; + } + +out: + return thr->iv; +} + +static void vdisk_exec_read(struct scst_cmd *cmd, + struct scst_vdisk_thr *thr, loff_t loff) +{ + mm_segment_t old_fs; + loff_t err; + ssize_t length, full_len; + uint8_t __user *address; + struct scst_vdisk_dev *virt_dev = + (struct scst_vdisk_dev *)cmd->dev->dh_priv; + struct file *fd = thr->fd; + struct iovec *iv; + int iv_count, i; + bool finished = false; + + if (virt_dev->nullio) + goto out; + + iv = vdisk_alloc_iv(cmd, thr); + if (iv == NULL) + goto out; + + length = scst_get_buf_first(cmd, (uint8_t __force **)&address); + if (unlikely(length < 0)) { + PRINT_ERROR("scst_get_buf_first() failed: %zd", length); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_hardw_error)); + goto out; + } + + old_fs = get_fs(); + set_fs(get_ds()); + + while (1) { + iv_count = 0; + full_len = 0; + i = -1; + while (length > 0) { + full_len += length; + i++; + iv_count++; + iv[i].iov_base = address; + iv[i].iov_len = length; + if (iv_count == UIO_MAXIOV) + break; + length = scst_get_buf_next(cmd, + (uint8_t __force **)&address); + } + if (length == 0) { + finished = true; + if (unlikely(iv_count == 0)) + break; + } else if (unlikely(length < 0)) { + PRINT_ERROR("scst_get_buf_next() failed: %zd", length); + scst_set_cmd_error(cmd, + SCST_LOAD_SENSE(scst_sense_hardw_error)); + goto out_set_fs; + } + + TRACE_DBG("(iv_count %d, full_len %zd)", iv_count, full_len); + /* SEEK */ + if (f
|
Pages: 1 Prev: SCST pass-through dev handlers Next: Msn Payment Approved |