From 476c00b2242904b75328c44eaf4cccb63114ea2d Mon Sep 17 00:00:00 2001 From: Michael McMaster Date: Wed, 19 Sep 2018 21:31:30 +1000 Subject: [PATCH] Improved scsi signal noise rejection, fixed write performance, fixed bug with multiple devices on the bus, and fixed bugs with non-512byte sectors. --- CHANGELOG | 5 + rtl/fpga_bitmap.o | Bin 32724 -> 32724 bytes src/firmware/bsp.c | 12 + src/firmware/bsp.h | 4 + src/firmware/config.c | 2 +- src/firmware/disk.c | 323 +++++++++--------- src/firmware/scsi.c | 18 +- src/firmware/scsi.h | 4 +- src/firmware/scsiPhy.c | 25 +- src/firmware/scsiPhy.h | 7 +- src/firmware/usb_device/usbd_msc_scsi.c | 20 +- src/firmware/usb_device/usbd_msc_storage_sd.c | 4 +- 12 files changed, 248 insertions(+), 176 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 812d5570..e8f1227d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +2018XXXX 6.2.1 + - Fix bug with non-512 byte sectors. + - Fix bug when writing with multiple SCSI devices on the chain + - Performance improvements to write speeds. + 20180430 6.1.4 - Fix bug in self-test function diff --git a/rtl/fpga_bitmap.o b/rtl/fpga_bitmap.o index 6613f58c1021a46619a652e14805282faf9d2ed4..cb1310f423c12d483492d9760546019b0f75956c 100644 GIT binary patch literal 32724 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 literal 32724 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 F{|5o{h5!Hn diff --git a/src/firmware/bsp.c b/src/firmware/bsp.c index 23da16a3..0c0a4774 100644 --- a/src/firmware/bsp.c +++ b/src/firmware/bsp.c @@ -23,6 +23,18 @@ static int usingFastClock = 0; // TODO keep clock routines consistent with those in STM32Cubemx main.c +uint32_t s2s_getSdRateMBs() +{ + if (usingFastClock) + { + return 18; // ((72MHz / 2) / 8bits) * 4bitparallel + } + else + { + return 12; // ((48MHz / 2) / 8bits) * 4bitparallel + } +} + // The standard clock is 108MHz with 48MHz SDIO clock void s2s_setNormalClock() { diff --git a/src/firmware/bsp.h b/src/firmware/bsp.h index 353c9ea4..a11c850a 100644 --- a/src/firmware/bsp.h +++ b/src/firmware/bsp.h @@ -18,6 +18,8 @@ #ifndef S2S_BSP_h #define S2S_BSP_h +#include + // For the STM32F205, DMA bursts may not cross 1KB address boundaries. // The maximum burst is 16 bytes. #define S2S_DMA_ALIGN __attribute__((aligned(1024))) @@ -25,5 +27,7 @@ void s2s_setNormalClock(); void s2s_setFastClock(); +uint32_t s2s_getSdRateMBs(); + #endif diff --git a/src/firmware/config.c b/src/firmware/config.c index 058c8c32..2f68ab50 100755 --- a/src/firmware/config.c +++ b/src/firmware/config.c @@ -38,7 +38,7 @@ #include -static const uint16_t FIRMWARE_VERSION = 0x0614; +static const uint16_t FIRMWARE_VERSION = 0x0620; // 1 flash row static const uint8_t DEFAULT_CONFIG[128] = diff --git a/src/firmware/disk.c b/src/firmware/disk.c index 5b114009..89db1799 100755 --- a/src/firmware/disk.c +++ b/src/firmware/disk.c @@ -18,12 +18,18 @@ #include "stm32f2xx.h" +// For SD write direct routines +#include "sdio.h" +#include "bsp_driver_sd.h" + + #include "scsi.h" #include "scsiPhy.h" #include "config.h" #include "disk.h" #include "sd.h" #include "time.h" +#include "bsp.h" #include @@ -533,7 +539,6 @@ void scsiDiskPoll() if (scsiDev.phase == DATA_IN && transfer.currentBlock != transfer.blocks) { - scsiEnterPhase(DATA_IN); int totalSDSectors = transfer.blocks * SDSectorsPerSCSISector(bytesPerSector); @@ -549,8 +554,15 @@ void scsiDiskPoll() int i = 0; int scsiActive __attribute__((unused)) = 0; // unused if DMA disabled int sdActive = 0; + + uint32_t partialScsiChunk = 0; + + // Start reading from the SD card FIRST, because we change state and + // wai for SCSI signals + int dataInStarted = 0; + while ((i < totalSDSectors) && - likely(scsiDev.phase == DATA_IN) && + (!dataInStarted || likely(scsiDev.phase == DATA_IN)) && likely(!scsiDev.resetFlag)) { int completedDmaSectors; @@ -594,9 +606,16 @@ void scsiDiskPoll() sdReadDMA(sdLBA + prep, sectors, &scsiDev.data[SD_SECTOR_SIZE * startBuffer]); sdActive = sectors; + + if (!dataInStarted) + { + dataInStarted = 1; + scsiEnterPhase(DATA_IN); // Will wait a few microseconds. + } } #ifdef SCSI_FSMC_DMA + #error this code not updated for 256 max bytes in scsi fifo if (scsiActive && scsiPhyComplete() && scsiWriteDMAPoll()) { scsiActive = 0; @@ -624,33 +643,49 @@ void scsiDiskPoll() if (dmaBytes == 0) dmaBytes = SD_SECTOR_SIZE; } - uint16_t* scsiDmaData = (uint16_t*) &(scsiDev.data[SD_SECTOR_SIZE * (i % buffers)]); // Manually unrolled loop for performance. // -Os won't unroll this for us automatically, // especially since scsiPhyTx does volatile stuff. // Reduces bus utilisation by making the fsmc split // 32bits into 2 16 bit writes. + + uint16_t* scsiDmaData = (uint16_t*) &(scsiDev.data[SD_SECTOR_SIZE * (i % buffers) + partialScsiChunk]); + + uint32_t chunk = ((dmaBytes - partialScsiChunk) > SCSI_FIFO_DEPTH) + ? SCSI_FIFO_DEPTH : (dmaBytes - partialScsiChunk); + int k = 0; - for (; k + 4 < (dmaBytes + 1) / 2; k += 4) + for (; k + 4 < (chunk + 1) / 2; k += 4) { scsiPhyTx32(scsiDmaData[k], scsiDmaData[k+1]); scsiPhyTx32(scsiDmaData[k+2], scsiDmaData[k+3]); } - for (; k < (dmaBytes + 1) / 2; ++k) + for (; k < (chunk + 1) / 2; ++k) { scsiPhyTx(scsiDmaData[k]); } - i++; while (!scsiPhyComplete() && !scsiDev.resetFlag) { __WFE(); // Wait for event } scsiPhyFifoFlip(); - scsiSetDataCount(dmaBytes); + scsiSetDataCount(chunk); + + partialScsiChunk += chunk; + if (partialScsiChunk == dmaBytes) + { + partialScsiChunk = 0; + ++i; + } } #endif } + if (!dataInStarted && !scsiDev.resetFlag) // zero bytes ? + { + scsiEnterPhase(DATA_IN); // Will wait a few microseconds. + } + // We've finished transferring the data to the FPGA, now wait until it's // written to he SCSI bus. while (!scsiPhyComplete() && @@ -679,22 +714,16 @@ void scsiDiskPoll() scsiDev.target->cfg->sdSectorStart, bytesPerSector, transfer.lba); - // int buffers = sizeof(scsiDev.data) / SD_SECTOR_SIZE; - // int prep = 0; int i = 0; - // int scsiDisconnected = 0; - int scsiComplete = 0; - //uint32_t lastActivityTime = s2s_getTime_ms(); - // int scsiActive = 0; - // int sdActive = 0; int clearBSY = 0; int parityError = 0; + int enableParity = scsiDev.boardCfg.flags & S2S_CFG_ENABLE_PARITY; + while ((i < totalSDSectors) && - (likely(scsiDev.phase == DATA_OUT) || // scsiDisconnect keeps our phase. - scsiComplete) && + likely(scsiDev.phase == DATA_OUT) && likely(!scsiDev.resetFlag) && - likely(!parityError)) + likely(!parityError || !enableParity)) { // Well, until we have some proper non-blocking SD code, we must // do this in a half-duplex fashion. We need to write as much as @@ -703,178 +732,146 @@ void scsiDiskPoll() uint32_t rem = totalSDSectors - i; uint32_t sectors = rem < maxSectors ? rem : maxSectors; - scsiRead(&scsiDev.data[0], sectors * SD_SECTOR_SIZE, &parityError); - if (i + sectors >= totalSDSectors) + if (bytesPerSector == SD_SECTOR_SIZE) { - // We're transferring over the SCSI bus faster than the SD card - // can write. All data is buffered, and we're just waiting for - // the SD card to complete. The host won't let us disconnect. - // Some drivers set a 250ms timeout on transfers to complete. - // SD card writes are supposed to complete - // within 200ms, but sometimes they don'to. - // Just pretend we're finished. - process_Status(); - process_MessageIn(); // Will go to BUS_FREE state - - // Try and prevent anyone else using the SCSI bus while we're not ready. - if (*SCSI_CTRL_BSY == 0) // Could be busy for a linked command + // We assume the SD card is faster than the SCSI interface, but has + // no flow control. This can be handled if a) the scsi interface + // doesn't block and b) we read enough SCSI sectors first so that + // the SD interface cannot catch up. + uint32_t totalBytes = sectors * SD_SECTOR_SIZE; + uint32_t readAheadBytes = sectors * SD_SECTOR_SIZE; + uint32_t sdSpeed = s2s_getSdRateMBs() + (scsiDev.sdUnderrunCount / 2); + uint32_t scsiSpeed = s2s_getScsiRateMBs(); + // if (have blind writes) + if (scsiSpeed > 0 && scsiDev.sdUnderrunCount < 16) { - *SCSI_CTRL_BSY = 1; - clearBSY = 1; + // readAhead = sectors * (sd / scsi - 1 + 0.1); + readAheadBytes = totalBytes * sdSpeed / scsiSpeed - totalBytes + SCSI_FIFO_DEPTH; + if (readAheadBytes < SCSI_FIFO_DEPTH) + { + readAheadBytes = SCSI_FIFO_DEPTH; + } + + if (readAheadBytes > totalBytes) + { + readAheadBytes = totalBytes; + } } - } + uint32_t chunk = (readAheadBytes > SCSI_FIFO_DEPTH) ? SCSI_FIFO_DEPTH : readAheadBytes; + scsiSetDataCount(chunk); - if (!parityError) - { - sdTmpWrite(&scsiDev.data[0], i + sdLBA, sectors); - } - i += sectors; + uint32_t scsiBytesRead = 0; + while (scsiBytesRead < readAheadBytes) + { + while (!scsiPhyComplete() && likely(!scsiDev.resetFlag)) + { + __WFE(); // Wait for event + } + parityError |= scsiParityError(); + scsiPhyFifoFlip(); + uint32_t nextChunk = ((totalBytes - scsiBytesRead - chunk) > SCSI_FIFO_DEPTH) + ? SCSI_FIFO_DEPTH : (totalBytes - scsiBytesRead - chunk); + + if (nextChunk > 0) scsiSetDataCount(nextChunk); + scsiReadPIO(&scsiDev.data[scsiBytesRead], chunk); + scsiBytesRead += chunk; + chunk = nextChunk; + } -#if 0 - // Wait for the next DMA interrupt. It's beneficial to halt the - // processor to give the DMA controller more memory bandwidth to - // work with. - int scsiBusy = 1; - int sdBusy = 1; - while (scsiBusy && sdBusy) - { - uint8_t intr = CyEnterCriticalSection(); - scsiBusy = scsiDMABusy(); - sdBusy = sdDMABusy(); - if (scsiBusy && sdBusy) + HAL_SD_WriteBlocks_DMA(&hsd, (uint32_t*) (&scsiDev.data[0]), (i + sdLBA) * 512ll, SD_SECTOR_SIZE, sectors); + + while (scsiBytesRead < totalBytes) { - __WFI(); + while (!scsiPhyComplete() && likely(!scsiDev.resetFlag)) + { + __WFE(); // Wait for event + } + parityError |= scsiParityError(); + scsiPhyFifoFlip(); + uint32_t nextChunk = ((totalBytes - scsiBytesRead - chunk) > SCSI_FIFO_DEPTH) + ? SCSI_FIFO_DEPTH : (totalBytes - scsiBytesRead - chunk); + + if (nextChunk > 0) scsiSetDataCount(nextChunk); + scsiReadPIO(&scsiDev.data[scsiBytesRead], chunk); + scsiBytesRead += chunk; + chunk = nextChunk; } - CyExitCriticalSection(intr); - } - if (sdActive && !sdBusy && sdWriteSectorDMAPoll()) - { - sdActive = 0; - i++; - } - if (!sdActive && ((prep - i) > 0)) - { - // Start an SD transfer if we have space. - sdWriteMultiSectorDMA(&scsiDev.data[SD_SECTOR_SIZE * (i % buffers)]); - sdActive = 1; - } + // Oh dear, SD finished first. + int underrun = totalBytes > readAheadBytes && hsd.DmaTransferCplt; - uint32_t now = getTime_ms(); + uint32_t dmaFinishTime = s2s_getTime_ms(); + while (!hsd.SdTransferCplt && + s2s_elapsedTime_ms(dmaFinishTime) < 180) + { + // Wait while keeping BSY. + } + while((__HAL_SD_SDIO_GET_FLAG(&hsd, SDIO_FLAG_TXACT)) && + s2s_elapsedTime_ms(dmaFinishTime) < 180) + { + // Wait for SD card while keeping BSY. + } - if (scsiActive && !scsiBusy && scsiReadDMAPoll()) - { - scsiActive = 0; - ++prep; - lastActivityTime = now; - } - if (!scsiActive && - ((prep - i) < buffers) && - (prep < totalSDSectors) && - likely(!scsiDisconnected)) - { - int dmaBytes = SD_SECTOR_SIZE; - if ((prep % sdPerScsi) == (sdPerScsi - 1)) + if (i + sectors >= totalSDSectors && + !underrun && + (!parityError || !enableParity)) { - dmaBytes = bytesPerSector % SD_SECTOR_SIZE; - if (dmaBytes == 0) dmaBytes = SD_SECTOR_SIZE; + // We're transferring over the SCSI bus faster than the SD card + // can write. All data is buffered, and we're just waiting for + // the SD card to complete. The host won't let us disconnect. + // Some drivers set a 250ms timeout on transfers to complete. + // SD card writes are supposed to complete + // within 200ms, but sometimes they don't. + // Just pretend we're finished. + process_Status(); + clearBSY = process_MessageIn(0); // Will go to BUS_FREE state but keep BSY asserted. } - scsiReadDMA(&scsiDev.data[SD_SECTOR_SIZE * (prep % buffers)], dmaBytes); - scsiActive = 1; - } - else if ( - (scsiDev.boardCfg.flags & CONFIG_ENABLE_DISCONNECT) && - (scsiActive == 0) && - likely(!scsiDisconnected) && - unlikely(scsiDev.discPriv) && - unlikely(diffTime_ms(lastActivityTime, now) >= 20) && - likely(scsiDev.phase == DATA_OUT)) - { - // We're transferring over the SCSI bus faster than the SD card - // can write. There is no more buffer space once we've finished - // this SCSI transfer. - // The NCR 53C700 interface chips have a 250ms "byte-to-byte" - // timeout buffer. SD card writes are supposed to complete - // within 200ms, but sometimes they don't. - // The NCR 53C700 series is used on HP 9000 workstations. - scsiDisconnect(); - scsiDisconnected = 1; - lastActivityTime = getTime_ms(); + + HAL_SD_CheckWriteOperation(&hsd, (uint32_t)SD_DATATIMEOUT); + + if (underrun) + { + // Try again. Data is still in memory. + sdTmpWrite(&scsiDev.data[0], i + sdLBA, sectors); + scsiDev.sdUnderrunCount++; + } + i += sectors; + } - else if (unlikely(scsiDisconnected) && - ( - (prep == i) || // Buffers empty. - // Send some messages every 100ms so we don't timeout. - // At a minimum, a reselection involves an IDENTIFY message. - unlikely(diffTime_ms(lastActivityTime, now) >= 100) - )) + else { - int reconnected = scsiReconnect(); - if (reconnected) + // Well, until we have some proper non-blocking SD code, we must + // do this in a half-duplex fashion. We need to write as much as + // possible in each SD card transaction. + // use sg_dd from sg_utils3 tools to test. + uint32_t maxSectors = sizeof(scsiDev.data) / SD_SECTOR_SIZE; + uint32_t rem = totalSDSectors - i; + uint32_t sectors = rem < maxSectors ? rem : maxSectors; + int scsiSector; + for (scsiSector = i; scsiSector < i + sectors; ++scsiSector) { - scsiDisconnected = 0; - lastActivityTime = getTime_ms(); // Don't disconnect immediately. + int dmaBytes = SD_SECTOR_SIZE; + if ((scsiSector % sdPerScsi) == (sdPerScsi - 1)) + { + dmaBytes = bytesPerSector % SD_SECTOR_SIZE; + if (dmaBytes == 0) dmaBytes = SD_SECTOR_SIZE; + } + scsiRead(&scsiDev.data[SD_SECTOR_SIZE * (scsiSector - i)], dmaBytes, &parityError); } - else if (diffTime_ms(lastActivityTime, getTime_ms()) >= 10000) + if (!parityError) { - // Give up after 10 seconds of trying to reconnect. - scsiDev.resetFlag = 1; + sdTmpWrite(&scsiDev.data[0], i + sdLBA, sectors); } + i += sectors; } - else if ( - likely(!scsiComplete) && - (sdActive == 1) && - (prep == totalSDSectors) && // All scsi data read and buffered - likely(!scsiDev.discPriv) && // Prefer disconnect where possible. - unlikely(diffTime_ms(lastActivityTime, now) >= 150) && - - likely(scsiDev.phase == DATA_OUT) && - !(scsiDev.cdb[scsiDev.cdbLen - 1] & 0x01) // Not linked command - ) - { - // We're transferring over the SCSI bus faster than the SD card - // can write. All data is buffered, and we're just waiting for - // the SD card to complete. The host won't let us disconnect. - // Some drivers set a 250ms timeout on transfers to complete. - // SD card writes are supposed to complete - // within 200ms, but sometimes they don'to. - // Just pretend we're finished. - scsiComplete = 1; - - process_Status(); - process_MessageIn(); // Will go to BUS_FREE state - - // Try and prevent anyone else using the SCSI bus while we're not ready. - SCSI_SetPin(SCSI_Out_BSY); - } -#endif } if (clearBSY) { - *SCSI_CTRL_BSY = 0; - } - -#if 0 - if (scsiComplete) - { - SCSI_ClearPin(SCSI_Out_BSY); - } - while ( - !scsiDev.resetFlag && - unlikely(scsiDisconnected) && - (s2s_elapsedTime_ms(lastActivityTime) <= 10000)) - { - scsiDisconnected = !scsiReconnect(); - } - if (scsiDisconnected) - { - // Failed to reconnect - scsiDev.resetFlag = 1; + enter_BusFree(); } -#endif if (scsiDev.phase == DATA_OUT) { diff --git a/src/firmware/scsi.c b/src/firmware/scsi.c index 81af617f..ce886e7e 100755 --- a/src/firmware/scsi.c +++ b/src/firmware/scsi.c @@ -38,7 +38,6 @@ ScsiDevice scsiDev S2S_DMA_ALIGN; static void enter_SelectionPhase(void); static void process_SelectionPhase(void); -static void enter_BusFree(void); static void enter_MessageIn(uint8_t message); static void enter_Status(uint8_t status); static void enter_DataIn(int len); @@ -48,7 +47,7 @@ static void process_Command(void); static void doReserveRelease(void); -static void enter_BusFree() +void enter_BusFree() { // This delay probably isn't needed for most SCSI hosts, but it won't // hurt either. It's possible some of the samplers needed this delay. @@ -84,7 +83,7 @@ static void enter_MessageIn(uint8_t message) scsiDev.phase = MESSAGE_IN; } -void process_MessageIn() +int process_MessageIn(int releaseBusFree) { scsiEnterPhase(MESSAGE_IN); scsiWriteByte(scsiDev.msgIn); @@ -94,6 +93,7 @@ void process_MessageIn() // If there was a parity error, we go // back to MESSAGE_OUT first, get out parity error message, then come // back here. + return 0; } else if ((scsiDev.msgIn == MSG_LINKED_COMMAND_COMPLETE) || (scsiDev.msgIn == MSG_LINKED_COMMAND_COMPLETE_WITH_FLAG)) @@ -106,10 +106,16 @@ void process_MessageIn() scsiDev.status = GOOD; transfer.blocks = 0; transfer.currentBlock = 0; + return 0; } - else /*if (scsiDev.msgIn == MSG_COMMAND_COMPLETE)*/ + else if (releaseBusFree) /*if (scsiDev.msgIn == MSG_COMMAND_COMPLETE)*/ { enter_BusFree(); + return 1; + } + else + { + return 1; } } @@ -517,6 +523,7 @@ static void scsiReset() scsiDev.postDataOutHook = NULL; + scsiDev.sdUnderrunCount = 0; // Sleep to allow the bus to settle down a bit. // We must be ready again within the "Reset to selection time" of @@ -865,6 +872,7 @@ static void process_MessageOut() uint8_t SDTR[] = {0x01, 0x03, 0x01, scsiDev.target->syncPeriod, scsiDev.target->syncOffset}; scsiWrite(SDTR, sizeof(SDTR)); scsiDev.needSyncNegotiationAck = 1; // Check if this message is rejected. + scsiDev.sdUnderrunCount = 0; // reset counter, may work now. } } else @@ -1004,7 +1012,7 @@ void scsiPoll(void) } else { - process_MessageIn(); + process_MessageIn(1); } break; diff --git a/src/firmware/scsi.h b/src/firmware/scsi.h index 50724564..5480a6a5 100755 --- a/src/firmware/scsi.h +++ b/src/firmware/scsi.h @@ -162,12 +162,14 @@ typedef struct uint8_t minSyncPeriod; // Debug use only. int needSyncNegotiationAck; + int sdUnderrunCount; } ScsiDevice; extern ScsiDevice scsiDev; void process_Status(void); -void process_MessageIn(void); +int process_MessageIn(int releaseBusFree); +void enter_BusFree(void); void scsiInit(void); void scsiPoll(void); diff --git a/src/firmware/scsiPhy.c b/src/firmware/scsiPhy.c index b2bb258f..739b21df 100755 --- a/src/firmware/scsiPhy.c +++ b/src/firmware/scsiPhy.c @@ -187,7 +187,7 @@ scsiReadByte(void) } -static void +void scsiReadPIO(uint8_t* data, uint32_t count) { uint16_t* fifoData = (uint16_t*)data; @@ -598,6 +598,29 @@ void scsiEnterPhase(int newPhase) } } +uint32_t s2s_getScsiRateMBs() +{ + if (scsiDev.target->syncOffset) + { + if (scsiDev.target->syncPeriod < 23) + { + return 20; + } + else if (scsiDev.target->syncPeriod <= 25) + { + return 10; + } + else + { + return 1000 / (scsiDev.target->syncPeriod * 4); + } + } + else + { + return 0; + } +} + void scsiPhyReset() { trace(trace_scsiPhyReset); diff --git a/src/firmware/scsiPhy.h b/src/firmware/scsiPhy.h index f8f5bdda..a99ed365 100755 --- a/src/firmware/scsiPhy.h +++ b/src/firmware/scsiPhy.h @@ -47,7 +47,7 @@ #define SCSI_STS_PARITY_ERR ((volatile uint8_t*)0x6000002C) #define SCSI_FIFO_DATA ((volatile uint16_t*)0x60000040) -#define SCSI_FIFO_DEPTH 512 +#define SCSI_FIFO_DEPTH 256 #define scsiPhyFifoFull() ((*SCSI_STS_FIFO & 0x01) == 0x01) @@ -109,9 +109,14 @@ extern volatile uint8_t scsiTxDMAComplete; void scsiReadDMA(uint8_t* data, uint32_t count); int scsiReadDMAPoll(); +// Low-level. +void scsiReadPIO(uint8_t* data, uint32_t count); + void scsiWriteDMA(const uint8_t* data, uint32_t count); int scsiWriteDMAPoll(); int scsiSelfTest(void); +uint32_t s2s_getScsiRateMBs(); + #endif diff --git a/src/firmware/usb_device/usbd_msc_scsi.c b/src/firmware/usb_device/usbd_msc_scsi.c index b7ea9a74..30b7fd1a 100755 --- a/src/firmware/usb_device/usbd_msc_scsi.c +++ b/src/firmware/usb_device/usbd_msc_scsi.c @@ -662,8 +662,24 @@ static int8_t SCSI_CheckAddressRange (USBD_HandleTypeDef *pdev, uint8_t lun , u { USBD_CompositeClassData *classData = (USBD_CompositeClassData*) pdev->pClassData; USBD_MSC_BOT_HandleTypeDef *hmsc = &(classData->msc); - - if ((blk_offset + blk_nbr) > hmsc->scsi_blk_nbr ) + + // michael@codesrc.com: Re-check block limits in cause we have different values + // for different LUN's. + uint32_t blkNbr; + uint16_t blkSize; + if(((USBD_StorageTypeDef *)pdev->pUserData)->GetCapacity(lun, &blkNbr, &blkSize) != 0) + { + SCSI_SenseCode(pdev, + lun, + NOT_READY, + MEDIUM_NOT_PRESENT); + return -1; + } + // global variables. wooo + hmsc->scsi_blk_size = blkSize; + hmsc->scsi_blk_nbr = blkNbr; + + if ((blk_offset + blk_nbr) > blkNbr ) { SCSI_SenseCode(pdev, lun, diff --git a/src/firmware/usb_device/usbd_msc_storage_sd.c b/src/firmware/usb_device/usbd_msc_storage_sd.c index 3cb44873..263eb2b7 100755 --- a/src/firmware/usb_device/usbd_msc_storage_sd.c +++ b/src/firmware/usb_device/usbd_msc_storage_sd.c @@ -205,6 +205,8 @@ int8_t s2s_usbd_storage_Write (uint8_t lun, for (int i = 0; i < SDSectorsPerSCSISector(cfg->bytesPerSector); ++i) { uint8_t partial[512] S2S_DMA_ALIGN; + memcpy(partial, buf, 512); + BSP_SD_WriteBlocks_DMA( (uint32_t*) partial, sdSectorNum * 512LL, @@ -215,8 +217,6 @@ int8_t s2s_usbd_storage_Write (uint8_t lun, int validBytes = cfg->bytesPerSector % SD_SECTOR_SIZE; if (validBytes == 0) validBytes = SD_SECTOR_SIZE; - memcpy(buf, partial, validBytes); - buf += validBytes; } -- 2.38.5