EA BaseBoard, playing wav, PC see\'s SD-card through USB port.

Dependencies:   mbed

msc_scsi.cpp

Committer:
Lerche
Date:
2011-11-22
Revision:
0:fef366d2ed20

File content as of revision 0:fef366d2ed20:

/*
    LPCUSB, an USB device driver for LPC microcontrollers    
    Copyright (C) 2006 Bertrik Sikken (bertrik@sikken.nl)

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:

    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.
    3. The name of the author may not be used to endorse or promote products
       derived from this software without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/**
    @file

    This is the SCSI layer of the USB mass storage application example.
    This layer depends directly on the blockdev layer.
    
    Windows peculiarities:
    * Size of REQUEST SENSE CDB is 12 bytes instead of expected 6
    * Windows requires VERIFY(10) command to do a format.
      This command is not mandatory in the SBC/SBC-2 specification.
*/
/*
 *  Modified for mbed NXP LPC1768 & LPCXpresso board
 *  original    LPC214x USB stack beta by bertrik
 *              target-20070727.tar.gr (119.5KB)
 *              http://sourceforge.net/projects/lpcusb/
 *  By Kenji Arai / JH1PJL on September 25th, 2010
 *      October 3rd, 2010
 */
#define FATFS

//#define DEBUG
#include "dbg.h"
#include "mbed.h"
#include <string.h>        // memcpy

#include "blockdev.h"
#include "msc_scsi.h"

//#include "SDFileSystem.h"
//extern SDFileSystem sd;

#define BLOCKSIZE        512

// SBC2 mandatory SCSI commands
#define    SCSI_CMD_TEST_UNIT_READY    0x00
#define SCSI_CMD_REQUEST_SENSE        0x03
#define SCSI_CMD_FORMAT_UNIT        0x04
#define SCSI_CMD_READ_6                0x08    /* not implemented yet */
#define SCSI_CMD_INQUIRY            0x12
#define SCSI_CMD_SEND_DIAGNOSTIC    0x1D    /* not implemented yet */
#define SCSI_CMD_READ_CAPACITY_10    0x25
#define SCSI_CMD_READ_10            0x28
#define SCSI_CMD_REPORT_LUNS        0xA0    /* not implemented yet */

// SBC2 optional SCSI commands
#define SCSI_CMD_WRITE_6            0x0A    /* not implemented yet */
#define SCSI_CMD_WRITE_10            0x2A
#define SCSI_CMD_VERIFY_10            0x2F    /* required for windows format */

// sense codes
#define WRITE_ERROR                0x030C00
#define READ_ERROR                0x031100
#define INVALID_CMD_OPCODE        0x052000
#define INVALID_FIELD_IN_CDB    0x052400

//    Sense code, which is set on error conditions
static uint32_t            dwSense;    // hex: 00aabbcc, where aa=KEY, bb=ASC, cc=ASCQ

static const uint8_t        abInquiry[] = {
    0x00,        // PDT = direct-access device
    0x80,        // removeable medium bit = set
    0x05,        // version = complies to SPC3
    0x02,        // response data format = SPC3
    0x1F,        // additional length
    0x00,
    0x00,
    0x00,
    'L','P','C','U','S','B',' ',' ',    // vendor
    'M','a','s','s',' ','s','t','o',    // product
    'r','a','g','e',' ',' ',' ',' ',
    '0','.','1',' '                        // revision
};

//    Data for "request sense" command. The 0xFF are filled in later
static const uint8_t abSense[] = { 0x70, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0A, 
                              0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00,
                              0x00, 0x00 };

//    Buffer for holding one block of disk data
static uint8_t abBlockBuf[512];


struct TCDB6 {
    uint8_t        bOperationCode;
    uint8_t        abLBA[3];
    uint8_t        bLength;
    uint8_t        bControl;
} __attribute__((packed));


/*************************************************************************
    SCSIReset
    =========
        Resets any SCSI state
        
**************************************************************************/
void SCSIReset(void)
{
    dwSense = 0;
}


