Source to src/scsi.c
/* SCSI Bus and Disk emulation */
#include "main.h"
#include "ioMem.h"
#include "ioMemTables.h"
#include "configuration.h"
#include "sysdeps.h"
#include "m68000.h"
#include "statusbar.h"
#include "scsi.h"
#include "file.h"
#define LOG_SCSI_LEVEL LOG_DEBUG /* Print debugging messages */
#define COMMAND_ReadInt16(a, i) (((unsigned) a[i] << 8) | a[i + 1])
#define COMMAND_ReadInt24(a, i) (((unsigned) a[i] << 16) | ((unsigned) a[i + 1] << 8) | a[i + 2])
#define COMMAND_ReadInt32(a, i) (((unsigned) a[i] << 24) | ((unsigned) a[i + 1] << 16) | ((unsigned) a[i + 2] << 8) | a[i + 3])
#define BLOCKSIZE 512
#define LUN_DISK 0 // for now only LUN 0 is valid for our phys drives
/* Status Codes */
#define STAT_GOOD 0x00
#define STAT_CHECK_COND 0x02
#define STAT_COND_MET 0x04
#define STAT_BUSY 0x08
#define STAT_INTERMEDIATE 0x10
#define STAT_INTER_COND_MET 0x14
#define STAT_RESERV_CONFL 0x18
/* Messages */
#define MSG_COMPLETE 0x00
#define MSG_SAVE_PTRS 0x02
#define MSG_RESTORE_PTRS 0x03
#define MSG_DISCONNECT 0x04
#define MSG_INITIATOR_ERR 0x05
#define MSG_ABORT 0x06
#define MSG_MSG_REJECT 0x07
#define MSG_NOP 0x08
#define MSG_PARITY_ERR 0x09
#define MSG_LINK_CMD_CMPLT 0x0A
#define MSG_LNKCMDCMPLTFLAG 0x0B
#define MSG_DEVICE_RESET 0x0C
#define MSG_IDENTIFY_MASK 0x80
#define MSG_ID_DISCONN 0x40
#define MSG_LUNMASK 0x07
/* Sense Keys */
#define SK_NOSENSE 0x00
#define SK_RECOVERED 0x01
#define SK_NOTREADY 0x02
#define SK_MEDIA 0x03
#define SK_HARDWARE 0x04
#define SK_ILLEGAL_REQ 0x05
#define SK_UNIT_ATN 0x06
#define SK_DATAPROTECT 0x07
#define SK_ABORTED_CMD 0x0B
#define SK_VOL_OVERFLOW 0x0D
#define SK_MISCOMPARE 0x0E
/* Additional Sense Codes */
#define SC_NO_ERROR 0x00 // 0
#define SC_NO_SECTOR 0x01 // 4
#define SC_WRITE_FAULT 0x03 // 5
#define SC_NOT_READY 0x04 // 2
#define SC_INVALID_CMD 0x20 // 5
#define SC_INVALID_LBA 0x21 // 5
#define SC_INVALID_CDB 0x24 // 5
#define SC_INVALID_LUN 0x25 // 5
#define SC_WRITE_PROTECT 0x27 // 7
/* SCSI Commands */
/* The following are multi-sector transfers with seek implied */
#define CMD_VERIFY_TRACK 0x05 /* Verify track */
#define CMD_FORMAT_TRACK 0x06 /* Format track */
#define CMD_READ_SECTOR 0x08 /* Read sector */
#define CMD_READ_SECTOR1 0x28 /* Read sector (class 1) */
#define CMD_WRITE_SECTOR 0x0A /* Write sector */
#define CMD_WRITE_SECTOR1 0x2A /* Write sector (class 1) */
/* Other codes */
#define CMD_TEST_UNIT_RDY 0x00 /* Test unit ready */
#define CMD_FORMAT_DRIVE 0x04 /* Format the whole drive */
#define CMD_SEEK 0x0B /* Seek */
#define CMD_CORRECTION 0x0D /* Correction */
#define CMD_INQUIRY 0x12 /* Inquiry */
#define CMD_MODESELECT 0x15 /* Mode select */
#define CMD_MODESENSE 0x1A /* Mode sense */
#define CMD_REQ_SENSE 0x03 /* Request sense */
#define CMD_SHIP 0x1B /* Ship drive */
#define CMD_READ_CAPACITY1 0x25 /* Read capacity (class 1) */
void SCSI_Emulate_Command(Uint8 *cdb);
void SCSI_Inquiry(Uint8 *cdb);
void SCSI_StartStop(Uint8 *cdb);
void SCSI_TestUnitReady(Uint8 *cdb);
void SCSI_ReadCapacity(Uint8 *cdb);
void SCSI_ReadSector(Uint8 *cdb);
void SCSI_WriteSector(Uint8 *cdb);
void SCSI_RequestSense(Uint8 *cdb);
void SCSI_ModeSense(Uint8 *cdb);
void SCSI_FormatDrive(Uint8 *cdb);
/* Helpers */
int SCSI_GetCommandLength(Uint8 opcode);
int SCSI_GetTransferLength(Uint8 opcode, Uint8 *cdb);
Uint64 SCSI_GetOffset(Uint8 opcode, Uint8 *cdb);
int SCSI_GetCount(Uint8 opcode, Uint8 *cdb);
void scsi_read_sector(void);
void scsi_write_sector(void);
/* SCSI disk */
struct {
SCSI_DEVTYPE devtype;
FILE* dsk;
Uint64 size;
bool readonly;
Uint8 lun;
Uint8 status;
Uint8 message;
struct {
Uint8 key;
Uint8 code;
bool valid;
Uint32 info;
} sense;
Uint32 lba;
Uint32 blockcounter;
Uint32 lastlba;
Uint8** shadow;
} SCSIdisk[ESP_MAX_DEVS];
/* Mode Pages */
#define MODEPAGE_MAX_SIZE 24
typedef struct {
Uint8 current[MODEPAGE_MAX_SIZE];
Uint8 changeable[MODEPAGE_MAX_SIZE];
Uint8 modepage[MODEPAGE_MAX_SIZE]; // default values
Uint8 saved[MODEPAGE_MAX_SIZE];
Uint8 pagesize;
} MODEPAGE;
MODEPAGE SCSI_GetModePage(Uint8 pagecode);
/* Initialize/Uninitialize SCSI disks */
void SCSI_Init(void) {
Log_Printf(LOG_WARN, "Loading SCSI disks:\n");
int i;
for (i = 0; i < ESP_MAX_DEVS; i++) {
SCSIdisk[i].devtype = ConfigureParams.SCSI.target[i].nDeviceType;
SCSI_Insert(i);
}
}
void SCSI_Uninit(void) {
int i;
for (i = 0; i < ESP_MAX_DEVS; i++) {
if (SCSIdisk[i].dsk) {
SCSI_Eject(i);
}
}
}
void SCSI_Reset(void) {
SCSI_Uninit();
SCSI_Init();
}
void SCSI_Eject(Uint8 i) {
File_Close(SCSIdisk[i].dsk);
SCSIdisk[i].dsk = NULL;
SCSIdisk[i].size = 0;
SCSIdisk[i].readonly = false;
SCSIdisk[i].shadow = NULL;
}
static void SCSI_EjectDisk(Uint8 i) {
ConfigureParams.SCSI.target[i].bDiskInserted = false;
ConfigureParams.SCSI.target[i].szImageName[0] = '\0';
SCSI_Eject(i);
}
void SCSI_Insert(Uint8 i) {
SCSIdisk[i].lun = SCSIdisk[i].status = SCSIdisk[i].message = 0;
SCSIdisk[i].sense.code = SCSIdisk[i].sense.key = SCSIdisk[i].sense.info = 0;
SCSIdisk[i].sense.valid = false;
SCSIdisk[i].lba = SCSIdisk[i].lastlba = SCSIdisk[i].blockcounter = 0;
SCSIdisk[i].shadow = NULL;
Log_Printf(LOG_WARN, "SCSI Disk%i: %s\n",i,ConfigureParams.SCSI.target[i].szImageName);
if (File_Exists(ConfigureParams.SCSI.target[i].szImageName) &&
ConfigureParams.SCSI.target[i].bDiskInserted) {
if (ConfigureParams.SCSI.target[i].bWriteProtected ||
ConfigureParams.SCSI.target[i].nDeviceType==DEVTYPE_CD) {
SCSIdisk[i].dsk = File_Open(ConfigureParams.SCSI.target[i].szImageName, "rb");
if (SCSIdisk[i].dsk == NULL) {
Log_Printf(LOG_WARN, "SCSI Disk%i: Cannot open image file %s\n",
i, ConfigureParams.SCSI.target[i].szImageName);
SCSIdisk[i].size = 0;
SCSIdisk[i].readonly = false;
if (SCSIdisk[i].devtype == DEVTYPE_HARDDISK) {
SCSIdisk[i].devtype = DEVTYPE_NONE;
}
} else {
SCSIdisk[i].size = File_Length(ConfigureParams.SCSI.target[i].szImageName);
SCSIdisk[i].readonly = true;
}
} else {
SCSIdisk[i].dsk = File_Open(ConfigureParams.SCSI.target[i].szImageName, "rb+");
if (SCSIdisk[i].dsk == NULL) {
SCSIdisk[i].dsk = File_Open(ConfigureParams.SCSI.target[i].szImageName, "rb");
if (SCSIdisk[i].dsk == NULL) {
Log_Printf(LOG_WARN, "SCSI Disk%i: Cannot open image file %s\n",
i, ConfigureParams.SCSI.target[i].szImageName);
SCSIdisk[i].size = 0;
SCSIdisk[i].readonly = false;
if (SCSIdisk[i].devtype == DEVTYPE_HARDDISK) {
SCSIdisk[i].devtype = DEVTYPE_NONE;
}
} else {
SCSIdisk[i].size = File_Length(ConfigureParams.SCSI.target[i].szImageName);
SCSIdisk[i].readonly = true;
Log_Printf(LOG_WARN, "SCSI Disk%i: Image file is not writable. Enabling write protection.\n", i);
}
} else {
SCSIdisk[i].size = File_Length(ConfigureParams.SCSI.target[i].szImageName);
SCSIdisk[i].readonly = false;
}
}
} else {
SCSIdisk[i].size = 0;
SCSIdisk[i].dsk = NULL;
SCSIdisk[i].readonly = false;
}
}
/* INQUIRY response data */
#define DEVTYPE_DISK 0x00 /* read/write disks */
#define DEVTYPE_TAPE 0x01 /* tapes and other sequential devices */
#define DEVTYPE_PRINTER 0x02 /* printers */
#define DEVTYPE_PROCESSOR 0x03 /* cpus */
#define DEVTYPE_WORM 0x04 /* write-once optical disks */
#define DEVTYPE_READONLY 0x05 /* cd-roms */
#define DEVTYPE_NOTPRESENT 0x7f /* logical unit not present */
static unsigned char inquiry_bytes[] =
{
0x00, /* 0: device type: see above */
0x00, /* 1: &0x7F - device type qualifier 0x00 unsupported, &0x80 - rmb: 0x00 = nonremovable, 0x80 = removable */
0x01, /* 2: ANSI SCSI standard (first release) compliant */
0x02, /* 3: Response format (format of following data): 0x01 SCSI-1 compliant */
0x31, /* 4: additional length of the following data */
0x00, 0x00, /* 5,6: reserved */
0x1C, /* 7: RelAdr=0, Wbus32=0, Wbus16=0, Sync=1, Linked=1, RSVD=1, CmdQue=0, SftRe=0 */
'P','r','e','v','i','o','u','s', /* 8-15: Vendor ASCII */
'H','D','D',' ',' ',' ',' ',' ', /* 16-23: Model ASCII */
' ',' ',' ',' ',' ',' ',' ',' ', /* 24-31: Blank space ASCII */
'0','0','0','0','0','0','0','1', /* 32-39: Revision ASCII */
'0','0','0','0','0','0','0','0', /* 40-47: Serial Number ASCII */
' ',' ',' ',' ',' ',' ' /* 48-53: Blank space ASCII */
};
Uint8 SCSIdisk_Send_Status(void) {
SCSIbus.phase = PHASE_MI;
return SCSIdisk[SCSIbus.target].status;
}
Uint8 SCSIdisk_Send_Message(void) {
return SCSIdisk[SCSIbus.target].message;
}
bool SCSIdisk_Select(Uint8 target) {
/* If there is no disk drive present, return timeout true */
if (SCSIdisk[target].devtype==DEVTYPE_NONE) {
Log_Printf(LOG_SCSI_LEVEL, "[SCSI] Selection timeout, target = %i", target);
SCSIbus.phase = PHASE_ST;
return true;
} else {
SCSIbus.target = target;
return false;
}
}
void SCSIdisk_Receive_Command(Uint8 *cdb, Uint8 identify) {
Uint8 lun = 0;
/* Get logical unit number */
if (identify&MSG_IDENTIFY_MASK) { /* if identify message is valid */
lun = identify&MSG_LUNMASK; /* use lun from identify message */
} else {
lun = (cdb[1]&0xE0)>>5; /* use lun specified in CDB */
}
SCSIdisk[SCSIbus.target].lun = lun;
Log_Printf(LOG_SCSI_LEVEL, "SCSI command: Opcode = $%02x, target = %i, lun = %i\n", cdb[0], SCSIbus.target,lun);
SCSI_Emulate_Command(cdb);
}
void SCSI_Emulate_Command(Uint8 *cdb) {
Uint8 opcode = cdb[0];
Uint8 target = SCSIbus.target;
/* First check for lun-independent commands */
switch (opcode) {
case CMD_INQUIRY:
Log_Printf(LOG_SCSI_LEVEL, "SCSI command: Inquiry\n");
SCSI_Inquiry(cdb);
break;
case CMD_REQ_SENSE:
Log_Printf(LOG_SCSI_LEVEL, "SCSI command: Request sense\n");
SCSI_RequestSense(cdb);
break;
/* Check if the specified lun is valid for our disk */
default:
if (SCSIdisk[target].lun!=LUN_DISK) {
Log_Printf(LOG_SCSI_LEVEL, "SCSI command: Invalid lun! Check condition.\n");
SCSIbus.phase = PHASE_ST;
SCSIdisk[target].status = STAT_CHECK_COND; /* status: check condition */
SCSIdisk[target].message = MSG_COMPLETE; /* TODO: CHECK THIS! */
SCSIdisk[target].sense.code = SC_INVALID_LUN;
SCSIdisk[target].sense.valid = false;
return;
}
/* Then check for lun-dependent commands */
switch(opcode) {
case CMD_TEST_UNIT_RDY:
Log_Printf(LOG_SCSI_LEVEL, "SCSI command: Test unit ready\n");
SCSI_TestUnitReady(cdb);
break;
case CMD_READ_CAPACITY1:
Log_Printf(LOG_SCSI_LEVEL, "SCSI command: Read capacity\n");
SCSI_ReadCapacity(cdb);
break;
case CMD_READ_SECTOR:
case CMD_READ_SECTOR1:
Log_Printf(LOG_SCSI_LEVEL, "SCSI command: Read sector\n");
SCSI_ReadSector(cdb);
break;
case CMD_WRITE_SECTOR:
case CMD_WRITE_SECTOR1:
Log_Printf(LOG_SCSI_LEVEL, "SCSI command: Write sector\n");
SCSI_WriteSector(cdb);
break;
case CMD_SEEK:
Log_Printf(LOG_WARN, "SCSI command: Seek\n");
abort();
break;
case CMD_SHIP:
Log_Printf(LOG_SCSI_LEVEL, "SCSI command: Ship\n");
SCSI_StartStop(cdb);
break;
case CMD_MODESELECT:
Log_Printf(LOG_WARN, "SCSI command: Mode select\n");
abort();
break;
case CMD_MODESENSE:
Log_Printf(LOG_SCSI_LEVEL, "SCSI command: Mode sense\n");
SCSI_ModeSense(cdb);
break;
case CMD_FORMAT_DRIVE:
Log_Printf(LOG_SCSI_LEVEL, "SCSI command: Format drive\n");
SCSI_FormatDrive(cdb);
break;
/* as of yet unsupported commands */
case CMD_VERIFY_TRACK:
case CMD_FORMAT_TRACK:
case CMD_CORRECTION:
default:
Log_Printf(LOG_WARN, "SCSI command: Unknown Command (%02X)\n",opcode);
SCSIdisk[target].status = STAT_CHECK_COND;
SCSIdisk[target].sense.code = SC_INVALID_CMD;
SCSIdisk[target].sense.valid = false;
SCSIbus.phase = PHASE_ST;
break;
}
break;
}
SCSIdisk[target].message = MSG_COMPLETE;
/* Update the led each time a command is processed */
Statusbar_BlinkLed(DEVICE_LED_SCSI);
}
/* Helpers */
int SCSI_GetCommandLength(Uint8 opcode) {
Uint8 group_code = (opcode&0xE0)>>5;
switch (group_code) {
case 0: return 6;
case 1: return 10;
case 5: return 12;
default:
Log_Printf(LOG_WARN, "[SCSI] Unimplemented Group Code!");
return 6;
}
}
int SCSI_GetTransferLength(Uint8 opcode, Uint8 *cdb)
{
return opcode < 0x20?
// class 0
cdb[4] :
// class 1
COMMAND_ReadInt16(cdb, 7);
}
Uint64 SCSI_GetOffset(Uint8 opcode, Uint8 *cdb)
{
return opcode < 0x20?
// class 0
(COMMAND_ReadInt24(cdb, 1) & 0x1FFFFF) :
// class 1
COMMAND_ReadInt32(cdb, 2);
}
// get reserved count for SCSI reply
int SCSI_GetCount(Uint8 opcode, Uint8 *cdb)
{
return opcode < 0x20?
// class 0
((cdb[4]==0)?0x100:cdb[4]) :
// class 1
COMMAND_ReadInt16(cdb, 7);
}
static void SCSI_GuessGeometry(Uint32 size, Uint32 *cylinders, Uint32 *heads, Uint32 *sectors)
{
Uint32 c,h,s;
for (h=16; h>0; h--) {
for (s=63; s>15; s--) {
if ((size%(s*h))==0) {
c=size/(s*h);
*cylinders=c;
*heads=h;
*sectors=s;
return;
}
}
}
Log_Printf(LOG_SCSI_LEVEL, "[SCSI] Disk geometry: No valid geometry found! Using default.");
h=16;
s=63;
c=size/(s*h);
if ((size%(s*h))!=0) {
c+=1;
}
*cylinders=c;
*heads=h;
*sectors=s;
}
#define SCSI_SEEK_TIME_HD 20000 /* 20 ms max seek time */
#define SCSI_SECTOR_TIME_HD 350 /* 1.4 MB/sec */
#define SCSI_SEEK_TIME_FD 200000 /* 200 ms max seek time */
#define SCSI_SECTOR_TIME_FD 5500 /* 90 kB/sec */
#define SCSI_SEEK_TIME_CD 500000 /* 500 ms max seek time */
#define SCSI_SECTOR_TIME_CD 3250 /* 150 kB/sec */
Sint64 SCSI_Seek_Time(void) {
Uint8 target = SCSIbus.target;
Sint64 seektime, seekoffset, disksize;
if (scsi_buffer.disk) {
switch (SCSIdisk[target].devtype) {
case DEVTYPE_HARDDISK:
seektime = SCSI_SEEK_TIME_HD;
break;
case DEVTYPE_CD:
seektime = SCSI_SEEK_TIME_CD;
break;
case DEVTYPE_FLOPPY:
seektime = SCSI_SEEK_TIME_FD;
break;
default:
return 0;
}
if (SCSIdisk[target].lba < SCSIdisk[target].lastlba) {
seekoffset = SCSIdisk[target].lastlba - SCSIdisk[target].lba;
} else {
seekoffset = SCSIdisk[target].lba - SCSIdisk[target].lastlba;
}
disksize = SCSIdisk[target].size/BLOCKSIZE;
if (disksize <= 0) { /* make sure no zero divide occurs */
return 0;
}
seektime *= seekoffset;
seektime /= disksize;
if (seektime > 500000) {
seektime = 500000;
}
return seektime;
} else {
return 0;
}
}
Sint64 SCSI_Sector_Time(void) {
int target = SCSIbus.target;
Sint64 sectors = SCSIdisk[target].blockcounter;
if (sectors <= 0) {
sectors = 1;
}
if (scsi_buffer.disk) {
switch (SCSIdisk[target].devtype) {
case DEVTYPE_HARDDISK:
return sectors * SCSI_SECTOR_TIME_HD;
case DEVTYPE_CD:
return sectors * SCSI_SECTOR_TIME_CD;
case DEVTYPE_FLOPPY:
return sectors * SCSI_SECTOR_TIME_FD;
default:
return 1000;
}
} else {
return 100;
}
}
MODEPAGE SCSI_GetModePage(Uint8 pagecode) {
Uint8 target = SCSIbus.target;
MODEPAGE page;
switch (pagecode) {
case 0x00: // operating page
page.pagesize = 4;
page.modepage[0] = 0x00; // &0x80: page savable? (not supported!), &0x7F: page code = 0x00
page.modepage[1] = 0x02; // page length = 2
page.modepage[2] = 0x80; // &0x80: usage bit = 1, &0x10: disable unit attention = 0
page.modepage[3] = 0x00; // &0x7F: device type qualifier = 0x00, see inquiry!
break;
case 0x01: // error recovery page
page.pagesize = 8;
page.modepage[0] = 0x01; // &0x80: page savable? (not supported!), &0x7F: page code = 0x01
page.modepage[1] = 0x06; // page length = 6
page.modepage[2] = 0x00; // AWRE, ARRE, TB, RC, EER, PER, DTE, DCR
page.modepage[3] = 0x1B; // retry count
page.modepage[4] = 0x0B; // correction span in bits
page.modepage[5] = 0x00; // head offset count
page.modepage[6] = 0x00; // data strobe offset count
page.modepage[7] = 0xFF; // recovery time limit
break;
case 0x03: // format device page
page.pagesize = 0;
Log_Printf(LOG_WARN, "[SCSI] Mode Sense: Page %02x not yet emulated!\n", pagecode);
//abort();
break;
case 0x04: // rigid disc geometry page
{
Uint32 num_sectors = SCSIdisk[target].size/BLOCKSIZE;
Uint32 cylinders, heads, sectors;
SCSI_GuessGeometry(num_sectors, &cylinders, &heads, §ors);
Log_Printf(LOG_SCSI_LEVEL, "[SCSI] Disk geometry: %i sectors, %i cylinders, %i heads\n", sectors, cylinders, heads);
page.pagesize = 20;
page.modepage[0] = 0x04; // &0x80: page savable? (not supported!), &0x7F: page code = 0x04
page.modepage[1] = 0x12;
page.modepage[2] = (cylinders >> 16) & 0xFF;
page.modepage[3] = (cylinders >> 8) & 0xFF;
page.modepage[4] = cylinders & 0xFF;
page.modepage[5] = heads;
page.modepage[6] = 0x00; // 6,7,8: starting cylinder - write precomp (not supported)
page.modepage[7] = 0x00;
page.modepage[8] = 0x00;
page.modepage[9] = 0x00; // 9,10,11: starting cylinder - reduced write current (not supported)
page.modepage[10] = 0x00;
page.modepage[11] = 0x00;
page.modepage[12] = 0x00; // 12,13: drive step rate (not supported)
page.modepage[13] = 0x00;
page.modepage[14] = 0x00; // 14,15,16: loading zone cylinder (not supported)
page.modepage[15] = 0x00;
page.modepage[16] = 0x00;
page.modepage[17] = 0x00; // &0x03: rotational position locking
page.modepage[18] = 0x00; // rotational position lock offset
page.modepage[19] = 0x00; // reserved
}
break;
case 0x02: // disconnect/reconnect page
case 0x08: // caching page
case 0x0C: // notch page
case 0x0D: // power condition page
case 0x38: // cache control page
case 0x3C: // soft ID page (EEPROM)
page.pagesize = 0;
Log_Printf(LOG_WARN, "[SCSI] Mode Sense: Page %02x not yet emulated!\n", pagecode);
//abort();
break;
default:
page.pagesize = 0;
Log_Printf(LOG_WARN, "[SCSI] Mode Sense: Invalid page code: %02x!\n", pagecode);
break;
}
return page;
}
/* SCSI Commands */
void SCSI_TestUnitReady(Uint8 *cdb) {
Uint8 target = SCSIbus.target;
if (SCSIdisk[target].devtype!=DEVTYPE_NONE &&
SCSIdisk[target].devtype!=DEVTYPE_HARDDISK &&
SCSIdisk[target].dsk==NULL) { /* Empty drive */
SCSIdisk[target].status = STAT_CHECK_COND;
SCSIdisk[target].sense.code = SC_NOT_READY;
SCSIbus.phase = PHASE_ST;
} else {
SCSIdisk[target].status = STAT_GOOD;
SCSIdisk[target].sense.code = SC_NO_ERROR;
SCSIbus.phase = PHASE_ST;
}
}
void SCSI_ReadCapacity(Uint8 *cdb) {
Uint8 target = SCSIbus.target;
Log_Printf(LOG_SCSI_LEVEL, "[SCSI] Read disk image: size = %llu byte\n", SCSIdisk[target].size);
Uint32 sectors = (SCSIdisk[target].size / BLOCKSIZE) - 1; /* last LBA */
static Uint8 scsi_disksize[8];
scsi_disksize[0] = (sectors >> 24) & 0xFF;
scsi_disksize[1] = (sectors >> 16) & 0xFF;
scsi_disksize[2] = (sectors >> 8) & 0xFF;
scsi_disksize[3] = sectors & 0xFF;
scsi_disksize[4] = (BLOCKSIZE >> 24) & 0xFF;
scsi_disksize[5] = (BLOCKSIZE >> 16) & 0xFF;
scsi_disksize[6] = (BLOCKSIZE >> 8) & 0xFF;
scsi_disksize[7] = BLOCKSIZE & 0xFF;
memcpy(scsi_buffer.data, scsi_disksize, 8);
scsi_buffer.limit=scsi_buffer.size=8;
scsi_buffer.disk=false;
SCSIdisk[target].status = STAT_GOOD;
SCSIbus.phase = PHASE_DI;
SCSIdisk[target].sense.code = SC_NO_ERROR;
SCSIdisk[target].sense.valid = false;
}
void SCSI_WriteSector(Uint8 *cdb) {
Uint8 target = SCSIbus.target;
SCSIdisk[target].lastlba = SCSIdisk[target].lba;
SCSIdisk[target].lba = SCSI_GetOffset(cdb[0], cdb);
SCSIdisk[target].blockcounter = SCSI_GetCount(cdb[0], cdb);
if (SCSIdisk[target].readonly) {
Log_Printf(LOG_SCSI_LEVEL, "[SCSI] Write sector: Disk is write protected! Check condition.");
SCSIdisk[target].status = STAT_CHECK_COND;
SCSIdisk[target].sense.code = SC_WRITE_PROTECT;
SCSIdisk[target].sense.valid = false;
SCSIbus.phase = PHASE_ST;
return;
}
scsi_buffer.disk=true;
scsi_buffer.size=0;
scsi_buffer.limit=BLOCKSIZE;
SCSIbus.phase = PHASE_DO;
Log_Printf(LOG_SCSI_LEVEL, "[SCSI] Write sector: %i block(s) at offset %i (blocksize: %i byte)",
SCSIdisk[target].blockcounter, SCSIdisk[target].lba, BLOCKSIZE);
}
void scsi_write_sector(void) {
Uint8 target = SCSIbus.target;
Uint64 offset = 0;
Log_Printf(LOG_SCSI_LEVEL, "[SCSI] Writing block at offset %i (%i blocks remaining).",
SCSIdisk[target].lba,SCSIdisk[target].blockcounter-1);
offset = ((Uint64)SCSIdisk[target].lba)*BLOCKSIZE;
if (offset < SCSIdisk[target].size) {
if (ConfigureParams.SCSI.nWriteProtection != WRITEPROT_ON) {
File_Write(scsi_buffer.data, BLOCKSIZE, offset, SCSIdisk[target].dsk);
} else {
Log_Printf(LOG_SCSI_LEVEL, "[SCSI] WARNING: File write disabled!");
if(SCSIdisk[target].shadow) {
if(!(SCSIdisk[target].shadow[SCSIdisk[target].lba]))
SCSIdisk[target].shadow[SCSIdisk[target].lba] = malloc(BLOCKSIZE);
memcpy(SCSIdisk[target].shadow[SCSIdisk[target].lba], scsi_buffer.data, BLOCKSIZE);
} else {
Uint32 blocks = SCSIdisk[target].size / BLOCKSIZE;
SCSIdisk[target].shadow = malloc(sizeof(Uint8*) * blocks);
for(int i = blocks; --i >= 0;)
SCSIdisk[target].shadow[i] = NULL;
}
}
scsi_buffer.limit=BLOCKSIZE;
scsi_buffer.size=0;
SCSIdisk[target].status = STAT_GOOD;
SCSIdisk[target].sense.code = SC_NO_ERROR;
SCSIdisk[target].sense.valid = false;
SCSIdisk[target].lba++;
SCSIdisk[target].blockcounter--;
if (SCSIdisk[target].blockcounter==0) {
SCSIbus.phase = PHASE_ST;
}
} else {
SCSIdisk[target].status = STAT_CHECK_COND;
SCSIdisk[target].sense.code = SC_INVALID_LBA;
SCSIdisk[target].sense.valid = true;
SCSIdisk[target].sense.info = SCSIdisk[target].lba;
SCSIbus.phase = PHASE_ST;
}
}
void SCSIdisk_Receive_Data(Uint8 val) {
/* Receive one byte. If the transfer is complete, set status phase
* and write the buffer contents to the disk. */
scsi_buffer.data[scsi_buffer.size]=val;
scsi_buffer.size++;
if (scsi_buffer.size==scsi_buffer.limit) {
if (scsi_buffer.disk==true) {
scsi_write_sector(); /* sets status phase if done or error */
} else {
SCSIbus.phase = PHASE_ST;
}
}
}
void SCSI_ReadSector(Uint8 *cdb) {
Uint8 target = SCSIbus.target;
SCSIdisk[target].lastlba = SCSIdisk[target].lba;
SCSIdisk[target].lba = SCSI_GetOffset(cdb[0], cdb);
SCSIdisk[target].blockcounter = SCSI_GetCount(cdb[0], cdb);
scsi_buffer.disk=true;
scsi_buffer.size=0;
SCSIbus.phase = PHASE_DI;
Log_Printf(LOG_SCSI_LEVEL, "[SCSI] Read sector: %i block(s) at offset %i (blocksize: %i byte)",
SCSIdisk[target].blockcounter, SCSIdisk[target].lba, BLOCKSIZE);
scsi_read_sector();
}
void scsi_read_sector(void) {
Uint8 target = SCSIbus.target;
Uint64 offset = 0;
if (SCSIdisk[target].blockcounter==0) {
SCSIbus.phase = PHASE_ST;
return;
}
Log_Printf(LOG_SCSI_LEVEL, "[SCSI] Reading block at offset %i (%i blocks remaining).",
SCSIdisk[target].lba,SCSIdisk[target].blockcounter-1);
offset = ((Uint64)SCSIdisk[target].lba)*BLOCKSIZE;
if (offset < SCSIdisk[target].size) {
if (SCSIdisk[target].shadow && SCSIdisk[target].shadow[SCSIdisk[target].lba]) {
memcpy(scsi_buffer.data, SCSIdisk[target].shadow[SCSIdisk[target].lba], BLOCKSIZE);
} else {
File_Read(scsi_buffer.data, BLOCKSIZE, offset, SCSIdisk[target].dsk);
}
scsi_buffer.limit=scsi_buffer.size=BLOCKSIZE;
SCSIdisk[target].status = STAT_GOOD;
SCSIdisk[target].sense.code = SC_NO_ERROR;
SCSIdisk[target].sense.valid = false;
SCSIdisk[target].lba++;
SCSIdisk[target].blockcounter--;
} else {
SCSIdisk[target].status = STAT_CHECK_COND;
SCSIdisk[target].sense.code = SC_INVALID_LBA;
SCSIdisk[target].sense.valid = true;
SCSIdisk[target].sense.info = SCSIdisk[target].lba;
SCSIbus.phase = PHASE_ST;
}
}
Uint8 SCSIdisk_Send_Data(void) {
/* Send one byte. If the transfer is complete, set status phase */
Uint8 val=scsi_buffer.data[scsi_buffer.limit-scsi_buffer.size];
scsi_buffer.size--;
if (scsi_buffer.size==0) {
if (scsi_buffer.disk==true) {
scsi_read_sector(); /* sets status phase if done or error */
} else {
SCSIbus.phase = PHASE_ST;
}
}
return val;
}
void SCSI_Inquiry (Uint8 *cdb) {
Uint8 target = SCSIbus.target;
switch (SCSIdisk[target].devtype) {
case DEVTYPE_HARDDISK:
inquiry_bytes[0] = DEVTYPE_DISK;
inquiry_bytes[1] &= ~0x80;
inquiry_bytes[16] = 'H';
inquiry_bytes[17] = 'D';
inquiry_bytes[18] = 'D';
inquiry_bytes[19] = ' ';
inquiry_bytes[20] = ' ';
inquiry_bytes[21] = ' ';
Log_Printf(LOG_SCSI_LEVEL, "[SCSI] Disk is HDD\n");
break;
case DEVTYPE_CD:
inquiry_bytes[0] = DEVTYPE_READONLY;
inquiry_bytes[1] |= 0x80;
inquiry_bytes[16] = 'C';
inquiry_bytes[17] = 'D';
inquiry_bytes[18] = '-';
inquiry_bytes[19] = 'R';
inquiry_bytes[20] = 'O';
inquiry_bytes[21] = 'M';
Log_Printf(LOG_SCSI_LEVEL, "[SCSI] Disk is CD-ROM\n");
break;
case DEVTYPE_FLOPPY:
inquiry_bytes[0] = DEVTYPE_DISK;
inquiry_bytes[1] |= 0x80;
inquiry_bytes[16] = 'F';
inquiry_bytes[17] = 'L';
inquiry_bytes[18] = 'O';
inquiry_bytes[19] = 'P';
inquiry_bytes[20] = 'P';
inquiry_bytes[21] = 'Y';
Log_Printf(LOG_SCSI_LEVEL, "[SCSI] Disk is Floppy\n");
break;
default:
break;
}
if (SCSIdisk[target].lun!=LUN_DISK) {
inquiry_bytes[0] = DEVTYPE_NOTPRESENT;
}
scsi_buffer.disk=false;
scsi_buffer.limit = scsi_buffer.size = SCSI_GetTransferLength(cdb[0], cdb);
if (scsi_buffer.limit > (int)sizeof(inquiry_bytes)) {
scsi_buffer.limit = scsi_buffer.size = sizeof(inquiry_bytes);
}
memcpy(scsi_buffer.data, inquiry_bytes, scsi_buffer.limit);
Log_Printf(LOG_SCSI_LEVEL, "[SCSI] Inquiry data length: %d", scsi_buffer.limit);
Log_Printf(LOG_SCSI_LEVEL, "[SCSI] Inquiry Data: %c,%c,%c,%c,%c,%c,%c,%c\n",scsi_buffer.data[8],
scsi_buffer.data[9],scsi_buffer.data[10],scsi_buffer.data[11],scsi_buffer.data[12],
scsi_buffer.data[13],scsi_buffer.data[14],scsi_buffer.data[15]);
SCSIdisk[target].status = STAT_GOOD;
SCSIbus.phase = PHASE_DI;
SCSIdisk[target].sense.code = SC_NO_ERROR;
SCSIdisk[target].sense.valid = false;
}
void SCSI_StartStop(Uint8 *cdb) {
Uint8 target = SCSIbus.target;
switch (cdb[4]&0x03) {
case 0:
Log_Printf(LOG_SCSI_LEVEL, "[SCSI] Stop disk %i", target);
break;
case 1:
Log_Printf(LOG_SCSI_LEVEL, "[SCSI] Start disk %i", target);
break;
case 2:
Log_Printf(LOG_WARN, "[SCSI] Eject disk %i", target);
if (SCSIdisk[target].devtype!=DEVTYPE_HARDDISK) {
SCSI_EjectDisk(target);
Statusbar_AddMessage("Ejecting SCSI media.", 0);
}
break;
default:
Log_Printf(LOG_WARN, "[SCSI] Invalid start/stop");
break;
}
SCSIdisk[target].status = STAT_GOOD;
SCSIbus.phase = PHASE_ST;
}
void SCSI_RequestSense(Uint8 *cdb) {
Uint8 target = SCSIbus.target;
int nRetLen;
Uint8 retbuf[22];
nRetLen = SCSI_GetCount(cdb[0], cdb);
if ((nRetLen<4 && nRetLen!=0) || nRetLen>22) {
Log_Printf(LOG_WARN, "[SCSI] *** Strange REQUEST SENSE *** len=%d!",nRetLen);
}
/* Limit to sane length */
if (nRetLen <= 0) {
nRetLen = 4;
} else if (nRetLen > 22) {
nRetLen = 22;
}
Log_Printf(LOG_WARN, "[SCSI] REQ SENSE size = %d %s at %d", nRetLen,__FILE__,__LINE__);
memset(retbuf, 0, nRetLen);
retbuf[0] = 0x70;
if (SCSIdisk[target].sense.valid) {
retbuf[0] |= 0x80;
retbuf[3] = SCSIdisk[target].sense.info >> 24;
retbuf[4] = SCSIdisk[target].sense.info >> 16;
retbuf[5] = SCSIdisk[target].sense.info >> 8;
retbuf[6] = SCSIdisk[target].sense.info;
}
switch (SCSIdisk[target].sense.code) {
case SC_NO_ERROR:
SCSIdisk[target].sense.key = SK_NOSENSE;
break;
case SC_NOT_READY:
SCSIdisk[target].sense.key = SK_NOTREADY;
break;
case SC_WRITE_FAULT:
case SC_INVALID_CMD:
case SC_INVALID_LBA:
case SC_INVALID_CDB:
case SC_INVALID_LUN:
SCSIdisk[target].sense.key = SK_ILLEGAL_REQ;
break;
case SC_WRITE_PROTECT:
SCSIdisk[target].sense.key = SK_DATAPROTECT;
break;
case SC_NO_SECTOR:
default:
SCSIdisk[target].sense.key = SK_HARDWARE;
break;
}
retbuf[2] = SCSIdisk[target].sense.key;
retbuf[7] = 14;
retbuf[12] = SCSIdisk[target].sense.code;
scsi_buffer.size=scsi_buffer.limit=nRetLen;
memcpy(scsi_buffer.data, retbuf, scsi_buffer.limit);
scsi_buffer.disk=false;
SCSIdisk[target].status = STAT_GOOD;
SCSIbus.phase = PHASE_DI;
}
void SCSI_ModeSense(Uint8 *cdb) {
Uint8 target = SCSIbus.target;
Uint8 retbuf[256];
MODEPAGE page;
Uint32 sectors = SCSIdisk[target].size / BLOCKSIZE;
Uint8 pagecontrol = (cdb[2] & 0x0C) >> 6;
Uint8 pagecode = cdb[2] & 0x3F;
Uint8 dbd = cdb[1] & 0x08; // disable block descriptor
Log_Printf(LOG_WARN, "[SCSI] Mode Sense: page = %02x, page_control = %i, %s\n", pagecode, pagecontrol, dbd == 0x08 ? "block descriptor disabled" : "block descriptor enabled");
/* Header */
retbuf[0] = 0x00; // length of following data
retbuf[1] = 0x00; // medium type (always 0)
retbuf[2] = SCSIdisk[target].readonly ? 0x80 : 0x00; // if media is read-only 0x80, else 0x00
retbuf[3] = 0x08; // block descriptor length
/* Block descriptor data */
Uint8 header_size = 4;
if (!dbd) {
retbuf[4] = 0x00; // media density code
retbuf[5] = sectors >> 16; // Number of blocks, high (?)
retbuf[6] = sectors >> 8; // Number of blocks, med (?)
retbuf[7] = sectors; // Number of blocks, low (?)
retbuf[8] = 0x00; // reserved
retbuf[9] = (BLOCKSIZE >> 16) & 0xFF; // Block size in bytes, high
retbuf[10] = (BLOCKSIZE >> 8) & 0xFF; // Block size in bytes, med
retbuf[11] = BLOCKSIZE & 0xFF; // Block size in bytes, low
header_size = 12;
Log_Printf(LOG_WARN, "[SCSI] Mode Sense: Block descriptor data: %s, size = %i blocks, blocksize = %i byte\n",
SCSIdisk[target].readonly ? "disk is read-only" : "disk is read/write" , sectors, BLOCKSIZE);
}
retbuf[0] = header_size - 1;
/* Mode Pages */
if (pagecode == 0x3F) { // return all pages!
Uint8 offset = header_size;
Uint8 counter;
for (pagecode = 0; pagecode < 0x3F; pagecode++) {
page = SCSI_GetModePage(pagecode);
switch (pagecontrol) {
case 0: // current values (not supported, using default values)
memcpy(page.current, page.modepage, page.pagesize);
for (counter = 0; counter < page.pagesize; counter++) {
retbuf[counter+offset] = page.current[counter];
}
break;
case 1: // changeable values (not supported, all 0)
memset(page.changeable, 0x00, page.pagesize);
for (counter = 0; counter < page.pagesize; counter++) {
retbuf[counter+offset] = page.changeable[counter];
}
break;
case 2: // default values
for (counter = 0; counter < page.pagesize; counter++) {
retbuf[counter+offset] = page.modepage[counter];
}
break;
case 3: // saved values (not supported, using default values)
memcpy(page.saved, page.modepage, page.pagesize);
for (counter = 0; counter < page.pagesize; counter++) {
retbuf[counter+offset] = page.saved[counter];
}
break;
default:
break;
}
offset += page.pagesize;
retbuf[0] += page.pagesize;
}
} else { // return only single requested page
page = SCSI_GetModePage(pagecode);
Uint8 counter;
switch (pagecontrol) {
case 0: // current values (not supported, using default values)
memcpy(page.current, page.modepage, page.pagesize);
for (counter = 0; counter < page.pagesize; counter++) {
retbuf[counter+header_size] = page.current[counter];
}
break;
case 1: // changeable values (not supported, all 0)
memset(page.changeable, 0x00, page.pagesize);
for (counter = 0; counter < page.pagesize; counter++) {
retbuf[counter+header_size] = page.changeable[counter];
}
break;
case 2: // default values
for (counter = 0; counter < page.pagesize; counter++) {
retbuf[counter+header_size] = page.modepage[counter];
}
break;
case 3: // saved values (not supported, using default values)
memcpy(page.saved, page.modepage, page.pagesize);
for (counter = 0; counter < page.pagesize; counter++) {
retbuf[counter+header_size] = page.saved[counter];
}
break;
default:
break;
}
retbuf[0] += page.pagesize;
}
scsi_buffer.disk=false;
scsi_buffer.limit = scsi_buffer.size = retbuf[0]+1;
if (scsi_buffer.limit > SCSI_GetTransferLength(cdb[0], cdb)) {
scsi_buffer.limit = scsi_buffer.size = SCSI_GetTransferLength(cdb[0], cdb);
}
memcpy(scsi_buffer.data, retbuf, scsi_buffer.limit);
SCSIdisk[target].status = STAT_GOOD;
SCSIbus.phase = PHASE_DI;
SCSIdisk[target].sense.code = SC_NO_ERROR;
SCSIdisk[target].sense.valid = false;
}
void SCSI_FormatDrive(Uint8 *cdb) {
Uint8 format_data = cdb[1]&0x10;
Log_Printf(LOG_WARN, "[SCSI] Format drive command with parameters %02X\n",cdb[1]&0x1F);
if (format_data) {
Log_Printf(LOG_WARN, "[SCSI] Format drive with format data unsupported!\n");
abort();
} else {
SCSIdisk[SCSIbus.target].status = STAT_GOOD;
SCSIbus.phase = PHASE_ST;
}
}