WinUAE/scsitape.cpp
2014-07-30 00:04:46 +02:00

676 lines
15 KiB
C++

/*
* UAE - The Un*x Amiga Emulator
*
* SCSI tape emulation
*
* Copyright 2013 Toni Wilen
*
*/
#include "sysconfig.h"
#include "sysdeps.h"
#include "options.h"
#include "filesys.h"
#include "scsi.h"
#include "blkdev.h"
#include "zfile.h"
#include "memory.h"
#include "threaddep/thread.h"
#include "a2091.h"
#include "fsdb.h"
int log_tapeemu = 1;
#define TAPE_INDEX _T("index.tape")
static struct scsi_data_tape *tapeunits[MAX_FILESYSTEM_UNITS];
static bool notape (struct scsi_data_tape *tape)
{
return tape->tape_dir[0] == 0 || tape->nomedia;
}
static int rl (uae_u8 *p)
{
return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | (p[3]);
}
static void wl (uae_u8 *p, int v)
{
p[0] = v >> 24;
p[1] = v >> 16;
p[2] = v >> 8;
p[3] = v;
}
void tape_free (struct scsi_data_tape *tape)
{
if (!tape)
return;
zfile_fclose (tape->zf);
zfile_fclose (tape->index);
zfile_closedir_archive (tape->zd);
xfree(tape);
for (int i = 0; i < MAX_FILESYSTEM_UNITS; i++) {
if (tapeunits[i] == tape)
tapeunits[i] = NULL;
}
}
static void tape_init (int unit, struct scsi_data_tape *tape, const TCHAR *tape_directory, bool readonly)
{
TCHAR path[MAX_DPATH];
memset (tape, 0, sizeof (struct scsi_data_tape));
_tcscpy (tape->tape_dir, tape_directory);
tape->blocksize = 512;
tape->wp = readonly;
tape->beom = -1;
tape->nomedia = false;
tape->unloaded = false;
if (my_existsdir (tape->tape_dir)) {
tape->realdir = true;
} else {
tape->zd = zfile_opendir_archive (tape_directory, ZFD_ARCHIVE | ZFD_NORECURSE);
if (!tape->zd)
tape->nomedia = true;
}
_tcscpy (path, tape_directory);
_tcscat (path, FSDB_DIR_SEPARATOR_S);
_tcscat (path, TAPE_INDEX);
tape->index = zfile_fopen (path, _T("rb"), ZFD_NORMAL);
if (tape->index)
write_log (_T("TAPEEMU INDEX: '%s'\n"), path);
tapeunits[unit] = tape;
}
struct scsi_data_tape *tape_alloc (int unitnum, const TCHAR *tape_directory, bool readonly)
{
struct scsi_data_tape *tape = xcalloc (struct scsi_data_tape, 1);
tape_init (unitnum, tape, tape_directory, readonly);
return tape;
}
void tape_media_change (int unitnum, struct uaedev_config_info *ci)
{
struct scsi_data_tape *tape = tapeunits[unitnum];
if (!tape)
return;
tape_init (unitnum, tape, ci->rootdir, ci->readonly);
}
bool tape_get_info (int unitnum, struct device_info *di)
{
struct scsi_data_tape *tape = tapeunits[unitnum];
memset (di, 0, sizeof (struct device_info));
if (!tape)
return false;
di->media_inserted = notape (tape) == false;
_tcscpy (di->label, tape->tape_dir);
return true;
}
static void rewind (struct scsi_data_tape *tape)
{
zfile_fclose (tape->zf);
tape->zf = NULL;
my_closedir (tape->od);
tape->file_number = 0;
tape->file_offset = 0;
tape->od = NULL;
tape->beom = -1;
if (tape->index)
zfile_fseek (tape->index, 0, SEEK_SET);
if (tape->zd)
zfile_resetdir_archive (tape->zd);
}
static void erase (struct scsi_data_tape *tape)
{
struct my_opendir_s *od;
if (!tape->realdir)
return;
rewind (tape);
od = my_opendir (tape->tape_dir);
if (od) {
for (;;) {
TCHAR path[MAX_DPATH], filename[MAX_DPATH];
if (!my_readdir (od, filename))
break;
TCHAR *ext = _tcsrchr (filename, '.');
if (ext && !_tcsicmp (ext, _T(".tape"))) {
_stprintf (path, _T("%s%s%s"), tape->tape_dir, FSDB_DIR_SEPARATOR_S, filename);
if (my_existsfile (path))
my_unlink (path);
}
}
my_closedir (od);
}
}
static bool next_file (struct scsi_data_tape *tape)
{
zfile_fclose (tape->zf);
tape->zf = NULL;
tape->file_offset = 0;
if (tape->index) {
TCHAR path[MAX_DPATH];
TCHAR name[256];
name[0] = 0;
zfile_fgets (name, sizeof name / sizeof (TCHAR), tape->index);
my_trim (name);
if (name[0] == 0)
goto end;
_tcscpy (path, tape->tape_dir);
_tcscat (path, FSDB_DIR_SEPARATOR_S);
_tcscat (path, name);
tape->zf = zfile_fopen (path, _T("rb"), ZFD_NORMAL);
write_log (_T("TAPEEMU: File '%s'\n"), path);
} else if (tape->realdir) {
TCHAR path[MAX_DPATH], filename[MAX_DPATH];
if (tape->od == NULL)
tape->od = my_opendir (tape->tape_dir);
if (!tape->od) {
tape_free (tape);
goto end;
}
for (;;) {
if (!my_readdir (tape->od, filename))
goto end;
if (_tcsicmp (filename, TAPE_INDEX))
continue;
_stprintf (path, _T("%s%s%s"), tape->tape_dir, FSDB_DIR_SEPARATOR_S, filename);
if (!my_existsfile (path))
continue;
tape->zf = zfile_fopen (path, _T("rb"), 0);
if (!tape->zf)
continue;
break;
}
if (tape->zf)
write_log (_T("TAPEEMU DIR: File '%s'\n"), zfile_getname (tape->zf));
} else {
tape->zf = zfile_readdir_archive_open (tape->zd, _T("rb"));
if (tape->zf)
write_log (_T("TAPEEMU ARC: File '%s'\n"), zfile_getname (tape->zf));
}
if (tape->zf) {
tape->file_number++;
return true;
}
end:
write_log (_T("TAPEEMU: end of tape\n"));
tape->beom = 1;
return false;
}
static int tape_read (struct scsi_data_tape *tape, uae_u8 *scsi_data, int len, bool *eof)
{
int got;
*eof = false;
if (tape->beom > 0) {
*eof = true;
return -1;
}
if (!tape->zf) {
rewind (tape);
if (!next_file (tape))
return -1;
}
zfile_fseek (tape->zf, tape->file_offset, SEEK_SET);
got = zfile_fread (scsi_data, 1, len, tape->zf);
tape->file_offset += got;
if (got < len) {
*eof = true;
next_file (tape);
}
return got;
}
static int tape_write (struct scsi_data_tape *tape, uae_u8 *scsi_data, int len)
{
TCHAR path[MAX_DPATH], numname[30];
int exists;
if (!tape->realdir)
return -1;
_stprintf (numname, _T("%05d.tape"), tape->file_number);
_stprintf (path, _T("%s%s%s"), tape->tape_dir, FSDB_DIR_SEPARATOR_S, numname);
exists = my_existsfile (path);
struct zfile *zf = zfile_fopen (path, _T("a+b"));
if (!zf)
return -1;
zfile_fseek (zf, 0, SEEK_END);
len = zfile_fwrite (scsi_data, 1, len, zf);
zfile_fclose (zf);
if (!exists) {
_stprintf (path, _T("%s%s%s"), tape->tape_dir, FSDB_DIR_SEPARATOR_S, TAPE_INDEX);
zf = zfile_fopen (path, _T("a+b"));
if (zf) {
zfile_fputs (zf, numname);
zfile_fputs (zf, _T("\n"));
}
zfile_fclose (zf);
}
return len;
}
int scsi_tape_emulate (struct scsi_data_tape *tape, uae_u8 *cmdbuf, int scsi_cmd_len,
uae_u8 *scsi_data, int *data_len, uae_u8 *r, int *reply_len, uae_u8 *s, int *sense_len)
{
uae_s64 len;
int lr = 0, ls = 0;
int scsi_len = -1;
int status = 0;
int lun;
bool eof;
if (log_tapeemu)
write_log (_T("TAPEEMU: %02X.%02X.%02X.%02X.%02X.%02X\n"),
cmdbuf[0], cmdbuf[1], cmdbuf[2], cmdbuf[3], cmdbuf[4], cmdbuf[5]);
if (cmdbuf[0] == 3) {
if (tape->beom == -1)
s[9] |= 0x8; // beginning of media
if (tape->beom == 1)
s[2] |= 0x40; // end of media
if (*sense_len < 0x12)
*sense_len = 0x12;
return 0;
}
*reply_len = *sense_len = 0;
memset (r, 0, 256);
memset (s, 0, 256);
s[0] = 0x70;
lun = cmdbuf[1] >> 5;
if (cmdbuf[0] != 0x03 && cmdbuf[0] != 0x12 && lun) {
status = 2; /* CHECK CONDITION */
s[0] = 0x70;
s[2] = 5; /* ILLEGAL REQUEST */
s[12] = 0x25; /* INVALID LUN */
ls = 0x12;
goto err;
}
switch (cmdbuf[0])
{
case 0x00: /* TEST UNIT READY */
if (notape (tape))
goto notape;
if (tape->unloaded)
goto unloaded;
scsi_len = 0;
break;
case 0x19: /* ERASE */
if (log_tapeemu)
write_log (_T("TAPEEMU ERASE\n"));
if (notape (tape))
goto notape;
if (tape->unloaded)
goto unloaded;
if (tape->wp)
goto writeprot;
erase (tape);
rewind (tape);
tape->beom = 1;
scsi_len = 0;
break;
case 0x1b: /* LOAD/UNLOAD */
{
bool load = (cmdbuf[4] & 1) != 0;
bool ret = (cmdbuf[4] & 2) != 0;
bool eot = (cmdbuf[4] & 4) != 0;
if (log_tapeemu)
write_log (_T("TAPEEMU LOAD/UNLOAD %d:%d:%d\n"), eot, ret, load);
if (notape (tape))
goto notape;
if (load && eot)
goto errreq;
rewind (tape);
tape->unloaded = !load;
if (eot) {
tape->beom = 1;
} else {
tape->beom = -1;
}
scsi_len = 0;
break;
}
case 0x01: /* REWIND */
if (log_tapeemu)
write_log (_T("TAPEEMU: REWIND. IMMED=%d\n"), cmdbuf[1] & 1);
if (notape (tape))
goto notape;
if (tape->unloaded)
goto unloaded;
rewind (tape);
scsi_len = 0;
break;
case 0x11: /* SPACE */
{
int code = cmdbuf[1] & 15;
int count = rl (cmdbuf + 1) & 0xffffff;
if (log_tapeemu)
write_log (_T("TAPEEMU: SPACE code=%d count=%d\n"), code, count);
if (notape (tape))
goto notape;
if (tape->unloaded)
goto unloaded;
if (code >= 2)
goto errreq;
if (code == 1) {
if (!next_file (tape))
goto endoftape;
}
scsi_len = 0;
}
break;
case 0x10: /* WRITE FILEMARK */
len = rl (cmdbuf + 1) & 0xffffff;
if (log_tapeemu)
write_log (_T("TAPEEMU WRITE FILEMARK %lld\n"), len);
if (notape (tape))
goto notape;
if (tape->unloaded)
goto unloaded;
if (tape->wp)
goto writeprot;
if (len > 0) {
tape->file_number++;
tape_write (tape, scsi_data, 0);
}
scsi_len = 0;
break;
case 0x0a: /* WRITE (6) */
len = rl (cmdbuf + 1) & 0xffffff;
if (cmdbuf[1] & 1)
len *= tape->blocksize;
if (log_tapeemu)
write_log (_T("TAPEEMU WRITE %lld (%d, %d)\n"), len, rl (cmdbuf + 1) & 0xffffff, cmdbuf[1] & 1);
if (notape (tape))
goto notape;
if (tape->unloaded)
goto unloaded;
if (tape->wp)
goto writeprot;
if (tape->beom < 0)
tape->beom = 0;
scsi_len = tape_write (tape, scsi_data, len);
if (scsi_len < 0)
goto writeprot;
break;
case 0x08: /* READ (6) */
len = rl (cmdbuf + 1) & 0xffffff;
if (cmdbuf[1] & 1)
len *= tape->blocksize;
if (log_tapeemu)
write_log (_T("TAPEEMU READ %lld (%d, %d)\n"), len, rl (cmdbuf + 1) & 0xffffff, cmdbuf[1] & 1);
if (notape (tape))
goto notape;
if (tape->unloaded)
goto unloaded;
if (tape->beom < 0)
tape->beom = 0;
scsi_len = tape_read (tape, scsi_data, len, &eof);
if (log_tapeemu)
write_log (_T("-> READ %d bytes\n"), scsi_len);
if ((cmdbuf[1] & 1) && scsi_len > 0 && scsi_len < len) {
int gap = tape->blocksize - (scsi_len & (tape->blocksize - 1));
if (gap > 0 && gap < tape->blocksize)
memset (scsi_data + scsi_len, 0, gap);
scsi_len += tape->blocksize - 1;
scsi_len &= ~(tape->blocksize - 1);
}
if (scsi_len < 0) {
tape->beom = 2;
status = SCSI_STATUS_CHECK_CONDITION;
s[0] = 0x80 | 0x70;
s[2] = 8; /* BLANK CHECK */
if (cmdbuf[1] & 1)
wl (&s[3], len / tape->blocksize);
else
wl (&s[3], len);
s[12] = 0;
s[13] = 2; /* End-of-partition/medium detected */
ls = 0x12;
} else if (eof) {
status = SCSI_STATUS_CHECK_CONDITION;
s[0] = 0x80 | 0x70; // Valid + code
s[2] = 0x80 | 0; /* File Mark Detected + NO SENSE */
if (cmdbuf[1] & 1)
wl (&s[3], (len - scsi_len) / tape->blocksize);
else
wl (&s[3], len);
s[12] = 0;
s[13] = 1; /* File Mark detected */
ls = 0x12;
if (log_tapeemu)
write_log (_T("TAPEEMU READ FILE END, %lld remaining\n"), len - scsi_len);
}
break;
case 0x5a: // MODE SENSE(10)
case 0x1a: /* MODE SENSE(6) */
{
uae_u8 *p;
int maxlen;
bool sense10 = cmdbuf[0] == 0x5a;
int totalsize, bdsize;
int pc = cmdbuf[2] >> 6;
int pcode = cmdbuf[2] & 0x3f;
int dbd = cmdbuf[1] & 8;
if (cmdbuf[0] == 0x5a)
dbd = 1;
if (log_tapeemu)
write_log (_T("TAPEEMU MODE SENSE PC=%d CODE=%d DBD=%d\n"), pc, pcode, dbd);
p = r;
if (sense10) {
totalsize = 8 - 2;
maxlen = (cmdbuf[7] << 8) | cmdbuf[8];
p[2] = 0;
p[3] = 0;
p[4] = 0;
p[5] = 0;
p[6] = 0;
p[7] = 0;
p += 8;
} else {
totalsize = 4 - 1;
maxlen = cmdbuf[4];
p[1] = 0;
p[2] = tape->wp ? 0x80 : 0;
p[3] = 0;
p += 4;
}
bdsize = 0;
if (!dbd) {
wl(p + 0, 0);
wl(p + 4, tape->blocksize);
bdsize = 8;
p += bdsize;
}
if (pcode)
goto errreq;
if (sense10) {
totalsize += bdsize;
r[6] = bdsize >> 8;
r[7] = bdsize & 0xff;
r[0] = totalsize >> 8;
r[1] = totalsize & 0xff;
} else {
totalsize += bdsize;
r[3] = bdsize & 0xff;
r[0] = totalsize & 0xff;
}
lr = totalsize + 1;
if (lr > maxlen)
lr = maxlen;
}
break;
case 0x55: // MODE SELECT(10)
case 0x15: // MODE SELECT(6)
{
uae_u8 *p;
bool mode10 = cmdbuf[0] == 0x55;
int nb = 0, bl = 512;
p = scsi_data + 4;
if (mode10)
p += 4;
if (scsi_data[3] >= 8) {
nb = rl (p) & 0xffffff;
bl = rl (p + 4) & 0xffffff;
}
if (log_tapeemu)
write_log (_T("TAPEEMU MODE SELECT BUFM=%d BDL=%d Density=%d NB=%d BL=%d\n"), (scsi_data[2] & 0x10) != 0, scsi_data[3], p[0], nb, bl);
if (nb || bl != 512)
goto err;
scsi_len = 0;
break;
}
case 0x05: /* READ BLOCK LIMITS */
if (log_tapeemu)
write_log (_T("TAPEEMU READ BLOCK LIMITS\n"));
r[0] = 0;
r[1] = 0;
r[2] = 2; // 0x200 = 512
r[3] = 0;
r[4] = 2; // 0x200 = 512
r[5] = 0;
lr = 6;
break;
case 0x16: /* RESERVE UNIT */
if (log_tapeemu)
write_log (_T("TAPEEMU RESERVE UNIT\n"));
scsi_len = 0;
break;
case 0x17: /* RELEASE UNIT */
if (log_tapeemu)
write_log (_T("TAPEEMU RELEASE UNIT\n"));
scsi_len = 0;
break;
case 0x1e: /* PREVENT/ALLOW MEDIUM REMOVAL */
if (log_tapeemu)
write_log (_T("TAPEEMU PREVENT/ALLOW MEDIUM REMOVAL\n"));
scsi_len = 0;
break;
case 0x12: /* INQUIRY */
{
if (log_tapeemu)
write_log (_T("TAPEEMU INQUIRY\n"));
if ((cmdbuf[1] & 1) || cmdbuf[2] != 0)
goto err;
len = cmdbuf[4];
if (cmdbuf[1] >> 5) {
r[0] = 0x7f;
} else {
r[0] = INQ_SEQD;
}
r[1] |= 0x80; // removable
r[2] = 2; /* supports SCSI-2 */
r[3] = 2; /* response data format */
r[4] = 32; /* additional length */
r[7] = 0;
scsi_len = lr = len < 36 ? len : 36;
r[2] = 2;
r[3] = 2;
char *s = ua (_T("UAE"));
memcpy (r + 8, s, strlen (s));
xfree (s);
s = ua (_T("SCSI TAPE"));
memcpy (r + 16, s, strlen (s));
xfree (s);
s = ua (_T("0.1"));
memcpy (r + 32, s, strlen (s));
xfree (s);
for (int i = 8; i < 36; i++) {
if (r[i] == 0)
r[i] = 32;
}
}
break;
default:
write_log (_T("TAPEEMU: unsupported scsi command 0x%02X\n"), cmdbuf[0]);
err:
status = SCSI_STATUS_CHECK_CONDITION;
s[0] = 0x70;
s[2] = SCSI_SK_ILLEGAL_REQ;
s[12] = SCSI_INVALID_COMMAND;
ls = 0x12;
break;
writeprot:
status = 2; /* CHECK CONDITION */
s[0] = 0x70;
s[2] = 7; /* DATA PROTECT */
s[12] = 0x27; /* WRITE PROTECTED */
ls = 0x12;
break;
errreq:
lr = -1;
status = SCSI_STATUS_CHECK_CONDITION;
s[0] = 0x70;
s[2] = SCSI_SK_ILLEGAL_REQ;
s[12] = SCSI_INVALID_FIELD;
ls = 0x12;
break;
endoftape:
status = SCSI_STATUS_CHECK_CONDITION;
s[0] = 0x70;
s[2] = SCSI_SK_MED_ERR;
s[12] = 0;
s[13] = 2; /* End-of-partition/medium detected */
ls = 0x12;
break;
unloaded:
status = SCSI_STATUS_CHECK_CONDITION;
s[0] = 0x70;
s[2] = SCSI_SK_NOT_READY;
s[12] = SCSI_NOT_READY;
ls = 0x12;
break;
notape:
status = SCSI_STATUS_CHECK_CONDITION;
s[0] = 0x70;
s[2] = SCSI_SK_NOT_READY;
s[12] = SCSI_MEDIUM_NOT_PRESENT;
ls = 0x12;
break;
}
*data_len = scsi_len;
*reply_len = lr;
*sense_len = ls;
if (ls > 0) {
if (tape->beom == 1)
s[2] |= 0x40;
if (tape->beom == -1)
s[9] |= 0x8;
if (log_tapeemu) {
write_log (_T("TAPEEMU SENSE: "));
for (int i = 0; i < ls; i++)
write_log (_T("%02X."), s[i]);
write_log (_T("\n"));
}
}
return status;
}