* Copyright 2009, Michael Lotz, mmlr@mlotz.ch.
* Copyright 2008, Marcus Overhagen.
* Copyright 2004-2008, Axel DΓΆrfler, axeld@pinc-software.de.
* Copyright 2002-2003, Thomas Kurschel.
*
* Distributed under the terms of the MIT License.
*/
#include "ATAPrivate.h"
ATAPIDevice::ATAPIDevice(ATAChannel *channel, uint8 index)
: ATADevice(channel, index)
{
}
ATAPIDevice::~ATAPIDevice()
{
}
status_t
ATAPIDevice::SendPacket(ATARequest *request)
{
TRACE_FUNCTION("%p\n", request);
uint8 command = fPacket[0];
request->SetUseDMA(UseDMA() && request->CCB()->sg_list != NULL
&& (command == SCSI_OP_READ_6 || command == SCSI_OP_WRITE_6
|| command == SCSI_OP_READ_10 || command == SCSI_OP_WRITE_10
|| command == SCSI_OP_READ_12 || command == SCSI_OP_WRITE_12
|| command == SCSI_OP_READ_CD)
&& fChannel->PrepareDMA(request) == B_OK);
TRACE("using dma: %s\n", request->UseDMA() ? "yes" : "no");
if (!request->UseDMA())
request->PrepareSGInfo();
if (_FillTaskFilePacket(request) != B_OK) {
TRACE_ERROR("failed to setup transfer request\n");
if (request->UseDMA())
fChannel->FinishDMA();
return B_ERROR;
}
status_t result = fChannel->SendRequest(request, 0);
if (result != B_OK) {
TRACE_ERROR("failed to send packet request\n");
if (request->UseDMA())
fChannel->FinishDMA();
return result;
}
if (fChannel->Wait(ATA_STATUS_DATA_REQUEST, ATA_STATUS_BUSY,
ATA_CHECK_ERROR_BIT | ATA_CHECK_DEVICE_FAULT, 100 * 1000) != B_OK) {
TRACE_ERROR("timeout waiting for data request\n");
if (request->UseDMA())
fChannel->FinishDMA();
request->SetStatus(SCSI_SEQUENCE_FAIL);
return B_TIMED_OUT;
}
fRegisterMask = ATA_MASK_INTERRUPT_REASON;
fChannel->ReadRegs(this);
if (!fTaskFile.packet_res.cmd_or_data
|| fTaskFile.packet_res.input_or_output) {
TRACE_ERROR("device doesn't ask for packet\n");
if (request->UseDMA())
fChannel->FinishDMA();
request->SetStatus(SCSI_SEQUENCE_FAIL);
return B_ERROR;
}
spin(10);
if (fChannel->WritePIO(fPacket, sizeof(fPacket)) != B_OK) {
TRACE_ERROR("failed to write packet\n");
if (request->UseDMA())
fChannel->FinishDMA();
request->SetStatus(SCSI_HBA_ERR);
return B_ERROR;
}
if (!request->HasData())
return _FinishRequest(request, ATA_WAIT_FINISH);
if (request->UseDMA()) {
fChannel->PrepareWaitingForInterrupt();
fChannel->StartDMA();
result = fChannel->WaitForInterrupt(request->Timeout());
status_t dmaResult = fChannel->FinishDMA();
if (result != B_OK) {
request->SetStatus(SCSI_CMD_TIMEOUT);
return B_TIMED_OUT;
}
result = _FinishRequest(request, ATA_WAIT_FINISH);
if (result != B_OK) {
TRACE_ERROR("device indicates transfer error after dma\n");
return result;
}
if (dmaResult == B_OK || dmaResult == B_DEV_DATA_OVERRUN) {
fDMAFailures = 0;
request->CCB()->data_resid = 0;
return B_OK;
}
TRACE_ERROR("dma transfer failed\n");
request->SetSense(SCSIS_KEY_HARDWARE_ERROR,
SCSIS_ASC_LUN_COM_FAILURE);
fDMAFailures++;
if (fDMAFailures >= ATA_MAX_DMA_FAILURES) {
TRACE_ALWAYS("disabling DMA after %u failures\n", fDMAFailures);
fUseDMA = false;
}
return B_ERROR;
}
result = fChannel->Wait(ATA_STATUS_DATA_REQUEST, ATA_STATUS_BUSY,
ATA_CHECK_ERROR_BIT | ATA_CHECK_DEVICE_FAULT, request->Timeout());
if (result != B_OK) {
if (result == B_TIMED_OUT) {
TRACE_ERROR("timeout waiting for device to request data\n");
request->SetStatus(SCSI_CMD_TIMEOUT);
return B_TIMED_OUT;
} else
return _FinishRequest(request, 0);
}
while (true) {
fRegisterMask = ATA_MASK_INTERRUPT_REASON | ATA_MASK_BYTE_COUNT;
fChannel->ReadRegs(this);
if (fTaskFile.packet_res.cmd_or_data) {
TRACE_ERROR("device expecting command instead of data\n");
request->SetStatus(SCSI_SEQUENCE_FAIL);
return B_ERROR;
}
size_t length = fTaskFile.packet_res.byte_count_0_7
| ((size_t)fTaskFile.packet_res.byte_count_8_15 << 8);
TRACE("about to transfer %lu bytes\n", length);
request->SetBytesLeft(length);
fChannel->ExecutePIOTransfer(request);
result = fChannel->Wait(0, ATA_STATUS_BUSY, 0, request->Timeout());
if (result != B_OK) {
if (result == B_TIMED_OUT) {
TRACE_ERROR("timeout waiting for device to finish transfer\n");
request->SetStatus(SCSI_CMD_TIMEOUT);
return B_TIMED_OUT;
} else
return _FinishRequest(request, 0);
}
if ((fChannel->AltStatus() & ATA_STATUS_DATA_REQUEST) == 0) {
TRACE("pio transfer complete\n");
break;
}
}
return _FinishRequest(request, ATA_WAIT_FINISH);
}
status_t
ATAPIDevice::ExecuteIO(ATARequest *request)
{
scsi_ccb *ccb = request->CCB();
if (ccb->target_lun != 0) {
TRACE_ERROR("invalid target lun %d\n", ccb->target_lun);
request->SetStatus(SCSI_SEL_TIMEOUT);
return B_BAD_INDEX;
}
memset(fPacket, 0, sizeof(fPacket));
memcpy(fPacket, ccb->cdb, ccb->cdb_length);
request->SetDevice(this);
request->SetIsWrite((ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_OUT);
return SendPacket(request);
}
status_t
ATAPIDevice::Configure()
{
if (fInfoBlock.word_0.atapi.atapi_device != ATA_WORD_0_ATAPI_DEVICE) {
TRACE_ERROR("infoblock indicates non-atapi device\n");
return B_ERROR;
}
fTaskFile.packet.lun = 0;
status_t result = ConfigureDMA();
if (result != B_OK)
return result;
result = DisableCommandQueueing();
if (result != B_OK)
return result;
return B_OK;
}
status_t
ATAPIDevice::_FillTaskFilePacket(ATARequest *request)
{
scsi_ccb *ccb = request->CCB();
fRegisterMask = ATA_MASK_FEATURES | ATA_MASK_BYTE_COUNT;
fTaskFile.packet.dma = request->UseDMA() ? 1 : 0;
fTaskFile.packet.ovl = 0;
fTaskFile.packet.byte_count_0_7 = ccb->data_length & 0xff;
fTaskFile.packet.byte_count_8_15 = ccb->data_length >> 8;
fTaskFile.packet.command = ATA_COMMAND_PACKET;
return B_OK;
}
status_t
ATAPIDevice::_FinishRequest(ATARequest *request, uint32 flags)
{
if (fChannel->FinishRequest(request, flags
| ATA_CHECK_DEVICE_FAULT, 0) != B_OK) {
request->ClearSense();
request->SetStatus(SCSI_REQ_CMP_ERR);
request->CCB()->device_status = SCSI_STATUS_CHECK_CONDITION;
return B_ERROR;
}
return B_OK;
}