mirror of
https://github.com/fhgwright/SCSI2SD.git
synced 2024-06-02 22:41:29 +00:00
295 lines
7.6 KiB
C
Executable File
295 lines
7.6 KiB
C
Executable File
// Copyright (C) 2013 Michael McMaster <michael@codesrc.com>
|
|
//
|
|
// This file is part of SCSI2SD.
|
|
//
|
|
// SCSI2SD 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, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// SCSI2SD 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.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with SCSI2SD. If not, see <http://www.gnu.org/licenses/>.
|
|
#include "device.h"
|
|
#include "scsi.h"
|
|
#include "config.h"
|
|
#include "inquiry.h"
|
|
|
|
#include <string.h>
|
|
|
|
static uint8 StandardResponse[] =
|
|
{
|
|
0x00, // "Direct-access device". AKA standard hard disk
|
|
0x00, // device type modifier
|
|
0x02, // Complies with ANSI SCSI-2.
|
|
0x01, // Response format is compatible with the old CCS format.
|
|
0x1f, // standard length.
|
|
0, 0, // Reserved
|
|
0x08 // Enable linked commands
|
|
};
|
|
// Vendor set by config 'c','o','d','e','s','r','c',' ',
|
|
// prodId set by config'S','C','S','I','2','S','D',' ',' ',' ',' ',' ',' ',' ',' ',' ',
|
|
// Revision set by config'2','.','0','a'
|
|
|
|
/* For reference, here's a dump from an Apple branded 500Mb drive from 1994.
|
|
$ sudo sg_inq -H /dev/sdd --len 255
|
|
standard INQUIRY:
|
|
00 00 00 02 01 31 00 00 18 51 55 41 4e 54 55 4d 20 ....1...QUANTUM
|
|
10 4c 50 53 32 37 30 20 20 20 20 20 20 20 20 20 20 LPS270
|
|
20 30 39 30 30 00 00 00 d9 b0 27 34 01 04 b3 01 1b 0900.....'4.....
|
|
30 07 00 a0 00 00 ff ......
|
|
Vendor identification: QUANTUM
|
|
Product identification: LPS270
|
|
Product revision level: 0900
|
|
*/
|
|
|
|
|
|
static const uint8 SupportedVitalPages[] =
|
|
{
|
|
0x00, // "Direct-access device". AKA standard hard disk
|
|
0x00, // Page Code
|
|
0x00, // Reserved
|
|
0x04, // Page length
|
|
0x00, // Support "Supported vital product data pages"
|
|
0x80, // Support "Unit serial number page"
|
|
0x81, // Support "Implemented operating definition page"
|
|
0x82 // Support "ASCII Implemented operating definition page"
|
|
};
|
|
|
|
static const uint8 UnitSerialNumber[] =
|
|
{
|
|
0x00, // "Direct-access device". AKA standard hard disk
|
|
0x80, // Page Code
|
|
0x00, // Reserved
|
|
0x10, // Page length
|
|
'c','o','d','e','s','r','c','-','1','2','3','4','5','6','7','8'
|
|
};
|
|
|
|
static const uint8 ImpOperatingDefinition[] =
|
|
{
|
|
0x00, // "Direct-access device". AKA standard hard disk
|
|
0x81, // Page Code
|
|
0x00, // Reserved
|
|
0x03, // Page length
|
|
0x03, // Current: SCSI-2 operating definition
|
|
0x03, // Default: SCSI-2 operating definition
|
|
0x03 // Supported (list): SCSI-2 operating definition.
|
|
};
|
|
|
|
static const uint8 AscImpOperatingDefinition[] =
|
|
{
|
|
0x00, // "Direct-access device". AKA standard hard disk
|
|
0x82, // Page Code
|
|
0x00, // Reserved
|
|
0x07, // Page length
|
|
0x06, // Ascii length
|
|
'S','C','S','I','-','2'
|
|
};
|
|
|
|
static void useCustomVPD(const TargetConfig* cfg, int pageCode)
|
|
{
|
|
int cfgIdx = 0;
|
|
int found = 0;
|
|
while ((cfgIdx < sizeof(cfg->vpd) - 4) &&
|
|
(cfg->vpd[cfgIdx + 3] != 0)
|
|
)
|
|
{
|
|
int pageSize = cfg->vpd[cfgIdx + 3] + 4;
|
|
int dataPageCode = cfg->vpd[cfgIdx + 1];
|
|
if (dataPageCode == pageCode)
|
|
{
|
|
memcpy(scsiDev.data, &(cfg->vpd[cfgIdx]), pageSize);
|
|
scsiDev.dataLen = pageSize;
|
|
scsiDev.phase = DATA_IN;
|
|
found = 1;
|
|
break;
|
|
}
|
|
cfgIdx += pageSize;
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
// error.
|
|
scsiDev.status = CHECK_CONDITION;
|
|
scsiDev.target->sense.code = ILLEGAL_REQUEST;
|
|
scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
|
|
scsiDev.phase = STATUS;
|
|
}
|
|
}
|
|
|
|
void scsiInquiry()
|
|
{
|
|
uint8 evpd = scsiDev.cdb[1] & 1; // enable vital product data.
|
|
uint8 pageCode = scsiDev.cdb[2];
|
|
uint32 allocationLength = scsiDev.cdb[4];
|
|
|
|
// SASI standard, X3T9.3_185_RevE states that 0 == 256 bytes
|
|
// BUT SCSI 2 standard says 0 == 0.
|
|
if (scsiDev.compatMode <= COMPAT_SCSI1) // excludes COMPAT_SCSI2_DISABLED
|
|
{
|
|
if (allocationLength == 0) allocationLength = 256;
|
|
}
|
|
|
|
if (!evpd)
|
|
{
|
|
if (pageCode)
|
|
{
|
|
// error.
|
|
scsiDev.status = CHECK_CONDITION;
|
|
scsiDev.target->sense.code = ILLEGAL_REQUEST;
|
|
scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
|
|
scsiDev.phase = STATUS;
|
|
}
|
|
else
|
|
{
|
|
const TargetConfig* config = scsiDev.target->cfg;
|
|
memcpy(scsiDev.data, StandardResponse, sizeof(StandardResponse));
|
|
scsiDev.data[1] = scsiDev.target->cfg->deviceTypeModifier;
|
|
|
|
if (scsiDev.compatMode >= COMPAT_SCSI2)
|
|
{
|
|
scsiDev.data[3] = 2; // SCSI 2 response format.
|
|
}
|
|
memcpy(&scsiDev.data[8], config->vendor, sizeof(config->vendor));
|
|
memcpy(&scsiDev.data[16], config->prodId, sizeof(config->prodId));
|
|
memcpy(&scsiDev.data[32], config->revision, sizeof(config->revision));
|
|
|
|
scsiDev.dataLen = sizeof(StandardResponse) +
|
|
sizeof(config->vendor) +
|
|
sizeof(config->prodId) +
|
|
sizeof(config->revision);
|
|
scsiDev.phase = DATA_IN;
|
|
}
|
|
}
|
|
else if (scsiDev.target->cfg->vpd[3] != 0)
|
|
{
|
|
useCustomVPD(scsiDev.target->cfg, pageCode);
|
|
}
|
|
else if (pageCode == 0x00)
|
|
{
|
|
memcpy(scsiDev.data, SupportedVitalPages, sizeof(SupportedVitalPages));
|
|
scsiDev.dataLen = sizeof(SupportedVitalPages);
|
|
scsiDev.phase = DATA_IN;
|
|
}
|
|
else if (pageCode == 0x80)
|
|
{
|
|
memcpy(scsiDev.data, UnitSerialNumber, sizeof(UnitSerialNumber));
|
|
scsiDev.dataLen = sizeof(UnitSerialNumber);
|
|
const TargetConfig* config = scsiDev.target->cfg;
|
|
memcpy(&scsiDev.data[4], config->serial, sizeof(config->serial));
|
|
scsiDev.phase = DATA_IN;
|
|
}
|
|
else if (pageCode == 0x81)
|
|
{
|
|
memcpy(
|
|
scsiDev.data,
|
|
ImpOperatingDefinition,
|
|
sizeof(ImpOperatingDefinition));
|
|
scsiDev.dataLen = sizeof(ImpOperatingDefinition);
|
|
scsiDev.phase = DATA_IN;
|
|
}
|
|
else if (pageCode == 0x82)
|
|
{
|
|
memcpy(
|
|
scsiDev.data,
|
|
AscImpOperatingDefinition,
|
|
sizeof(AscImpOperatingDefinition));
|
|
scsiDev.dataLen = sizeof(AscImpOperatingDefinition);
|
|
scsiDev.phase = DATA_IN;
|
|
}
|
|
else
|
|
{
|
|
// error.
|
|
scsiDev.status = CHECK_CONDITION;
|
|
scsiDev.target->sense.code = ILLEGAL_REQUEST;
|
|
scsiDev.target->sense.asc = INVALID_FIELD_IN_CDB;
|
|
scsiDev.phase = STATUS;
|
|
}
|
|
|
|
|
|
if (scsiDev.phase == DATA_IN)
|
|
{
|
|
// "real" hard drives send back exactly allocationLenth bytes, padded
|
|
// with zeroes. This only seems to happen for Inquiry responses, and not
|
|
// other commands that also supply an allocation length such as Mode Sense or
|
|
// Request Sense.
|
|
// (See below for exception to this rule when 0 allocation length)
|
|
if (scsiDev.dataLen < allocationLength)
|
|
{
|
|
memset(
|
|
&scsiDev.data[scsiDev.dataLen],
|
|
0,
|
|
allocationLength - scsiDev.dataLen);
|
|
}
|
|
// Spec 8.2.5 requires us to simply truncate the response if it's
|
|
// too big.
|
|
scsiDev.dataLen = allocationLength;
|
|
|
|
// Set the device type as needed.
|
|
scsiDev.data[0] = getDeviceTypeQualifier();
|
|
|
|
switch (scsiDev.target->cfg->deviceType)
|
|
{
|
|
case CONFIG_OPTICAL:
|
|
scsiDev.data[1] |= 0x80; // Removable bit.
|
|
break;
|
|
|
|
case CONFIG_SEQUENTIAL:
|
|
scsiDev.data[1] |= 0x80; // Removable bit.
|
|
break;
|
|
|
|
case CONFIG_MO:
|
|
scsiDev.data[1] |= 0x80; // Removable bit.
|
|
break;
|
|
|
|
case CONFIG_FLOPPY_14MB:
|
|
case CONFIG_REMOVEABLE:
|
|
scsiDev.data[1] |= 0x80; // Removable bit.
|
|
break;
|
|
default:
|
|
// Accept defaults for a fixed disk.
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
// Set the first byte to indicate LUN presence.
|
|
if (scsiDev.lun) // We only support lun 0
|
|
{
|
|
scsiDev.data[0] = 0x7F;
|
|
}
|
|
}
|
|
|
|
uint8_t getDeviceTypeQualifier()
|
|
{
|
|
// Set the device type as needed.
|
|
switch (scsiDev.target->cfg->deviceType)
|
|
{
|
|
case CONFIG_OPTICAL:
|
|
return 0x05;
|
|
break;
|
|
|
|
case CONFIG_SEQUENTIAL:
|
|
return 0x01;
|
|
break;
|
|
|
|
case CONFIG_MO:
|
|
return 0x07;
|
|
break;
|
|
|
|
case CONFIG_FLOPPY_14MB:
|
|
case CONFIG_REMOVEABLE:
|
|
return 0;
|
|
break;
|
|
|
|
default:
|
|
// Accept defaults for a fixed disk.
|
|
return 0;
|
|
}
|
|
}
|
|
|