Skip to content

Commit

Permalink
Initiator improvements
Browse files Browse the repository at this point in the history
Changes adapted from @erichelgeson pull request:
BlueSCSI/BlueSCSI-v2#122

Changes from the above pull request
 - Correctly ID SCSI-1 drives
 - Don't reset bus - causes some drives to hang, no issues if skipped.
 - Fix logging of retry count
 - Fix image file opening as readable when never read from
 - Report how many unrecoverable bad sectors were found.
 - Print % complete in log.
 - InitiatorMaxRetry - default 5 - min 0 -> max 255
 - Eject and continue scanning IDs that have CD's or Eject-able media.
 - Fix incorrect sense key parsing.

Local Changes
 - Support for device with the removable flag
 - Support for Magneto-optical drives - writes as `MOx0_imaged(x).img`
 - Support for removable devices like zip drives and other devices.
   Writes as `RMx0_imaged(x).img`
 - Every time a removable media is inserted it the image filename
   increments the number between the "(x)" parenthesis e.g.
   `CD30_imaged(3).iso`
 - The ability to copy and paste `zuluscsi.ini` text from the log,
   including Vendor, Product, Version, and Type along with the SCSI
   device header `[SCSIx]`
  • Loading branch information
morio committed Feb 16, 2024
1 parent bb2a0f6 commit 8a7716e
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 38 deletions.
2 changes: 1 addition & 1 deletion src/ZuluSCSI_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
#include <ZuluSCSI_platform.h>

// Use variables for version number
#define FW_VER_NUM "24.02.07"
#define FW_VER_NUM "24.02.15"
#define FW_VER_SUFFIX "dev"
#define ZULU_FW_VERSION FW_VER_NUM "-" FW_VER_SUFFIX

Expand Down
186 changes: 149 additions & 37 deletions src/ZuluSCSI_initiator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,19 @@ static struct {
uint32_t sectorcount_all;
uint32_t sectors_done;
uint32_t max_sector_per_transfer;
uint32_t bad_sector_count;
uint8_t ansi_version;
uint8_t max_retry_count;
uint8_t device_type;

// Retry information for sector reads.
// If a large read fails, retry is done sector-by-sector.
int retrycount;
uint32_t failposition;
bool eject_when_done;
bool removable;

uint32_t removable_count[8];

FsFile target_file;
} g_initiator_state;
Expand All @@ -103,6 +111,12 @@ void scsiInitiatorInit()
logmsg("InitiatorID set to illegal value in, ", CONFIGFILE, ", defaulting to 7");
g_initiator_state.initiator_id = 7;
}
else
{
logmsg("InitiatorID set to ID ", g_initiator_state.initiator_id);
}
g_initiator_state.max_retry_count = ini_getl("SCSI", "InitiatorMaxRetry", 5, CONFIGFILE);

// treat initiator id as already imaged drive so it gets skipped
g_initiator_state.drives_imaged = 1 << g_initiator_state.initiator_id;

Expand All @@ -114,7 +128,12 @@ void scsiInitiatorInit()
g_initiator_state.retrycount = 0;
g_initiator_state.failposition = 0;
g_initiator_state.max_sector_per_transfer = 512;

g_initiator_state.ansi_version = 0;
g_initiator_state.bad_sector_count = 0;
g_initiator_state.device_type = SCSI_DEVICE_TYPE_DIRECT_ACCESS;
g_initiator_state.removable = false;
g_initiator_state.eject_when_done = false;
memset(g_initiator_state.removable_count, 0, sizeof(g_initiator_state.removable_count));

}

Expand Down Expand Up @@ -150,6 +169,30 @@ void delay_with_poll(uint32_t ms)
}
}

static int scsiTypeToIniType(int scsi_type, bool removable)
{
int ini_type = -1;
switch (scsi_type)
{
case SCSI_DEVICE_TYPE_DIRECT_ACCESS:
ini_type = removable ? S2S_CFG_REMOVABLE : S2S_CFG_FIXED;
break;
case 1:
ini_type = -1; // S2S_CFG_SEQUENTIAL
break;
case SCSI_DEVICE_TYPE_CD:
ini_type = S2S_CFG_OPTICAL;
break;
case SCSI_DEVICE_TYPE_MO:
ini_type = S2S_CFG_MO;
break;
default:
ini_type = -1;
break;
}
return ini_type;
}

