lkml.org 
[lkml]   [2010]   [Sep]   [14]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
Date
From
Subject[PATCH 13/17]: SCST vdisk dev handler
This patch contains SCST vdisk dev handler. This dev handler allows
to create virtual disks and CDROMs from files on file system.

Signed-off-by: Vladislav Bolkhovitin <vst@vlnb.net>
---
scst_vdisk.c | 3966 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 3966 insertions(+)
diff -uprN orig/linux-2.6.35/drivers/scst/dev_handlers/scst_vdisk.c linux-2.6.35/drivers/scst/dev_handlers/scst_vdisk.c
--- orig/linux-2.6.35/drivers/scst/dev_handlers/scst_vdisk.c
+++ linux-2.6.35/drivers/scst/dev_handlers/scst_vdisk.c
@@ -0,0 +1,3966 @@
+/*
+ * scst_vdisk.c
+ *
+ * Copyright (C) 2004 - 2010 Vladislav Bolkhovitin <vst@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>
+#include <asm/unaligned.h>
+#include <linux/slab.h>
+#include <linux/bio.h>
+
+#define LOG_PREFIX "dev_vdisk"
+
+#include <scst/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 " 210"
+
+#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 */
+ uint16_t command_set_version;
+ 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 scst_dev_type *vdev_devt;
+};
+
+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 int blockio_flush(struct block_device *bdev);
+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_nv_cache_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,
+ .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 = "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 = 1,
+ .parse_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_blockio_device,
+ .del_device = vdisk_del_device,
+ .dev_attrs = vdisk_blockio_attrs,
+ .add_device_parameters = "filename, blocksize, nv_cache, 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,
+ .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 = "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,
+ .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 = 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;
+}
+
+static void vdisk_blockio_check_flush_support(struct scst_vdisk_dev *virt_dev)
+{
+ struct inode *inode;
+ struct file *fd;
+
+ if (!virt_dev->blockio || virt_dev->rd_only || virt_dev->nv_cache)
+ goto out;
+
+ fd = filp_open(virt_dev->filename, O_LARGEFILE, 0600);
+ if (IS_ERR(fd)) {
+ PRINT_ERROR("filp_open(%s) returned error %ld",
+ virt_dev->filename, PTR_ERR(fd));
+ goto out;
+ }
+
+ inode = fd->f_dentry->d_inode;
+
+ if (!S_ISBLK(inode->i_mode)) {
+ PRINT_ERROR("%s is NOT a block device", virt_dev->filename);
+ goto out_close;
+ }
+
+ if (blockio_flush(inode->i_bdev) != 0) {
+ PRINT_WARNING("Device %s doesn't support barriers, switching "
+ "to NV_CACHE mode. Read README for more details.",
+ virt_dev->filename);
+ virt_dev->nv_cache = 1;
+ }
+
+out_close:
+ filp_close(fd, NULL);
+
+out:
+ return;
+}
+
+/* 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);
+
+ vdisk_blockio_check_flush_support(virt_dev);
+ } 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 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)
+{
+ int res = 0;
+
+ /* Nothing to do */
+ return res;
+}
+
+static void vdisk_detach_tgt(struct scst_tgt_dev *tgt_dev)
+{
+
+ scst_del_all_thr_data(tgt_dev);
+ return;
+}
+
+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:
+ {
+ 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 (fua)
+ vdisk_fsync(thr, loff, data_len, cmd, dev);
+ break;
+ }
+ case WRITE_VERIFY:
+ case WRITE_VERIFY_12:
+ case WRITE_VERIFY_16:
+ {
+ /* 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);
+ 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, 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 */
+ if (virt_dev->dev->type == TYPE_DISK) {
+ buf[3] += 1;
+ buf[7] = 0xB0; /* block limits */
+ }
+ 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;
+
+ /*
+ * Relative target port identifier
+ */
+ buf[num + 0] = 0x01; /* binary */
+ /* Relative target port id */
+ buf[num + 1] = 0x10 | 0x04;
+
+ put_unaligned(cpu_to_be16(cmd->tgt->rel_tgt_id),
+ (__be16 *)&buf[num + 4 + 2]);
+
+ buf[num + 3] = 4;
+ num += buf[num + 3];
+
+ num += 4;
+
+ /*
+ * IEEE id
+ */
+ buf[num + 0] = 0x01; /* binary */
+
+ /* 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 if ((0xB0 == cmd->cdb[2]) &&
+ (virt_dev->dev->type == TYPE_DISK)) {
+ /* block limits */
+ int max_transfer;
+ buf[1] = 0xB0;
+ buf[3] = 0x1C;
+ /* Optimal transfer granuality is PAGE_SIZE */
+ put_unaligned(cpu_to_be16(max_t(int,
+ PAGE_SIZE/virt_dev->block_size, 1)),
+ (uint16_t *)&buf[6]);
+ /* Max transfer len is min of sg limit and 8M */
+ max_transfer = min_t(int,
+ cmd->tgt_dev->max_sg_cnt << PAGE_SHIFT,
+ 8*1024*1024) / virt_dev->block_size;
+ put_unaligned(cpu_to_be32(max_transfer),
+ (uint32_t *)&buf[8]);
+ /*
+ * Let's have optimal transfer len 1MB. Better to not
+ * set it at all, because we don't have such limit,
+ * but some initiators may not understand that (?).
+ * From other side, too big transfers are not optimal,
+ * because SGV cache supports only <4M buffers.
+ */
+ put_unaligned(cpu_to_be32(min_t(int,
+ max_transfer,
+ 1*1024*1024 / virt_dev->block_size)),
+ (uint32_t *)&buf[12]);
+ resp_len = buf[3] + 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, num;
+
+ 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);
+
+ /** Version descriptors **/
+
+ buf[4] += 58 - 36;
+ num = 0;
+
+ /* SAM-3 T10/1561-D revision 14 */
+ buf[58 + num] = 0x0;
+ buf[58 + num + 1] = 0x76;
+ num += 2;
+
+ /* Physical transport */
+ if (cmd->tgtt->get_phys_transport_version != NULL) {
+ uint16_t v = cmd->tgtt->get_phys_transport_version(cmd->tgt);
+ if (v != 0) {
+ *((__be16 *)&buf[58 + num]) = cpu_to_be16(v);
+ num += 2;
+ }
+ }
+
+ /* SCSI transport */
+ if (cmd->tgtt->get_scsi_transport_version != NULL) {
+ *((__be16 *)&buf[58 + num]) =
+ cpu_to_be16(cmd->tgtt->get_scsi_transport_version(cmd->tgt));
+ num += 2;
+ }
+
+ /* SPC-3 T10/1416-D revision 23 */
+ buf[58 + num] = 0x3;
+ buf[58 + num + 1] = 0x12;
+ num += 2;
+
+ /* Device command set */
+ if (virt_dev->command_set_version != 0) {
+ *((__be16 *)&buf[58 + num]) =
+ cpu_to_be16(virt_dev->command_set_version);
+ num += 2;
+ }
+
+ buf[4] += num;
+ 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 | ((__force u32)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, 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;
+
+ if (!virt_dev->blockio)
+ dev_spec |= 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[8];
+
+ virt_dev = (struct scst_vdisk_dev *)cmd->dev->dh_priv;
+ blocksize = virt_dev->block_size;
+ nblocks = virt_dev->nblocks;
+
+ if ((cmd->cdb[8] & 1) == 0) {
+ uint64_t lba = be64_to_cpu(get_unaligned((__be64 *)&cmd->cdb[2]));
+ if (lba != 0) {
+ TRACE_DBG("PMI zero and LBA not zero (cmd %p)", cmd);
+ scst_set_cmd_error(cmd,
+ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+ goto out;
+ }
+ }
+
+ /* 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;
+ }
+
+ length = min_t(int, length, sizeof(buffer));
+
+ 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[32];
+
+ virt_dev = (struct scst_vdisk_dev *)cmd->dev->dh_priv;
+ blocksize = virt_dev->block_size;
+ nblocks = virt_dev->nblocks - 1;
+
+ if ((cmd->cdb[14] & 1) == 0) {
+ uint64_t lba = be64_to_cpu(get_unaligned((__be64 *)&cmd->cdb[2]));
+ if (lba != 0) {
+ TRACE_DBG("PMI zero and LBA not zero (cmd %p)", cmd);
+ scst_set_cmd_error(cmd,
+ SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
+ goto out;
+ }
+ }
+
+ 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;
+ case 4096:
+ default:
+ 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;
+ }
+
+ length = min_t(int, length, sizeof(buffer));
+
+ 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]);
+
+ 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;
+
+ /* Hopefully, the compiler will generate the single comparison */
+ if (virt_dev->nv_cache || virt_dev->wt_flag ||
+ virt_dev->o_direct_flag || virt_dev->nullio)
+ goto out;
+
+ if (virt_dev->blockio) {
+ res = blockio_flush(thr->bdev);
+ goto out;
+ }
+
+ file = thr->fd;
+
+#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));
+ }
+ }
+
+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 (fd->f_op->llseek)
+ err = fd->f_op->llseek(fd, loff, 0/*SEEK_SET*/);
+ else
+ err = default_llseek(fd, loff, 0/*SEEK_SET*/);
+ if (err != loff) {
+ PRINT_ERROR("lseek trouble %lld != %lld",
+ (long long unsigned int)err,
+ (long long unsigned int)loff);
+ scst_set_cmd_error(cmd,
+ SCST_LOAD_SENSE(scst_sense_hardw_error));
+ goto out_set_fs;
+ }
+
+ /* READ */
+ err = vfs_readv(fd, (struct iovec __force __user *)iv, iv_count,
+ &fd->f_pos);
+
+ if ((err < 0) || (err < full_len)) {
+ PRINT_ERROR("readv() returned %lld from %zd",
+ (long long unsigned int)err,
+ full_len);
+ if (err == -EAGAIN)
+ scst_set_busy(cmd);
+ else {
+ scst_set_cmd_error(cmd,
+ SCST_LOAD_SENSE(scst_sense_read_error));
+ }
+ goto out_set_fs;
+ }
+
+ for (i = 0; i < iv_count; i++)
+ scst_put_buf(cmd, (void __force *)(iv[i].iov_base));
+
+ if (finished)
+ break;
+
+ loff += full_len;
+ length = scst_get_buf_next(cmd, (uint8_t __force **)&address);
+ };
+
+ set_fs(old_fs);
+
+out:
+ return;
+
+out_set_fs:
+ set_fs(old_fs);
+ for (i = 0; i < iv_count; i++)
+ scst_put_buf(cmd, (void __force *)(iv[i].iov_base));
+ goto out;
+}
+
+static void vdisk_exec_write(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, saved_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, *eiv;
+ int i, iv_count, eiv_count;
+ 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;
+ }
+
+ saved_full_len = full_len;
+ eiv = iv;
+ eiv_count = iv_count;
+restart:
+ TRACE_DBG("writing(eiv_count %d, full_len %zd)", eiv_count, full_len);
+
+ /* SEEK */
+ if (fd->f_op->llseek)
+ err = fd->f_op->llseek(fd, loff, 0 /*SEEK_SET */);
+ else
+ err = default_llseek(fd, loff, 0 /*SEEK_SET */);
+ if (err != loff) {
+ PRINT_ERROR("lseek trouble %lld != %lld",
+ (long long unsigned int)err,
+ (long long unsigned int)loff);
+ scst_set_cmd_error(cmd,
+ SCST_LOAD_SENSE(scst_sense_hardw_error));
+ goto out_set_fs;
+ }
+
+ /* WRITE */
+ err = vfs_writev(fd, (struct iovec __force __user *)eiv, eiv_count,
+ &fd->f_pos);
+
+ if (err < 0) {
+ PRINT_ERROR("write() returned %lld from %zd",
+ (long long unsigned int)err,
+ full_len);
+ if (err == -EAGAIN)
+ scst_set_busy(cmd);
+ else {
+ scst_set_cmd_error(cmd,
+ SCST_LOAD_SENSE(scst_sense_write_error));
+ }
+ goto out_set_fs;
+ } else if (err < full_len) {
+ /*
+ * Probably that's wrong, but sometimes write() returns
+ * value less, than requested. Let's restart.
+ */
+ int e = eiv_count;
+ TRACE_MGMT_DBG("write() returned %d from %zd "
+ "(iv_count=%d)", (int)err, full_len,
+ eiv_count);
+ if (err == 0) {
+ PRINT_INFO("Suspicious: write() returned 0 from "
+ "%zd (iv_count=%d)", full_len, eiv_count);
+ }
+ full_len -= err;
+ for (i = 0; i < e; i++) {
+ if ((long long)eiv->iov_len < err) {
+ err -= eiv->iov_len;
+ eiv++;
+ eiv_count--;
+ } else {
+ eiv->iov_base =
+ (uint8_t __force __user *)eiv->iov_base + err;
+ eiv->iov_len -= err;
+ break;
+ }
+ }
+ goto restart;
+ }
+
+ for (i = 0; i < iv_count; i++)
+ scst_put_buf(cmd, (void __force *)(iv[i].iov_base));
+
+ if (finished)
+ break;
+
+ loff += saved_full_len;
+ length = scst_get_buf_next(cmd, (uint8_t __force **)&address);
+ }
+
+ set_fs(old_fs);
+
+out:
+ return;
+
+out_set_fs:
+ set_fs(old_fs);
+ for (i = 0; i < iv_count; i++)
+ scst_put_buf(cmd, (void __force *)(iv[i].iov_base));
+ goto out;
+}
+
+struct scst_blockio_work {
+ atomic_t bios_inflight;
+ struct scst_cmd *cmd;
+};
+
+static inline void blockio_check_finish(struct scst_blockio_work *blockio_work)
+{
+ /* Decrement the bios in processing, and if zero signal completion */
+ if (atomic_dec_and_test(&blockio_work->bios_inflight)) {
+ blockio_work->cmd->completed = 1;
+ blockio_work->cmd->scst_cmd_done(blockio_work->cmd,
+ SCST_CMD_STATE_DEFAULT, scst_estimate_context());
+ kmem_cache_free(blockio_work_cachep, blockio_work);
+ }
+ return;
+}
+
+static void blockio_endio(struct bio *bio, int error)
+{
+ struct scst_blockio_work *blockio_work = bio->bi_private;
+
+ if (unlikely(!bio_flagged(bio, BIO_UPTODATE))) {
+ if (error == 0) {
+ PRINT_ERROR("Not up to date bio with error 0 for "
+ "cmd %p, returning -EIO", blockio_work->cmd);
+ error = -EIO;
+ }
+ }
+
+ if (unlikely(error != 0)) {
+ static DEFINE_SPINLOCK(blockio_endio_lock);
+
+ PRINT_ERROR("cmd %p returned error %d", blockio_work->cmd,
+ error);
+
+ /* To protect from several bios finishing simultaneously */
+ spin_lock_bh(&blockio_endio_lock);
+
+ if (bio->bi_rw & (1 << BIO_RW))
+ scst_set_cmd_error(blockio_work->cmd,
+ SCST_LOAD_SENSE(scst_sense_write_error));
+ else
+ scst_set_cmd_error(blockio_work->cmd,
+ SCST_LOAD_SENSE(scst_sense_read_error));
+
+ spin_unlock_bh(&blockio_endio_lock);
+ }
+
+ blockio_check_finish(blockio_work);
+
+ bio_put(bio);
+ return;
+}
+
+static void blockio_exec_rw(struct scst_cmd *cmd, struct scst_vdisk_thr *thr,
+ u64 lba_start, int write)
+{
+ struct scst_vdisk_dev *virt_dev =
+ (struct scst_vdisk_dev *)cmd->dev->dh_priv;
+ struct block_device *bdev = thr->bdev;
+ struct request_queue *q = bdev_get_queue(bdev);
+ int length, max_nr_vecs = 0;
+ uint8_t *address;
+ struct bio *bio = NULL, *hbio = NULL, *tbio = NULL;
+ int need_new_bio;
+ struct scst_blockio_work *blockio_work;
+ int bios = 0;
+
+ if (virt_dev->nullio)
+ goto out;
+
+ /* Allocate and initialize blockio_work struct */
+ blockio_work = kmem_cache_alloc(blockio_work_cachep, GFP_KERNEL);
+ if (blockio_work == NULL)
+ goto out_no_mem;
+
+ blockio_work->cmd = cmd;
+
+ if (q)
+ max_nr_vecs = min(bio_get_nr_vecs(bdev), BIO_MAX_PAGES);
+ else
+ max_nr_vecs = 1;
+
+ need_new_bio = 1;
+
+ length = scst_get_buf_first(cmd, &address);
+ while (length > 0) {
+ int len, bytes, off, thislen;
+ uint8_t *addr;
+ u64 lba_start0;
+
+ addr = address;
+ off = offset_in_page(addr);
+ len = length;
+ thislen = 0;
+ lba_start0 = lba_start;
+
+ while (len > 0) {
+ int rc;
+ struct page *page = virt_to_page(addr);
+
+ if (need_new_bio) {
+ bio = bio_kmalloc(GFP_KERNEL, max_nr_vecs);
+ if (!bio) {
+ PRINT_ERROR("Failed to create bio "
+ "for data segment %d (cmd %p)",
+ cmd->get_sg_buf_entry_num, cmd);
+ goto out_no_bio;
+ }
+
+ bios++;
+ need_new_bio = 0;
+ bio->bi_end_io = blockio_endio;
+ bio->bi_sector = lba_start0 <<
+ (virt_dev->block_shift - 9);
+ bio->bi_bdev = bdev;
+ bio->bi_private = blockio_work;
+ /*
+ * Better to fail fast w/o any local recovery
+ * and retries.
+ */
+#ifdef BIO_RW_FAILFAST
+ bio->bi_rw |= (1 << BIO_RW_FAILFAST);
+#else
+ bio->bi_rw |= (1 << BIO_RW_FAILFAST_DEV) |
+ (1 << BIO_RW_FAILFAST_TRANSPORT) |
+ (1 << BIO_RW_FAILFAST_DRIVER);
+#endif
+#if 0 /* It could be win, but could be not, so a performance study is needed */
+ bio->bi_rw |= 1 << BIO_RW_SYNC;
+#endif
+ if (!hbio)
+ hbio = tbio = bio;
+ else
+ tbio = tbio->bi_next = bio;
+ }
+
+ bytes = min_t(unsigned int, len, PAGE_SIZE - off);
+
+ rc = bio_add_page(bio, page, bytes, off);
+ if (rc < bytes) {
+ BUG_ON(rc != 0);
+ need_new_bio = 1;
+ lba_start0 += thislen >> virt_dev->block_shift;
+ thislen = 0;
+ continue;
+ }
+
+ addr += PAGE_SIZE;
+ thislen += bytes;
+ len -= bytes;
+ off = 0;
+ }
+
+ lba_start += length >> virt_dev->block_shift;
+
+ scst_put_buf(cmd, address);
+ length = scst_get_buf_next(cmd, &address);
+ }
+
+ /* +1 to prevent erroneous too early command completion */
+ atomic_set(&blockio_work->bios_inflight, bios+1);
+
+ while (hbio) {
+ bio = hbio;
+ hbio = hbio->bi_next;
+ bio->bi_next = NULL;
+ submit_bio((write != 0), bio);
+ }
+
+ if (q && q->unplug_fn)
+ q->unplug_fn(q);
+
+ blockio_check_finish(blockio_work);
+
+out:
+ return;
+
+out_no_bio:
+ while (hbio) {
+ bio = hbio;
+ hbio = hbio->bi_next;
+ bio_put(bio);
+ }
+ kmem_cache_free(blockio_work_cachep, blockio_work);
+
+out_no_mem:
+ scst_set_busy(cmd);
+ goto out;
+}
+
+static int blockio_flush(struct block_device *bdev)
+{
+ int res = 0;
+
+ res = blkdev_issue_flush(bdev, GFP_KERNEL, NULL, BLKDEV_IFL_WAIT);
+ if (res != 0)
+ PRINT_ERROR("blkdev_issue_flush() failed: %d", res);
+ return res;
+}
+
+static void vdisk_exec_verify(struct scst_cmd *cmd,
+ struct scst_vdisk_thr *thr, loff_t loff)
+{
+ mm_segment_t old_fs;
+ loff_t err;
+ ssize_t length, len_mem = 0;
+ uint8_t *address_sav, *address;
+ int compare;
+ struct scst_vdisk_dev *virt_dev =
+ (struct scst_vdisk_dev *)cmd->dev->dh_priv;
+ struct file *fd = thr->fd;
+ uint8_t *mem_verify = NULL;
+
+ if (vdisk_fsync(thr, loff, cmd->bufflen, cmd, cmd->dev) != 0)
+ goto out;
+
+ /*
+ * Until the cache is cleared prior the verifying, there is not
+ * much point in this code. ToDo.
+ *
+ * Nevertherless, this code is valuable if the data have not read
+ * from the file/disk yet.
+ */
+
+ /* SEEK */
+ old_fs = get_fs();
+ set_fs(get_ds());
+
+ if (!virt_dev->nullio) {
+ if (fd->f_op->llseek)
+ err = fd->f_op->llseek(fd, loff, 0/*SEEK_SET*/);
+ else
+ err = default_llseek(fd, loff, 0/*SEEK_SET*/);
+ if (err != loff) {
+ PRINT_ERROR("lseek trouble %lld != %lld",
+ (long long unsigned int)err,
+ (long long unsigned int)loff);
+ scst_set_cmd_error(cmd,
+ SCST_LOAD_SENSE(scst_sense_hardw_error));
+ goto out_set_fs;
+ }
+ }
+
+ mem_verify = vmalloc(LEN_MEM);
+ if (mem_verify == NULL) {
+ PRINT_ERROR("Unable to allocate memory %d for verify",
+ LEN_MEM);
+ scst_set_cmd_error(cmd,
+ SCST_LOAD_SENSE(scst_sense_hardw_error));
+ goto out_set_fs;
+ }
+
+ length = scst_get_buf_first(cmd, &address);
+ address_sav = address;
+ if (!length && cmd->data_len) {
+ length = cmd->data_len;
+ compare = 0;
+ } else
+ compare = 1;
+
+ while (length > 0) {
+ len_mem = (length > LEN_MEM) ? LEN_MEM : length;
+ TRACE_DBG("Verify: length %zd - len_mem %zd", length, len_mem);
+
+ if (!virt_dev->nullio)
+ err = vfs_read(fd, (char __force __user *)mem_verify,
+ len_mem, &fd->f_pos);
+ else
+ err = len_mem;
+ if ((err < 0) || (err < len_mem)) {
+ PRINT_ERROR("verify() returned %lld from %zd",
+ (long long unsigned int)err, len_mem);
+ if (err == -EAGAIN)
+ scst_set_busy(cmd);
+ else {
+ scst_set_cmd_error(cmd,
+ SCST_LOAD_SENSE(scst_sense_read_error));
+ }
+ if (compare)
+ scst_put_buf(cmd, address_sav);
+ goto out_set_fs;
+ }
+ if (compare && memcmp(address, mem_verify, len_mem) != 0) {
+ TRACE_DBG("Verify: error memcmp length %zd", length);
+ scst_set_cmd_error(cmd,
+ SCST_LOAD_SENSE(scst_sense_miscompare_error));
+ scst_put_buf(cmd, address_sav);
+ goto out_set_fs;
+ }
+ length -= len_mem;
+ address += len_mem;
+ if (compare && length <= 0) {
+ scst_put_buf(cmd, address_sav);
+ length = scst_get_buf_next(cmd, &address);
+ address_sav = address;
+ }
+ }
+
+ if (length < 0) {
+ PRINT_ERROR("scst_get_buf_() failed: %zd", length);
+ scst_set_cmd_error(cmd,
+ SCST_LOAD_SENSE(scst_sense_hardw_error));
+ }
+
+out_set_fs:
+ set_fs(old_fs);
+ if (mem_verify)
+ vfree(mem_verify);
+
+out:
+ return;
+}
+
+static int vdisk_task_mgmt_fn(struct scst_mgmt_cmd *mcmd,
+ struct scst_tgt_dev *tgt_dev)
+{
+
+ if ((mcmd->fn == SCST_LUN_RESET) || (mcmd->fn == SCST_TARGET_RESET)) {
+ /* Restore default values */
+ struct scst_device *dev = tgt_dev->dev;
+ struct scst_vdisk_dev *virt_dev =
+ (struct scst_vdisk_dev *)dev->dh_priv;
+
+ 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;
+
+ spin_lock(&virt_dev->flags_lock);
+ virt_dev->prevent_allow_medium_removal = 0;
+ spin_unlock(&virt_dev->flags_lock);
+ } else if (mcmd->fn == SCST_PR_ABORT_ALL) {
+ struct scst_device *dev = tgt_dev->dev;
+ struct scst_vdisk_dev *virt_dev =
+ (struct scst_vdisk_dev *)dev->dh_priv;
+ spin_lock(&virt_dev->flags_lock);
+ virt_dev->prevent_allow_medium_removal = 0;
+ spin_unlock(&virt_dev->flags_lock);
+ }
+ return SCST_DEV_TM_NOT_COMPLETED;
+}
+
+static void vdisk_report_registering(const struct scst_vdisk_dev *virt_dev)
+{
+ char buf[128];
+ int i, j;
+
+ i = snprintf(buf, sizeof(buf), "Registering virtual %s device %s ",
+ virt_dev->vdev_devt->name, virt_dev->name);
+ j = i;
+
+ if (virt_dev->wt_flag)
+ i += snprintf(&buf[i], sizeof(buf) - i, "(WRITE_THROUGH");
+
+ if (virt_dev->nv_cache)
+ i += snprintf(&buf[i], sizeof(buf) - i, "%sNV_CACHE",
+ (j == i) ? "(" : ", ");
+
+ if (virt_dev->rd_only)
+ i += snprintf(&buf[i], sizeof(buf) - i, "%sREAD_ONLY",
+ (j == i) ? "(" : ", ");
+
+ if (virt_dev->o_direct_flag)
+ i += snprintf(&buf[i], sizeof(buf) - i, "%sO_DIRECT",
+ (j == i) ? "(" : ", ");
+
+ if (virt_dev->nullio)
+ i += snprintf(&buf[i], sizeof(buf) - i, "%sNULLIO",
+ (j == i) ? "(" : ", ");
+
+ if (virt_dev->blockio)
+ i += snprintf(&buf[i], sizeof(buf) - i, "%sBLOCKIO",
+ (j == i) ? "(" : ", ");
+
+ if (virt_dev->removable)
+ i += snprintf(&buf[i], sizeof(buf) - i, "%sREMOVABLE",
+ (j == i) ? "(" : ", ");
+
+ if (j == i)
+ PRINT_INFO("%s", buf);
+ else
+ PRINT_INFO("%s)", buf);
+
+ return;
+}
+
+static int vdisk_resync_size(struct scst_vdisk_dev *virt_dev)
+{
+ loff_t file_size;
+ int res = 0;
+
+ BUG_ON(virt_dev->nullio);
+
+ res = vdisk_get_file_size(virt_dev->filename,
+ virt_dev->blockio, &file_size);
+ if (res != 0)
+ goto out;
+
+ if (file_size == virt_dev->file_size) {
+ PRINT_INFO("Size of virtual disk %s remained the same",
+ virt_dev->name);
+ goto out;
+ }
+
+ res = scst_suspend_activity(true);
+ if (res != 0)
+ goto out;
+
+ virt_dev->file_size = file_size;
+ virt_dev->nblocks = virt_dev->file_size >> virt_dev->block_shift;
+
+ scst_dev_del_all_thr_data(virt_dev->dev);
+
+ PRINT_INFO("New size of SCSI target virtual disk %s "
+ "(fs=%lldMB, bs=%d, nblocks=%lld, cyln=%lld%s)",
+ virt_dev->name, 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" : "");
+
+ scst_capacity_data_changed(virt_dev->dev);
+
+ scst_resume_activity();
+
+out:
+ return res;
+}
+
+static int vdev_create(struct scst_dev_type *devt,
+ const char *name, struct scst_vdisk_dev **res_virt_dev)
+{
+ int res = 0;
+ struct scst_vdisk_dev *virt_dev;
+ uint64_t dev_id_num;
+ int dev_id_len;
+ char dev_id_str[17];
+ int32_t i;
+
+ virt_dev = kzalloc(sizeof(*virt_dev), GFP_KERNEL);
+ if (virt_dev == NULL) {
+ PRINT_ERROR("Allocation of virtual device %s failed",
+ devt->name);
+ res = -ENOMEM;
+ goto out;
+ }
+
+ spin_lock_init(&virt_dev->flags_lock);
+ virt_dev->vdev_devt = devt;
+
+ virt_dev->rd_only = DEF_RD_ONLY;
+ virt_dev->removable = DEF_REMOVABLE;
+
+ virt_dev->block_size = DEF_DISK_BLOCKSIZE;
+ virt_dev->block_shift = DEF_DISK_BLOCKSIZE_SHIFT;
+
+ if (strlen(name) >= sizeof(virt_dev->name)) {
+ PRINT_ERROR("Name %s is too long (max allowed %zd)", name,
+ sizeof(virt_dev->name)-1);
+ res = -EINVAL;
+ goto out_free;
+ }
+ strcpy(virt_dev->name, name);
+
+ dev_id_num = vdisk_gen_dev_id_num(virt_dev->name);
+ dev_id_len = scnprintf(dev_id_str, sizeof(dev_id_str), "%llx",
+ dev_id_num);
+
+ i = strlen(virt_dev->name) + 1; /* for ' ' */
+ memset(virt_dev->t10_dev_id, ' ', i + dev_id_len);
+ memcpy(virt_dev->t10_dev_id, virt_dev->name, i-1);
+ memcpy(virt_dev->t10_dev_id + i, dev_id_str, dev_id_len);
+ TRACE_DBG("t10_dev_id %s", virt_dev->t10_dev_id);
+
+ scnprintf(virt_dev->usn, sizeof(virt_dev->usn), "%llx", dev_id_num);
+ TRACE_DBG("usn %s", virt_dev->usn);
+
+ *res_virt_dev = virt_dev;
+
+out:
+ return res;
+
+out_free:
+ kfree(virt_dev);
+ goto out;
+}
+
+static void vdev_destroy(struct scst_vdisk_dev *virt_dev)
+{
+ kfree(virt_dev->filename);
+ kfree(virt_dev);
+ return;
+}
+
+/* scst_vdisk_mutex supposed to be held */
+static struct scst_vdisk_dev *vdev_find(const char *name)
+{
+ struct scst_vdisk_dev *res, *vv;
+
+ res = NULL;
+ list_for_each_entry(vv, &vdev_list, vdev_list_entry) {
+ if (strcmp(vv->name, name) == 0) {
+ res = vv;
+ break;
+ }
+ }
+ return res;
+}
+
+static int vdev_parse_add_dev_params(struct scst_vdisk_dev *virt_dev,
+ char *params, const char *allowed_params[])
+{
+ int res = 0;
+ unsigned long val;
+ char *param, *p, *pp;
+
+ while (1) {
+ param = scst_get_next_token_str(&params);
+ if (param == NULL)
+ break;
+
+ p = scst_get_next_lexem(&param);
+ if (*p == '\0') {
+ PRINT_ERROR("Syntax error at %s (device %s)",
+ param, virt_dev->name);
+ res = -EINVAL;
+ goto out;
+ }
+
+ if (allowed_params != NULL) {
+ const char **a = allowed_params;
+ bool allowed = false;
+
+ while (*a != NULL) {
+ if (!strcasecmp(*a, p)) {
+ allowed = true;
+ break;
+ }
+ a++;
+ }
+
+ if (!allowed) {
+ PRINT_ERROR("Unknown parameter %s (device %s)", p,
+ virt_dev->name);
+ res = -EINVAL;
+ goto out;
+ }
+ }
+
+ pp = scst_get_next_lexem(&param);
+ if (*pp == '\0') {
+ PRINT_ERROR("Parameter %s value missed for device %s",
+ p, virt_dev->name);
+ res = -EINVAL;
+ goto out;
+ }
+
+ if (scst_get_next_lexem(&param)[0] != '\0') {
+ PRINT_ERROR("Too many parameter's %s values (device %s)",
+ p, virt_dev->name);
+ res = -EINVAL;
+ goto out;
+ }
+
+ if (!strcasecmp("filename", p)) {
+ if (*pp != '/') {
+ PRINT_ERROR("Filename %s must be global "
+ "(device %s)", pp, virt_dev->name);
+ res = -EINVAL;
+ goto out;
+ }
+
+ virt_dev->filename = kstrdup(pp, GFP_KERNEL);
+ if (virt_dev->filename == NULL) {
+ PRINT_ERROR("Unable to duplicate file name %s "
+ "(device %s)", pp, virt_dev->name);
+ res = -ENOMEM;
+ goto out;
+ }
+ continue;
+ }
+
+ res = strict_strtoul(pp, 0, &val);
+ if (res != 0) {
+ PRINT_ERROR("strict_strtoul() for %s failed: %d "
+ "(device %s)", pp, res, virt_dev->name);
+ goto out;
+ }
+
+ if (!strcasecmp("write_through", p)) {
+ virt_dev->wt_flag = val;
+ TRACE_DBG("WRITE THROUGH %d", virt_dev->wt_flag);
+ } else if (!strcasecmp("nv_cache", p)) {
+ virt_dev->nv_cache = val;
+ TRACE_DBG("NON-VOLATILE CACHE %d", virt_dev->nv_cache);
+ } else if (!strcasecmp("o_direct", p)) {
+#if 0
+ virt_dev->o_direct_flag = val;
+ TRACE_DBG("O_DIRECT %d", virt_dev->o_direct_flag);
+#else
+ PRINT_INFO("O_DIRECT flag doesn't currently"
+ " work, ignoring it, use fileio_tgt "
+ "in O_DIRECT mode instead (device %s)", virt_dev->name);
+#endif
+ } else if (!strcasecmp("read_only", p)) {
+ virt_dev->rd_only = val;
+ TRACE_DBG("READ ONLY %d", virt_dev->rd_only);
+ } else if (!strcasecmp("removable", p)) {
+ virt_dev->removable = val;
+ TRACE_DBG("REMOVABLE %d", virt_dev->removable);
+ } else if (!strcasecmp("blocksize", p)) {
+ virt_dev->block_size = val;
+ virt_dev->block_shift = scst_calc_block_shift(
+ virt_dev->block_size);
+ if (virt_dev->block_shift < 9) {
+ res = -EINVAL;
+ goto out;
+ }
+ TRACE_DBG("block_size %d, block_shift %d",
+ virt_dev->block_size,
+ virt_dev->block_shift);
+ } else {
+ PRINT_ERROR("Unknown parameter %s (device %s)", p,
+ virt_dev->name);
+ res = -EINVAL;
+ goto out;
+ }
+ }
+
+out:
+ return res;
+}
+
+/* scst_vdisk_mutex supposed to be held */
+static int vdev_fileio_add_device(const char *device_name, char *params)
+{
+ int res = 0;
+ struct scst_vdisk_dev *virt_dev;
+
+ res = vdev_create(&vdisk_file_devtype, device_name, &virt_dev);
+ if (res != 0)
+ goto out;
+
+ virt_dev->command_set_version = 0x04C0; /* SBC-3 */
+
+ virt_dev->wt_flag = DEF_WRITE_THROUGH;
+ virt_dev->nv_cache = DEF_NV_CACHE;
+ virt_dev->o_direct_flag = DEF_O_DIRECT;
+
+ res = vdev_parse_add_dev_params(virt_dev, params, NULL);
+ if (res != 0)
+ goto out_destroy;
+
+ if (virt_dev->rd_only && (virt_dev->wt_flag || virt_dev->nv_cache)) {
+ PRINT_ERROR("Write options on read only device %s",
+ virt_dev->name);
+ res = -EINVAL;
+ goto out_destroy;
+ }
+
+ if (virt_dev->filename == NULL) {
+ PRINT_ERROR("File name required (device %s)", virt_dev->name);
+ res = -EINVAL;
+ goto out_destroy;
+ }
+
+ list_add_tail(&virt_dev->vdev_list_entry, &vdev_list);
+
+ vdisk_report_registering(virt_dev);
+
+ virt_dev->virt_id = scst_register_virtual_device(virt_dev->vdev_devt,
+ virt_dev->name);
+ if (virt_dev->virt_id < 0) {
+ res = virt_dev->virt_id;
+ goto out_del;
+ }
+
+ TRACE_DBG("Registered virt_dev %s with id %d", virt_dev->name,
+ virt_dev->virt_id);
+
+out:
+ return res;
+
+out_del:
+ list_del(&virt_dev->vdev_list_entry);
+
+out_destroy:
+ vdev_destroy(virt_dev);
+ goto out;
+}
+
+/* scst_vdisk_mutex supposed to be held */
+static int vdev_blockio_add_device(const char *device_name, char *params)
+{
+ int res = 0;
+ const char *allowed_params[] = { "filename", "read_only", "removable",
+ "blocksize", "nv_cache", NULL };
+ struct scst_vdisk_dev *virt_dev;
+
+ res = vdev_create(&vdisk_blk_devtype, device_name, &virt_dev);
+ if (res != 0)
+ goto out;
+
+ virt_dev->command_set_version = 0x04C0; /* SBC-3 */
+
+ virt_dev->blockio = 1;
+
+ res = vdev_parse_add_dev_params(virt_dev, params, allowed_params);
+ if (res != 0)
+ goto out_destroy;
+
+ if (virt_dev->filename == NULL) {
+ PRINT_ERROR("File name required (device %s)", virt_dev->name);
+ res = -EINVAL;
+ goto out_destroy;
+ }
+
+ list_add_tail(&virt_dev->vdev_list_entry, &vdev_list);
+
+ vdisk_report_registering(virt_dev);
+
+ virt_dev->virt_id = scst_register_virtual_device(virt_dev->vdev_devt,
+ virt_dev->name);
+ if (virt_dev->virt_id < 0) {
+ res = virt_dev->virt_id;
+ goto out_del;
+ }
+
+ TRACE_DBG("Registered virt_dev %s with id %d", virt_dev->name,
+ virt_dev->virt_id);
+
+out:
+ return res;
+
+out_del:
+ list_del(&virt_dev->vdev_list_entry);
+
+out_destroy:
+ vdev_destroy(virt_dev);
+ goto out;
+}
+
+/* scst_vdisk_mutex supposed to be held */
+static int vdev_nullio_add_device(const char *device_name, char *params)
+{
+ int res = 0;
+ const char *allowed_params[] = { "read_only", "removable",
+ "blocksize", NULL };
+ struct scst_vdisk_dev *virt_dev;
+
+ res = vdev_create(&vdisk_null_devtype, device_name, &virt_dev);
+ if (res != 0)
+ goto out;
+
+ virt_dev->command_set_version = 0x04C0; /* SBC-3 */
+
+ virt_dev->nullio = 1;
+
+ res = vdev_parse_add_dev_params(virt_dev, params, allowed_params);
+ if (res != 0)
+ goto out_destroy;
+
+ list_add_tail(&virt_dev->vdev_list_entry, &vdev_list);
+
+ vdisk_report_registering(virt_dev);
+
+ virt_dev->virt_id = scst_register_virtual_device(virt_dev->vdev_devt,
+ virt_dev->name);
+ if (virt_dev->virt_id < 0) {
+ res = virt_dev->virt_id;
+ goto out_del;
+ }
+
+ TRACE_DBG("Registered virt_dev %s with id %d", virt_dev->name,
+ virt_dev->virt_id);
+
+out:
+ return res;
+
+out_del:
+ list_del(&virt_dev->vdev_list_entry);
+
+out_destroy:
+ vdev_destroy(virt_dev);
+ goto out;
+}
+
+static ssize_t vdisk_add_fileio_device(const char *device_name, char *params)
+{
+ int res;
+
+ if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
+ res = -EINTR;
+ goto out;
+ }
+
+ res = vdev_fileio_add_device(device_name, params);
+
+ mutex_unlock(&scst_vdisk_mutex);
+
+out:
+ return res;
+}
+
+static ssize_t vdisk_add_blockio_device(const char *device_name, char *params)
+{
+ int res;
+
+ if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
+ res = -EINTR;
+ goto out;
+ }
+
+ res = vdev_blockio_add_device(device_name, params);
+
+ mutex_unlock(&scst_vdisk_mutex);
+
+out:
+ return res;
+
+}
+
+static ssize_t vdisk_add_nullio_device(const char *device_name, char *params)
+{
+ int res;
+
+ if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
+ res = -EINTR;
+ goto out;
+ }
+
+ res = vdev_nullio_add_device(device_name, params);
+
+ mutex_unlock(&scst_vdisk_mutex);
+
+out:
+ return res;
+
+}
+
+/* scst_vdisk_mutex supposed to be held */
+static void vdev_del_device(struct scst_vdisk_dev *virt_dev)
+{
+
+ scst_unregister_virtual_device(virt_dev->virt_id);
+
+ list_del(&virt_dev->vdev_list_entry);
+
+ PRINT_INFO("Virtual device %s unregistered", virt_dev->name);
+ TRACE_DBG("virt_id %d unregistered", virt_dev->virt_id);
+
+ vdev_destroy(virt_dev);
+
+ return;
+}
+
+static ssize_t vdisk_del_device(const char *device_name)
+{
+ int res = 0;
+ struct scst_vdisk_dev *virt_dev;
+
+ if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
+ res = -EINTR;
+ goto out;
+ }
+
+ virt_dev = vdev_find(device_name);
+ if (virt_dev == NULL) {
+ PRINT_ERROR("Device %s not found", device_name);
+ res = -EINVAL;
+ goto out_unlock;
+ }
+
+ vdev_del_device(virt_dev);
+
+out_unlock:
+ mutex_unlock(&scst_vdisk_mutex);
+
+out:
+ return res;
+}
+
+/* scst_vdisk_mutex supposed to be held */
+static ssize_t __vcdrom_add_device(const char *device_name, char *params)
+{
+ int res = 0;
+ const char *allowed_params[] = { NULL }; /* no params */
+ struct scst_vdisk_dev *virt_dev;
+
+ res = vdev_create(&vcdrom_devtype, device_name, &virt_dev);
+ if (res != 0)
+ goto out;
+
+ virt_dev->command_set_version = 0x02A0; /* MMC-3 */
+
+ virt_dev->rd_only = 1;
+ virt_dev->removable = 1;
+ virt_dev->cdrom_empty = 1;
+
+ virt_dev->block_size = DEF_CDROM_BLOCKSIZE;
+ virt_dev->block_shift = DEF_CDROM_BLOCKSIZE_SHIFT;
+
+ res = vdev_parse_add_dev_params(virt_dev, params, allowed_params);
+ if (res != 0)
+ goto out_destroy;
+
+ list_add_tail(&virt_dev->vdev_list_entry, &vdev_list);
+
+ vdisk_report_registering(virt_dev);
+
+ virt_dev->virt_id = scst_register_virtual_device(virt_dev->vdev_devt,
+ virt_dev->name);
+ if (virt_dev->virt_id < 0) {
+ res = virt_dev->virt_id;
+ goto out_del;
+ }
+
+ TRACE_DBG("Registered virt_dev %s with id %d", virt_dev->name,
+ virt_dev->virt_id);
+
+out:
+ return res;
+
+out_del:
+ list_del(&virt_dev->vdev_list_entry);
+
+out_destroy:
+ vdev_destroy(virt_dev);
+ goto out;
+}
+
+static ssize_t vcdrom_add_device(const char *device_name, char *params)
+{
+ int res;
+
+ if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
+ res = -EINTR;
+ goto out;
+ }
+
+ res = __vcdrom_add_device(device_name, params);
+
+ mutex_unlock(&scst_vdisk_mutex);
+
+out:
+ return res;
+
+}
+
+static ssize_t vcdrom_del_device(const char *device_name)
+{
+ int res = 0;
+ struct scst_vdisk_dev *virt_dev;
+
+ if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
+ res = -EINTR;
+ goto out;
+ }
+
+ virt_dev = vdev_find(device_name);
+ if (virt_dev == NULL) {
+ PRINT_ERROR("Device %s not found", device_name);
+ res = -EINVAL;
+ goto out_unlock;
+ }
+
+ vdev_del_device(virt_dev);
+
+out_unlock:
+ mutex_unlock(&scst_vdisk_mutex);
+
+out:
+ return res;
+}
+
+static int vcdrom_change(struct scst_vdisk_dev *virt_dev,
+ char *buffer)
+{
+ loff_t err;
+ char *old_fn, *p, *pp;
+ const char *filename = NULL;
+ int length = strlen(buffer);
+ int res = 0;
+
+ p = buffer;
+
+ while (isspace(*p) && *p != '\0')
+ p++;
+ filename = p;
+ p = &buffer[length-1];
+ pp = &buffer[length];
+ while (isspace(*p) && (*p != '\0')) {
+ pp = p;
+ p--;
+ }
+ *pp = '\0';
+
+ res = scst_suspend_activity(true);
+ if (res != 0)
+ goto out;
+
+ /* To sync with detach*() functions */
+ mutex_lock(&scst_mutex);
+
+ if (*filename == '\0') {
+ virt_dev->cdrom_empty = 1;
+ TRACE_DBG("%s", "No media");
+ } else if (*filename != '/') {
+ PRINT_ERROR("File path \"%s\" is not absolute", filename);
+ res = -EINVAL;
+ goto out_unlock;
+ } else
+ virt_dev->cdrom_empty = 0;
+
+ old_fn = virt_dev->filename;
+
+ if (!virt_dev->cdrom_empty) {
+ int len = strlen(filename) + 1;
+ char *fn = kmalloc(len, GFP_KERNEL);
+ if (fn == NULL) {
+ TRACE(TRACE_OUT_OF_MEM, "%s",
+ "Allocation of filename failed");
+ res = -ENOMEM;
+ goto out_unlock;
+ }
+
+ strlcpy(fn, filename, len);
+ virt_dev->filename = fn;
+
+ res = vdisk_get_file_size(virt_dev->filename,
+ virt_dev->blockio, &err);
+ if (res != 0)
+ goto out_free_fn;
+ } else {
+ err = 0;
+ virt_dev->filename = NULL;
+ }
+
+ if (virt_dev->prevent_allow_medium_removal) {
+ PRINT_ERROR("Prevent medium removal for "
+ "virtual device with name %s", virt_dev->name);
+ res = -EINVAL;
+ goto out_free_fn;
+ }
+
+ virt_dev->file_size = err;
+ virt_dev->nblocks = virt_dev->file_size >> virt_dev->block_shift;
+ if (!virt_dev->cdrom_empty)
+ virt_dev->media_changed = 1;
+
+ mutex_unlock(&scst_mutex);
+
+ scst_dev_del_all_thr_data(virt_dev->dev);
+
+ if (!virt_dev->cdrom_empty) {
+ PRINT_INFO("Changed SCSI target virtual cdrom %s "
+ "(file=\"%s\", fs=%lldMB, bs=%d, nblocks=%lld,"
+ " cyln=%lld%s)", 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("Removed media from SCSI target virtual cdrom %s",
+ virt_dev->name);
+ }
+
+ kfree(old_fn);
+
+out_resume:
+ scst_resume_activity();
+
+out:
+ return res;
+
+out_free_fn:
+ kfree(virt_dev->filename);
+ virt_dev->filename = old_fn;
+
+out_unlock:
+ mutex_unlock(&scst_mutex);
+ goto out_resume;
+}
+
+static int vcdrom_sysfs_process_filename_store(struct scst_sysfs_work_item *work)
+{
+ int res;
+ struct scst_device *dev = work->dev;
+ struct scst_vdisk_dev *virt_dev;
+
+ /* It's safe, since we taken dev_kobj and dh_priv NULLed in attach() */
+ virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+ res = vcdrom_change(virt_dev, work->buf);
+
+ kobject_put(&dev->dev_kobj);
+ return res;
+}
+
+static ssize_t vcdrom_sysfs_filename_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int res;
+ char *i_buf;
+ struct scst_sysfs_work_item *work;
+ struct scst_device *dev;
+
+ dev = container_of(kobj, struct scst_device, dev_kobj);
+
+ i_buf = kmalloc(count+1, GFP_KERNEL);
+ if (i_buf == NULL) {
+ PRINT_ERROR("Unable to alloc intermediate buffer with size %zd",
+ count+1);
+ res = -ENOMEM;
+ goto out;
+ }
+ memcpy(i_buf, buf, count);
+ i_buf[count] = '\0';
+
+ res = scst_alloc_sysfs_work(vcdrom_sysfs_process_filename_store, &work);
+ if (res != 0)
+ goto out_free;
+
+ work->buf = i_buf;
+ work->dev = dev;
+
+ kobject_get(&dev->dev_kobj);
+
+ res = scst_sysfs_queue_wait_work(work);
+ if (res == 0)
+ res = count;
+
+out:
+ return res;
+
+out_free:
+ kfree(i_buf);
+ goto out;
+}
+
+static ssize_t vdev_sysfs_size_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int pos = 0;
+ struct scst_device *dev;
+ struct scst_vdisk_dev *virt_dev;
+
+ dev = container_of(kobj, struct scst_device, dev_kobj);
+ virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+ pos = sprintf(buf, "%lld\n", virt_dev->file_size / 1024 / 1024);
+ return pos;
+}
+
+static ssize_t vdisk_sysfs_blocksize_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int pos = 0;
+ struct scst_device *dev;
+ struct scst_vdisk_dev *virt_dev;
+
+ dev = container_of(kobj, struct scst_device, dev_kobj);
+ virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+ pos = sprintf(buf, "%d\n%s", (int)virt_dev->block_size,
+ (virt_dev->block_size == DEF_DISK_BLOCKSIZE) ? "" :
+ SCST_SYSFS_KEY_MARK "\n");
+ return pos;
+}
+
+static ssize_t vdisk_sysfs_rd_only_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int pos = 0;
+ struct scst_device *dev;
+ struct scst_vdisk_dev *virt_dev;
+
+ dev = container_of(kobj, struct scst_device, dev_kobj);
+ virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+ pos = sprintf(buf, "%d\n%s", virt_dev->rd_only ? 1 : 0,
+ (virt_dev->rd_only == DEF_RD_ONLY) ? "" :
+ SCST_SYSFS_KEY_MARK "");
+ return pos;
+}
+
+static ssize_t vdisk_sysfs_wt_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int pos = 0;
+ struct scst_device *dev;
+ struct scst_vdisk_dev *virt_dev;
+
+ dev = container_of(kobj, struct scst_device, dev_kobj);
+ virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+ pos = sprintf(buf, "%d\n%s", virt_dev->wt_flag ? 1 : 0,
+ (virt_dev->wt_flag == DEF_WRITE_THROUGH) ? "" :
+ SCST_SYSFS_KEY_MARK "");
+ return pos;
+}
+
+static ssize_t vdisk_sysfs_nv_cache_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int pos = 0;
+ struct scst_device *dev;
+ struct scst_vdisk_dev *virt_dev;
+
+ dev = container_of(kobj, struct scst_device, dev_kobj);
+ virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+ pos = sprintf(buf, "%d\n%s", virt_dev->nv_cache ? 1 : 0,
+ (virt_dev->nv_cache == DEF_NV_CACHE) ? "" :
+ SCST_SYSFS_KEY_MARK "");
+ return pos;
+}
+
+static ssize_t vdisk_sysfs_o_direct_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int pos = 0;
+ struct scst_device *dev;
+ struct scst_vdisk_dev *virt_dev;
+
+ dev = container_of(kobj, struct scst_device, dev_kobj);
+ virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+ pos = sprintf(buf, "%d\n%s", virt_dev->o_direct_flag ? 1 : 0,
+ (virt_dev->o_direct_flag == DEF_O_DIRECT) ? "" :
+ SCST_SYSFS_KEY_MARK "");
+ return pos;
+}
+
+static ssize_t vdisk_sysfs_removable_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int pos = 0;
+ struct scst_device *dev;
+ struct scst_vdisk_dev *virt_dev;
+
+ dev = container_of(kobj, struct scst_device, dev_kobj);
+ virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+ pos = sprintf(buf, "%d\n", virt_dev->removable ? 1 : 0);
+
+ if ((virt_dev->dev->type != TYPE_ROM) &&
+ (virt_dev->removable != DEF_REMOVABLE))
+ pos += sprintf(&buf[pos], "%s\n", SCST_SYSFS_KEY_MARK);
+ return pos;
+}
+
+static int vdev_sysfs_process_get_filename(struct scst_sysfs_work_item *work)
+{
+ int res = 0;
+ struct scst_device *dev;
+ struct scst_vdisk_dev *virt_dev;
+
+ dev = work->dev;
+
+ if (mutex_lock_interruptible(&scst_vdisk_mutex) != 0) {
+ res = -EINTR;
+ goto out_put;
+ }
+
+ virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+ if (virt_dev == NULL)
+ goto out_unlock;
+
+ if (virt_dev->filename != NULL)
+ work->res_buf = kasprintf(GFP_KERNEL, "%s\n%s\n",
+ vdev_get_filename(virt_dev), SCST_SYSFS_KEY_MARK);
+ else
+ work->res_buf = kasprintf(GFP_KERNEL, "%s\n",
+ vdev_get_filename(virt_dev));
+
+out_unlock:
+ mutex_unlock(&scst_vdisk_mutex);
+
+out_put:
+ kobject_put(&dev->dev_kobj);
+ return res;
+}
+
+static ssize_t vdev_sysfs_filename_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int res = 0;
+ struct scst_device *dev;
+ struct scst_sysfs_work_item *work;
+
+ dev = container_of(kobj, struct scst_device, dev_kobj);
+
+ res = scst_alloc_sysfs_work(vdev_sysfs_process_get_filename, &work);
+ if (res != 0)
+ goto out;
+
+ work->dev = dev;
+
+ kobject_get(&dev->dev_kobj);
+
+ scst_sysfs_work_get(work);
+
+ res = scst_sysfs_queue_wait_work(work);
+ if (res != 0)
+ goto out_put;
+
+ res = snprintf(buf, SCST_SYSFS_BLOCK_SIZE, "%s\n", work->res_buf);
+
+out_put:
+ scst_sysfs_work_put(work);
+
+out:
+ return res;
+}
+
+static int vdisk_sysfs_process_resync_size_store(
+ struct scst_sysfs_work_item *work)
+{
+ int res;
+ struct scst_device *dev = work->dev;
+ struct scst_vdisk_dev *virt_dev;
+
+ /* It's safe, since we taken dev_kobj and dh_priv NULLed in attach() */
+ virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+ res = vdisk_resync_size(virt_dev);
+
+ kobject_put(&dev->dev_kobj);
+ return res;
+}
+
+static ssize_t vdisk_sysfs_resync_size_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int res;
+ struct scst_device *dev;
+ struct scst_sysfs_work_item *work;
+
+ dev = container_of(kobj, struct scst_device, dev_kobj);
+
+ res = scst_alloc_sysfs_work(vdisk_sysfs_process_resync_size_store,
+ &work);
+ if (res != 0)
+ goto out;
+
+ work->dev = dev;
+
+ kobject_get(&dev->dev_kobj);
+
+ res = scst_sysfs_queue_wait_work(work);
+ if (res == 0)
+ res = count;
+
+out:
+ return res;
+}
+
+static ssize_t vdev_sysfs_t10_dev_id_store(struct kobject *kobj,
+ struct kobj_attribute *attr, const char *buf, size_t count)
+{
+ int res, i;
+ struct scst_device *dev;
+ struct scst_vdisk_dev *virt_dev;
+
+ dev = container_of(kobj, struct scst_device, dev_kobj);
+ virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+ write_lock_bh(&vdisk_t10_dev_id_rwlock);
+
+ if ((count > sizeof(virt_dev->t10_dev_id)) ||
+ ((count == sizeof(virt_dev->t10_dev_id)) &&
+ (buf[count-1] != '\n'))) {
+ PRINT_ERROR("T10 device id is too long (max %zd "
+ "characters)", sizeof(virt_dev->t10_dev_id)-1);
+ res = -EINVAL;
+ goto out_unlock;
+ }
+
+ memset(virt_dev->t10_dev_id, 0, sizeof(virt_dev->t10_dev_id));
+ memcpy(virt_dev->t10_dev_id, buf, count);
+
+ i = 0;
+ while (i < sizeof(virt_dev->t10_dev_id)) {
+ if (virt_dev->t10_dev_id[i] == '\n') {
+ virt_dev->t10_dev_id[i] = '\0';
+ break;
+ }
+ i++;
+ }
+
+ virt_dev->t10_dev_id_set = 1;
+
+ res = count;
+
+ PRINT_INFO("T10 device id for device %s changed to %s", virt_dev->name,
+ virt_dev->t10_dev_id);
+
+out_unlock:
+ write_unlock_bh(&vdisk_t10_dev_id_rwlock);
+ return res;
+}
+
+static ssize_t vdev_sysfs_t10_dev_id_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int pos = 0;
+ struct scst_device *dev;
+ struct scst_vdisk_dev *virt_dev;
+
+ dev = container_of(kobj, struct scst_device, dev_kobj);
+ virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+ read_lock_bh(&vdisk_t10_dev_id_rwlock);
+ pos = sprintf(buf, "%s\n%s", virt_dev->t10_dev_id,
+ virt_dev->t10_dev_id_set ? SCST_SYSFS_KEY_MARK "\n" : "");
+ read_unlock_bh(&vdisk_t10_dev_id_rwlock);
+ return pos;
+}
+
+static ssize_t vdev_sysfs_usn_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ int pos = 0;
+ struct scst_device *dev;
+ struct scst_vdisk_dev *virt_dev;
+
+ dev = container_of(kobj, struct scst_device, dev_kobj);
+ virt_dev = (struct scst_vdisk_dev *)dev->dh_priv;
+
+ pos = sprintf(buf, "%s\n", virt_dev->usn);
+ return pos;
+}
+
+static int __init init_scst_vdisk(struct scst_dev_type *devtype)
+{
+ int res = 0;
+
+ devtype->module = THIS_MODULE;
+
+ res = scst_register_virtual_dev_driver(devtype);
+ if (res < 0)
+ goto out;
+
+out:
+ return res;
+
+}
+
+static void exit_scst_vdisk(struct scst_dev_type *devtype)
+{
+
+ mutex_lock(&scst_vdisk_mutex);
+ while (1) {
+ struct scst_vdisk_dev *virt_dev;
+
+ if (list_empty(&vdev_list))
+ break;
+
+ virt_dev = list_entry(vdev_list.next, typeof(*virt_dev),
+ vdev_list_entry);
+
+ vdev_del_device(virt_dev);
+ }
+ mutex_unlock(&scst_vdisk_mutex);
+
+ scst_unregister_virtual_dev_driver(devtype);
+ return;
+}
+
+static int __init init_scst_vdisk_driver(void)
+{
+ int res;
+
+ vdisk_thr_cachep = KMEM_CACHE(scst_vdisk_thr, SCST_SLAB_FLAGS);
+ if (vdisk_thr_cachep == NULL) {
+ res = -ENOMEM;
+ goto out;
+ }
+
+ blockio_work_cachep = KMEM_CACHE(scst_blockio_work, SCST_SLAB_FLAGS);
+ if (blockio_work_cachep == NULL) {
+ res = -ENOMEM;
+ goto out_free_vdisk_cache;
+ }
+
+ if (num_threads < 1) {
+ PRINT_ERROR("num_threads can not be less than 1, use "
+ "default %d", DEF_NUM_THREADS);
+ num_threads = DEF_NUM_THREADS;
+ }
+
+ vdisk_file_devtype.threads_num = num_threads;
+ vcdrom_devtype.threads_num = num_threads;
+
+ atomic_set(&nullio_thr_data.hdr.ref, 1); /* never destroy it */
+
+ res = init_scst_vdisk(&vdisk_file_devtype);
+ if (res != 0)
+ goto out_free_slab;
+
+ res = init_scst_vdisk(&vdisk_blk_devtype);
+ if (res != 0)
+ goto out_free_vdisk;
+
+ res = init_scst_vdisk(&vdisk_null_devtype);
+ if (res != 0)
+ goto out_free_blk;
+
+ res = init_scst_vdisk(&vcdrom_devtype);
+ if (res != 0)
+ goto out_free_null;
+
+out:
+ return res;
+
+out_free_null:
+ exit_scst_vdisk(&vdisk_null_devtype);
+
+out_free_blk:
+ exit_scst_vdisk(&vdisk_blk_devtype);
+
+out_free_vdisk:
+ exit_scst_vdisk(&vdisk_file_devtype);
+
+out_free_slab:
+ kmem_cache_destroy(blockio_work_cachep);
+
+out_free_vdisk_cache:
+ kmem_cache_destroy(vdisk_thr_cachep);
+ goto out;
+}
+
+static void __exit exit_scst_vdisk_driver(void)
+{
+ exit_scst_vdisk(&vdisk_null_devtype);
+ exit_scst_vdisk(&vdisk_blk_devtype);
+ exit_scst_vdisk(&vdisk_file_devtype);
+ exit_scst_vdisk(&vcdrom_devtype);
+
+ kmem_cache_destroy(blockio_work_cachep);
+ kmem_cache_destroy(vdisk_thr_cachep);
+}
+
+module_init(init_scst_vdisk_driver);
+module_exit(exit_scst_vdisk_driver);
+
+MODULE_AUTHOR("Vladislav Bolkhovitin & Leonid Stoljar");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SCSI disk (type 0) and CDROM (type 5) dev handler for "
+ "SCST using files on file systems or block devices");
+MODULE_VERSION(SCST_VERSION_STRING);


\
 
 \ /
  Last update: 2010-09-14 16:51    [from the cache]
©2003-2011 Jasper Spaans