/*************************************************************************
    SCSIHandleCmd
    =============
        Verifies a SCSI CDB and indicates the direction and amount of data
        that the device wants to transfer.
        
    If this call fails, a sense code is set in dwSense.

    IN        pbCDB        Command data block
            iCDBLen        Command data block len
    OUT        *piRspLen    Length of intended response data:
            *pfDevIn    true if data is transferred from device-to-host
    
    Returns a pointer to the data exchange buffer if successful,
    return NULL otherwise.
**************************************************************************/
uint8_t * SCSIHandleCmd(uint8_t *pbCDB, uint8_t iCDBLen, int *piRspLen, bool *pfDevIn)
{
    static const uint8_t aiCDBLen[] = {6, 10, 10, 0, 16, 12, 0, 0};
    int        i;
    TCDB6    *pCDB;
    uint32_t        dwLen, dwLBA;
    uint8_t        bGroupCode;
    
    pCDB = (TCDB6 *)pbCDB;
    
    // default direction is from device to host
    *pfDevIn = true;
    
    // check CDB length
    bGroupCode = (pCDB->bOperationCode >> 5) & 0x7;
    if (iCDBLen < aiCDBLen[bGroupCode]) {
        DBG("Invalid CBD len (expected %d)!\n", aiCDBLen[bGroupCode]);
        return NULL;
    }

    switch (pCDB->bOperationCode) {

    // test unit ready (6)
    case SCSI_CMD_TEST_UNIT_READY:
        DBG("TEST UNIT READY\n");
        *piRspLen = 0;
        break;
    
    // request sense (6)
    case SCSI_CMD_REQUEST_SENSE:
        DBG("REQUEST SENSE (%06X)\n", dwSense);
        // check params
        *piRspLen = MIN(18, pCDB->bLength);
        break;
    
    case SCSI_CMD_FORMAT_UNIT:
        DBG("FORMAT UNIT %02X\n", pbCDB[1]);
        *piRspLen = 0;
        break;
    
    // inquiry (6)
    case SCSI_CMD_INQUIRY:
        DBG("INQUIRY\n");
        // see SPC3r23, 4.3.4.6
        *piRspLen = MIN(36, pCDB->bLength);
        break;
        
    // read capacity (10)
    case SCSI_CMD_READ_CAPACITY_10:
        DBG("READ CAPACITY\n");
        *piRspLen = 8;
        break;
        
    // read (10)
    case SCSI_CMD_READ_10:
        dwLBA = (pbCDB[2] << 24) | (pbCDB[3] << 16) | (pbCDB[4] << 8) | (pbCDB[5]);
        dwLen = (pbCDB[7] << 8) | pbCDB[8];
        DBG("READ10, LBA=%d, len=%d\n", dwLBA, dwLen);
        *piRspLen = dwLen * BLOCKSIZE;
        break;

    // write (10)
    case SCSI_CMD_WRITE_10:
        dwLBA = (pbCDB[2] << 24) | (pbCDB[3] << 16) | (pbCDB[4] << 8) | (pbCDB[5]);
        dwLen = (pbCDB[7] << 8) | pbCDB[8];
        DBG("WRITE10, LBA=%d, len=%d\n", dwLBA, dwLen);
        *piRspLen = dwLen * BLOCKSIZE;
        *pfDevIn = false;
        break;

    case SCSI_CMD_VERIFY_10:
        DBG("VERIFY10\n");
        if ((pbCDB[1] & (1 << 1)) != 0) {
            // we don't support BYTCHK
            DBG("BYTCHK not supported\n");
            return NULL;
        }
        *piRspLen = 0;
        break;
    
    default:
        DBG("Unhandled SCSI: ");        
        for (i = 0; i < iCDBLen; i++) {
            DBG(" %02X", pbCDB[i]);
        }
        DBG("\n");
        // unsupported command
        dwSense = INVALID_CMD_OPCODE;
        *piRspLen = 0;
        return NULL;
    }
    
    
    return abBlockBuf;
}