// High level logic of the initiator mode
void scsiInitiatorMainLoop()
{
Expand All @@ -166,14 +209,14 @@ void scsiInitiatorMainLoop()
g_initiator_state.sectors_done = 0;
g_initiator_state.retrycount = 0;
g_initiator_state.max_sector_per_transfer = 512;
g_initiator_state.bad_sector_count = 0;
g_initiator_state.eject_when_done = false;

if (!(g_initiator_state.drives_imaged & (1 << g_initiator_state.target_id)))
{
delay_with_poll(1000);

uint8_t inquiry_data[36];
char vendor[9], product[17], revision[5];
int type;
uint8_t inquiry_data[36] = {0};

LED_ON();
bool startstopok =
Expand All @@ -188,14 +231,6 @@ void scsiInitiatorMainLoop()
bool inquiryok = startstopok &&
scsiInquiry(g_initiator_state.target_id, inquiry_data);

memcpy(vendor, &inquiry_data[8], 8);
vendor[8]=0;
memcpy(product, &inquiry_data[16], 16);
product[16]=0;
memcpy(revision, &inquiry_data[32], 4);
revision[4]=0;
type=inquiry_data[0]&0x1f;

LED_OFF();

uint64_t total_bytes = 0;
Expand All @@ -205,12 +240,6 @@ void scsiInitiatorMainLoop()
" capacity ", (int)g_initiator_state.sectorcount,
" sectors x ", (int)g_initiator_state.sectorsize, " bytes");

logmsg("[SCSI", g_initiator_state.target_id,"]");
logmsg(" Vendor = \"", vendor,"\"");
logmsg(" Product = \"", product,"\"");
logmsg(" Version = \"", revision,"\"");
logmsg(" Type = ", type);

g_initiator_state.sectorcount_all = g_initiator_state.sectorcount;

total_bytes = (uint64_t)g_initiator_state.sectorcount * g_initiator_state.sectorsize;
Expand Down Expand Up @@ -242,22 +271,79 @@ void scsiInitiatorMainLoop()

char filename_base[12];
strncpy(filename_base, "HD00_imaged", sizeof(filename_base));
const char *filename_extention = ".hda";
const char *filename_extension = ".hda";

if (inquiryok)
{
if ((inquiry_data[0] & 0x1F) == 5)
char vendor[9], product[17], revision[5];
g_initiator_state.device_type=inquiry_data[0] & 0x1f;
g_initiator_state.ansi_version = inquiry_data[2] & 0x7;
g_initiator_state.removable = !!(inquiry_data[1] & 0x80);
g_initiator_state.eject_when_done = g_initiator_state.removable;
memcpy(vendor, &inquiry_data[8], 8);
vendor[8]=0;
memcpy(product, &inquiry_data[16], 16);
product[16]=0;
memcpy(revision, &inquiry_data[32], 4);
revision[4]=0;

if(g_initiator_state.ansi_version != 0x02)
{
// this is a SCSI-1 drive, use READ6 and 256 bytes to be safe.
g_initiator_state.max_sector_per_transfer = 256;
}
int ini_type = scsiTypeToIniType(g_initiator_state.device_type, g_initiator_state.removable);
logmsg("SCSI Version ", (int) g_initiator_state.ansi_version);
logmsg("[SCSI", g_initiator_state.target_id,"]");
logmsg(" Vendor = \"", vendor,"\"");
logmsg(" Product = \"", product,"\"");
logmsg(" Version = \"", revision,"\"");
if (ini_type == -1)
logmsg("Type = Not Supported, trying direct access");
else
logmsg(" Type = ", ini_type);

if (g_initiator_state.device_type == SCSI_DEVICE_TYPE_CD)
{
strncpy(filename_base, "CD00_imaged", sizeof(filename_base));
filename_extention = ".iso";
filename_extension = ".iso";
}
else if (g_initiator_state.device_type == SCSI_DEVICE_TYPE_MO)
{
strncpy(filename_base, "MO00_imaged", sizeof(filename_base));
filename_extension = ".ing";
}
else if (g_initiator_state.device_type != SCSI_DEVICE_TYPE_DIRECT_ACCESS)
{
logmsg("Unhandled scsi device type: ", g_initiator_state.device_type, ". Handling it as Direct Access Device.");
g_initiator_state.device_type = SCSI_DEVICE_TYPE_DIRECT_ACCESS;
}

if (g_initiator_state.device_type == SCSI_DEVICE_TYPE_DIRECT_ACCESS && g_initiator_state.removable)
{
strncpy(filename_base, "RM00_imaged", sizeof(filename_base));
filename_extension = ".img";
}
}

if (g_initiator_state.eject_when_done && g_initiator_state.removable_count[g_initiator_state.target_id] == 0)
{
g_initiator_state.removable_count[g_initiator_state.target_id] = 1;
}

if (g_initiator_state.sectorcount > 0)
{
char filename[32] = {0};
filename_base[2] += g_initiator_state.target_id;
strncpy(filename, filename_base, sizeof(filename) - 1);
strncat(filename, filename_extention, sizeof(filename) - 1);
if (g_initiator_state.eject_when_done)
{
auto removable_count = g_initiator_state.removable_count[g_initiator_state.target_id];
snprintf(filename, sizeof(filename), "%s(%lu)%s",filename_base, removable_count, filename_extension);
}
else
{
snprintf(filename, sizeof(filename), "%s%s", filename_base, filename_extension);
}
static int handling = -1;
if (handling == -1)
{
Expand Down Expand Up @@ -291,12 +377,16 @@ void scsiInitiatorMainLoop()
return;
}
char filename_copy[6] = {0};
snprintf(filename_copy, sizeof(filename_copy), "_%03lu", i);

strncpy(filename, filename_base, sizeof(filename) - 1);
strncat(filename, filename_copy, sizeof(filename) - 1);
strncat(filename, filename_extention, sizeof(filename) - 1);

if (g_initiator_state.eject_when_done)
{
auto removable_count = g_initiator_state.removable_count[g_initiator_state.target_id];
snprintf(filename, sizeof(filename), "%s(%lu)-%03lu%s", filename_base, removable_count, i, filename_extension);
}
else
{
snprintf(filename, sizeof(filename), "%s-%03lu%s", filename_base, i, filename_extension);
}
snprintf(filename_copy, sizeof(filename_copy), "-%03lu", i);
if (SD.exists(filename))
continue;
break;
Expand Down Expand Up @@ -333,7 +423,7 @@ void scsiInitiatorMainLoop()
return;
}

g_initiator_state.target_file = SD.open(filename, O_RDWR | O_CREAT | O_TRUNC);
g_initiator_state.target_file = SD.open(filename, O_WRONLY | O_CREAT | O_TRUNC);
if (!g_initiator_state.target_file.isOpen())
{
logmsg("Failed to open file for writing: ", filename);
Expand Down Expand Up @@ -368,7 +458,17 @@ void scsiInitiatorMainLoop()
logmsg("Please reformat the SD card with exFAT format to image this drive fully");
}

g_initiator_state.drives_imaged |= (1 << g_initiator_state.target_id);
if(g_initiator_state.bad_sector_count != 0)
{
logmsg_f("NOTE: There were %d bad sectors that could not be read off this drive.", g_initiator_state.bad_sector_count);
}

if (!g_initiator_state.eject_when_done)
{
logmsg("Marking SCSI ID, ", g_initiator_state.target_id, ", as imaged, wont ask it again.");
g_initiator_state.drives_imaged |= (1 << g_initiator_state.target_id);
}

g_initiator_state.imaging = false;
g_initiator_state.target_file.close();
return;
Expand All @@ -394,11 +494,12 @@ void scsiInitiatorMainLoop()
{
logmsg("Failed to transfer ", numtoread, " sectors starting at ", (int)g_initiator_state.sectors_done);

if (g_initiator_state.retrycount < 5)
if (g_initiator_state.retrycount < g_initiator_state.max_retry_count)
{
logmsg("Retrying.. ", g_initiator_state.retrycount, "/5");
logmsg("Retrying.. ", g_initiator_state.retrycount + 1, "/", (int) g_initiator_state.max_retry_count);
delay_with_poll(200);
scsiHostPhyReset();
// This reset causes some drives to hang and seems to have no effect if left off.
// scsiHostPhyReset();
delay_with_poll(200);

g_initiator_state.retrycount++;
Expand All @@ -415,6 +516,7 @@ void scsiInitiatorMainLoop()
logmsg("Retry limit exceeded, skipping one sector");
g_initiator_state.retrycount = 0;
g_initiator_state.sectors_done++;
g_initiator_state.bad_sector_count++;
g_initiator_state.target_file.seek((uint64_t)g_initiator_state.sectors_done * g_initiator_state.sectorsize);
}
}
Expand All @@ -427,7 +529,8 @@ void scsiInitiatorMainLoop()
int speed_kbps = numtoread * g_initiator_state.sectorsize / (millis() - time_start);
logmsg("SCSI read succeeded, sectors done: ",
(int)g_initiator_state.sectors_done, " / ", (int)g_initiator_state.sectorcount,
" speed ", speed_kbps, " kB/s");
" speed ", speed_kbps, " kB/s - ",
(int)(100 * g_initiator_state.sectors_done / g_initiator_state.sectorcount), "%");
}
}
}
Expand Down Expand Up @@ -570,7 +673,7 @@ bool scsiRequestSense(int target_id, uint8_t *sense_key)

