LPC18xx SD write DMA buffer overrun

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

LPC18xx SD write DMA buffer overrun

362 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by joepbrown on Thu Oct 22 13:27:06 MST 2015
This is directly related to this topic on the lpc43xx: https://www.lpcware.com/content/forum/lpcopen-v202-sdmmcspeedc-hard-faults.

This is also all in the current LPCOpen library.

There a bug in the DMA setup of the SDMMC card write handling where if the number of bytes exceeds 4096*17 the function that sets up the DMA for the SD write will overrun and corrupt the callback function pointers (and a bunch of other stuff) and cause the device to hardfault.

The SDMMC card info struct contains an array that is passed to the SDMMC DMA for data transfer. The array is defined as

typedef struct _sdif_device {
pSDMMC_DMA_T mci_dma_dd[1 + (0x10000 / MCI_DMADES1_MAXTR)];
} sdif_device;


MCI_DMADES1_MAXTR is defined as 4096, so the array length is 17. In Chip_SDIF_DmaSetup, if the size passed in is greater than 17*4096, the psdif_dev[] array will overrun and corrupt the callback pointers in the g_card_info structure.

The way I found this issue was using the USB masstorage driver from LPCOpen on a linux machine. Windows seems to never write more than 128 blocks at a time (128*512B), but once I plugged my device into a linux machine it attempted to write 240 blocks, which caused the overrun.

I'm not quite sure how to fix this problem. I've tried hardcoding a larger array size for mci_dma_dd, but it causes the SD card writes to hang (the DMA write complete interrupt never fires?).

Has anyone else run into this problem on the lpx18xx?
Labels (1)
0 Kudos
2 Replies

288 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by joepbrown on Fri Nov 27 14:50:42 MST 2015
For anyone that has used the USB MSC code provided with LPCOpen, specifically the SCSI code -- The device will not work on linux. Linux, by default, will send SCSI reads and writes in 240-block chunks. This will overrun the disk cache for reads and writes in the SCSI read/write code. To fix this, you have to break the reads/writes into pieces.

Here is the original
SCSI_Command_ReadWrite_10()
:

static bool SCSI_Command_ReadWrite_10(USB_ClassInfo_MS_Device_t * const MSInterfaceInfo, const bool IsDataRead)
{
    uint32_t BlockAddress;
    uint16_t TotalBlocks;
    ...
    BlockAddress = (MSInterfaceInfo->State.CommandBlock.SCSICommandData[2] << 24)
                 + (MSInterfaceInfo->State.CommandBlock.SCSICommandData[3] << 16)
                 + (MSInterfaceInfo->State.CommandBlock.SCSICommandData[4] << 8)
                 + (MSInterfaceInfo->State.CommandBlock.SCSICommandData[5]);
    TotalBlocks = (MSInterfaceInfo->State.CommandBlock.SCSICommandData[7] << 8)
                 + MSInterfaceInfo->State.CommandBlock.SCSICommandData[8];
    ...
    if (IsDataRead == DATA_READ)
    {
        status = Chip_SDMMC_ReadBlocks((void*) disk_cache, BlockAddress,TotalBlocks);
        while (!Endpoint_IsINReady(MSInterfaceInfo->Config.PortNumber));
        Endpoint_Streaming(MSInterfaceInfo->Config.PortNumber, (uint8_t*) disk_cache, VIRTUAL_MEMORY_BLOCK_SIZE, TotalBlocks,0);
    }
    else
    {
        Endpoint_Streaming(MSInterfaceInfo->Config.PortNumber,(uint8_t*) disk_cache, VIRTUAL_MEMORY_BLOCK_SIZE, TotalBlocks,0);
        while (!Endpoint_IsOUTReceived(MSInterfaceInfo->Config.PortNumber));
        status = Chip_SDMMC_WriteBlocks((void*) disk_cache, BlockAddress,TotalBlocks);
    }
    ...
}


You'll need to do something like this:

static bool SCSI_Command_ReadWrite_10(USB_ClassInfo_MS_Device_t * const MSInterfaceInfo, const bool IsDataRead)
{
    uint32_t BlockAddress;
    uint16_t TotalBlocks;
    uint16_t Remainder;
    uint32_t RemainderAddress;
    int32_t status = 0;
    ...
    BlockAddress = (MSInterfaceInfo->State.CommandBlock.SCSICommandData[2] << 24)
                 + (MSInterfaceInfo->State.CommandBlock.SCSICommandData[3] << 16)
                 + (MSInterfaceInfo->State.CommandBlock.SCSICommandData[4] << 8)
                 + (MSInterfaceInfo->State.CommandBlock.SCSICommandData[5]);
    TotalBlocks = (MSInterfaceInfo->State.CommandBlock.SCSICommandData[7] << 8)
                 + MSInterfaceInfo->State.CommandBlock.SCSICommandData[8];
    Remainder = TotalBlocks - 128;
    RemainderAddress = BlockAddress + 128;
    ...
    if (IsDataRead == DATA_READ)
    {
        if (TotalBlocks <= 128)
        {
            status = Chip_SDMMC_ReadBlocks((void*) disk_cache, BlockAddress,TotalBlocks);
            while (!Endpoint_IsINReady(MSInterfaceInfo->Config.PortNumber));
            Endpoint_Streaming(MSInterfaceInfo->Config.PortNumber, (uint8_t*) disk_cache, VIRTUAL_MEMORY_BLOCK_SIZE, TotalBlocks,0);
        }
        else
        {
            // first 128
            status = Chip_SDMMC_ReadBlocks((void*) disk_cache, BlockAddress,128);
            while (!Endpoint_IsINReady(MSInterfaceInfo->Config.PortNumber));
            Endpoint_Streaming(MSInterfaceInfo->Config.PortNumber, (uint8_t*) disk_cache, VIRTUAL_MEMORY_BLOCK_SIZE, 128,0);
            // remainder
            status = Chip_SDMMC_ReadBlocks((void*) disk_cache, RemainderAddress,Remainder);
            while (!Endpoint_IsINReady(MSInterfaceInfo->Config.PortNumber));
            Endpoint_Streaming(MSInterfaceInfo->Config.PortNumber, (uint8_t*) disk_cache, VIRTUAL_MEMORY_BLOCK_SIZE, Remainder,0);
        }
    }
    else
    {
        if (TotalBlocks <= 128)
        {
            Endpoint_Streaming(MSInterfaceInfo->Config.PortNumber,(uint8_t*) disk_cache, VIRTUAL_MEMORY_BLOCK_SIZE, TotalBlocks,0);
            while (!Endpoint_IsOUTReceived(MSInterfaceInfo->Config.PortNumber));
            debug("Write %d @ 0x%08x\n", TotalBlocks, BlockAddress);
            status = Chip_SDMMC_WriteBlocks((void*) disk_cache, BlockAddress,TotalBlocks);
        }
        else
        {
            // first 128
            Endpoint_Streaming(MSInterfaceInfo->Config.PortNumber,(uint8_t*) disk_cache, VIRTUAL_MEMORY_BLOCK_SIZE, 128,0);
            while (!Endpoint_IsOUTReceived(MSInterfaceInfo->Config.PortNumber));
            status = Chip_SDMMC_WriteBlocks((void*) disk_cache, BlockAddress,128);
            // remainder
            Endpoint_Streaming(MSInterfaceInfo->Config.PortNumber,(uint8_t*) disk_cache, VIRTUAL_MEMORY_BLOCK_SIZE, Remainder,0);
            while (!Endpoint_IsOUTReceived(MSInterfaceInfo->Config.PortNumber));
            status = Chip_SDMMC_WriteBlocks((void*) disk_cache, RemainderAddress,Remainder);

        }
    }
    ...
}


There will only ever be, at most, two reads or writes since the SCSI parameter maxes our at 256. Hopefully this helps someone else out.
0 Kudos

288 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by joepbrown on Thu Oct 22 14:24:04 MST 2015
Just a little more info:

The root problem is that 0x10000 is the max size of the AHB SRAM buffer the USB uses for transfers, so the DMA array makes sense.

A more appropriate question may be how, in the SCSI driver in LPCOpen, can I limit the total number of blocks written, or somehow tell the linux machine not to send more than 128 blocks?
0 Kudos