/*
 * Copyright 2009, Michael Lotz, mmlr@mlotz.ch.
 * Distributed under the terms of the MIT License.
 */
 
#include "ATAPrivate.h"
 
 
ATARequest::ATARequest(bool hasLock)
	:
	fHasLock(hasLock),
	fDevice(NULL),
	fTimeout(0),
	fBytesLeft(0),
	fIsWrite(false),
	fUseDMA(false),
	fCCB(NULL)
{
	if (hasLock)
		mutex_init(&fLock, "ata request");
 
	ClearSense();
}
 
 
ATARequest::~ATARequest()
{
	if (fHasLock)
		mutex_destroy(&fLock);
}
 
 
void
ATARequest::SetStatus(uint8 status)
{
	fStatus = status;
}
 
 
void
ATARequest::SetSense(uint8 key, uint16 codeQualifier)
{
	fSenseKey = key;
	fSenseCode = (uint8)(codeQualifier >> 8);
	fSenseQualifier = (uint8)(codeQualifier & 0xff);
}
 
 
void
ATARequest::ClearSense()
{
	fSenseKey = fSenseCode = fSenseQualifier = 0;
}
 
 
void
ATARequest::SetDevice(ATADevice *device)
{
	fDevice = device;
}
 
 
void
ATARequest::SetTimeout(bigtime_t timeout)
{
	fTimeout = timeout;
}
 
 
void
ATARequest::SetIsWrite(bool isWrite)
{
	fIsWrite = isWrite;
}
 
 
void
ATARequest::SetUseDMA(bool useDMA)
{
	fUseDMA = useDMA;
}
 
 
void
ATARequest::SetBytesLeft(uint32 bytesLeft)
{
	fBytesLeft = bytesLeft;
}
 
 
status_t
ATARequest::Start(scsi_ccb *ccb)
{
	if (mutex_trylock(&fLock) != B_OK)
		return B_BUSY;
 
	fCCB = ccb;
	fStatus = SCSI_REQ_CMP;
	fCCB->device_status = SCSI_STATUS_GOOD;
	fIsWrite = false;
	return B_OK;
}
 
 
status_t
ATARequest::Finish(bool resubmit)
{
	// when the request completed and has set sense
    // data, report this to the scsi stack by setting
    // CHECK CONDITION status
	if (fStatus == SCSI_REQ_CMP && fSenseKey != 0) {
		TRACE("setting check condition\n");
 
		fCCB->subsys_status = SCSI_REQ_CMP_ERR;
		fCCB->device_status = SCSI_STATUS_CHECK_CONDITION;
 
		// copy sense data if caller requested it
		if ((fCCB->flags & SCSI_DIS_AUTOSENSE) == 0) {
			// we cannot copy sense directly as sense buffer may be too small
			scsi_sense sense;
			_FillSense(&sense);
 
			size_t senseLength = MIN(sizeof(fCCB->sense), sizeof(sense));
			memcpy(fCCB->sense, &sense, senseLength);
			fCCB->sense_resid = SCSI_MAX_SENSE_SIZE - senseLength;
			fCCB->subsys_status |= SCSI_AUTOSNS_VALID;
			ClearSense();
		}
	} else
		fCCB->subsys_status = fStatus;
 
	mutex_unlock(&fLock);
 
	if (resubmit)
		gSCSIModule->resubmit(fCCB);
	else
		gSCSIModule->finished(fCCB, 1);
 
	return B_OK;
}
 
 
void
ATARequest::RequestSense()
{
	// Copy sense data from last request into data buffer of current request.
	// The sense data of last request is still present in the current request,
	// as it isn't cleared on SCSI_OP_REQUEST_SENSE.
	scsi_sense sense;
	if (fSenseKey != 0)
		_FillSense(&sense);
	else
		memset(&sense, 0, sizeof(sense));
 
	scsi_cmd_request_sense *command = (scsi_cmd_request_sense *)fCCB->cdb;
	copy_sg_data(fCCB, 0, command->allocation_length, &sense, sizeof(sense),
		false);
 
	fCCB->data_resid = fCCB->data_length - MIN(MIN(sizeof(sense),
		command->allocation_length), fCCB->data_length);
	ClearSense();
}
 
 
void
ATARequest::PrepareSGInfo()
{
	fSGElementsLeft = fCCB->sg_count;
	fCurrentSGElement = fCCB->sg_list;
	fCurrentSGOffset = 0;
	fHasOddByte = false;
	fCCB->data_resid = fCCB->data_length;
}
 
 
void
ATARequest::AdvanceSG(uint32 bytes)
{
	uint32 bytesLeft = fCurrentSGElement->size - fCurrentSGOffset;
	if (bytesLeft <= bytes) {
		fCurrentSGOffset = 0;
		fCurrentSGElement++;
		fSGElementsLeft--;
	} else
		fCurrentSGOffset += bytes;
}
 
 
void
ATARequest::SetOddByte(uint8 byte)
{
	fOddByte = byte;
	fHasOddByte = true;
}
 
 
bool
ATARequest::GetOddByte(uint8 *byte)
{
	if (!fHasOddByte)
		return false;
 
	if (byte != NULL)
		*byte = fOddByte;
 
	fHasOddByte = false;
	return true;
}
 
 
void
ATARequest::_FillSense(scsi_sense *sense)
{
	memset(sense, 0, sizeof(*sense));
	sense->error_code = SCSIS_CURR_ERROR;
	sense->sense_key = fSenseKey;
	sense->add_sense_length = sizeof(*sense) - 7;
	sense->asc = fSenseCode;
	sense->ascq = fSenseQualifier;
	sense->sense_key_spec.raw.SKSV = 0;	// no additional info
}

V512 A call of the 'memcpy' function will lead to underflow of the buffer 'fCCB->sense'.