From c8389e5ff70b709d3aa0f685541bd94da830c446 Mon Sep 17 00:00:00 2001 From: Michael McMaster Date: Fri, 20 Dec 2013 21:54:41 +1000 Subject: [PATCH] Read performance improvements - More than 2x improvement in read performance, to 930KB/sec - Added compatibility for the Keil ARM compiler. Provides an additional 1.5% performance improvement. --- STATUS | 2 - readme.txt | 5 +- software/SCSI2SD/SCSI2SD.cydsn/SCSI2SD.cyfit | Bin 228955 -> 228996 bytes software/SCSI2SD/SCSI2SD.cydsn/SCSI2SD.cyprj | 348 ++++++++++++++++++- software/SCSI2SD/SCSI2SD.cydsn/bits.h | 2 +- software/SCSI2SD/SCSI2SD.cydsn/blinky.c | 2 +- software/SCSI2SD/SCSI2SD.cydsn/config.c | 16 +- software/SCSI2SD/SCSI2SD.cydsn/config.h | 4 +- software/SCSI2SD/SCSI2SD.cydsn/diagnostic.c | 10 +- software/SCSI2SD/SCSI2SD.cydsn/diagnostic.h | 6 +- software/SCSI2SD/SCSI2SD.cydsn/disk.c | 4 +- software/SCSI2SD/SCSI2SD.cydsn/disk.h | 8 +- software/SCSI2SD/SCSI2SD.cydsn/geometry.c | 5 +- software/SCSI2SD/SCSI2SD.cydsn/inquiry.c | 3 +- software/SCSI2SD/SCSI2SD.cydsn/inquiry.h | 2 +- software/SCSI2SD/SCSI2SD.cydsn/led.h | 2 +- software/SCSI2SD/SCSI2SD.cydsn/loopback.c | 5 +- software/SCSI2SD/SCSI2SD.cydsn/main.c | 5 - software/SCSI2SD/SCSI2SD.cydsn/mode.c | 4 +- software/SCSI2SD/SCSI2SD.cydsn/mode.h | 2 +- software/SCSI2SD/SCSI2SD.cydsn/scsi.c | 92 +++-- software/SCSI2SD/SCSI2SD.cydsn/scsi.h | 6 +- software/SCSI2SD/SCSI2SD.cydsn/scsiPhy.c | 3 +- software/SCSI2SD/SCSI2SD.cydsn/scsiPhy.h | 4 +- software/SCSI2SD/SCSI2SD.cydsn/sd.c | 143 +++++--- software/SCSI2SD/SCSI2SD.cydsn/sd.h | 14 +- 26 files changed, 544 insertions(+), 153 deletions(-) diff --git a/STATUS b/STATUS index 6e099b1..7074591 100644 --- a/STATUS +++ b/STATUS @@ -1,4 +1,2 @@ -- DMA is not used for SPI transfers - - Potential for large performance improvement. - Parity checking not implemented for the PSoC Datapath implementation diff --git a/readme.txt b/readme.txt index f2e9811..8236382 100644 --- a/readme.txt +++ b/readme.txt @@ -43,7 +43,7 @@ Performance As currently implemented: -Sequential read: 424kb/sec Sequential write: 414kb/sec +Sequential read: 930kb/sec Sequential write: 414kb/sec Tested with a 16GB class 10 SD card, via the commands: @@ -53,9 +53,6 @@ Tested with a 16GB class 10 SD card, via the commands: # READ TEST sudo dd bs=8192 count=100 if=/dev/sdX of=/dev/null -I am working on updating the SD card communication to use DMA, to allow simultaneous use of the SD and SCSI interfaces. I expect the performance to reach 1Mb/sec. - - Compatibility Tested with Linux (current), Apple Macintosh System 7.5.3 on LC-III, and LC-475 diff --git a/software/SCSI2SD/SCSI2SD.cydsn/SCSI2SD.cyfit b/software/SCSI2SD/SCSI2SD.cydsn/SCSI2SD.cyfit index 2ffb942d2710a570a3718964665c655df06903eb..c6d5f47658aab450a6e36b6c7270726e7811c97a 100755 GIT binary patch delta 25684 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 HwcP&zsRPGS delta 25658 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 b=MnTWDOS)bk>J!@Xxm8WHOgmUBwzhM?}Fe` diff --git a/software/SCSI2SD/SCSI2SD.cydsn/SCSI2SD.cyprj b/software/SCSI2SD/SCSI2SD.cydsn/SCSI2SD.cyprj index d2665e2..9bcf221 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/SCSI2SD.cyprj +++ b/software/SCSI2SD/SCSI2SD.cydsn/SCSI2SD.cyprj @@ -26,13 +26,6 @@ - - - - - - - @@ -204,13 +197,6 @@ - - - - - - - @@ -2584,6 +2570,338 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -2591,7 +2909,7 @@ - + diff --git a/software/SCSI2SD/SCSI2SD.cydsn/bits.h b/software/SCSI2SD/SCSI2SD.cydsn/bits.h index 7369ce6..45adfcb 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/bits.h +++ b/software/SCSI2SD/SCSI2SD.cydsn/bits.h @@ -24,4 +24,4 @@ extern const uint8 Lookup_OddParity[256]; uint8 countBits(uint8 value); -#endif \ No newline at end of file +#endif diff --git a/software/SCSI2SD/SCSI2SD.cydsn/blinky.c b/software/SCSI2SD/SCSI2SD.cydsn/blinky.c index 676ec5d..4d9d7ae 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/blinky.c +++ b/software/SCSI2SD/SCSI2SD.cydsn/blinky.c @@ -28,4 +28,4 @@ void scsi2sd_test_blink(void) LED1_Write(1); CyDelay(250); // ms } -} \ No newline at end of file +} diff --git a/software/SCSI2SD/SCSI2SD.cydsn/config.c b/software/SCSI2SD/SCSI2SD.cydsn/config.c index fc1809d..674d752 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/config.c +++ b/software/SCSI2SD/SCSI2SD.cydsn/config.c @@ -92,6 +92,9 @@ static void saveConfig() void configInit() { + int shadowRows, shadowBytes; + uint8* eeprom = (uint8*)CYDEV_EE_BASE; + // We could map cfgPtr directly into the EEPROM memory, // but that would waste power. Copy it to RAM then turn off // the EEPROM. @@ -99,9 +102,9 @@ void configInit() CyDelayUs(5); // 5us to start per datasheet. // Check magic - int shadowRows = (sizeof(shadow) / CYDEV_EEPROM_ROW_SIZE) + 1; - int shadowBytes = CYDEV_EEPROM_ROW_SIZE * shadowRows; - uint8* eeprom = (uint8*)CYDEV_EE_BASE; + shadowRows = (sizeof(shadow) / CYDEV_EEPROM_ROW_SIZE) + 1; + shadowBytes = CYDEV_EEPROM_ROW_SIZE * shadowRows; + if (memcmp(eeprom + shadowBytes, magic, sizeof(magic))) { saveConfig(); @@ -143,9 +146,12 @@ void configPoll() if(USBFS_GetEPState(USB_EP_OUT) == USBFS_OUT_BUFFER_FULL) { - ledOn(); + int byteCount; + + ledOn(); + // The host sent us some data! - int byteCount = USBFS_GetEPCount(USB_EP_OUT); + byteCount = USBFS_GetEPCount(USB_EP_OUT); // Assume that byteCount <= sizeof(shadow). // shadow should be padded out to 64bytes, which is the largest diff --git a/software/SCSI2SD/SCSI2SD.cydsn/config.h b/software/SCSI2SD/SCSI2SD.cydsn/config.h index 4cb26ef..6b048bd 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/config.h +++ b/software/SCSI2SD/SCSI2SD.cydsn/config.h @@ -36,7 +36,7 @@ typedef struct extern Config* config; -void configInit(); -void configPoll(); +void configInit(void); +void configPoll(void); #endif diff --git a/software/SCSI2SD/SCSI2SD.cydsn/diagnostic.c b/software/SCSI2SD/SCSI2SD.cydsn/diagnostic.c index 3f27b83..f6899cd 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/diagnostic.c +++ b/software/SCSI2SD/SCSI2SD.cydsn/diagnostic.c @@ -122,11 +122,13 @@ void scsiReceiveDiagnostic() scsiDev.dataLen = allocLength; } - uint8 lun = scsiDev.cdb[1] >> 5; - // Set the first byte to indicate LUN presence. - if (lun) // We only support lun 0 { - scsiDev.data[0] = 0x7F; + uint8 lun = scsiDev.cdb[1] >> 5; + // Set the first byte to indicate LUN presence. + if (lun) // We only support lun 0 + { + scsiDev.data[0] = 0x7F; + } } } diff --git a/software/SCSI2SD/SCSI2SD.cydsn/diagnostic.h b/software/SCSI2SD/SCSI2SD.cydsn/diagnostic.h index cec3f42..e3f09be 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/diagnostic.h +++ b/software/SCSI2SD/SCSI2SD.cydsn/diagnostic.h @@ -17,7 +17,7 @@ #ifndef DIAGNOSTIC_H #define DIAGNOSTIC_H -void scsiSendDiagnostic(); -void scsiReceiveDiagnostic(); +void scsiSendDiagnostic(void); +void scsiReceiveDiagnostic(void); -#endif \ No newline at end of file +#endif diff --git a/software/SCSI2SD/SCSI2SD.cydsn/disk.c b/software/SCSI2SD/SCSI2SD.cydsn/disk.c index b5b3fd8..427112c 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/disk.c +++ b/software/SCSI2SD/SCSI2SD.cydsn/disk.c @@ -33,7 +33,7 @@ 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)) @@ -420,10 +420,10 @@ void scsiDiskInit() if (SD_CD_Read() == 1) { + int retry; blockDev.state = blockDev.state | DISK_PRESENT; // Wait up to 5 seconds for the SD card to wake up. - int retry; for (retry = 0; retry < 5; ++retry) { if (doSdInit()) diff --git a/software/SCSI2SD/SCSI2SD.cydsn/disk.h b/software/SCSI2SD/SCSI2SD.cydsn/disk.h index 5d2d04d..2133279 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/disk.h +++ b/software/SCSI2SD/SCSI2SD.cydsn/disk.h @@ -51,9 +51,9 @@ typedef struct extern BlockDevice blockDev; extern Transfer transfer; -void scsiDiskInit(); -void scsiDiskReset(); -void scsiDiskPoll(); -int scsiDiskCommand(); +void scsiDiskInit(void); +void scsiDiskReset(void); +void scsiDiskPoll(void); +int scsiDiskCommand(void); #endif diff --git a/software/SCSI2SD/SCSI2SD.cydsn/geometry.c b/software/SCSI2SD/SCSI2SD.cydsn/geometry.c index 5665ae5..f42152b 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/geometry.c +++ b/software/SCSI2SD/SCSI2SD.cydsn/geometry.c @@ -91,7 +91,7 @@ uint64 scsiByteAddress(int format, const uint8* addr) } break; default: - result = -1; + result = (uint64) -1; } return result; @@ -123,10 +123,11 @@ void scsiSaveByteAddress(int format, uint64 byteAddr, uint8* buf) uint32 cyl; uint8 head; uint32 sector; + uint32 bytes; LBA2CHS(lba, &cyl, &head, §or); - uint32 bytes = sector * SCSI_SECTOR_SIZE + byteOffset; + bytes = sector * SCSI_SECTOR_SIZE + byteOffset; buf[0] = cyl >> 16; buf[1] = cyl >> 8; diff --git a/software/SCSI2SD/SCSI2SD.cydsn/inquiry.c b/software/SCSI2SD/SCSI2SD.cydsn/inquiry.c index bd7f7cd..9cc0f23 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/inquiry.c +++ b/software/SCSI2SD/SCSI2SD.cydsn/inquiry.c @@ -98,8 +98,9 @@ void scsiInquiry() } else { + uint8* out; memcpy(scsiDev.data, StandardResponse, sizeof(StandardResponse)); - uint8* out = scsiDev.data + sizeof(StandardResponse); + out = scsiDev.data + sizeof(StandardResponse); memcpy(out, config->vendor, sizeof(config->vendor)); out += sizeof(config->vendor); memcpy(out, config->prodId, sizeof(config->prodId)); diff --git a/software/SCSI2SD/SCSI2SD.cydsn/inquiry.h b/software/SCSI2SD/SCSI2SD.cydsn/inquiry.h index d068796..c707b27 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/inquiry.h +++ b/software/SCSI2SD/SCSI2SD.cydsn/inquiry.h @@ -17,6 +17,6 @@ #ifndef INQUIRY_H #define INQUIRY_H -void scsiInquiry(); +void scsiInquiry(void); #endif diff --git a/software/SCSI2SD/SCSI2SD.cydsn/led.h b/software/SCSI2SD/SCSI2SD.cydsn/led.h index 48e1b83..e021891 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/led.h +++ b/software/SCSI2SD/SCSI2SD.cydsn/led.h @@ -22,4 +22,4 @@ #define ledOn() LED1_Write(0) #define ledOff() LED1_Write(1) -#endif \ No newline at end of file +#endif diff --git a/software/SCSI2SD/SCSI2SD.cydsn/loopback.c b/software/SCSI2SD/SCSI2SD.cydsn/loopback.c index ecddd64..b64405a 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/loopback.c +++ b/software/SCSI2SD/SCSI2SD.cydsn/loopback.c @@ -72,11 +72,12 @@ static int test_data_10MHz(void) int i; for (i = 0; i < 100; ++i) { + uint8 dbx; // We write using Active High SCSI_Out_DBx_Write(0xFF); CyDelayCycles(3); // And expect an Active Low response. - uint8 dbx = SCSI_In_DBx_Read(); + dbx = SCSI_In_DBx_Read(); result = result && (dbx == 0); // We write using Active High @@ -145,4 +146,4 @@ void scsi2sd_test_loopback(void) { test_success(); } -} \ No newline at end of file +} diff --git a/software/SCSI2SD/SCSI2SD.cydsn/main.c b/software/SCSI2SD/SCSI2SD.cydsn/main.c index bb0deb6..4ae3d96 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/main.c +++ b/software/SCSI2SD/SCSI2SD.cydsn/main.c @@ -17,7 +17,6 @@ #include "device.h" #include "blinky.h" -#include "loopback.h" #include "scsi.h" #include "scsiPhy.h" #include "config.h" @@ -38,10 +37,6 @@ int main() // Set interrupt handlers. scsiPhyInit(); - // Loopback test requires the interrupt handers. - // Will not return if uncommented. - // scsi2sd_test_loopback(); - configInit(); scsiInit(); diff --git a/software/SCSI2SD/SCSI2SD.cydsn/mode.c b/software/SCSI2SD/SCSI2SD.cydsn/mode.c index cb6d7f4..99e89b1 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/mode.c +++ b/software/SCSI2SD/SCSI2SD.cydsn/mode.c @@ -133,6 +133,8 @@ static void doModeSense( } else { + int pageFound = 1; + ////////////// Mode Parameter Header //////////////////////////////////// @@ -195,8 +197,6 @@ static void doModeSense( scsiDev.data[idx++] = SCSI_BLOCK_SIZE & 0xFF; } - int pageFound = 1; - switch (pageCode) { case 0x3F: diff --git a/software/SCSI2SD/SCSI2SD.cydsn/mode.h b/software/SCSI2SD/SCSI2SD.cydsn/mode.h index c097807..819b1f5 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/mode.h +++ b/software/SCSI2SD/SCSI2SD.cydsn/mode.h @@ -17,6 +17,6 @@ #ifndef MODE_H #define MODE_H -int scsiModeCommand(); +int scsiModeCommand(void); #endif diff --git a/software/SCSI2SD/SCSI2SD.cydsn/scsi.c b/software/SCSI2SD/SCSI2SD.cydsn/scsi.c index 8b69da0..245bf1d 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/scsi.c +++ b/software/SCSI2SD/SCSI2SD.cydsn/scsi.c @@ -32,19 +32,19 @@ // Global SCSI device state. ScsiDevice scsiDev; -static void enter_SelectionPhase(); -static void process_SelectionPhase(); -static void enter_BusFree(); +static void enter_SelectionPhase(void); +static void process_SelectionPhase(void); +static void enter_BusFree(void); static void enter_MessageIn(uint8 message); -static void process_MessageIn(); +static void process_MessageIn(void); static void enter_Status(uint8 status); -static void process_Status(); +static void process_Status(void); static void enter_DataIn(int len); -static void process_DataIn(); -static void process_DataOut(); -static void process_Command(); +static void process_DataIn(void); +static void process_DataOut(void); +static void process_Command(void); -static void doReserveRelease(); +static void doReserveRelease(void); static void enter_BusFree() { @@ -109,17 +109,20 @@ static void enter_DataIn(int len) static void process_DataIn() { + uint32 len; + if (scsiDev.dataLen > sizeof(scsiDev.data)) { scsiDev.dataLen = sizeof(scsiDev.data); } - scsiEnterPhase(DATA_IN); - - uint32 len = scsiDev.dataLen - scsiDev.dataPtr; - scsiWrite(scsiDev.data + scsiDev.dataPtr, len); - scsiDev.dataPtr += len; - + len = scsiDev.dataLen - scsiDev.dataPtr; + if (len > 0) + { + scsiEnterPhase(DATA_IN); + scsiWrite(scsiDev.data + scsiDev.dataPtr, len); + scsiDev.dataPtr += len; + } if ((scsiDev.dataPtr >= scsiDev.dataLen) && (transfer.currentBlock == transfer.blocks)) @@ -130,24 +133,29 @@ static void process_DataIn() static void process_DataOut() { + uint32 len; + if (scsiDev.dataLen > sizeof(scsiDev.data)) { scsiDev.dataLen = sizeof(scsiDev.data); } - scsiEnterPhase(DATA_OUT); - scsiDev.parityError = 0; - uint32 len = scsiDev.dataLen - scsiDev.dataPtr; - scsiRead(scsiDev.data + scsiDev.dataPtr, len); - scsiDev.dataPtr += len; - - // TODO re-implement parity checking - if (0 && scsiDev.parityError && config->enableParity) + len = scsiDev.dataLen - scsiDev.dataPtr; + if (len > 0) { - scsiDev.sense.code = ABORTED_COMMAND; - scsiDev.sense.asc = SCSI_PARITY_ERROR; - enter_Status(CHECK_CONDITION); + scsiEnterPhase(DATA_OUT); + + scsiRead(scsiDev.data + scsiDev.dataPtr, len); + scsiDev.dataPtr += len; + + // TODO re-implement parity checking + if (0 && scsiDev.parityError && config->enableParity) + { + scsiDev.sense.code = ABORTED_COMMAND; + scsiDev.sense.asc = SCSI_PARITY_ERROR; + enter_Status(CHECK_CONDITION); + } } if ((scsiDev.dataPtr >= scsiDev.dataLen) && @@ -160,18 +168,23 @@ static void process_DataOut() static const uint8 CmdGroupBytes[8] = {6, 10, 10, 6, 6, 12, 6, 6}; static void process_Command() { + int group; + int cmdSize; + uint8 command; + uint8 lun; + scsiEnterPhase(COMMAND); scsiDev.parityError = 0; memset(scsiDev.cdb, 0, sizeof(scsiDev.cdb)); scsiDev.cdb[0] = scsiReadByte(); - int group = scsiDev.cdb[0] >> 5; - int cmdSize = CmdGroupBytes[group]; + group = scsiDev.cdb[0] >> 5; + cmdSize = CmdGroupBytes[group]; scsiRead(scsiDev.cdb + 1, cmdSize - 1); - uint8 command = scsiDev.cdb[0]; - uint8 lun = scsiDev.cdb[1] >> 5; + command = scsiDev.cdb[0]; + lun = scsiDev.cdb[1] >> 5; if (scsiDev.parityError) { @@ -395,15 +408,17 @@ static void process_SelectionPhase() // Save our initiator now that we're no longer in a time-critical // section. - uint8 initiatorMask = mask ^ scsiDev.scsiIdMask; - scsiDev.initiatorId = 0; - int i; - for (i = 0; i < 8; ++i) { - if (initiatorMask & (1 << i)) + int i; + uint8 initiatorMask = mask ^ scsiDev.scsiIdMask; + scsiDev.initiatorId = 0; + for (i = 0; i < 8; ++i) { - scsiDev.initiatorId = i; - break; + if (initiatorMask & (1 << i)) + { + scsiDev.initiatorId = i; + break; + } } } @@ -515,10 +530,11 @@ static void process_MessageOut() } else if (scsiDev.msgOut == 0x01) { + int i; + // Extended message. int msgLen = scsiReadByte(); if (msgLen == 0) msgLen = 256; - int i; for (i = 0; i < msgLen && !scsiDev.resetFlag; ++i) { // Discard bytes. diff --git a/software/SCSI2SD/SCSI2SD.cydsn/scsi.h b/software/SCSI2SD/SCSI2SD.cydsn/scsi.h index 91c0fa6..68ec8bd 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/scsi.h +++ b/software/SCSI2SD/SCSI2SD.cydsn/scsi.h @@ -89,8 +89,8 @@ typedef struct // Only let the reserved initiator talk to us. // A 3rd party may be sending the RESERVE/RELEASE commands int initiatorId; // 0 -> 7. Set during the selection phase. - int reservedId;; // 0 -> 7 if reserved. -1 if not reserved. - int reserverId;; // 0 -> 7 if reserved. -1 if not reserved. + int reservedId; // 0 -> 7 if reserved. -1 if not reserved. + int reserverId; // 0 -> 7 if reserved. -1 if not reserved. // SCSI_STATUS value. // Change to SCSI_STATUS_CHECK_CONDITION when setting a SENSE value @@ -106,7 +106,7 @@ typedef struct extern ScsiDevice scsiDev; -void scsiInit(); +void scsiInit(void); void scsiPoll(void); diff --git a/software/SCSI2SD/SCSI2SD.cydsn/scsiPhy.c b/software/SCSI2SD/SCSI2SD.cydsn/scsiPhy.c index 4819615..d6d1a91 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/scsiPhy.c +++ b/software/SCSI2SD/SCSI2SD.cydsn/scsiPhy.c @@ -52,8 +52,7 @@ uint8 scsiReadByte(void) while (!(CY_GET_REG8(scsiTarget_StatusReg__STATUS_REG) & 1)) {} CY_SET_REG8(scsiTarget_datapath__F0_REG, 0); while (!(CY_GET_REG8(scsiTarget_StatusReg__STATUS_REG) & 2)) {} - uint8 value = CY_GET_REG8(scsiTarget_datapath__F1_REG); - return value; + return CY_GET_REG8(scsiTarget_datapath__F1_REG); } void scsiRead(uint8* data, uint32 count) diff --git a/software/SCSI2SD/SCSI2SD.cydsn/scsiPhy.h b/software/SCSI2SD/SCSI2SD.cydsn/scsiPhy.h index 8a37e67..658c1df 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/scsiPhy.h +++ b/software/SCSI2SD/SCSI2SD.cydsn/scsiPhy.h @@ -30,13 +30,13 @@ // Contains the odd-parity flag for a given 8-bit value. extern const uint8 Lookup_OddParity[256]; -void scsiPhyInit(); +void scsiPhyInit(void); uint8 scsiReadByte(void); void scsiRead(uint8* data, uint32 count); void scsiWriteByte(uint8 value); void scsiWrite(uint8* data, uint32 count); -uint8 scsiReadDBxPins(); +uint8 scsiReadDBxPins(void); void scsiEnterPhase(int phase); diff --git a/software/SCSI2SD/SCSI2SD.cydsn/sd.c b/software/SCSI2SD/SCSI2SD.cydsn/sd.c index 2d31294..21a8ecc 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/sd.c +++ b/software/SCSI2SD/SCSI2SD.cydsn/sd.c @@ -22,6 +22,8 @@ #include "sd.h" #include "led.h" +#include "scsiPhy.h" + #include // Global @@ -126,12 +128,13 @@ static void sdClearStatus() void sdPrepareRead() { + uint8 v; uint32 len = (transfer.lba + transfer.currentBlock); if (!sdDev.ccs) { len = len * SCSI_BLOCK_SIZE; } - uint8 v = sdCommandAndResponse(SD_READ_MULTIPLE_BLOCK, len); + v = sdCommandAndResponse(SD_READ_MULTIPLE_BLOCK, len); if (v) { scsiDiskReset(); @@ -146,13 +149,16 @@ void sdPrepareRead() void sdReadSector() { + int prep, i, guard; + // Wait for a start-block token. - // Don't wait more than 200ms. - int maxWait = 200000; + // Don't wait more than 100ms, which is the timeout recommended + // in the standard. + //100ms @ 64Hz = 6400000 + int maxWait = 6400000; uint8 token = sdSpiByte(0xFF); while (token != 0xFE && (maxWait-- > 0)) { - CyDelayUs(1); token = sdSpiByte(0xFF); } if (token != 0xFE) @@ -168,28 +174,65 @@ void sdReadSector() return; } - int prep = 0; - int i = 0; + // Don't do a bus settle delay if we're already in the correct phase. + if (transfer.currentBlock == 0) + { + scsiEnterPhase(DATA_IN); + } + + // Quickly seed the FIFO + prep = 4; + CY_SET_REG8(SDCard_TXDATA_PTR, 0xFF); // Put a byte in the FIFO + CY_SET_REG8(SDCard_TXDATA_PTR, 0xFF); // Put a byte in the FIFO + CY_SET_REG8(SDCard_TXDATA_PTR, 0xFF); // Put a byte in the FIFO + CY_SET_REG8(SDCard_TXDATA_PTR, 0xFF); // Put a byte in the FIFO + + i = 0; + guard = 0; + + // This loop is critically important for performance. + // 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) { - if (prep < SCSI_BLOCK_SIZE && (SDCard_ReadTxStatus() & SDCard_STS_TX_FIFO_NOT_FULL)) + uint8_t sdRxStatus = CY_GET_REG8(SDCard_RX_STATUS_PTR); + uint8_t scsiStatus = CY_GET_REG8(scsiTarget_StatusReg__STATUS_REG); + + // Read from the SPIM fifo if there is room to stream the byte to the + // SCSI fifos + if((sdRxStatus & SDCard_STS_RX_FIFO_NOT_EMPTY) && + (scsiStatus & 1) // SCSI TX FIFO NOT FULL + ) { - SDCard_WriteTxData(0xFF); - prep++; + uint8_t val = CY_GET_REG8(SDCard_RXDATA_PTR); + CY_SET_REG8(scsiTarget_datapath__F0_REG, val); + guard++; } - if(SDCard_ReadRxStatus() & SDCard_STS_RX_FIFO_NOT_EMPTY) + // Byte has been sent out the SCSI interface. + if (scsiStatus & 2) // SCSI RX FIFO NOT EMPTY { - scsiDev.data[i] = SDCard_ReadRxData(); - i++; + CY_GET_REG8(scsiTarget_datapath__F1_REG); + ++i; } - } + // How many bytes are in a 4-byte FIFO ? 5. 4 FIFO bytes PLUS one byte + // being processed bit-by-bit. Artifically limit the number of bytes in the + // "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)) + { + CY_SET_REG8(SDCard_TXDATA_PTR, 0xFF); // Put a byte in the FIFO + prep++; + } + } sdSpiByte(0xFF); // CRC sdSpiByte(0xFF); // CRC scsiDev.dataLen = SCSI_BLOCK_SIZE; - scsiDev.dataPtr = 0; + scsiDev.dataPtr = SCSI_BLOCK_SIZE; } void sdCompleteRead() @@ -200,8 +243,9 @@ void sdCompleteRead() // an error condition as we're trying to read past-the-end of the storage // device. // ie. do not use sdCommandAndResponse here. + uint8 r1b; sdSendCommand(SD_STOP_TRANSMISSION, 0); - uint8 r1b = sdReadResp(); + r1b = sdReadResp(); if (r1b) { @@ -212,7 +256,7 @@ void sdCompleteRead() r1b = sdCommandAndResponse(SD_STOP_TRANSMISSION, 0); retries--; } - + scsiDev.status = CHECK_CONDITION; scsiDev.sense.code = HARDWARE_ERROR; scsiDev.sense.asc = UNRECOVERED_READ_ERROR; @@ -220,11 +264,13 @@ void sdCompleteRead() } // R1b has an optional trailing "busy" signal. - uint8 busy; - do { - busy = sdSpiByte(0xFF); - } while (busy == 0); + uint8 busy; + do + { + busy = sdSpiByte(0xFF); + } while (busy == 0); + } } static void sdWaitWriteBusy() @@ -238,12 +284,13 @@ static void sdWaitWriteBusy() int sdWriteSector() { - int result; + int result, i, maxWait; + uint8 dataToken; + // Wait for a previously-written sector to complete. sdWaitWriteBusy(); sdSpiByte(0xFC); // MULTIPLE byte start token - int i; for (i = 0; i < SCSI_BLOCK_SIZE; i++) { while(!(SDCard_ReadTxStatus() & SDCard_STS_TX_FIFO_NOT_FULL)) @@ -252,30 +299,31 @@ int sdWriteSector() } while(!(SDCard_ReadTxStatus() & SDCard_STS_SPI_DONE)) {} SDCard_ClearFIFO(); - + sdSpiByte(0x00); // CRC sdSpiByte(0x00); // CRC - // Don't wait more than 1000ms. + // Don't wait more than 1s. // My 2g Kingston micro-sd card doesn't respond immediately. // My 16Gb card does. - int maxWait = 1000; - uint8 dataToken = sdSpiByte(0xFF); // Response + maxWait = 1000000; + dataToken = sdSpiByte(0xFF); // Response while (dataToken == 0xFF && maxWait-- > 0) { - CyDelay(1); // 1ms. + CyDelayUs(1); dataToken = sdSpiByte(0xFF); } if (((dataToken & 0x1F) >> 1) != 0x2) // Accepted. { + uint8 r1b, busy; + sdWaitWriteBusy(); - uint8 r1b = sdCommandAndResponse(SD_STOP_TRANSMISSION, 0); + r1b = sdCommandAndResponse(SD_STOP_TRANSMISSION, 0); (void) r1b; sdSpiByte(0xFF); // R1b has an optional trailing "busy" signal. - uint8 busy; do { busy = sdSpiByte(0xFF); @@ -305,6 +353,8 @@ int sdWriteSector() void sdCompleteWrite() { + uint8 r1, r2; + // Wait for a previously-written sector to complete. sdWaitWriteBusy(); @@ -312,8 +362,8 @@ void sdCompleteWrite() // Wait for the card to come out of busy. sdWaitWriteBusy(); - uint8 r1 = sdCommandAndResponse(13, 0); // send status - uint8 r2 = sdSpiByte(0xFF); + r1 = sdCommandAndResponse(13, 0); // send status + r2 = sdSpiByte(0xFF); if (r1 || r2) { sdClearStatus(); @@ -381,11 +431,12 @@ static int sdOpCond() static int sdReadOCR() { + uint8 buf[4]; + int i; + uint8 status = sdCRCCommandAndResponse(SD_READ_OCR, 0); if(status){goto bad;} - uint8 buf[4]; - int i; for (i = 0; i < 4; ++i) { buf[i] = sdSpiByte(0xFF); @@ -400,19 +451,20 @@ bad: static int sdReadCSD() { + uint8 startToken; + int maxWait, i; + uint8 buf[16]; + uint8 status = sdCRCCommandAndResponse(SD_SEND_CSD, 0); if(status){goto bad;} - - uint8 startToken; - int maxWait = 1023; + + maxWait = 1023; do { startToken = sdSpiByte(0xFF); } while(maxWait-- && (startToken != 0xFE)); if (startToken != 0xFE) { goto bad; } - uint8 buf[16]; - int i; for (i = 0; i < 16; ++i) { buf[i] = sdSpiByte(0xFF); @@ -452,18 +504,20 @@ bad: int sdInit() { + int result = 0; + int i; + uint8 v; + sdDev.version = 0; sdDev.ccs = 0; sdDev.capacity = 0; - int result = 0; SD_CS_Write(1); // Set CS inactive (active low) SD_Init_Clk_Start(); // Turn on the slow 400KHz clock SD_Clk_Ctl_Write(0); // Select the 400KHz clock source. SDCard_Start(); // Enable SPI hardware // Power on sequence. 74 clock cycles of a "1" while CS unasserted. - int i; for (i = 0; i < 10; ++i) { sdSpiByte(0xFF); @@ -472,7 +526,7 @@ int sdInit() SD_CS_Write(0); // Set CS active (active low) CyDelayUs(1); - uint8 v = sdCRCCommandAndResponse(SD_GO_IDLE_STATE, 0); + v = sdCRCCommandAndResponse(SD_GO_IDLE_STATE, 0); if(v != 1){goto bad;} ledOn(); @@ -525,6 +579,9 @@ 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. @@ -533,12 +590,12 @@ void sdPrepareWrite() sdCommandAndResponse(SD_APP_CMD, 0); sdCommandAndResponse(SD_APP_SET_WR_BLK_ERASE_COUNT, blocks); - uint32 len = (transfer.lba + transfer.currentBlock); + len = (transfer.lba + transfer.currentBlock); if (!sdDev.ccs) { len = len * SCSI_BLOCK_SIZE; } - uint8 v = sdCommandAndResponse(25, len); + v = sdCommandAndResponse(25, len); if (v) { scsiDiskReset(); diff --git a/software/SCSI2SD/SCSI2SD.cydsn/sd.h b/software/SCSI2SD/SCSI2SD.cydsn/sd.h index b8e3c31..7cdca9a 100755 --- a/software/SCSI2SD/SCSI2SD.cydsn/sd.h +++ b/software/SCSI2SD/SCSI2SD.cydsn/sd.h @@ -54,13 +54,13 @@ typedef struct extern SdDevice sdDev; -int sdInit(); -void sdPrepareWrite(); -int sdWriteSector(); -void sdCompleteWrite(); +int sdInit(void); +void sdPrepareWrite(void); +int sdWriteSector(void); +void sdCompleteWrite(void); -void sdPrepareRead(); -void sdReadSector(); -void sdCompleteRead(); +void sdPrepareRead(void); +void sdReadSector(void); +void sdCompleteRead(void); #endif -- 2.38.5