/******************************************************************************
*
* Copyright (C) 2010 - 2014 Xilinx, Inc.  All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal 
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* Use of the Software is limited solely to applications:
* (a) running on a Xilinx device, or
* (b) that interact with a Xilinx device through a bus or interconnect.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* XILINX CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Except as contained in this notice, the name of the Xilinx shall not be used
* in advertising or otherwise to promote the sale, use or other dealings in
* this Software without prior written authorization from Xilinx.
*
******************************************************************************/
/*****************************************************************************/
/**
 * @file xusbps_class_storage.c
 *
 * This file contains the implementation of the storage class code for the
 * example.
 *
 *<pre>
 * MODIFICATION HISTORY:
 *
 * Ver   Who  Date     Changes
 * ----- ---- -------- ---------------------------------------------------------
 * 1.00a wgr  10/10/10 First release
 * 2.1   kpc  4/28/14  Align DMA buffers to cache line boundary
 *</pre>
 ******************************************************************************/

/***************************** Include Files *********************************/

#include <string.h>

#include "xusbps.h"		/* USB controller driver */

#include "xusbps_ch9_storage.h"
#include "xusbps_ch9.h"
#include "xusbps_class_storage.h"
#include "xil_printf.h"

/* #define CLASS_STORAGE_DEBUG */

#ifdef CLASS_STORAGE_DEBUG
#define printf xil_printf
#endif

/************************** Constant Definitions *****************************/


/************************** Function Prototypes ******************************/

/************************** Variable Definitions *****************************/

/* Pre-manufactured response to the SCSI Inquirey command.
 */
const static SCSI_INQUIRY scsiInquiry ALIGNMENT_CACHELINE = {
	0x00,
	0x80,
	0x00,
	0x01,
	0x1f,
	0x00,
	0x00,
	0x00,
	{"Xilinx  "},		/* Vendor ID:  must be  8 characters long. */
	{"PS USB VirtDisk"},	/* Product ID: must be 16 characters long. */
	{"1.00"}		/* Revision:   must be  4 characters long. */
};
static u8 MaxLUN ALIGNMENT_CACHELINE = 0;
/* Buffer for virtual flash disk space. */
static u8 VirtFlash[VFLASH_SIZE] ALIGNMENT_CACHELINE;

static USB_CBW lastCBW ALIGNMENT_CACHELINE;

/* Local transmit buffer for simple replies. */
static u8 txBuffer[128] ALIGNMENT_CACHELINE;