/*************************************************************************
    SCSIHandleData
    ==============
        Handles a block of SCSI data.
        
    IN        pbCDB        Command data block
            iCDBLen        Command data block len
    IN/OUT    pbData        Data buffer
    IN        dwOffset    Offset in data
    
    Returns a pointer to the next data to be exchanged if successful,
    returns NULL otherwise.
**************************************************************************/
uint8_t * SCSIHandleData(uint8_t *pbCDB, uint8_t iCDBLen, uint8_t *pbData, uint32_t dwOffset)
{
    TCDB6    *pCDB;
    uint32_t        dwLBA;
    uint32_t        dwBufPos, dwBlockNr;
    uint32_t        dwDevSize, dwMaxBlock;
    
    pCDB = (TCDB6 *)pbCDB;
    
    switch (pCDB->bOperationCode) {

    // test unit ready
    case SCSI_CMD_TEST_UNIT_READY:
        if (dwSense != 0) {
            return NULL;
        }
        break;
    
    // request sense
    case SCSI_CMD_REQUEST_SENSE:
        memcpy(pbData, abSense, 18);
        // fill in KEY/ASC/ASCQ
        pbData[2] = (dwSense >> 16) & 0xFF;
        pbData[12] = (dwSense >> 8) & 0xFF;
        pbData[13] = (dwSense >> 0) & 0xFF;
        // reset sense data
        dwSense = 0;
        break;
    
    case SCSI_CMD_FORMAT_UNIT:
        // nothing to do, ignore this command
        break;
    
    // inquiry
    case SCSI_CMD_INQUIRY:
        memcpy(pbData, abInquiry, sizeof(abInquiry));
        break;
        
    // read capacity
    case SCSI_CMD_READ_CAPACITY_10:
#ifdef FATFS
        // get size of drive (bytes)
        dwDevSize = BlockDevGetSize();
//        dwDevSize = sd.disk_sectors() * 512;
#else
        // get size of drive (bytes)
        BlockDevGetSize(&dwDevSize);
#endif
        // calculate highest LBA
        dwMaxBlock = (dwDevSize - 1) / 512;
        
        pbData[0] = (dwMaxBlock >> 24) & 0xFF;
        pbData[1] = (dwMaxBlock >> 16) & 0xFF;
        pbData[2] = (dwMaxBlock >> 8) & 0xFF;
        pbData[3] = (dwMaxBlock >> 0) & 0xFF;
        pbData[4] = (BLOCKSIZE >> 24) & 0xFF;
        pbData[5] = (BLOCKSIZE >> 16) & 0xFF;
        pbData[6] = (BLOCKSIZE >> 8) & 0xFF;
        pbData[7] = (BLOCKSIZE >> 0) & 0xFF;
        break;
        
    // read10
    case SCSI_CMD_READ_10:
        dwLBA = (pbCDB[2] << 24) | (pbCDB[3] << 16) | (pbCDB[4] << 8) | (pbCDB[5]);

        // copy data from block buffer
        dwBufPos = (dwOffset & (BLOCKSIZE - 1));
        if (dwBufPos == 0) {
            // read new block
            dwBlockNr = dwLBA + (dwOffset / BLOCKSIZE);
            DBG("R");
            if (BlockDevRead(dwBlockNr, abBlockBuf)) {
//            if (sd.disk_read((char*)abBlockBuf, dwBlockNr)) {
                dwSense = READ_ERROR;
                DBG("BlockDevRead failed\n");
                return NULL;
            }
        }
        // return pointer to data
        return abBlockBuf + dwBufPos;

    // write10
    case SCSI_CMD_WRITE_10:
        dwLBA = (pbCDB[2] << 24) | (pbCDB[3] << 16) | (pbCDB[4] << 8) | (pbCDB[5]);
        
        // copy data to block buffer
        dwBufPos = ((dwOffset + 64) & (BLOCKSIZE - 1));
        if (dwBufPos == 0) {
            // write new block
            dwBlockNr = dwLBA + (dwOffset / BLOCKSIZE);
            DBG("W");
            if (BlockDevWrite(dwBlockNr, abBlockBuf)) {
//            if (sd.disk_write((char*)abBlockBuf, dwBlockNr)) {
                dwSense = WRITE_ERROR;
                DBG("BlockDevWrite failed\n");
                return NULL;
            }
        }
        // return pointer to next data
        return abBlockBuf + dwBufPos;
        
    case SCSI_CMD_VERIFY_10:
        // dummy implementation
        break;
        
    default:
        // unsupported command
        dwSense = INVALID_CMD_OPCODE;
        return NULL;
    }
    
    // default: return pointer to start of block buffer
    return abBlockBuf;
}