logmsg("RequestSense response: ", bytearray(response, 18));

*sense_key = response[2];
*sense_key = response[2] % 0xF;
return status == 0;
}

Expand All @@ -585,6 +688,15 @@ bool scsiStartStopUnit(int target_id, bool start)
command[4] |= 1; // Start
command[1] = 0; // Immediate
}
else // stop
{
if(g_initiator_state.eject_when_done)
{
logmsg("Ejecting media");
g_initiator_state.removable_count[g_initiator_state.target_id]++;
command[4] = 0b00000010; // eject(6), stop(7).
}
}

int status = scsiInitiatorRunCommand(target_id,
command, sizeof(command),
Expand Down Expand Up @@ -769,7 +881,7 @@ bool scsiInitiatorReadDataToFile(int target_id, uint32_t start_sector, uint32_t

// Read6 command supports 21 bit LBA - max of 0x1FFFFF
// ref: https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf pg 134
if (start_sector < 0x1FFFFF && sectorcount <= 256)
if (g_initiator_state.ansi_version != 0x02 || (start_sector < 0x1FFFFF && sectorcount <= 256))
{
// Use READ6 command for compatibility with old SCSI1 drives
uint8_t command[6] = {0x08,
Expand Down
4 changes: 4 additions & 0 deletions src/ZuluSCSI_initiator.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
#include <stdint.h>
#include <stdlib.h>

#define SCSI_DEVICE_TYPE_CD 0x5
#define SCSI_DEVICE_TYPE_MO 0x7
#define SCSI_DEVICE_TYPE_DIRECT_ACCESS 0x0

void scsiInitiatorInit();

void scsiInitiatorMainLoop();
Expand Down
1 change: 1 addition & 0 deletions zuluscsi.ini
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

#Initiator settings
#InitiatorID = 7 # SCSI ID, 0-7, when the device is in initiator mode, default is 7
#InitiatorMaxRetry = 5 # number of retries on failed reads 0-255, default is 5
#InitiatorImageHandling = 0 # 0: skip exisitng images, 1: create new image with incrementing suffix, 2: overwrite exising image

#EnableCDAudio = 0 # Enable CD audio - an external I2S DAC on the v1.2 is required
Expand Down

0 comments on commit 8a7716e

Please sign in to comment.