/*****************************************************************************/
/**
* This function handles Reduced Block Command (RBC) requests from the host.
*
* @param	InstancePtr is a pointer to XUsbPs instance of the controller.
* @param	EpNum is the number of the endpoint on which the RBC was received.
* @param	BufferPtr is the data buffer containing the RBC or data.
* @param	BufferLen is the length of the data buffer.
*
* @return	None.
*
* @note		None.
*
******************************************************************************/
void XUsbPs_HandleStorageReq(XUsbPs *InstancePtr, u8 EpNum,
				u8 *BufferPtr, u32 BufferLen)
{
	USB_CBW	*CBW;
	u32	Offset;
	static u8 *VirtFlashWritePointer = VirtFlash;
	/* Static variables used for data transfers.*/
	static int	rxBytesLeft;

	/* Current SCSI machine state. */
	static int	phase = USB_EP_STATE_COMMAND;

	/* COMMAND phase. */
	if (USB_EP_STATE_COMMAND == phase) {
		CBW = (USB_CBW *) BufferPtr;

		switch (CBW->CBWCB[0]) {
		case USB_RBC_INQUIRY:
#ifdef CLASS_STORAGE_DEBUG
 			printf("SCSI: INQUIRY\n");
#endif
			XUsbPs_EpBufferSend(InstancePtr, 1,
						(void *) &scsiInquiry,
						sizeof(scsiInquiry));
			/* Send Success Status 	 */
			CBW->dCBWSignature = 0x55534253;
			CBW->dCBWDataTransferLength = 0;
			CBW->bmCBWFlags = 0;

			XUsbPs_EpBufferSend(InstancePtr, 1, (void *) CBW, 13);
			break;


		case USB_UFI_GET_CAP_LIST:
		{
			SCSI_CAP_LIST	*CapList;

			CapList = (SCSI_CAP_LIST *) txBuffer;
#ifdef CLASS_STORAGE_DEBUG
 			printf("SCSI: CAPLIST\n");
#endif
			CapList->listLength	= 8;
			CapList->descCode	= 3;
			CapList->numBlocks	= htonl(VFLASH_NUM_BLOCKS);
			CapList->blockLength	= htons(VFLASH_BLOCK_SIZE);
			XUsbPs_EpBufferSend(InstancePtr, 1, txBuffer,
						      sizeof(SCSI_CAP_LIST));
			/* Send Success Status
			 */
			CBW->dCBWSignature = 0x55534253;
			CBW->dCBWDataTransferLength =
				be2le(be2le(CBW->dCBWDataTransferLength) -
						      sizeof(SCSI_CAP_LIST));
			CBW->bmCBWFlags = 0;

			XUsbPs_EpBufferSend(InstancePtr, 1, (u8 *) CBW, 13);
			break;
		}

		case USB_RBC_READ_CAP:
		{
			SCSI_READ_CAPACITY	*Cap;

			Cap = (SCSI_READ_CAPACITY *) txBuffer;
#ifdef CLASS_STORAGE_DEBUG
 			printf("SCSI: READCAP\n");
#endif
			Cap->numBlocks = htonl(VFLASH_NUM_BLOCKS - 1);
			Cap->blockSize = htonl(VFLASH_BLOCK_SIZE);
			XUsbPs_EpBufferSend(InstancePtr, 1, txBuffer,
					      sizeof(SCSI_READ_CAPACITY));
			/* Send Success Status  */
			CBW->dCBWSignature = 0x55534253;
			CBW->dCBWDataTransferLength = 0;
			CBW->bmCBWFlags = 0;

			XUsbPs_EpBufferSend(InstancePtr, 1, (u8 *) CBW, 13);
			break;
		}

		case USB_RBC_READ:
			Offset = htonl(((SCSI_READ_WRITE *) CBW->CBWCB)->
				       block) * VFLASH_BLOCK_SIZE;
#ifdef CLASS_STORAGE_DEBUG
			printf("SCSI: READ Offset 0x%08x\n", (int) Offset);
#endif
			XUsbPs_EpBufferSend(InstancePtr, 1, &VirtFlash[Offset],
				      htons(((SCSI_READ_WRITE *) CBW->CBWCB)->
					    length) * VFLASH_BLOCK_SIZE);
			/* Send Success Status */
			CBW->dCBWSignature = 0x55534253;
			CBW->dCBWDataTransferLength = 0;
			CBW->bmCBWFlags = 0;

			XUsbPs_EpBufferSend(InstancePtr, 1, (u8 *) CBW, 13);
			break;

		case USB_RBC_MODE_SENSE:
#ifdef CLASS_STORAGE_DEBUG
 			printf("SCSI: MODE SENSE\n");
#endif
			XUsbPs_EpBufferSend(InstancePtr, 1,
				      (u8 *) "\003\000\000\000", 4);

			/* Send Success Status */
			CBW->dCBWSignature = 0x55534253;
			CBW->dCBWDataTransferLength =
				be2le(be2le(CBW->dCBWDataTransferLength) - 4);
			CBW->bmCBWFlags = 0;

			XUsbPs_EpBufferSend(InstancePtr, 1, (u8 *) CBW, 13);
			break;


		case USB_RBC_TEST_UNIT_READY:
		case USB_RBC_MEDIUM_REMOVAL:
		case USB_RBC_VERIFY:
#ifdef CLASS_STORAGE_DEBUG
 			printf("SCSI: TEST UNIT READY\n");
#endif
			/* Send Success Status */
			CBW->dCBWSignature = 0x55534253;
			CBW->dCBWDataTransferLength = 0;
			CBW->bmCBWFlags = 0;

			XUsbPs_EpBufferSend(InstancePtr, 1, (u8 *) CBW, 13);
			break;


		case USB_RBC_WRITE:
			Offset = htonl(((SCSI_READ_WRITE *) CBW->CBWCB)->
				       block) * VFLASH_BLOCK_SIZE;
#ifdef CLASS_STORAGE_DEBUG
			printf("SCSI: WRITE Offset 0x%08x\n", (int) Offset);
#endif
			VirtFlashWritePointer = &VirtFlash[Offset];
			/* Save the CBW for the DATA and STATUS phases. */
			lastCBW = *CBW;
			rxBytesLeft =
				htons(((SCSI_READ_WRITE *) CBW->CBWCB)->length)
							* VFLASH_BLOCK_SIZE;

			phase = USB_EP_STATE_DATA;
			break;


		case USB_RBC_STARTSTOP_UNIT:
		{
			u8 immed;

			immed = ((SCSI_START_STOP *) CBW->CBWCB)->immed;
#ifdef CLASS_STORAGE_DEBUG
			printf("SCSI: START/STOP unit: immed %02x\n", immed);
#endif
			/* If the immediate bit is 0 we are supposed to send
			 * a success status.
			 */
			if (0 == (immed & 0x01)) {
				/* Send Success Status */
				CBW->dCBWSignature = 0x55534253;
				CBW->dCBWDataTransferLength = 0;
				CBW->bmCBWFlags = 0;

				XUsbPs_EpBufferSend(InstancePtr, 1,
							(u8 *) CBW, 13);
			}
			break;
		}


		/* Commands that we do not support for this example. */
		case 0x04:	/* Format Unit */
		case 0x15:	/* Mode Select */
		case 0x5e:	/* Persistent Reserve In */
		case 0x5f:	/* Persistent Reserve Out */
		case 0x17:	/* Release */
		case 0x03:	/* Request Sense */
		case 0x16:	/* Reserve */
		case 0x35:	/* Sync Cache */
		case 0x3b:	/* Write Buffer */
#ifdef CLASS_STORAGE_DEBUG
			printf("SCSI: Got unhandled command %02x\n", CBW->CBWCB[0]);
#endif
		default:
			break;
		}
	}
	/* DATA phase.
	 */
	else if (USB_EP_STATE_DATA == phase) {
		switch (lastCBW.CBWCB[0]) {
		case USB_RBC_WRITE:
			/* Copy the data we just read into the VirtFlash buffer. */
			memcpy(VirtFlashWritePointer, BufferPtr, BufferLen);
			VirtFlashWritePointer += BufferLen;

			rxBytesLeft -= BufferLen;

			if (rxBytesLeft <= 0) {
				/* Send Success Status */
				lastCBW.dCBWSignature = 0x55534253;
				lastCBW.dCBWDataTransferLength = 0;
				lastCBW.bmCBWFlags = 0;

				XUsbPs_EpBufferSend(InstancePtr, 1,
						      (void *) &lastCBW, 13);

				phase = USB_EP_STATE_COMMAND;
			}
			break;
		}
	}
}


/*****************************************************************************/
/**
* This function handles a Storage Class Setup request from the host.
*
* @param	InstancePtr is a pointer to XUsbPs instance of the controller.
* @param	SetupData is the setup data structure containing the setup
*		request.
*
* @return	None.
*
* @note		None.
*
******************************************************************************/
void XUsbPs_ClassReq(XUsbPs *InstancePtr, XUsbPs_SetupData *SetupData)
{

	Xil_AssertVoid(InstancePtr != NULL);
	Xil_AssertVoid(SetupData   != NULL);


	switch (SetupData->bRequest) {

	case XUSBPS_CLASSREQ_MASS_STORAGE_RESET:
		XUsbPs_EpBufferSend(InstancePtr, 0, NULL, 0);
		break;

	case XUSBPS_CLASSREQ_GET_MAX_LUN:
		XUsbPs_EpBufferSend(InstancePtr, 0, &MaxLUN, 1);
		break;

	default:
		XUsbPs_EpStall(InstancePtr, 0, XUSBPS_EP_DIRECTION_IN);
		break;
	}
}