blob: b2d242fc9d5cfc57500cda32d1170e2d13a690ef [file] [log] [blame]
/*
* common.c
*
* Copyright (C) 2007 - 2018 Vladislav Bolkhovitin <vst@vlnb.net>
* Copyright (C) 2007 - 2018 Western Digital Corporation
*
* 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 <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <getopt.h>
#include <inttypes.h>
#include <sys/ioctl.h>
#include <poll.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "common.h"
static void exec_inquiry(struct vdisk_cmd *vcmd);
static void exec_request_sense(struct vdisk_cmd *vcmd);
static void exec_mode_sense(struct vdisk_cmd *vcmd);
static void exec_mode_select(struct vdisk_cmd *vcmd);
static void exec_read_capacity(struct vdisk_cmd *vcmd);
static void exec_read_capacity16(struct vdisk_cmd *vcmd);
static void exec_read_toc(struct vdisk_cmd *vcmd);
static void exec_prevent_allow_medium_removal(struct vdisk_cmd *vcmd);
static int exec_fsync(struct vdisk_cmd *vcmd);
static void exec_read(struct vdisk_cmd *vcmd, loff_t loff);
static void exec_write(struct vdisk_cmd *vcmd, loff_t loff);
static void exec_verify(struct vdisk_cmd *vcmd, loff_t loff);
static void exec_write_same(struct vdisk_cmd *vcmd);
static int open_dev_fd(struct vdisk_dev *dev)
{
int res;
int open_flags = O_LARGEFILE;
if (dev->rd_only_flag)
open_flags |= O_RDONLY;
else
open_flags |= O_RDWR;
if (dev->o_direct_flag)
open_flags |= O_DIRECT;
if (dev->wt_flag)
open_flags |= O_DSYNC;
TRACE_DBG("Opening file %s, flags 0x%x", dev->file_name, open_flags);
res = open(dev->file_name, open_flags);
return res;
}
static void set_resp_data_len(struct vdisk_cmd *vcmd, int32_t resp_data_len)
{
struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
TRACE_ENTRY();
if (vcmd->may_need_to_free_pbuf && (resp_data_len == 0)) {
struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
free((void *)(unsigned long)cmd->pbuf);
cmd->pbuf = 0;
reply->pbuf = 0;
}
reply->resp_data_len = resp_data_len;
TRACE_EXIT();
return;
}
static inline void set_cmd_error_status(struct vdisk_cmd *vcmd,
int status)
{
struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
reply->status = status;
set_resp_data_len(vcmd, 0);
return;
}
static int set_sense(uint8_t *buffer, int len, int key, int asc, int ascq)
{
int res = 18;
EXTRACHECKS_BUG_ON(len < res);
memset(buffer, 0, res);
buffer[0] = 0x70; /* Error Code */
buffer[2] = key; /* Sense Key */
buffer[7] = 0x0a; /* Additional Sense Length */
buffer[12] = asc; /* ASC */
buffer[13] = ascq; /* ASCQ */
TRACE_BUFFER("Sense set", buffer, res);
return res;
}
/*
* ToDo: implement analogs of scst_set_invalid_field_in_cdb() and
* scst_set_invalid_field_in_parm_list()
*/
static void set_cmd_error(struct vdisk_cmd *vcmd, int key, int asc, int ascq)
{
struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
TRACE_ENTRY();
EXTRACHECKS_BUG_ON(vcmd->cmd->subcode != SCST_USER_EXEC);
set_cmd_error_status(vcmd, SAM_STAT_CHECK_CONDITION);
reply->sense_len = set_sense(vcmd->sense, sizeof(vcmd->sense), key,
asc, ascq);
reply->psense_buffer = (unsigned long)vcmd->sense;
TRACE_EXIT();
return;
}
static void set_busy(struct vdisk_cmd *vcmd)
{
TRACE_ENTRY();
set_cmd_error_status(vcmd, SAM_STAT_TASK_SET_FULL);
TRACE_MGMT_DBG("%s", "Sending QUEUE_FULL status");
TRACE_EXIT();
return;
}
static int do_parse(struct vdisk_cmd *vcmd)
{
int res = 0;
struct scst_user_scsi_cmd_parse *cmd = &vcmd->cmd->parse_cmd;
struct scst_user_scsi_cmd_reply_parse *reply = &vcmd->reply->parse_reply;
TRACE_ENTRY();
memset(reply, 0, sizeof(*reply));
vcmd->reply->cmd_h = vcmd->cmd->cmd_h;
vcmd->reply->subcode = vcmd->cmd->subcode;
if (cmd->expected_values_set == 0) {
PRINT_ERROR("%s", "Oops, expected values are not set");
reply->bufflen = -1; /* invalid value */
goto out;
}
reply->queue_type = cmd->queue_type;
reply->data_direction = cmd->expected_data_direction;
reply->lba = cmd->lba;
reply->data_len = cmd->expected_transfer_len;
reply->bufflen = cmd->expected_transfer_len;
reply->out_bufflen = cmd->expected_out_transfer_len;
reply->cdb_len = cmd->cdb_len;
if (cmd->op_flags & SCST_INFO_VALID)
reply->op_flags = cmd->op_flags;
else {
TRACE_DBG("Extra parse (op %x)", cmd->cdb[0]);
if (reply->data_direction & SCST_DATA_WRITE)
reply->op_flags |= SCST_WRITE_MEDIUM;
reply->op_flags |= SCST_INFO_VALID;
}
out:
TRACE_EXIT_RES(res);
return res;
}
static struct vdisk_tgt_dev *find_tgt_dev(struct vdisk_dev *dev, uint64_t sess_h)
{
unsigned int i;
struct vdisk_tgt_dev *res = NULL;
for(i = 0; i < ARRAY_SIZE(dev->tgt_devs); i++) {
if (dev->tgt_devs[i].sess_h == sess_h) {
res = &dev->tgt_devs[i];
break;
}
}
return res;
}
static struct vdisk_tgt_dev *find_empty_tgt_dev(struct vdisk_dev *dev)
{
unsigned int i;
struct vdisk_tgt_dev *res = NULL;
for(i = 0; i < ARRAY_SIZE(dev->tgt_devs); i++) {
if (dev->tgt_devs[i].sess_h == 0) {
res = &dev->tgt_devs[i];
break;
}
}
return res;
}
static int do_exec(struct vdisk_cmd *vcmd)
{
int res = 0;
struct vdisk_dev *dev = vcmd->dev;
struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
uint64_t lba_start = cmd->lba;
int64_t data_len = cmd->data_len;
uint8_t *cdb = cmd->cdb;
int opcode = cdb[0];
loff_t loff;
int fua = 0;
TRACE_ENTRY();
/* Must be reinitialized each time to avoid crash on stale value */
vcmd->may_need_to_free_pbuf = 0;
switch(cmd->queue_type) {
case SCST_CMD_QUEUE_ORDERED:
TRACE(TRACE_ORDER, "ORDERED cmd_h %d", vcmd->cmd->cmd_h);
break;
case SCST_CMD_QUEUE_HEAD_OF_QUEUE:
TRACE(TRACE_ORDER, "HQ cmd_h %d", vcmd->cmd->cmd_h);
break;
default:
break;
}
memset(reply, 0, sizeof(*reply));
vcmd->reply->cmd_h = vcmd->cmd->cmd_h;
vcmd->reply->subcode = vcmd->cmd->subcode;
reply->reply_type = SCST_EXEC_REPLY_COMPLETED;
#ifdef DEBUG_SENSE
if ((random() % 100000) == 75) {
set_cmd_error(vcmd, SCST_LOAD_SENSE(scst_sense_internal_failure));
goto out;
}
#endif
#ifdef DEBUG_TM_IGNORE
if (dev->debug_tm_ignore && (random() % 10000) == 75) {
TRACE_MGMT_DBG("Ignore cmd op %x (h=%d)", cdb[0],
vcmd->cmd->cmd_h);
res = 150;
goto out;
}
#endif
if ((cmd->pbuf == 0) && (cmd->alloc_len != 0)) {
#ifdef DEBUG_NOMEM
if ((random() % 100) == 75)
cmd->pbuf = 0;
else
#endif
cmd->pbuf = (unsigned long)dev->alloc_fn(cmd->alloc_len);
vcmd->may_need_to_free_pbuf = 1;
TRACE_MEM("Buf %"PRIx64" alloced, len %d", cmd->pbuf,
cmd->alloc_len);
reply->pbuf = cmd->pbuf;
if (cmd->pbuf == 0) {
TRACE(TRACE_OUT_OF_MEM, "Unable to allocate buffer "
"(len %d)", cmd->alloc_len);
#ifndef DEBUG_NOMEM
set_busy(vcmd);
#endif
goto out;
}
}
if (cmd->data_direction & SCST_DATA_READ)
reply->resp_data_len = cmd->bufflen;
loff = (loff_t)lba_start << dev->block_shift;
TRACE_DBG("cmd %d, buf %"PRIx64", lba_start %"PRId64", loff %"PRId64
", data_len %"PRId64, vcmd->cmd->cmd_h, cmd->pbuf, lba_start,
(uint64_t)loff, data_len);
if ((loff < 0) || (data_len < 0) || ((loff + data_len) > dev->file_size)) {
PRINT_INFO("Access beyond the end of the device "
"(%"PRId64" of %"PRId64", len %"PRId64")", (uint64_t)loff,
(uint64_t)dev->file_size, data_len);
set_cmd_error(vcmd, SCST_LOAD_SENSE(
scst_sense_block_out_range_error));
goto out;
}
switch (opcode) {
case WRITE_10:
case WRITE_12:
case WRITE_16:
fua = (cdb[1] & 0x8);
if (cdb[1] & 0x8) {
TRACE(TRACE_ORDER, "FUA(%d): loff=%"PRId64", "
"data_len=%"PRId64, fua, (uint64_t)loff,
data_len);
}
break;
}
switch (opcode) {
case READ_6:
case READ_10:
case READ_12:
case READ_16:
exec_read(vcmd, loff);
break;
case WRITE_6:
case WRITE_10:
case WRITE_12:
case WRITE_16:
if (!dev->rd_only_flag) {
struct vdisk_tgt_dev *tgt_dev;
tgt_dev = find_tgt_dev(dev, cmd->sess_h);
if (tgt_dev == NULL) {
PRINT_ERROR("Session %"PRIx64" not found",
cmd->sess_h);
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_hardw_error));
goto out;
}
exec_write(vcmd, loff);
/* O_DSYNC flag is used for WT devices */
if (fua)
exec_fsync(vcmd);
} else {
PRINT_WARNING("Attempt to write to read-only "
"device %s", dev->name);
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_data_protect));
}
break;
case WRITE_VERIFY:
case WRITE_VERIFY_12:
case WRITE_VERIFY_16:
if (!dev->rd_only_flag) {
struct vdisk_tgt_dev *tgt_dev;
tgt_dev = find_tgt_dev(dev, cmd->sess_h);
if (tgt_dev == NULL) {
PRINT_ERROR("Session %"PRIx64" not found",
cmd->sess_h);
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_hardw_error));
goto out;
}
exec_write(vcmd, loff);
/* O_DSYNC flag is used for WT devices */
if (reply->status == 0)
exec_verify(vcmd, loff);
} else {
PRINT_WARNING("Attempt to write to read-only "
"device %s", dev->name);
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_data_protect));
}
break;
case SYNCHRONIZE_CACHE:
case SYNCHRONIZE_CACHE_16:
{
int immed = cdb[1] & 0x2;
if (data_len == 0)
data_len = dev->file_size -
((loff_t)lba_start << dev->block_shift);
TRACE(TRACE_ORDER, "SYNCHRONIZE_CACHE: "
"loff=%"PRId64", data_len=%"PRId64", immed=%d",
(uint64_t)loff, data_len, immed);
if (immed) {
/* ToDo: backgroung exec */
exec_fsync(vcmd);
break;
} else {
exec_fsync(vcmd);
break;
}
}
case VERIFY:
case VERIFY_12:
case VERIFY_16:
exec_verify(vcmd, loff);
break;
case MODE_SENSE:
case MODE_SENSE_10:
exec_mode_sense(vcmd);
break;
case MODE_SELECT:
case MODE_SELECT_10:
exec_mode_select(vcmd);
break;
case ALLOW_MEDIUM_REMOVAL:
exec_prevent_allow_medium_removal(vcmd);
break;
case READ_TOC:
exec_read_toc(vcmd);
break;
case START_STOP:
exec_fsync(vcmd/*, 0, dev->file_size*/);
break;
case RESERVE:
case RESERVE_10:
case RELEASE:
case RELEASE_10:
case TEST_UNIT_READY:
break;
case INQUIRY:
exec_inquiry(vcmd);
break;
case REQUEST_SENSE:
exec_request_sense(vcmd);
break;
case READ_CAPACITY:
exec_read_capacity(vcmd);
break;
case WRITE_SAME_10:
case WRITE_SAME_16:
exec_write_same(vcmd);
break;
case SERVICE_ACTION_IN_16:
if ((cmd->cdb[1] & 0x1f) == SAI_READ_CAPACITY_16)
exec_read_capacity16(vcmd);
else {
TRACE_DBG("Invalid service action %d for SERVICE "
"ACTION IN", cmd->cdb[1] & 0x1f);
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
}
break;
case REPORT_LUNS:
default:
TRACE_DBG("Invalid opcode 0x%x", opcode);
set_cmd_error(vcmd, SCST_LOAD_SENSE(scst_sense_invalid_opcode));
break;
}
out:
TRACE_EXIT();
return res;
}
static int do_alloc_mem(struct vdisk_cmd *vcmd)
{
struct scst_user_get_cmd *cmd = vcmd->cmd;
struct scst_user_reply_cmd *reply = vcmd->reply;
int res = 0;
TRACE_ENTRY();
TRACE_MEM("Alloc mem (cmd %d, sess_h %"PRIx64", cdb_len %d, "
"alloc_len %d, queue_type %d, data_direction %d)",
cmd->cmd_h, cmd->alloc_cmd.sess_h,
cmd->alloc_cmd.cdb_len, cmd->alloc_cmd.alloc_len,
cmd->alloc_cmd.queue_type, cmd->alloc_cmd.data_direction);
TRACE_BUFF_FLAG(TRACE_MEMORY, "CDB", cmd->alloc_cmd.cdb,
cmd->alloc_cmd.cdb_len);
memset(reply, 0, sizeof(*reply));
reply->cmd_h = cmd->cmd_h;
reply->subcode = cmd->subcode;
#ifdef DEBUG_NOMEM
if ((random() % 100) == 75)
reply->alloc_reply.pbuf = 0;
else
#endif
reply->alloc_reply.pbuf = (unsigned long)vcmd->dev->alloc_fn(
cmd->alloc_cmd.alloc_len);
TRACE_MEM("Buf %"PRIx64" alloced, len %d", reply->alloc_reply.pbuf,
cmd->alloc_cmd.alloc_len);
if (reply->alloc_reply.pbuf == 0) {
TRACE(TRACE_OUT_OF_MEM, "Unable to allocate buffer (len %d)",
cmd->alloc_cmd.alloc_len);
}
TRACE_EXIT_RES(res);
return res;
}
static int do_cached_mem_free(struct vdisk_cmd *vcmd)
{
struct scst_user_get_cmd *cmd = vcmd->cmd;
struct scst_user_reply_cmd *reply = vcmd->reply;
int res = 0;
TRACE_ENTRY();
TRACE_MEM("Cached mem free (cmd %x, buf %"PRIx64")", cmd->cmd_h,
cmd->on_cached_mem_free.pbuf);
free((void *)(unsigned long)cmd->on_cached_mem_free.pbuf);
memset(reply, 0, sizeof(*reply));
reply->cmd_h = cmd->cmd_h;
reply->subcode = cmd->subcode;
TRACE_EXIT_RES(res);
return res;
}
static int do_on_free_cmd(struct vdisk_cmd *vcmd)
{
struct scst_user_get_cmd *cmd = vcmd->cmd;
struct scst_user_reply_cmd *reply = vcmd->reply;
int res = 0;
TRACE_ENTRY();
TRACE_DBG("On free cmd (cmd %d, resp_data_len %d, aborted %d, "
"status %d, delivery_status %d)", cmd->cmd_h,
cmd->on_free_cmd.resp_data_len, cmd->on_free_cmd.aborted,
cmd->on_free_cmd.status, cmd->on_free_cmd.delivery_status);
TRACE_MEM("On free cmd (cmd %d, buf %"PRIx64", buffer_cached %d)",
cmd->cmd_h, cmd->on_free_cmd.pbuf,
cmd->on_free_cmd.buffer_cached);
if (!cmd->on_free_cmd.buffer_cached && (cmd->on_free_cmd.pbuf != 0)) {
TRACE_MEM("Freeing buf %"PRIx64, cmd->on_free_cmd.pbuf);
free((void *)(unsigned long)cmd->on_free_cmd.pbuf);
}
memset(reply, 0, sizeof(*reply));
reply->cmd_h = cmd->cmd_h;
reply->subcode = cmd->subcode;
TRACE_EXIT_RES(res);
return res;
}
#ifdef DEBUG_EXT_COPY_REMAP
static int do_ext_copy_remap(struct vdisk_cmd *vcmd)
{
struct scst_user_get_cmd *cmd = vcmd->cmd;
struct scst_user_ext_copy_remap *rcmd = &cmd->remap_cmd;
struct scst_user_reply_cmd *reply = vcmd->reply;
struct scst_user_ext_copy_reply_remap *rreply = &reply->remap_reply;
static struct scst_user_ext_copy_data_descr d[1];
int res = 0;
TRACE_ENTRY();
TRACE_DBG("Ext copy remap cmd %d", cmd->cmd_h);
memset(reply, 0, sizeof(*reply));
reply->cmd_h = cmd->cmd_h;
reply->subcode = cmd->subcode;
memset(rreply, 0, sizeof(*rreply));
/* It's only a debug code, so it's OK to use static descr */
memset(d, 0, sizeof(d));
d[0].data_len = rcmd->data_descr.data_len;
d[0].src_lba = rcmd->data_descr.src_lba;
d[0].dst_lba = rcmd->data_descr.dst_lba;
#if 1
rreply->remap_descriptors = (unsigned long)d;
rreply->remap_descriptors_len = sizeof(d);
#else
rreply->status = SAM_STAT_CHECK_CONDITION;
rreply->sense_len = set_sense(vcmd->sense, sizeof(vcmd->sense),
SCST_LOAD_SENSE(scst_sense_data_protect));
rreply->psense_buffer = (unsigned long)vcmd->sense;
#endif
TRACE_EXIT_RES(res);
return res;
}
#endif
static int do_tm(struct vdisk_cmd *vcmd, int done)
{
struct scst_user_get_cmd *cmd = vcmd->cmd;
struct scst_user_reply_cmd *reply = vcmd->reply;
struct vdisk_dev *dev = vcmd->dev;
int res = 0;
TRACE_ENTRY();
if (cmd->tm_cmd.fn <= SCST_TARGET_RESET)
TRACE(TRACE_MGMT, "%s TM fn %d (sess_h %"PRIx64", "
"cmd_h_to_abort %d)", done ? "Done" : "Received",
cmd->tm_cmd.fn, cmd->tm_cmd.sess_h,
cmd->tm_cmd.cmd_h_to_abort);
else
TRACE_MGMT_DBG("%s TM fn %d (sess_h %"PRIx64", "
"cmd_h_to_abort %d)", done ? "Done" : "Received",
cmd->tm_cmd.fn, cmd->tm_cmd.sess_h,
cmd->tm_cmd.cmd_h_to_abort);
if (done) {
switch (cmd->tm_cmd.fn) {
case SCST_LUN_RESET:
case SCST_TARGET_RESET:
case SCST_PR_ABORT_ALL:
pthread_mutex_lock(&dev->dev_mutex);
dev->prevent_allow_medium_removal = 0;
pthread_mutex_unlock(&dev->dev_mutex);
break;
}
}
memset(reply, 0, sizeof(*reply));
reply->cmd_h = cmd->cmd_h;
reply->subcode = cmd->subcode;
reply->result = 0;
TRACE_EXIT_RES(res);
return res;
}
static int do_sess(struct vdisk_cmd *vcmd)
{
struct scst_user_get_cmd *cmd = vcmd->cmd;
struct scst_user_reply_cmd *reply = vcmd->reply;
int res = 0;
struct vdisk_tgt_dev *tgt_dev;
TRACE_ENTRY();
/*
* We are guaranteed to have one and only one command at this point,
* which is ATTACH_SESS/DETACH_SESS, so no protection is needed
*/
tgt_dev = find_tgt_dev(vcmd->dev, cmd->sess.sess_h);
if (cmd->subcode == SCST_USER_ATTACH_SESS) {
if (tgt_dev != NULL) {
PRINT_ERROR("Session %"PRIx64" already exists)",
cmd->sess.sess_h);
res = EEXIST;
goto reply;
}
tgt_dev = find_empty_tgt_dev(vcmd->dev);
if (tgt_dev == NULL) {
PRINT_ERROR("Too many initiators, session %"PRIx64
" refused)", cmd->sess.sess_h);
res = ENOMEM;
goto reply;
}
tgt_dev->sess_h = cmd->sess.sess_h;
PRINT_INFO("Session from initiator %s (target %s) attached "
"(LUN %"PRIx64", threads_num %d, rd_only %d, sess_h "
"%"PRIx64")", cmd->sess.initiator_name,
cmd->sess.target_name, cmd->sess.lun,
cmd->sess.threads_num, cmd->sess.rd_only,
cmd->sess.sess_h);
} else {
if (tgt_dev == NULL) {
PRINT_ERROR("Session %"PRIx64" not found)",
cmd->sess.sess_h);
res = ESRCH;
goto reply;
}
tgt_dev->sess_h = 0;
PRINT_INFO("Session detached (sess_h %"PRIx64")",
cmd->sess.sess_h);
}
reply:
memset(reply, 0, sizeof(*reply));
reply->cmd_h = cmd->cmd_h;
reply->subcode = cmd->subcode;
reply->result = res;
TRACE_EXIT_RES(res);
return res;
}
static int process_cmd(struct vdisk_cmd *vcmd)
{
struct scst_user_get_cmd *cmd = vcmd->cmd;
struct scst_user_reply_cmd *reply = vcmd->reply;
int res = 0;
TRACE_ENTRY();
TRACE_BUFFER("Received cmd", cmd, sizeof(*cmd));
switch(cmd->subcode) {
case SCST_USER_EXEC:
if (cmd->exec_cmd.data_direction & SCST_DATA_WRITE) {
TRACE_BUFFER("Received cmd data",
(void *)(unsigned long)cmd->exec_cmd.pbuf,
cmd->exec_cmd.bufflen);
}
res = do_exec(vcmd);
if ((reply->exec_reply.resp_data_len != 0) && (res != 150)) {
TRACE_BUFFER("Reply data",
(void *)(unsigned long)reply->exec_reply.pbuf,
reply->exec_reply.resp_data_len);
}
break;
case SCST_USER_ALLOC_MEM:
res = do_alloc_mem(vcmd);
break;
case SCST_USER_PARSE:
res = do_parse(vcmd);
break;
case SCST_USER_ON_CACHED_MEM_FREE:
res = do_cached_mem_free(vcmd);
break;
case SCST_USER_ON_FREE_CMD:
res = do_on_free_cmd(vcmd);
break;
#ifdef DEBUG_EXT_COPY_REMAP
case SCST_USER_EXT_COPY_REMAP:
res = do_ext_copy_remap(vcmd);
break;
#endif
case SCST_USER_TASK_MGMT_RECEIVED:
res = do_tm(vcmd, 0);
break;
case SCST_USER_TASK_MGMT_DONE:
res = do_tm(vcmd, 1);
#ifdef DEBUG_TM_FN_IGNORE
if (dev->debug_tm_ignore) {
sleep(15);
}
#endif
break;
case SCST_USER_ATTACH_SESS:
case SCST_USER_DETACH_SESS:
res = do_sess(vcmd);
break;
default:
PRINT_ERROR("Unknown or wrong cmd subcode %x",
cmd->subcode);
res = -EINVAL;
goto out;
}
out:
TRACE_EXIT_RES(res);
return res;
}
void *main_loop(void *arg)
{
int res = 0, i, j;
struct vdisk_dev *dev = (struct vdisk_dev *)arg;
struct scst_user_get_cmd cmd;
struct scst_user_reply_cmd reply;
struct vdisk_cmd vcmd = {
.fd = -1,
.cmd = &cmd,
.dev = dev,
.may_need_to_free_pbuf = 0,
.reply = &reply,
.sense = {0}
};
int scst_usr_fd = dev->scst_usr_fd;
struct pollfd pl;
#define MULTI_CMDS_CNT 2
struct {
struct scst_user_reply_cmd replies[MULTI_CMDS_CNT];
struct scst_user_get_multi multi_cmd;
struct scst_user_get_cmd cmds[MULTI_CMDS_CNT];
} multi;
TRACE_ENTRY();
vcmd.fd = open_dev_fd(dev);
if (vcmd.fd < 0) {
res = -errno;
PRINT_ERROR("Unable to open file %s (%s)", dev->file_name,
strerror(-res));
goto out;
}
memset(&pl, 0, sizeof(pl));
pl.fd = scst_usr_fd;
pl.events = POLLIN;
cmd.preply = 0;
multi.multi_cmd.preplies = (uintptr_t)&multi.replies[0];
multi.multi_cmd.replies_cnt = 0;
multi.multi_cmd.cmds_cnt = MULTI_CMDS_CNT;
while(1) {
#ifdef DEBUG_TM_IGNORE_ALL
if (dev->debug_tm_ignore && (random() % 50000) == 55) {
TRACE_MGMT_DBG("%s", "Ignore ALL");
dev->debug_tm_ignore_all = 1;
}
if (dev->debug_tm_ignore_all) {
/* Go Astral */
while(1) {
sleep(60);
}
}
#endif
if (use_multi) {
TRACE_DBG("preplies %p (first: %p), replies_cnt %d, "
"replies_done %d, cmds_cnt %d", (void *)(uintptr_t)multi.multi_cmd.preplies,
&multi.replies[0], multi.multi_cmd.replies_cnt,
multi.multi_cmd.replies_done, multi.multi_cmd.cmds_cnt);
res = ioctl(scst_usr_fd, SCST_USER_REPLY_AND_GET_MULTI, &multi.multi_cmd);
} else
res = ioctl(scst_usr_fd, SCST_USER_REPLY_AND_GET_CMD, &cmd);
if (res != 0) {
res = errno;
switch(res) {
case ESRCH:
case EBUSY:
TRACE_MGMT_DBG("SCST_USER returned %d (%s)", res, strerror(res));
cmd.preply = 0;
multi.multi_cmd.preplies = (uintptr_t)&multi.replies[0];
multi.multi_cmd.replies_cnt = 0;
multi.multi_cmd.cmds_cnt = MULTI_CMDS_CNT;
/* fall through */
case EINTR:
continue;
case EAGAIN:
TRACE_DBG("SCST_USER returned EAGAIN (%d)", res);
cmd.preply = 0;
multi.multi_cmd.preplies = (uintptr_t)&multi.replies[0];
multi.multi_cmd.replies_cnt = 0;
multi.multi_cmd.cmds_cnt = MULTI_CMDS_CNT;
if (dev->non_blocking)
break;
else
continue;
default:
PRINT_ERROR("SCST_USER failed: %s (%d)", strerror(res), res);
#if 1
cmd.preply = 0;
multi.multi_cmd.preplies = (uintptr_t)&multi.replies[0];
multi.multi_cmd.replies_cnt = 0;
multi.multi_cmd.cmds_cnt = MULTI_CMDS_CNT;
continue;
#else
goto out_close;
#endif
}
again_poll:
res = poll(&pl, 1, -1);
if (res > 0)
continue;
else if (res == 0)
goto again_poll;
else {
res = errno;
switch(res) {
case ESRCH:
case EBUSY:
case EAGAIN:
TRACE_MGMT_DBG("poll() returned %d "
"(%s)", res, strerror(res));
case EINTR:
goto again_poll;
default:
PRINT_ERROR("poll() failed: %s", strerror(res));
#if 1
goto again_poll;
#else
goto out_close;
#endif
}
}
}
if (use_multi) {
if (multi.multi_cmd.replies_done < multi.multi_cmd.replies_cnt) {
TRACE_MGMT_DBG("replies_done %d < replies_cnt %d (dev %s)",
multi.multi_cmd.replies_done, multi.multi_cmd.replies_cnt, dev->name);
multi.multi_cmd.preplies = (uintptr_t)&multi.replies[multi.multi_cmd.replies_done];
multi.multi_cmd.replies_cnt = multi.multi_cmd.replies_cnt - multi.multi_cmd.replies_done;
multi.multi_cmd.cmds_cnt = MULTI_CMDS_CNT;
continue;
}
TRACE_DBG("cmds_cnt %d", multi.multi_cmd.cmds_cnt);
multi.multi_cmd.preplies = (uintptr_t)&multi.replies[0];
for (i = 0, j = 0; i < multi.multi_cmd.cmds_cnt; i++, j++) {
vcmd.cmd = &multi.cmds[i];
vcmd.reply = &multi.replies[j];
res = process_cmd(&vcmd);
#ifdef DEBUG_TM_IGNORE
if (res == 150) {
j--;
continue;
}
#endif
if (res != 0)
goto out_close;
TRACE_BUFFER("Sending reply", vcmd.reply, sizeof(reply));
}
multi.multi_cmd.replies_cnt = j;
multi.multi_cmd.cmds_cnt = MULTI_CMDS_CNT;
} else {
res = process_cmd(&vcmd);
#ifdef DEBUG_TM_IGNORE
if (res == 150) {
cmd.preply = 0;
continue;
}
#endif
if (res != 0)
goto out_close;
cmd.preply = (unsigned long)&reply;
TRACE_BUFFER("Sending reply", &reply, sizeof(reply));
}
}
out_close:
close(vcmd.fd);
out:
PRINT_INFO("Thread %d exiting (res=%d)", gettid(), res);
TRACE_EXIT_RES(res);
return (void *)(long)res;
}
uint64_t gen_dev_id_num(const struct vdisk_dev *dev)
{
uint32_t dev_id_num;
dev_id_num = crc32buf(dev->name, strlen(dev->name)+1);
return ((uint64_t)vdisk_ID << 32) | dev_id_num;
}
static void exec_inquiry(struct vdisk_cmd *vcmd)
{
struct vdisk_dev *dev = vcmd->dev;
struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
int resp_len = 0;
int length = cmd->bufflen;
unsigned int i;
uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf;
uint8_t buf[INQ_BUF_SZ];
TRACE_ENTRY();
if (cmd->cdb[1] & CMDDT) {
TRACE_DBG("%s", "INQUIRY: CMDDT is unsupported");
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
goto out;
}
memset(buf, 0, sizeof(buf));
buf[0] = dev->type; /* type dev */
if (buf[0] == TYPE_ROM)
buf[1] = 0x80; /* removable */
/* Vital Product */
if (cmd->cdb[1] & EVPD) {
uint64_t dev_id_num = gen_dev_id_num(dev);
if (0 == cmd->cdb[2]) { /* supported vital product data pages */
buf[3] = 5;
buf[4] = 0x0; /* this page */
buf[5] = 0x80; /* unit serial number */
buf[6] = 0x83; /* device identification */
buf[7] = 0xB0; /* block limits */
buf[8] = 0xB1; /* block device characteristics */
resp_len = buf[3] + 6;
} else if (0x80 == cmd->cdb[2]) { /* unit serial number */
unsigned int usn_len = strlen(dev->usn);
buf[1] = 0x80;
buf[3] = usn_len;
assert(4 + usn_len <= sizeof(buf));
memcpy(&buf[4], dev->usn, usn_len);
resp_len = buf[3] + 4;
} else if (0x83 == cmd->cdb[2]) { /* device identification */
int num = 4;
char *t10_id = (char *)&buf[num + 12];
buf[1] = 0x83;
/* Two identification descriptors: */
/* T10 vendor identifier field format (faked) */
buf[num + 0] = 0x2; /* ASCII */
buf[num + 1] = 0x1; /* Vendor ID */
memcpy(&buf[num + 4], VENDOR, 8);
snprintf(t10_id, sizeof(buf) - num - 12,
"%"PRIx64"-%s", dev_id_num, dev->name);
i = strlen(t10_id) + 1;
TRACE_DBG("t10_dev_id %s", t10_id);
buf[num + 3] = 8 + i;
num += buf[num + 3];
num += 4;
/* NAA IEEE registered identifier */
buf[num + 0] = 0x1; /* binary */
buf[num + 1] = 0x3; /* NAA */
buf[num + 2] = 0x0; /* reserved */
buf[num + 3] = NAA_ID_LEN; /* 16 butes */
memcpy(&buf[num + 4], dev->naa_id, NAA_ID_LEN);
num = num + 20 - 4;
resp_len = num;
buf[2] = (resp_len >> 8) & 0xFF;
buf[3] = resp_len & 0xFF;
resp_len += 4;
} else if (0xB0 == cmd->cdb[2]) {
/* Block Limits */
int max_transfer;
buf[1] = 0xB0;
buf[3] = 0x3C;
buf[5] = 0xFF; /* No MAXIMUM COMPARE AND WRITE LENGTH limit */
/* Optimal transfer granuality is PAGE_SIZE */
max_transfer = max((int)(4096/dev->block_size), (int)1);
buf[6] = (max_transfer >> 8) & 0xff;
buf[7] = max_transfer & 0xff;
/*
* Max transfer len is min of sg limit and 8M, but we
* don't have access to them here, so let's use 1M.
*/
max_transfer = 1*1024*1024;
buf[8] = (max_transfer >> 24) & 0xff;
buf[9] = (max_transfer >> 16) & 0xff;
buf[10] = (max_transfer >> 8) & 0xff;
buf[11] = max_transfer & 0xff;
/*
* Let's have optimal transfer len 512KB. 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.
*/
max_transfer = min((int)max_transfer, (int)(512*1024 / dev->block_size));
buf[12] = (max_transfer >> 24) & 0xff;
buf[13] = (max_transfer >> 16) & 0xff;
buf[14] = (max_transfer >> 8) & 0xff;
buf[15] = max_transfer & 0xff;
resp_len = buf[3] + 4;
} else if (0xB1 == cmd->cdb[2]) {
int r;
/* Block Device Characteristics */
buf[1] = 0xB1;
buf[3] = 0x3C;
#if 0
if (virt_dev->rotational) {
#endif
/* 15K RPM */
r = 0x3A98;
#if 0
} else
r = 1;
#endif
buf[4] = (r >> 8) & 0xff;
buf[5] = r & 0xff;
resp_len = buf[3] + 4;
} else {
TRACE_DBG("INQUIRY: Unsupported EVPD page %x",
cmd->cdb[2]);
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
goto out;
}
} else {
int len;
if (cmd->cdb[2] != 0) {
TRACE_DBG("INQUIRY: Unsupported page %x", cmd->cdb[2]);
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
goto out;
}
buf[2] = 6; /* Device complies to SPC-4 */
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 */
memcpy(&buf[8], VENDOR, 8);
/* 16 byte ASCII Product Identification of the target - left aligned */
memset(&buf[16], ' ', 16);
len = min(strlen(dev->name), (size_t)16);
memcpy(&buf[16], dev->name, len);
/* 4 byte ASCII Product Revision Level of the target - left aligned */
memcpy(&buf[32], FIO_REV, 4);
resp_len = buf[4] + 5;
}
sBUG_ON(resp_len >= (int)sizeof(buf));
if (length > resp_len)
length = resp_len;
memcpy(address, buf, length);
if (length < reply->resp_data_len)
set_resp_data_len(vcmd, length);
out:
TRACE_EXIT();
return;
}
static void exec_request_sense(struct vdisk_cmd *vcmd)
{
struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
int length = cmd->bufflen, l;
uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf;
uint8_t b[SCST_STANDARD_SENSE_LEN];
TRACE_ENTRY();
l = set_sense(b, sizeof(b), SCST_LOAD_SENSE(scst_sense_no_sense));
length = min(l, length);
memcpy(address, b, length);
if (length < reply->resp_data_len)
set_resp_data_len(vcmd, length);
TRACE_EXIT();
return;
}
/*
* <<Following mode pages info copied from ST318451LW with some corrections>>
*
* ToDo: revise them
*/
static int err_recov_pg(unsigned char *p, int pcontrol)
{ /* 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 disconnect_pg(unsigned char *p, int pcontrol)
{ /* 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 rigid_geo_pg(unsigned char *p, int pcontrol,
struct vdisk_dev *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;
memcpy(p, geo_m_pg, sizeof(geo_m_pg));
ncyl = dev->nblocks / (DEF_HEADS * DEF_SECTORS);
if ((dev->nblocks % (DEF_HEADS * DEF_SECTORS)) != 0)
ncyl++;
memcpy(&n, p + 2, sizeof(uint32_t));
n = n | (htonl(ncyl) >> 8);
memcpy(p + 2, &n, sizeof(uint32_t));
if (1 == pcontrol)
memset(p + 2, 0, sizeof(geo_m_pg) - 2);
return sizeof(geo_m_pg);
}
static int format_pg(unsigned char *p, int pcontrol,
struct vdisk_dev *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] = (dev->block_size >> 8) & 0xff;
p[13] = dev->block_size & 0xff;
if (1 == pcontrol)
memset(p + 2, 0, sizeof(format_pg) - 2);
return sizeof(format_pg);
}
static int caching_pg(unsigned char *p, int pcontrol,
struct vdisk_dev *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] |= !(dev->wt_flag) ? WCE : 0;
if (1 == pcontrol)
memset(p + 2, 0, sizeof(caching_pg) - 2);
return sizeof(caching_pg);
}
static int ctrl_m_pg(unsigned char *p, int pcontrol,
struct vdisk_dev *dev)
{ /* Control mode page for mode_sense */
const unsigned char ctrl_m_pg[] = {0xa, 0xa, 0x20, 0, 0, 0x40, 0, 0,
0, 0, 0x2, 0x4b};
memcpy(p, ctrl_m_pg, sizeof(ctrl_m_pg));
if (!dev->wt_flag && !dev->nv_cache)
p[3] |= 0x10; /* Enable unrestricted reordering */
if (1 == pcontrol)
memset(p + 2, 0, sizeof(ctrl_m_pg) - 2);
return sizeof(ctrl_m_pg);
}
static int iec_m_pg(unsigned char *p, int pcontrol)
{ /* 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 exec_mode_sense(struct vdisk_cmd *vcmd)
{
struct vdisk_dev *dev = vcmd->dev;
struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
int length = cmd->bufflen;
uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf;
uint8_t buf[MSENSE_BUF_SZ];
int 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;
TRACE_ENTRY();
blocksize = dev->block_size;
nblocks = dev->nblocks;
type = 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 = (dev->rd_only_flag ? WP : 0) | DPOFUA;
memset(buf, 0, sizeof(buf));
if (0x3 == pcontrol) {
TRACE_DBG("%s", "MODE SENSE: Saving values not supported");
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_saving_params_unsup));
goto out;
}
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");
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
goto out;
}
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 {
buf[offset + 0] = (nblocks >> (BYTE * 3)) & 0xFF;/* num blks */
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 = err_recov_pg(bp, pcontrol);
break;
case 0x2: /* Disconnect-Reconnect page, all devices */
len = disconnect_pg(bp, pcontrol);
break;
case 0x3: /* Format device page, direct access */
len = format_pg(bp, pcontrol, dev);
break;
case 0x4: /* Rigid disk geometry */
len = rigid_geo_pg(bp, pcontrol, dev);
break;
case 0x8: /* Caching page, direct access */
len = caching_pg(bp, pcontrol, dev);
break;
case 0xa: /* Control Mode page, all devices */
len = ctrl_m_pg(bp, pcontrol, dev);
break;
case 0x1c: /* Informational Exceptions Mode page, all devices */
len = iec_m_pg(bp, pcontrol);
break;
case 0x3f: /* Read all Mode pages */
len = err_recov_pg(bp, pcontrol);
len += disconnect_pg(bp + len, pcontrol);
len += format_pg(bp + len, pcontrol, dev);
len += caching_pg(bp + len, pcontrol, dev);
len += ctrl_m_pg(bp + len, pcontrol, dev);
len += iec_m_pg(bp + len, pcontrol);
len += rigid_geo_pg(bp + len, pcontrol, dev);
break;
default:
TRACE_DBG("MODE SENSE: Unsupported page %x", pcode);
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
goto out;
}
offset += len;
if (msense_6)
buf[0] = offset - 1;
else {
buf[0] = ((offset - 2) >> 8) & 0xff;
buf[1] = (offset - 2) & 0xff;
}
sBUG_ON(offset >= (int)sizeof(buf));
if (offset > length)
offset = length;
memcpy(address, buf, offset);
if (offset < reply->resp_data_len)
set_resp_data_len(vcmd, offset);
out:
TRACE_EXIT();
return;
}
static int set_wt(struct vdisk_dev *dev, int wt)
{
int res = 0;
TRACE_ENTRY();
if ((dev->wt_flag == wt) || dev->nullio)
goto out;
pthread_mutex_lock(&dev->dev_mutex);
dev->wt_flag = wt;
pthread_mutex_unlock(&dev->dev_mutex);
out:
TRACE_EXIT_RES(res);
return res;
}
static void exec_mode_select(struct vdisk_cmd *vcmd)
{
struct vdisk_dev *dev = vcmd->dev;
struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
int length = cmd->bufflen;
uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf;
int mselect_6, offset;
TRACE_ENTRY();
mselect_6 = (MODE_SELECT == cmd->cdb[0]);
if (!(cmd->cdb[1] & PF) || (cmd->cdb[1] & SP)) {
PRINT_ERROR("MODE SELECT: PF and/or SP are wrongly set "
"(cdb[1]=%x)", cmd->cdb[1]);
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
goto out;
}
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 "
"length");
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_invalid_field_in_parm_list));
goto out;
}
while (length > offset + 2) {
if (address[offset] & PS) {
PRINT_ERROR("%s", "MODE SELECT: Illegal PS bit");
set_cmd_error(vcmd, SCST_LOAD_SENSE(
scst_sense_invalid_field_in_parm_list));
goto out;
}
if ((address[offset] & 0x3f) == 0x8) { /* Caching page */
if (address[offset + 1] != 18) {
PRINT_ERROR("%s", "MODE SELECT: Invalid "
"caching page request");
set_cmd_error(vcmd, SCST_LOAD_SENSE(
scst_sense_invalid_field_in_parm_list));
goto out;
}
if (set_wt(dev,
(address[offset + 2] & WCE) ? 0 : 1) != 0) {
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_hardw_error));
goto out;
}
break;
}
offset += address[offset + 1];
}
out:
TRACE_EXIT();
return;
}
static void exec_read_capacity(struct vdisk_cmd *vcmd)
{
struct vdisk_dev *dev = vcmd->dev;
struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
int length = cmd->bufflen;
uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf;
uint32_t blocksize;
uint64_t nblocks;
uint8_t buffer[8];
TRACE_ENTRY();
blocksize = dev->block_size;
nblocks = dev->nblocks;
/* last block on the 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 = min(length, (int)sizeof(buffer));
memcpy(address, buffer, length);
if (length < reply->resp_data_len)
set_resp_data_len(vcmd, length);
TRACE_EXIT();
return;
}
static void exec_read_capacity16(struct vdisk_cmd *vcmd)
{
struct vdisk_dev *dev = vcmd->dev;
struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
int length = cmd->bufflen;
uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf;
uint32_t blocksize;
uint64_t nblocks;
uint8_t buffer[32];
TRACE_ENTRY();
blocksize = dev->block_size;
nblocks = dev->nblocks - 1;
memset(buffer, 0, sizeof(buffer));
buffer[0] = nblocks >> 56;
buffer[1] = (nblocks >> 48) & 0xFF;
buffer[2] = (nblocks >> 40) & 0xFF;
buffer[3] = (nblocks >> 32) & 0xFF;
buffer[4] = (nblocks >> 24) & 0xFF;
buffer[5] = (nblocks >> 16) & 0xFF;
buffer[6] = (nblocks >> 8) & 0xFF;
buffer[7] = nblocks & 0xFF;
buffer[8] = (blocksize >> (BYTE * 3)) & 0xFF;
buffer[9] = (blocksize >> (BYTE * 2)) & 0xFF;
buffer[10] = (blocksize >> (BYTE * 1)) & 0xFF;
buffer[11] = (blocksize >> (BYTE * 0)) & 0xFF;
switch (blocksize) {
case 512:
buffer[13] = 3;
break;
case 1024:
buffer[13] = 2;
break;
case 2048:
buffer[13] = 1;
break;
case 4096:
default:
buffer[13] = 0;
break;
}
length = min(length, (int)sizeof(buffer));
memcpy(address, buffer, length);
if (length < reply->resp_data_len)
set_resp_data_len(vcmd, length);
TRACE_EXIT();
return;
}
static void exec_read_toc(struct vdisk_cmd *vcmd)
{
struct vdisk_dev *dev = vcmd->dev;
struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
int32_t off = 0;
int length = cmd->bufflen;
uint8_t *address = (uint8_t *)(unsigned long)cmd->pbuf;
uint32_t nblocks;
uint8_t buffer[4+8+8] = { 0x00, 0x0a, 0x01, 0x01, 0x00, 0x14,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00 };
TRACE_ENTRY();
if (dev->type != TYPE_ROM) {
PRINT_ERROR("%s", "READ TOC for non-CDROM device");
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_invalid_opcode));
goto out;
}
if (cmd->cdb[2] & 0x0e/*Format*/) {
PRINT_ERROR("%s", "READ TOC: invalid requested data format");
set_cmd_error(vcmd,
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]);
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_invalid_field_in_cdb));
goto out;
}
/* ToDo when you have > 8TB ROM device. */
nblocks = (uint32_t)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 */
buffer[off+1] = 0x14; /* ADDR 0x10 - Q Sub-channel encodes current position data
CONTROL 0x04 - Data track, recoreded uninterrupted */
buffer[off+2] = 0x01; /* Track Number */
off += 8;
}
if (!(cmd->cdb[2] & 0x01))
{
/* Lead-out area TOC Track Descriptor */
buffer[off+1] = 0x14;
buffer[off+2] = 0xAA; /* Track Number */
buffer[off+4] = (nblocks >> (BYTE * 3)) & 0xFF; /* Track Start Address */
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);
if (off < reply->resp_data_len)
set_resp_data_len(vcmd, off);
out:
TRACE_EXIT();
return;
}
static void exec_prevent_allow_medium_removal(struct vdisk_cmd *vcmd)
{
struct vdisk_dev *dev = vcmd->dev;
struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
TRACE_DBG("PERSIST/PREVENT 0x%02x", cmd->cdb[4]);
pthread_mutex_lock(&dev->dev_mutex);
dev->prevent_allow_medium_removal = cmd->cdb[4] & 0x01 ? 1 : 0;
pthread_mutex_unlock(&dev->dev_mutex);
return;
}
static int exec_fsync(struct vdisk_cmd *vcmd)
{
int res = 0;
struct vdisk_dev *dev = vcmd->dev;
/* Hopefully, the compiler will generate the single comparison */
if (dev->nv_cache || dev->wt_flag || dev->rd_only_flag ||
dev->o_direct_flag || dev->nullio)
goto out;
/* ToDo: use sync_file_range() instead */
fsync(vcmd->fd);
out:
TRACE_EXIT_RES(res);
return res;
}
static void exec_read(struct vdisk_cmd *vcmd, loff_t loff)
{
struct vdisk_dev *dev = vcmd->dev;
struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
int length = cmd->bufflen;
uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf;
int fd = vcmd->fd;
loff_t err;
TRACE_ENTRY();
TRACE_DBG("reading off %"PRId64", len %d", loff, length);
if (dev->nullio)
err = length;
else {
/* SEEK */
err = lseek64(fd, loff, 0/*SEEK_SET*/);
if (err != loff) {
PRINT_ERROR("lseek trouble %"PRId64" != %"PRId64
" (errno %d)", (uint64_t)err, (uint64_t)loff,
errno);
set_cmd_error(vcmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
goto out;
}
/* READ */
err = read(fd, address, length);
}
if ((err < 0) || (err < length)) {
PRINT_ERROR("read() returned %"PRId64" from %d (errno %d)",
(uint64_t)err, length, errno);
if (err == -EAGAIN)
set_busy(vcmd);
else {
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_read_error));
}
goto out;
}
set_resp_data_len(vcmd, cmd->bufflen);
out:
TRACE_EXIT();
return;
}
static void exec_write(struct vdisk_cmd *vcmd, loff_t loff)
{
struct vdisk_dev *dev = vcmd->dev;
struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
loff_t err;
int length = cmd->bufflen;
uint8_t *address = (uint8_t*)(unsigned long)cmd->pbuf;
int fd = vcmd->fd;
TRACE_ENTRY();
restart:
TRACE_DBG("writing off %"PRId64", len %d", loff, length);
if (dev->nullio)
err = length;
else {
/* SEEK */
err = lseek64(fd, loff, 0/*SEEK_SET*/);
if (err != loff) {
PRINT_ERROR("lseek trouble %"PRId64" != %"PRId64
" (errno %d)", (uint64_t)err, (uint64_t)loff,
errno);
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_hardw_error));
goto out;
}
/* WRITE */
err = write(fd, address, length);
}
if (err < 0) {
PRINT_ERROR("write() returned %"PRId64" from %d (errno %d, "
"cmd_h %x)", err, length, errno, vcmd->cmd->cmd_h);
if (err == -EAGAIN)
set_busy(vcmd);
else {
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_write_error));
}
goto out;
} else if (err < length) {
/*
* Probably that's wrong, but sometimes write() returns
* value less, than requested. Let's restart.
*/
TRACE_MGMT_DBG("write() returned %d from %d", (int)err, length);
if (err == 0) {
PRINT_INFO("Suspicious: write() returned 0 from "
"%d", length);
}
length -= err;
goto restart;
}
out:
TRACE_EXIT();
return;
}
static void exec_verify(struct vdisk_cmd *vcmd, loff_t loff)
{
struct vdisk_dev *dev = vcmd->dev;
struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
loff_t err;
int64_t length = cmd->bufflen;
uint8_t *address = (uint8_t *)(unsigned long)cmd->pbuf;
int compare;
int fd = vcmd->fd;
uint8_t mem_verify[128*1024];
TRACE_ENTRY();
if (exec_fsync(vcmd) != 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 */
if (!dev->nullio) {
err = lseek64(fd, loff, 0/*SEEK_SET*/);
if (err != loff) {
PRINT_ERROR("lseek trouble %"PRId64" != %"PRId64
" (errno %d)", (uint64_t)err,
(uint64_t)loff, errno);
set_cmd_error(vcmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
goto out;
}
}
if ((length == 0) && (cmd->data_len != 0)) {
length = cmd->data_len;
compare = 0;
} else
compare = 1;
while (length > 0) {
int64_t len_mem = (length > (int)sizeof(mem_verify)) ?
(int)sizeof(mem_verify) : length;
TRACE_DBG("Verify: length %"PRId64" - len_mem %"PRId64,
length, len_mem);
if (!dev->nullio)
err = read(fd, (char *)mem_verify, len_mem);
else
err = len_mem;
if ((err < 0) || (err < len_mem)) {
PRINT_ERROR("read() returned %"PRId64" from %"PRId64" "
"(errno %d)", (uint64_t)err, len_mem, errno);
if (err == -EAGAIN)
set_busy(vcmd);
else {
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_read_error));
}
goto out;
}
if (compare && memcmp(address, mem_verify, len_mem) != 0) {
TRACE_DBG("Verify: error memcmp length %"PRId64, length);
set_cmd_error(vcmd,
SCST_LOAD_SENSE(scst_sense_miscompare_error));
goto out;
}
length -= len_mem;
address += len_mem;
}
if (length < 0) {
PRINT_ERROR("Failure: %"PRId64, length);
set_cmd_error(vcmd, SCST_LOAD_SENSE(scst_sense_hardw_error));
}
out:
TRACE_EXIT();
return;
}
static void exec_write_same(struct vdisk_cmd *vcmd)
{
struct vdisk_dev *dev = vcmd->dev;
struct scst_user_scsi_cmd_exec *cmd = &vcmd->cmd->exec_cmd;
struct scst_user_scsi_cmd_reply_exec *reply = &vcmd->reply->exec_reply;
uint64_t blocks = cmd->data_len >> dev->block_shift;
static struct scst_user_data_descriptor uwhere[3];
TRACE_ENTRY();
if (blocks < 2)
goto out;
/* It's only a debug code, so it's OK to use static descr */
memset(uwhere, 0, sizeof(uwhere));
uwhere[0].usdd_lba = cmd->lba;
uwhere[0].usdd_blocks = 1;
uwhere[1].usdd_lba = cmd->lba+1;
uwhere[1].usdd_blocks = blocks-1;
reply->reply_type = SCST_EXEC_REPLY_DO_WRITE_SAME;
reply->ws_descriptors_len = sizeof(uwhere);
reply->ws_descriptors = (unsigned long)uwhere;
out:
TRACE_EXIT();
return;
}