mirror of
https://github.com/fhgwright/SCSI2SD.git
synced 2024-12-29 11:31:00 +00:00
Added configurable block-size support
This commit is contained in:
parent
7231a4e210
commit
0bb5ed8349
@ -5,6 +5,7 @@
|
||||
- scsi2sd-config can be used to disable it for those systems that
|
||||
truely require it (eg. Mac Plus).
|
||||
- Added Linked commands support.
|
||||
- Added support for configurable sector sizes between 64 and 2048 bytes.
|
||||
- Powerbook firmware added
|
||||
|
||||
20140214 3.2
|
||||
|
@ -27,7 +27,7 @@
|
||||
#include <string.h>
|
||||
|
||||
// CYDEV_EEPROM_ROW_SIZE == 16.
|
||||
static char magic[CYDEV_EEPROM_ROW_SIZE] = "codesrc_00000001";
|
||||
static char magic[CYDEV_EEPROM_ROW_SIZE] = "codesrc_00000002";
|
||||
|
||||
// Config shadow RAM (copy of EEPROM)
|
||||
static Config shadow =
|
||||
@ -38,7 +38,9 @@ static Config shadow =
|
||||
" 3.3", // revision (68k Apple Drive Setup: Set to "1.0 ")
|
||||
1, // enable parity
|
||||
1, // enable unit attention,
|
||||
0 // Max blocks (0 == disabled)
|
||||
0, // RESERVED
|
||||
0, // Max sectors (0 == disabled)
|
||||
512 // Sector size
|
||||
// reserved bytes will be initialised to 0.
|
||||
};
|
||||
|
||||
@ -68,6 +70,12 @@ static uint32_t ntohl(uint32_t val)
|
||||
((val >> 8) & 0xFF00) |
|
||||
((val >> 24) & 0xFF);
|
||||
}
|
||||
static uint16_t ntohs(uint16_t val)
|
||||
{
|
||||
return
|
||||
((val & 0xFF) << 8) |
|
||||
((val >> 8) & 0xFF);
|
||||
}
|
||||
static uint32_t htonl(uint32_t val)
|
||||
{
|
||||
return
|
||||
@ -76,6 +84,12 @@ static uint32_t htonl(uint32_t val)
|
||||
((val >> 8) & 0xFF00) |
|
||||
((val >> 24) & 0xFF);
|
||||
}
|
||||
static uint16_t htons(uint16_t val)
|
||||
{
|
||||
return
|
||||
((val & 0xFF) << 8) |
|
||||
((val >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
static void saveConfig()
|
||||
{
|
||||
@ -111,6 +125,15 @@ void configInit()
|
||||
|
||||
if (memcmp(eeprom + shadowBytes, magic, sizeof(magic)))
|
||||
{
|
||||
// Initial state, invalid, or upgrade required.
|
||||
if (!memcmp(eeprom + shadowBytes, magic, sizeof(magic) - 1) &&
|
||||
((eeprom + shadowBytes)[sizeof(magic) - 2] == '1'))
|
||||
{
|
||||
// Upgrade from version 1.
|
||||
memcpy(&shadow, eeprom, sizeof(shadow));
|
||||
shadow.bytesPerSector = 512;
|
||||
}
|
||||
|
||||
saveConfig();
|
||||
}
|
||||
else
|
||||
@ -161,7 +184,17 @@ void configPoll()
|
||||
// shadow should be padded out to 64bytes, which is the largest
|
||||
// possible HID transfer.
|
||||
USBFS_ReadOutEP(USB_EP_OUT, (uint8 *)&shadow, byteCount);
|
||||
shadow.maxBlocks = htonl(shadow.maxBlocks);
|
||||
shadow.maxSectors = ntohl(shadow.maxSectors);
|
||||
shadow.bytesPerSector = ntohs(shadow.bytesPerSector);
|
||||
|
||||
if (shadow.bytesPerSector > MAX_SECTOR_SIZE)
|
||||
{
|
||||
shadow.bytesPerSector = MAX_SECTOR_SIZE;
|
||||
}
|
||||
else if (shadow.bytesPerSector < MIN_SECTOR_SIZE)
|
||||
{
|
||||
shadow.bytesPerSector = MIN_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
CFG_EEPROM_Start();
|
||||
saveConfig(); // write to eeprom
|
||||
@ -173,14 +206,18 @@ void configPoll()
|
||||
// Allow the host to send us another updated config.
|
||||
USBFS_EnableOutEP(USB_EP_OUT);
|
||||
|
||||
// Set unt attention as the block size may have changed.
|
||||
scsiDev.unitAttention = MODE_PARAMETERS_CHANGED;
|
||||
|
||||
ledOff();
|
||||
}
|
||||
|
||||
switch (usbInEpState)
|
||||
{
|
||||
case USB_IDLE:
|
||||
shadow.maxBlocks = htonl(shadow.maxBlocks);
|
||||
|
||||
shadow.maxSectors = htonl(shadow.maxSectors);
|
||||
shadow.bytesPerSector = htons(shadow.bytesPerSector);
|
||||
|
||||
#ifdef MM_DEBUG
|
||||
memcpy(&shadow.reserved, &scsiDev.cdb, 12);
|
||||
shadow.reserved[12] = scsiDev.msgIn;
|
||||
@ -197,12 +234,11 @@ void configPoll()
|
||||
shadow.reserved[23] = scsiDev.msgCount;
|
||||
shadow.reserved[24] = scsiDev.cmdCount;
|
||||
shadow.reserved[25] = scsiDev.watchdogTick;
|
||||
shadow.reserved[26] = blockDev.state;
|
||||
shadow.reserved[27] = scsiReadDBxPins();
|
||||
#endif
|
||||
|
||||
USBFS_LoadInEP(USB_EP_IN, (uint8 *)&shadow, sizeof(shadow));
|
||||
shadow.maxBlocks = ntohl(shadow.maxBlocks);
|
||||
shadow.maxSectors = ntohl(shadow.maxSectors);
|
||||
shadow.bytesPerSector = ntohs(shadow.bytesPerSector);
|
||||
usbInEpState = USB_DATA_SENT;
|
||||
break;
|
||||
|
||||
@ -216,3 +252,11 @@ void configPoll()
|
||||
}
|
||||
}
|
||||
|
||||
// Public method for storing MODE SELECT results.
|
||||
void configSave()
|
||||
{
|
||||
CFG_EEPROM_Start();
|
||||
saveConfig(); // write to eeprom
|
||||
CFG_EEPROM_Stop();
|
||||
}
|
||||
|
||||
|
@ -28,15 +28,17 @@ typedef struct
|
||||
uint8 enableParity;
|
||||
uint8 enableUnitAttention;
|
||||
uint8 reserved1; // Unused. Ensures maxBlocks is aligned.
|
||||
uint32 maxBlocks;
|
||||
uint32 maxSectors;
|
||||
uint16 bytesPerSector;
|
||||
|
||||
// Pad to 64 bytes, which is what we can fit into a USB HID packet.
|
||||
char reserved[28];
|
||||
char reserved[26];
|
||||
} Config;
|
||||
|
||||
extern Config* config;
|
||||
|
||||
void configInit(void);
|
||||
void configPoll(void);
|
||||
void configSave(void);
|
||||
|
||||
#endif
|
||||
|
@ -34,11 +34,6 @@ static int doSdInit()
|
||||
if (result)
|
||||
{
|
||||
blockDev.state = blockDev.state | DISK_INITIALISED;
|
||||
|
||||
// artificially limit this value according to EEPROM config.
|
||||
blockDev.capacity =
|
||||
(config->maxBlocks && (sdDev.capacity > config->maxBlocks))
|
||||
? config->maxBlocks : sdDev.capacity;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -52,12 +47,14 @@ static void doFormatUnit()
|
||||
|
||||
static void doReadCapacity()
|
||||
{
|
||||
uint32 lba = (((uint32) scsiDev.cdb[2]) << 24) +
|
||||
uint32_t lba = (((uint32) scsiDev.cdb[2]) << 24) +
|
||||
(((uint32) scsiDev.cdb[3]) << 16) +
|
||||
(((uint32) scsiDev.cdb[4]) << 8) +
|
||||
scsiDev.cdb[5];
|
||||
int pmi = scsiDev.cdb[8] & 1;
|
||||
|
||||
uint32_t capacity = getScsiCapacity();
|
||||
|
||||
if (!pmi && lba)
|
||||
{
|
||||
// error.
|
||||
@ -69,19 +66,19 @@ static void doReadCapacity()
|
||||
scsiDev.sense.asc = INVALID_FIELD_IN_CDB;
|
||||
scsiDev.phase = STATUS;
|
||||
}
|
||||
else if (blockDev.capacity > 0)
|
||||
else if (capacity > 0)
|
||||
{
|
||||
uint32 highestBlock = blockDev.capacity - 1;
|
||||
uint32_t highestBlock = capacity - 1;
|
||||
|
||||
scsiDev.data[0] = highestBlock >> 24;
|
||||
scsiDev.data[1] = highestBlock >> 16;
|
||||
scsiDev.data[2] = highestBlock >> 8;
|
||||
scsiDev.data[3] = highestBlock;
|
||||
|
||||
scsiDev.data[4] = blockDev.bs >> 24;
|
||||
scsiDev.data[5] = blockDev.bs >> 16;
|
||||
scsiDev.data[6] = blockDev.bs >> 8;
|
||||
scsiDev.data[7] = blockDev.bs;
|
||||
scsiDev.data[4] = config->bytesPerSector >> 24;
|
||||
scsiDev.data[5] = config->bytesPerSector >> 16;
|
||||
scsiDev.data[6] = config->bytesPerSector >> 8;
|
||||
scsiDev.data[7] = config->bytesPerSector;
|
||||
scsiDev.dataLen = 8;
|
||||
scsiDev.phase = DATA_IN;
|
||||
}
|
||||
@ -103,7 +100,7 @@ static void doWrite(uint32 lba, uint32 blocks)
|
||||
scsiDev.sense.asc = WRITE_PROTECTED;
|
||||
scsiDev.phase = STATUS;
|
||||
}
|
||||
else if (((uint64) lba) + blocks > blockDev.capacity)
|
||||
else if (((uint64) lba) + blocks > getScsiCapacity())
|
||||
{
|
||||
scsiDev.status = CHECK_CONDITION;
|
||||
scsiDev.sense.code = ILLEGAL_REQUEST;
|
||||
@ -117,11 +114,11 @@ static void doWrite(uint32 lba, uint32 blocks)
|
||||
transfer.blocks = blocks;
|
||||
transfer.currentBlock = 0;
|
||||
scsiDev.phase = DATA_OUT;
|
||||
scsiDev.dataLen = SCSI_BLOCK_SIZE;
|
||||
scsiDev.dataPtr = SCSI_BLOCK_SIZE; // TODO FIX scsiDiskPoll()
|
||||
scsiDev.dataLen = config->bytesPerSector;
|
||||
scsiDev.dataPtr = config->bytesPerSector; // TODO FIX scsiDiskPoll()
|
||||
|
||||
// No need for single-block reads atm. Overhead of the
|
||||
// multi-block read is minimal.
|
||||
// No need for single-block writes atm. Overhead of the
|
||||
// multi-block write is minimal.
|
||||
transfer.multiBlock = 1;
|
||||
sdPrepareWrite();
|
||||
}
|
||||
@ -130,7 +127,8 @@ static void doWrite(uint32 lba, uint32 blocks)
|
||||
|
||||
static void doRead(uint32 lba, uint32 blocks)
|
||||
{
|
||||
if (((uint64) lba) + blocks > blockDev.capacity)
|
||||
uint32_t capacity = getScsiCapacity();
|
||||
if (((uint64) lba) + blocks > capacity)
|
||||
{
|
||||
scsiDev.status = CHECK_CONDITION;
|
||||
scsiDev.sense.code = ILLEGAL_REQUEST;
|
||||
@ -147,7 +145,7 @@ static void doRead(uint32 lba, uint32 blocks)
|
||||
scsiDev.dataLen = 0; // No data yet
|
||||
|
||||
if ((blocks == 1) ||
|
||||
(((uint64) lba) + blocks == blockDev.capacity)
|
||||
(((uint64) lba) + blocks == capacity)
|
||||
)
|
||||
{
|
||||
// We get errors on reading the last sector using a multi-sector
|
||||
@ -164,7 +162,7 @@ static void doRead(uint32 lba, uint32 blocks)
|
||||
|
||||
static void doSeek(uint32 lba)
|
||||
{
|
||||
if (lba >= blockDev.capacity)
|
||||
if (lba >= getScsiCapacity())
|
||||
{
|
||||
scsiDev.status = CHECK_CONDITION;
|
||||
scsiDev.sense.code = ILLEGAL_REQUEST;
|
||||
@ -455,8 +453,6 @@ void scsiDiskReset()
|
||||
|
||||
void scsiDiskInit()
|
||||
{
|
||||
blockDev.bs = SCSI_BLOCK_SIZE;
|
||||
blockDev.capacity = 0;
|
||||
transfer.inProgress = 0;
|
||||
scsiDiskReset();
|
||||
|
||||
|
@ -33,9 +33,6 @@ typedef enum
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32 bs; // Block size.
|
||||
uint32 capacity; // In blocks.
|
||||
|
||||
int state;
|
||||
} BlockDevice;
|
||||
|
||||
|
@ -20,6 +20,22 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
uint32_t getScsiCapacity()
|
||||
{
|
||||
uint32_t capacity = sdDev.capacity / SDSectorsPerSCSISector();
|
||||
if (config->maxSectors && (capacity > config->maxSectors))
|
||||
{
|
||||
capacity = config->maxSectors;
|
||||
}
|
||||
return capacity;
|
||||
}
|
||||
|
||||
|
||||
uint32_t SCSISector2SD(uint32_t scsiSector)
|
||||
{
|
||||
return scsiSector * SDSectorsPerSCSISector();
|
||||
}
|
||||
|
||||
// Standard mapping according to ECMA-107 and ISO/IEC 9293:1994
|
||||
// Sector always starts at 1. There is no 0 sector.
|
||||
uint64 CHS2LBA(uint32 c, uint8 h, uint32 s)
|
||||
@ -51,7 +67,7 @@ uint64 scsiByteAddress(int format, const uint8* addr)
|
||||
(((uint32) addr[2]) << 8) +
|
||||
addr[3];
|
||||
|
||||
result = (uint64) SCSI_BLOCK_SIZE * lba;
|
||||
result = (uint64_t) config->bytesPerSector * lba;
|
||||
} break;
|
||||
|
||||
case ADDRESS_PHYSICAL_BYTE:
|
||||
@ -69,7 +85,7 @@ uint64 scsiByteAddress(int format, const uint8* addr)
|
||||
(((uint32) addr[6]) << 8) +
|
||||
addr[7];
|
||||
|
||||
result = CHS2LBA(cyl, head, 1) * (uint64) SCSI_SECTOR_SIZE + bytes;
|
||||
result = CHS2LBA(cyl, head, 1) * (uint64_t) config->bytesPerSector + bytes;
|
||||
} break;
|
||||
|
||||
case ADDRESS_PHYSICAL_SECTOR:
|
||||
@ -87,7 +103,7 @@ uint64 scsiByteAddress(int format, const uint8* addr)
|
||||
(((uint32) addr[6]) << 8) +
|
||||
addr[7];
|
||||
|
||||
result = CHS2LBA(cyl, head, sector) * (uint64) SCSI_SECTOR_SIZE;
|
||||
result = CHS2LBA(cyl, head, sector) * (uint64_t) config->bytesPerSector;
|
||||
} break;
|
||||
|
||||
default:
|
||||
@ -100,8 +116,8 @@ uint64 scsiByteAddress(int format, const uint8* addr)
|
||||
|
||||
void scsiSaveByteAddress(int format, uint64 byteAddr, uint8* buf)
|
||||
{
|
||||
uint32 lba = byteAddr / SCSI_BLOCK_SIZE;
|
||||
uint32 byteOffset = byteAddr % SCSI_BLOCK_SIZE;
|
||||
uint32 lba = byteAddr / config->bytesPerSector;
|
||||
uint32 byteOffset = byteAddr % config->bytesPerSector;
|
||||
|
||||
switch (format)
|
||||
{
|
||||
@ -127,7 +143,7 @@ void scsiSaveByteAddress(int format, uint64 byteAddr, uint8* buf)
|
||||
|
||||
LBA2CHS(lba, &cyl, &head, §or);
|
||||
|
||||
bytes = sector * SCSI_SECTOR_SIZE + byteOffset;
|
||||
bytes = sector * config->bytesPerSector + byteOffset;
|
||||
|
||||
buf[0] = cyl >> 16;
|
||||
buf[1] = cyl >> 8;
|
||||
|
@ -19,10 +19,8 @@
|
||||
|
||||
#include "device.h"
|
||||
|
||||
// We make some assumptions that the block size and sector size
|
||||
// are always equal.
|
||||
#define SCSI_BLOCK_SIZE 512
|
||||
#define SCSI_SECTOR_SIZE 512
|
||||
#include "config.h"
|
||||
#include "sd.h"
|
||||
|
||||
// Max allowed by legacy IBM-PC Bios (6 bits)
|
||||
#define SCSI_SECTORS_PER_TRACK 63
|
||||
@ -37,6 +35,14 @@ typedef enum
|
||||
ADDRESS_PHYSICAL_SECTOR = 5
|
||||
} SCSI_ADDRESS_FORMAT;
|
||||
|
||||
static inline int SDSectorsPerSCSISector()
|
||||
{
|
||||
return (config->bytesPerSector + SD_SECTOR_SIZE - 1) / SD_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
uint32_t getScsiCapacity();
|
||||
|
||||
uint32_t SCSISector2SD(uint32_t scsiSector);
|
||||
|
||||
uint64 CHS2LBA(uint32 c, uint8 h, uint32 s);
|
||||
void LBA2CHS(uint32 lba, uint32* c, uint8* h, uint32* s);
|
||||
|
@ -54,14 +54,14 @@ static const uint8 DisconnectReconnectPage[] =
|
||||
|
||||
static const uint8 FormatDevicePage[] =
|
||||
{
|
||||
0x03, // Page code
|
||||
0x03 | 0x80, // Page code | PS (persist) bit.
|
||||
0x16, // Page length
|
||||
0x00, 0x00, // Single zone
|
||||
0x00, 0x00, // No alternate sectors
|
||||
0x00, 0x00, // No alternate tracks
|
||||
0x00, 0x00, // No alternate tracks per lun
|
||||
0x00, SCSI_SECTORS_PER_TRACK, // Sectors per track
|
||||
SCSI_SECTOR_SIZE >> 8, SCSI_SECTOR_SIZE & 0xFF, // Data bytes per physical sector
|
||||
0xFF, 0xFF, // Data bytes per physical sector. Configurable.
|
||||
0x00, 0x01, // Interleave
|
||||
0x00, 0x00, // Track skew factor
|
||||
0x00, 0x00, // Cylinder skew factor
|
||||
@ -111,7 +111,6 @@ static const uint8 ControlModePage[] =
|
||||
|
||||
// Allow Apple 68k Drive Setup to format this drive.
|
||||
// Code
|
||||
// TODO make this string configurable.
|
||||
static const uint8 AppleVendorPage[] =
|
||||
{
|
||||
0x30, // Page code
|
||||
@ -143,7 +142,7 @@ static void doModeSense(
|
||||
else
|
||||
{
|
||||
int pageFound = 1;
|
||||
|
||||
|
||||
////////////// Mode Parameter Header
|
||||
////////////////////////////////////
|
||||
|
||||
@ -201,9 +200,9 @@ static void doModeSense(
|
||||
scsiDev.data[idx++] = 0; // reserved
|
||||
|
||||
// Block length
|
||||
scsiDev.data[idx++] = SCSI_BLOCK_SIZE >> 16;
|
||||
scsiDev.data[idx++] = SCSI_BLOCK_SIZE >> 8;
|
||||
scsiDev.data[idx++] = SCSI_BLOCK_SIZE & 0xFF;
|
||||
scsiDev.data[idx++] = config->bytesPerSector >> 16;
|
||||
scsiDev.data[idx++] = config->bytesPerSector >> 8;
|
||||
scsiDev.data[idx++] = config->bytesPerSector & 0xFF;
|
||||
}
|
||||
|
||||
switch (pageCode)
|
||||
@ -223,6 +222,19 @@ static void doModeSense(
|
||||
|
||||
case 0x03:
|
||||
pageIn(pc, idx, FormatDevicePage, sizeof(FormatDevicePage));
|
||||
if (pc != 0x01)
|
||||
{
|
||||
// Fill out the configured bytes-per-sector
|
||||
scsiDev.data[idx+12] = config->bytesPerSector >> 8;
|
||||
scsiDev.data[idx+13] = config->bytesPerSector & 0xFF;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set a mask for the changeable values.
|
||||
scsiDev.data[idx+12] = 0xFF;
|
||||
scsiDev.data[idx+13] = 0xFF;
|
||||
}
|
||||
|
||||
idx += sizeof(FormatDevicePage);
|
||||
if (pageCode != 0x3f) break;
|
||||
|
||||
@ -236,7 +248,7 @@ static void doModeSense(
|
||||
uint32 cyl;
|
||||
uint8 head;
|
||||
uint32 sector;
|
||||
LBA2CHS(blockDev.capacity, &cyl, &head, §or);
|
||||
LBA2CHS(getScsiCapacity(), &cyl, &head, §or);
|
||||
|
||||
scsiDev.data[idx+2] = cyl >> 16;
|
||||
scsiDev.data[idx+3] = cyl >> 8;
|
||||
@ -309,6 +321,74 @@ static void doModeSense(
|
||||
}
|
||||
}
|
||||
|
||||
// Callback after the DATA OUT phase is complete.
|
||||
static void doModeSelect(void)
|
||||
{
|
||||
if (scsiDev.status == GOOD) // skip if we've already encountered an error
|
||||
{
|
||||
// scsiDev.dataLen bytes are in scsiDev.data
|
||||
|
||||
int idx;
|
||||
if (scsiDev.cdb[0] == 0x15)
|
||||
{
|
||||
int blockDescLen =
|
||||
(((uint16_t)scsiDev.data[6]) << 8) |scsiDev.data[7];
|
||||
idx = 8 + blockDescLen;
|
||||
}
|
||||
else
|
||||
{
|
||||
int blockDescLen = scsiDev.data[3];
|
||||
idx = 4 + blockDescLen;
|
||||
}
|
||||
if (idx > scsiDev.dataLen) goto bad;
|
||||
|
||||
while (idx < scsiDev.dataLen)
|
||||
{
|
||||
int pageLen = scsiDev.data[idx + 1];
|
||||
if (idx + 2 + pageLen > scsiDev.dataLen) goto bad;
|
||||
|
||||
int pageCode = scsiDev.data[idx] & 0x3F;
|
||||
switch (pageCode)
|
||||
{
|
||||
case 0x03: // Format Device Page
|
||||
{
|
||||
if (pageLen != 0x16) goto bad;
|
||||
|
||||
// Fill out the configured bytes-per-sector
|
||||
uint16_t bytesPerSector =
|
||||
(((uint16_t)scsiDev.data[idx+12]) << 8) |
|
||||
scsiDev.data[idx+13];
|
||||
|
||||
// Sane values only, ok ?
|
||||
if ((bytesPerSector < MIN_SECTOR_SIZE) ||
|
||||
(bytesPerSector > MAX_SECTOR_SIZE))
|
||||
{
|
||||
goto bad;
|
||||
}
|
||||
|
||||
config->bytesPerSector = bytesPerSector;
|
||||
if (scsiDev.cdb[1] & 1) // SP Save Pages flag
|
||||
{
|
||||
configSave();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
goto bad;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
goto out;
|
||||
bad:
|
||||
scsiDev.status = CHECK_CONDITION;
|
||||
scsiDev.sense.code = ILLEGAL_REQUEST;
|
||||
scsiDev.sense.asc = INVALID_FIELD_IN_PARAMETER_LIST;
|
||||
|
||||
out:
|
||||
scsiDev.phase = STATUS;
|
||||
}
|
||||
|
||||
int scsiModeCommand()
|
||||
{
|
||||
int commandHandled = 1;
|
||||
@ -343,16 +423,39 @@ int scsiModeCommand()
|
||||
{
|
||||
// MODE SELECT(6)
|
||||
int len = scsiDev.cdb[4];
|
||||
if (len == 0) len = 256;
|
||||
scsiDev.dataLen = len;
|
||||
scsiDev.phase = DATA_OUT;
|
||||
if (len == 0)
|
||||
{
|
||||
// If len == 0, then transfer no data. From the SCSI 2 standard:
|
||||
// A parameter list length of zero indicates that no data shall
|
||||
// be transferred. This condition shall not be considered as an
|
||||
// error.
|
||||
scsiDev.phase = STATUS;
|
||||
}
|
||||
else
|
||||
{
|
||||
scsiDev.dataLen = len;
|
||||
scsiDev.phase = DATA_OUT;
|
||||
scsiDev.postDataOutHook = doModeSelect;
|
||||
}
|
||||
}
|
||||
else if (command == 0x55)
|
||||
{
|
||||
// MODE SELECT(10)
|
||||
int allocLength = (((uint16) scsiDev.cdb[7]) << 8) + scsiDev.cdb[8];
|
||||
scsiDev.dataLen = allocLength;
|
||||
scsiDev.phase = DATA_OUT;
|
||||
if (allocLength == 0)
|
||||
{
|
||||
// If len == 0, then transfer no data. From the SCSI 2 standard:
|
||||
// A parameter list length of zero indicates that no data shall
|
||||
// be transferred. This condition shall not be considered as an
|
||||
// error.
|
||||
scsiDev.phase = STATUS;
|
||||
}
|
||||
else
|
||||
{
|
||||
scsiDev.dataLen = allocLength;
|
||||
scsiDev.phase = DATA_OUT;
|
||||
scsiDev.postDataOutHook = doModeSelect;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -178,7 +178,14 @@ static void process_DataIn()
|
||||
if ((scsiDev.dataPtr >= scsiDev.dataLen) &&
|
||||
(transfer.currentBlock == transfer.blocks))
|
||||
{
|
||||
enter_Status(GOOD);
|
||||
if (scsiDev.postDataOutHook != NULL)
|
||||
{
|
||||
scsiDev.postDataOutHook();
|
||||
}
|
||||
else
|
||||
{
|
||||
enter_Status(GOOD);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -439,6 +446,8 @@ static void scsiReset()
|
||||
scsiDev.sense.asc = NO_ADDITIONAL_SENSE_INFORMATION;
|
||||
scsiDiskReset();
|
||||
|
||||
scsiDev.postDataOutHook = NULL;
|
||||
|
||||
// Sleep to allow the bus to settle down a bit.
|
||||
// We must be ready again within the "Reset to selection time" of
|
||||
// 250ms.
|
||||
@ -463,6 +472,8 @@ static void enter_SelectionPhase()
|
||||
|
||||
transfer.blocks = 0;
|
||||
transfer.currentBlock = 0;
|
||||
|
||||
scsiDev.postDataOutHook = NULL;
|
||||
}
|
||||
|
||||
static void process_SelectionPhase()
|
||||
|
@ -62,6 +62,10 @@ typedef enum
|
||||
MSG_LINKED_COMMAND_COMPLETE_WITH_FLAG = 0x0B
|
||||
} SCSI_MESSAGE;
|
||||
|
||||
// Maximum value for bytes-per-sector.
|
||||
#define MAX_SECTOR_SIZE 2048
|
||||
#define MIN_SECTOR_SIZE 64
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t scsiIdMask;
|
||||
@ -78,7 +82,7 @@ typedef struct
|
||||
|
||||
int phase;
|
||||
|
||||
uint8 data[SCSI_BLOCK_SIZE];
|
||||
uint8 data[MAX_SECTOR_SIZE];
|
||||
int dataPtr; // Index into data, reset on [re]selection to savedDataPtr
|
||||
int savedDataPtr; // Index into data, initially 0.
|
||||
int dataLen;
|
||||
@ -103,6 +107,8 @@ typedef struct
|
||||
uint8 msgIn;
|
||||
uint8 msgOut;
|
||||
|
||||
void (*postDataOutHook)(void);
|
||||
|
||||
#ifdef MM_DEBUG
|
||||
uint8 cmdCount;
|
||||
uint8 selCount;
|
||||
|
@ -129,12 +129,14 @@ static void sdClearStatus()
|
||||
void sdPrepareRead()
|
||||
{
|
||||
uint8 v;
|
||||
uint32 len = (transfer.lba + transfer.currentBlock);
|
||||
uint32 scsiLBA = (transfer.lba + transfer.currentBlock);
|
||||
uint32 sdLBA = SCSISector2SD(scsiLBA);
|
||||
|
||||
if (!sdDev.ccs)
|
||||
{
|
||||
len = len * SCSI_BLOCK_SIZE;
|
||||
sdLBA = sdLBA * SD_SECTOR_SIZE;
|
||||
}
|
||||
v = sdCommandAndResponse(SD_READ_MULTIPLE_BLOCK, len);
|
||||
v = sdCommandAndResponse(SD_READ_MULTIPLE_BLOCK, sdLBA);
|
||||
if (v)
|
||||
{
|
||||
scsiDiskReset();
|
||||
@ -151,7 +153,7 @@ void sdPrepareRead()
|
||||
}
|
||||
}
|
||||
|
||||
static void doReadSector()
|
||||
static void doReadSector(uint32_t numBytes)
|
||||
{
|
||||
int prep, i, guard;
|
||||
|
||||
@ -184,12 +186,9 @@ static void doReadSector()
|
||||
// Don't do a bus settle delay if we're already in the correct phase.
|
||||
if (transfer.currentBlock == 0)
|
||||
{
|
||||
//scsiEnterPhase(DATA_OUT);
|
||||
//CyDelayUs(200);
|
||||
scsiEnterPhase(DATA_IN);
|
||||
//CyDelayUs(200); // TODO BLOODY SLOW INTERLEAVE
|
||||
}
|
||||
|
||||
|
||||
// Quickly seed the FIFO
|
||||
prep = 4;
|
||||
CY_SET_REG8(SDCard_TXDATA_PTR, 0xFF); // Put a byte in the FIFO
|
||||
@ -204,7 +203,7 @@ static void doReadSector()
|
||||
// We stream data straight from the SDCard fifos into the SCSI component
|
||||
// FIFO's. If the loop isn't fast enough, the transmit FIFO's will empty,
|
||||
// and performance will suffer. Every clock cycle counts.
|
||||
while (i < SCSI_BLOCK_SIZE && !scsiDev.resetFlag)
|
||||
while (i < numBytes && !scsiDev.resetFlag)
|
||||
{
|
||||
uint8_t sdRxStatus = CY_GET_REG8(SDCard_RX_STATUS_PTR);
|
||||
uint8_t scsiStatus = CY_GET_REG8(scsiTarget_StatusReg__STATUS_REG);
|
||||
@ -232,7 +231,25 @@ static void doReadSector()
|
||||
// "combined" SPIM TX and RX FIFOS to the individual FIFO size.
|
||||
// Unlike the SCSI component, SPIM doesn't check if there's room in
|
||||
// the output FIFO before starting to transmit.
|
||||
if ((prep - guard < 4) && (prep < SCSI_BLOCK_SIZE))
|
||||
if ((prep - guard < 4) && (prep < numBytes))
|
||||
{
|
||||
CY_SET_REG8(SDCard_TXDATA_PTR, 0xFF); // Put a byte in the FIFO
|
||||
prep++;
|
||||
}
|
||||
}
|
||||
|
||||
// Read and discard remaining bytes.
|
||||
while (i < SD_SECTOR_SIZE)
|
||||
{
|
||||
uint8_t sdRxStatus = CY_GET_REG8(SDCard_RX_STATUS_PTR);
|
||||
if(sdRxStatus & SDCard_STS_RX_FIFO_NOT_EMPTY)
|
||||
{
|
||||
CY_GET_REG8(SDCard_RXDATA_PTR);
|
||||
guard++;
|
||||
i++;
|
||||
}
|
||||
|
||||
if ((prep - guard < 4) && (prep < SD_SECTOR_SIZE))
|
||||
{
|
||||
CY_SET_REG8(SDCard_TXDATA_PTR, 0xFF); // Put a byte in the FIFO
|
||||
prep++;
|
||||
@ -241,21 +258,20 @@ static void doReadSector()
|
||||
|
||||
sdSpiByte(0xFF); // CRC
|
||||
sdSpiByte(0xFF); // CRC
|
||||
scsiDev.dataLen = SCSI_BLOCK_SIZE;
|
||||
scsiDev.dataPtr = SCSI_BLOCK_SIZE;
|
||||
scsiDev.dataLen = numBytes;
|
||||
scsiDev.dataPtr = numBytes;
|
||||
|
||||
while (SCSI_ReadPin(SCSI_In_ACK) && !scsiDev.resetFlag) {}
|
||||
}
|
||||
|
||||
void sdReadSectorSingle()
|
||||
static void doReadSectorSingle(uint32 sdBlock, int sdBytes)
|
||||
{
|
||||
uint8 v;
|
||||
uint32 len = (transfer.lba + transfer.currentBlock);
|
||||
if (!sdDev.ccs)
|
||||
{
|
||||
len = len * SCSI_BLOCK_SIZE;
|
||||
sdBlock = sdBlock * SD_SECTOR_SIZE;
|
||||
}
|
||||
v = sdCommandAndResponse(SD_READ_SINGLE_BLOCK, len);
|
||||
v = sdCommandAndResponse(SD_READ_SINGLE_BLOCK, sdBlock);
|
||||
if (v)
|
||||
{
|
||||
scsiDiskReset();
|
||||
@ -268,15 +284,47 @@ void sdReadSectorSingle()
|
||||
}
|
||||
else
|
||||
{
|
||||
doReadSector();
|
||||
doReadSector(sdBytes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void sdReadSectorSingle()
|
||||
{
|
||||
uint32 scsiLBA = (transfer.lba + transfer.currentBlock);
|
||||
uint32 sdLBA = SCSISector2SD(scsiLBA);
|
||||
|
||||
int sdSectors = SDSectorsPerSCSISector();
|
||||
int i;
|
||||
for (i = 0; (i < sdSectors - 1) && (scsiDev.status != CHECK_CONDITION); ++i)
|
||||
{
|
||||
doReadSectorSingle(sdLBA + i, SD_SECTOR_SIZE);
|
||||
}
|
||||
|
||||
if (scsiDev.status != CHECK_CONDITION)
|
||||
{
|
||||
int remaining = config->bytesPerSector % SD_SECTOR_SIZE;
|
||||
if (remaining == 0) remaining = SD_SECTOR_SIZE; // Full sector needed.
|
||||
doReadSectorSingle(sdLBA + i, remaining);
|
||||
}
|
||||
}
|
||||
|
||||
void sdReadSectorMulti()
|
||||
{
|
||||
// Pre: sdPrepareRead called.
|
||||
|
||||
doReadSector();
|
||||
int sdSectors = SDSectorsPerSCSISector();
|
||||
int i;
|
||||
for (i = 0; (i < sdSectors - 1) && (scsiDev.status != CHECK_CONDITION); ++i)
|
||||
{
|
||||
doReadSector(SD_SECTOR_SIZE);
|
||||
}
|
||||
|
||||
if (scsiDev.status != CHECK_CONDITION)
|
||||
{
|
||||
int remaining = config->bytesPerSector % SD_SECTOR_SIZE;
|
||||
if (remaining == 0) remaining = SD_SECTOR_SIZE; // Full sector needed.
|
||||
doReadSector(remaining);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -329,7 +377,7 @@ static void sdWaitWriteBusy()
|
||||
} while (val != 0xFF);
|
||||
}
|
||||
|
||||
int sdWriteSector()
|
||||
static int doWriteSector(uint32_t numBytes)
|
||||
{
|
||||
int prep, i, guard;
|
||||
int result, maxWait;
|
||||
@ -351,7 +399,7 @@ int sdWriteSector()
|
||||
// We stream data straight from the SCSI fifos into the SPIM component
|
||||
// FIFO's. If the loop isn't fast enough, the transmit FIFO's will empty,
|
||||
// and performance will suffer. Every clock cycle counts.
|
||||
while (i < SCSI_BLOCK_SIZE)
|
||||
while (i < numBytes && !scsiDev.resetFlag)
|
||||
{
|
||||
uint8_t sdRxStatus = CY_GET_REG8(SDCard_RX_STATUS_PTR);
|
||||
uint8_t scsiStatus = CY_GET_REG8(scsiTarget_StatusReg__STATUS_REG);
|
||||
@ -375,7 +423,7 @@ int sdWriteSector()
|
||||
++i;
|
||||
}
|
||||
|
||||
if (prep < SCSI_BLOCK_SIZE &&
|
||||
if (prep < numBytes &&
|
||||
(scsiDev.resetFlag || (scsiStatus & 1)) // SCSI TX FIFO NOT FULL
|
||||
)
|
||||
{
|
||||
@ -385,6 +433,25 @@ int sdWriteSector()
|
||||
}
|
||||
}
|
||||
|
||||
// Write remaining bytes as 0x00
|
||||
while (i < SD_SECTOR_SIZE)
|
||||
{
|
||||
uint8_t sdRxStatus = CY_GET_REG8(SDCard_RX_STATUS_PTR);
|
||||
|
||||
if(guard - i < 4)
|
||||
{
|
||||
CY_SET_REG8(SDCard_TXDATA_PTR, 0x00);
|
||||
guard++;
|
||||
}
|
||||
|
||||
// Byte has been sent out the SPIM interface.
|
||||
if (sdRxStatus & SDCard_STS_RX_FIFO_NOT_EMPTY)
|
||||
{
|
||||
CY_GET_REG8(SDCard_RXDATA_PTR);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
sdSpiByte(0x00); // CRC
|
||||
sdSpiByte(0x00); // CRC
|
||||
|
||||
@ -438,6 +505,26 @@ int sdWriteSector()
|
||||
return result;
|
||||
}
|
||||
|
||||
int sdWriteSector()
|
||||
{
|
||||
int result = 1;
|
||||
// Pre: sdPrepareWrite called.
|
||||
int sdSectors = SDSectorsPerSCSISector();
|
||||
int i;
|
||||
for (i = 0; result && (i < sdSectors - 1) && (scsiDev.status != CHECK_CONDITION); ++i)
|
||||
{
|
||||
result = doWriteSector(SD_SECTOR_SIZE);
|
||||
}
|
||||
|
||||
if (result && scsiDev.status != CHECK_CONDITION)
|
||||
{
|
||||
int remaining = config->bytesPerSector % SD_SECTOR_SIZE;
|
||||
if (remaining == 0) remaining = SD_SECTOR_SIZE; // Full sector needed.
|
||||
result = doWriteSector(remaining);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void sdCompleteWrite()
|
||||
{
|
||||
transfer.inProgress = 0;
|
||||
@ -565,7 +652,7 @@ static int sdReadCSD()
|
||||
uint32 c_size = (((((uint32)buf[6]) & 0x3) << 16) | (((uint32)buf[7]) << 8) | buf[8]) >> 6;
|
||||
uint32 c_mult = (((((uint32)buf[9]) & 0x3) << 8) | ((uint32)buf[0xa])) >> 7;
|
||||
uint32 sectorSize = buf[5] & 0x0F;
|
||||
sdDev.capacity = ((c_size+1) * ((uint64)1 << (c_mult+2)) * ((uint64)1 << sectorSize)) / SCSI_BLOCK_SIZE;
|
||||
sdDev.capacity = ((c_size+1) * ((uint64)1 << (c_mult+2)) * ((uint64)1 << sectorSize)) / SD_SECTOR_SIZE;
|
||||
}
|
||||
else if ((buf[0] >> 6) == 0x01)
|
||||
{
|
||||
@ -622,7 +709,7 @@ int sdInit()
|
||||
|
||||
// This command will be ignored if sdDev.ccs is set.
|
||||
// SDHC and SDXC are always 512bytes.
|
||||
v = sdCRCCommandAndResponse(SD_SET_BLOCKLEN, SCSI_BLOCK_SIZE); //Force sector size
|
||||
v = sdCRCCommandAndResponse(SD_SET_BLOCKLEN, SD_SECTOR_SIZE); //Force sector size
|
||||
if(v){goto bad;}
|
||||
v = sdCRCCommandAndResponse(SD_CRC_ON_OFF, 0); //crc off
|
||||
if(v){goto bad;}
|
||||
@ -671,23 +758,24 @@ out:
|
||||
|
||||
void sdPrepareWrite()
|
||||
{
|
||||
uint32 len;
|
||||
uint8 v;
|
||||
|
||||
// Set the number of blocks to pre-erase by the multiple block write command
|
||||
// We don't care about the response - if the command is not accepted, writes
|
||||
// will just be a bit slower.
|
||||
// Max 22bit parameter.
|
||||
uint32 blocks = transfer.blocks > 0x7FFFFF ? 0x7FFFFF : transfer.blocks;
|
||||
uint32_t sdBlocks = transfer.blocks * SDSectorsPerSCSISector();
|
||||
uint32 blocks = sdBlocks > 0x7FFFFF ? 0x7FFFFF : sdBlocks;
|
||||
sdCommandAndResponse(SD_APP_CMD, 0);
|
||||
sdCommandAndResponse(SD_APP_SET_WR_BLK_ERASE_COUNT, blocks);
|
||||
|
||||
len = (transfer.lba + transfer.currentBlock);
|
||||
uint32 scsiLBA = (transfer.lba + transfer.currentBlock);
|
||||
uint32 sdLBA = SCSISector2SD(scsiLBA);
|
||||
if (!sdDev.ccs)
|
||||
{
|
||||
len = len * SCSI_BLOCK_SIZE;
|
||||
sdLBA = sdLBA * SD_SECTOR_SIZE;
|
||||
}
|
||||
v = sdCommandAndResponse(25, len);
|
||||
v = sdCommandAndResponse(25, sdLBA);
|
||||
if (v)
|
||||
{
|
||||
scsiDiskReset();
|
||||
|
@ -17,6 +17,8 @@
|
||||
#ifndef SD_H
|
||||
#define SD_H
|
||||
|
||||
#define SD_SECTOR_SIZE 512
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SD_GO_IDLE_STATE = 0,
|
||||
|
@ -45,7 +45,8 @@ enum
|
||||
PARAM_APPLE,
|
||||
PARAM_VENDOR,
|
||||
PARAM_PRODID,
|
||||
PARAM_REV
|
||||
PARAM_REV,
|
||||
PARAM_BYTESPERSECTOR
|
||||
};
|
||||
|
||||
// Must be consistent with the structure defined in the SCSI2SD config.h header.
|
||||
@ -59,7 +60,9 @@ typedef struct __attribute((packed))
|
||||
uint8_t enableParity;
|
||||
uint8_t enableUnitAttention;
|
||||
uint8_t reserved1; // Unused. Ensures maxBlocks is aligned.
|
||||
uint32_t maxBlocks;
|
||||
uint32_t maxSectors;
|
||||
uint16_t bytesPerSector;
|
||||
|
||||
|
||||
// Pad to 64 bytes, which is what we can fit into a USB HID packet.
|
||||
char reserved[28];
|
||||
@ -74,10 +77,11 @@ static void printConfig(ConfigPacket* packet)
|
||||
printf("\n");
|
||||
printf("Parity Checking:\t\t%s\n", packet->enableParity ? "enabled" : "disabled");
|
||||
printf("Unit Attention Condition:\t%s\n", packet->enableUnitAttention ? "enabled" : "disabled");
|
||||
if (packet->maxBlocks)
|
||||
printf("Bytes per sector:\t\t%d\n", packet->bytesPerSector);
|
||||
if (packet->maxSectors)
|
||||
{
|
||||
char sizeBuf[64];
|
||||
uint64_t maxBytes = packet->maxBlocks * (uint64_t) 512;
|
||||
uint64_t maxBytes = packet->maxSectors * (uint64_t) packet->bytesPerSector;
|
||||
if (maxBytes > (1024*1024*1024))
|
||||
{
|
||||
sprintf(sizeBuf, "%.02fGB", maxBytes / (1024.0*1024.0*1024.0));
|
||||
@ -95,7 +99,7 @@ static void printConfig(ConfigPacket* packet)
|
||||
sprintf(sizeBuf, "%" PRIu64 " bytes", maxBytes);
|
||||
}
|
||||
|
||||
printf("Maximum Size:\t\t\t%s (%d blocks)\n", sizeBuf, packet->maxBlocks);
|
||||
printf("Maximum Size:\t\t\t%s (%d sectors)\n", sizeBuf, packet->maxSectors);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -117,7 +121,8 @@ static int readConfig(hid_device* handle, ConfigPacket* packet)
|
||||
}
|
||||
|
||||
memcpy(packet, buf, result);
|
||||
packet->maxBlocks = ntohl(packet->maxBlocks);
|
||||
packet->maxSectors = ntohl(packet->maxSectors);
|
||||
packet->bytesPerSector = ntohs(packet->bytesPerSector);
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -127,9 +132,11 @@ static int writeConfig(hid_device* handle, ConfigPacket* packet)
|
||||
unsigned char buf[1 + sizeof(ConfigPacket)];
|
||||
buf[0] = 0; // report ID
|
||||
|
||||
packet->maxBlocks = htonl(packet->maxBlocks);
|
||||
packet->maxSectors = htonl(packet->maxSectors);
|
||||
packet->bytesPerSector = htons(packet->bytesPerSector);
|
||||
memcpy(buf + 1, packet, sizeof(ConfigPacket));
|
||||
packet->maxBlocks = ntohl(packet->maxBlocks);
|
||||
packet->maxSectors = ntohl(packet->maxSectors);
|
||||
packet->bytesPerSector = ntohs(packet->bytesPerSector);
|
||||
|
||||
int result = hid_write(handle, buf, sizeof(buf));
|
||||
|
||||
@ -159,6 +166,8 @@ static void usage()
|
||||
printf("\t\tEach block is 512 bytes. The maximum possible size is 2TB.\n");
|
||||
printf("\t\tThe reported size will be the lower of this value and the SD\n");
|
||||
printf("\t\tcard size. 0 disables the limit.\n\n");
|
||||
printf("--sector={64-2048}\n\t\tSet the bytes-per-sector. Normally 512 bytes.\n");
|
||||
printf("\t\tCan also be set with a SCSI MODE SELECT command.\n\n");
|
||||
printf("--apple\t\tSet the vendor, product ID and revision fields to simulate an \n");
|
||||
printf("\t\tapple-suppled disk. Provides support for the Apple Drive Setup\n");
|
||||
printf("\t\tutility.\n\n");
|
||||
@ -250,6 +259,9 @@ int main(int argc, char* argv[])
|
||||
{
|
||||
"rev", required_argument, NULL, PARAM_REV
|
||||
},
|
||||
{
|
||||
"sector", required_argument, NULL, PARAM_BYTESPERSECTOR
|
||||
},
|
||||
{
|
||||
NULL, 0, NULL, 0
|
||||
}
|
||||
@ -295,11 +307,11 @@ int main(int argc, char* argv[])
|
||||
|
||||
case PARAM_MAXBLOCKS:
|
||||
{
|
||||
int64_t maxBlocks = -1;
|
||||
if (sscanf(optarg, "%" PRId64, &maxBlocks) == 1 &&
|
||||
maxBlocks >= 0 && maxBlocks <= UINT32_MAX)
|
||||
int64_t maxSectors = -1;
|
||||
if (sscanf(optarg, "%" PRId64, &maxSectors) == 1 &&
|
||||
maxSectors >= 0 && maxSectors <= UINT32_MAX)
|
||||
{
|
||||
packet.maxBlocks = maxBlocks;
|
||||
packet.maxSectors = maxSectors;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -329,6 +341,20 @@ int main(int argc, char* argv[])
|
||||
memcpy(packet.revision, optarg, MIN(strlen(optarg), 4));
|
||||
break;
|
||||
|
||||
case PARAM_BYTESPERSECTOR:
|
||||
{
|
||||
int64_t bytesPerSector = -1;
|
||||
if (sscanf(optarg, "%" PRId64, &bytesPerSector) == 1 &&
|
||||
bytesPerSector >= 64 && bytesPerSector <= 2048)
|
||||
{
|
||||
packet.bytesPerSector = bytesPerSector;
|
||||
}
|
||||
else
|
||||
{
|
||||
usage();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '?':
|
||||
usage();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user